slint/internal/renderers/femtovg/opengl.rs
Ashley 13623bc152
Some checks are pending
autofix.ci / format_fix (push) Waiting to run
autofix.ci / lint_typecheck (push) Waiting to run
CI / node_test (macos-14) (push) Blocked by required conditions
CI / files-changed (push) Waiting to run
CI / build_and_test (--exclude bevy-example, ubuntu-22.04, 1.85) (push) Blocked by required conditions
CI / build_and_test (--exclude ffmpeg --exclude gstreamer-player, --exclude bevy-example, windows-2022, 1.85) (push) Blocked by required conditions
CI / build_and_test (--exclude ffmpeg --exclude gstreamer-player, macos-14, stable) (push) Blocked by required conditions
CI / build_and_test (--exclude ffmpeg --exclude gstreamer-player, windows-2022, beta) (push) Blocked by required conditions
CI / build_and_test (--exclude ffmpeg --exclude gstreamer-player, windows-2022, stable) (push) Blocked by required conditions
CI / build_and_test (ubuntu-22.04, nightly) (push) Blocked by required conditions
CI / node_test (ubuntu-22.04) (push) Blocked by required conditions
CI / node_test (windows-2022) (push) Blocked by required conditions
CI / python_test (macos-14) (push) Blocked by required conditions
CI / python_test (ubuntu-22.04) (push) Blocked by required conditions
CI / python_test (windows-2022) (push) Blocked by required conditions
CI / cpp_cmake (ubuntu-22.04, stable) (push) Blocked by required conditions
CI / cpp_test_driver (macos-14) (push) Blocked by required conditions
CI / cpp_test_driver (ubuntu-22.04) (push) Blocked by required conditions
CI / cpp_test_driver (windows-2022) (push) Blocked by required conditions
CI / cpp_cmake (macos-14, 1.85) (push) Blocked by required conditions
CI / cpp_cmake (windows-2022, nightly) (push) Blocked by required conditions
CI / cpp_package_test (push) Blocked by required conditions
CI / vsce_build_test (push) Blocked by required conditions
CI / mcu (pico-st7789, thumbv6m-none-eabi) (push) Blocked by required conditions
CI / mcu (pico2-st7789, thumbv8m.main-none-eabihf) (push) Blocked by required conditions
CI / mcu (stm32h735g, thumbv7em-none-eabihf) (push) Blocked by required conditions
CI / mcu-embassy (push) Blocked by required conditions
CI / ffi_32bit_build (push) Blocked by required conditions
CI / docs (push) Blocked by required conditions
CI / wasm (push) Blocked by required conditions
CI / wasm_demo (push) Blocked by required conditions
CI / tree-sitter (push) Blocked by required conditions
CI / updater_test (0.3.0) (push) Blocked by required conditions
CI / fmt_test (push) Blocked by required conditions
CI / esp-idf-quick (push) Blocked by required conditions
CI / android (push) Blocked by required conditions
CI / miri (push) Blocked by required conditions
CI / test-figma-inspector (push) Blocked by required conditions
CI / material-components (push) Blocked by required conditions
feature/fontique: switch femtovg to parley (#9466)
* Parlay init

* Start on femtovg

* Cargo fmt

* Decimate fonts.rs

* Use fill_glyphs

* [autofix.ci] apply automated fixes

* Use positioned_glyphs instead

* Clean up a little

* Format

* [autofix.ci] apply automated fixes

* Few fixes

* [autofix.ci] apply automated fixes

* More small changes

* Clean up

* [autofix.ci] apply automated fixes

* Display text cursor

* Handle text_input_cursor_rect_for_byte_offset

* stoke glyphs

* Handle text selections

* Stroke selection as well

* Fix wierd cargo.toml padding

* Move selection and stroking to brush settings

* Removed commented out code

* [autofix.ci] apply automated fixes

* Cursor sizing

* [autofix.ci] apply automated fixes

* Mark unused variables

* _scale -> scale

* Handle a lot more layout options

* Use the parley cursor

* Removed unused PhysicalPoint

* Start combining stuff WIP

* Move things into i_slint_core::textlayout::sharedparley

* [autofix.ci] apply automated fixes

* Go back to splitting paragraphs correctly

* Handle font_metrics via ttf_parser

* Move (lack of) overflow handling to sharedparley

* [autofix.ci] apply automated fixes

* impl Deref for Layout

* Be more explit about the width passed to layout being physical

* Cargo fmt, rename fonts to font_cache

* Use a thread local for layout context

* Use parley selection

* fix femtovg wgpu

* Switch femtovg branch

* max_physical_width -> max_width

* Box contexts

* [autofix.ci] apply automated fixes

* Fallback to GenericFamily::SansSerif if no font is set

* Add paint.set_font_size

* Use max_physical_height

* Fix text_size to return correct logical sizes

* Use femtovg from crates.io

* Fix C++ build

The `sharedparley` module declares a public `Layout` struct, which
clashes with the `Layout` struct we use in the C++ API. The former
however is entirely internal to Rust, so we can instruct cbindgen to
ignore the module.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Simon Hausmann <simon.hausmann@slint.dev>
2025-09-26 02:48:48 +12:00

259 lines
9.6 KiB
Rust

// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
use std::{cell::RefCell, num::NonZeroU32, rc::Rc};
use i_slint_core::api::PlatformError;
use crate::{FemtoVGRenderer, GraphicsBackend, WindowSurface};
/// This trait describes the interface GPU accelerated renderers in Slint require to render with OpenGL.
///
/// It serves the purpose to ensure that the OpenGL context is current before running any OpenGL
/// commands, as well as providing access to the OpenGL implementation by function pointers.
///
/// # Safety
///
/// This trait is unsafe because an implementation of get_proc_address could return dangling
/// pointers. In practice an implementation of this trait should just forward to the EGL/WGL/CGL
/// C library that implements EGL/CGL/WGL.
#[allow(unsafe_code)]
pub unsafe trait OpenGLInterface {
/// Ensures that the OpenGL context is current when returning from this function.
fn ensure_current(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
/// This function is called by the renderers when all OpenGL commands have been issued and
/// the back buffer is reading for on-screen presentation. Typically implementations forward
/// this to platform specific APIs such as eglSwapBuffers.
fn swap_buffers(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
/// This function is called by the renderers when the surface needs to be resized, typically
/// in response to the windowing system notifying of a change in the window system.
/// For most implementations this is a no-op, with the exception for wayland for example.
fn resize(
&self,
width: NonZeroU32,
height: NonZeroU32,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
/// Returns the address of the OpenGL function specified by name, or a null pointer if the
/// function does not exist.
fn get_proc_address(&self, name: &std::ffi::CStr) -> *const std::ffi::c_void;
}
#[cfg(target_arch = "wasm32")]
struct WebGLNeedsNoCurrentContext;
#[cfg(target_arch = "wasm32")]
unsafe impl OpenGLInterface for WebGLNeedsNoCurrentContext {
fn ensure_current(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Ok(())
}
fn swap_buffers(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Ok(())
}
fn resize(
&self,
_width: NonZeroU32,
_height: NonZeroU32,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Ok(())
}
fn get_proc_address(&self, _: &std::ffi::CStr) -> *const std::ffi::c_void {
unreachable!()
}
}
struct SuspendedRenderer {}
unsafe impl OpenGLInterface for SuspendedRenderer {
fn ensure_current(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Err("ensure current called on suspended renderer".to_string().into())
}
fn swap_buffers(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Err("swap_buffers called on suspended renderer".to_string().into())
}
fn resize(
&self,
_: NonZeroU32,
_: NonZeroU32,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Ok(())
}
fn get_proc_address(&self, _: &std::ffi::CStr) -> *const std::ffi::c_void {
panic!("get_proc_address called on suspended renderer")
}
}
pub struct OpenGLBackend {
opengl_context: RefCell<Box<dyn OpenGLInterface>>,
#[cfg(target_family = "wasm")]
html_canvas: RefCell<Option<web_sys::HtmlCanvasElement>>,
}
impl OpenGLBackend {
pub fn set_opengl_context(
&self,
renderer: &FemtoVGRenderer<Self>,
#[cfg(not(target_arch = "wasm32"))] opengl_context: impl OpenGLInterface + 'static,
#[cfg(target_arch = "wasm32")] html_canvas: web_sys::HtmlCanvasElement,
) -> Result<(), i_slint_core::platform::PlatformError> {
#[cfg(target_arch = "wasm32")]
let opengl_context = WebGLNeedsNoCurrentContext {};
let opengl_context = Box::new(opengl_context);
#[cfg(not(target_arch = "wasm32"))]
let gl_renderer = unsafe {
femtovg::renderer::OpenGl::new_from_function_cstr(|name| {
opengl_context.get_proc_address(name)
})
.unwrap()
};
#[cfg(target_arch = "wasm32")]
let gl_renderer = match femtovg::renderer::OpenGl::new_from_html_canvas(&html_canvas) {
Ok(gl_renderer) => gl_renderer,
Err(_) => {
use wasm_bindgen::JsCast;
// I don't believe that there's a way of disabling the 2D canvas.
let context_2d = html_canvas
.get_context("2d")
.unwrap()
.unwrap()
.dyn_into::<web_sys::CanvasRenderingContext2d>()
.unwrap();
context_2d.set_font("20px serif");
// We don't know if we're rendering on dark or white background, so choose a "color" in the middle for the text.
context_2d.set_fill_style_str("red");
context_2d
.fill_text("Slint requires WebGL to be enabled in your browser", 0., 30.)
.unwrap();
panic!("Cannot proceed without WebGL - aborting")
}
};
let femtovg_canvas = femtovg::Canvas::new_with_text_context(
gl_renderer,
crate::font_cache::FONT_CACHE.with(|cache| cache.borrow().text_context.clone()),
)
.unwrap();
*self.opengl_context.borrow_mut() = opengl_context;
#[cfg(target_family = "wasm")]
{
*self.html_canvas.borrow_mut() = Some(html_canvas);
}
let canvas = Rc::new(RefCell::new(femtovg_canvas));
renderer.reset_canvas(canvas);
Ok(())
}
}
pub struct GLWindowSurface {}
impl WindowSurface<femtovg::renderer::OpenGl> for GLWindowSurface {
fn render_surface(&self) -> &<femtovg::renderer::OpenGl as femtovg::Renderer>::Surface {
&()
}
}
impl GraphicsBackend for OpenGLBackend {
type Renderer = femtovg::renderer::OpenGl;
type WindowSurface = GLWindowSurface;
const NAME: &'static str = "OpenGL";
fn new_suspended() -> Self {
Self {
opengl_context: RefCell::new(Box::new(SuspendedRenderer {})),
#[cfg(target_family = "wasm")]
html_canvas: RefCell::new(None),
}
}
fn clear_graphics_context(&self) {
*self.opengl_context.borrow_mut() = Box::new(SuspendedRenderer {});
}
/// Ensures that the OpenGL context is current when returning from this function.
fn begin_surface_rendering(
&self,
) -> Result<GLWindowSurface, Box<dyn std::error::Error + Send + Sync>> {
self.opengl_context.borrow().ensure_current()?;
Ok(GLWindowSurface {})
}
fn submit_commands(&self, _commands: <Self::Renderer as femtovg::Renderer>::CommandBuffer) {}
/// This function is called by the renderers when all OpenGL commands have been issued and
/// the back buffer is reading for on-screen presentation. Typically implementations forward
/// this to platform specific APIs such as eglSwapBuffers.
fn present_surface(
&self,
_surface: GLWindowSurface,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.opengl_context.borrow().swap_buffers()
}
#[cfg(not(target_family = "wasm"))]
fn with_graphics_api<R>(
&self,
callback: impl FnOnce(Option<i_slint_core::api::GraphicsAPI<'_>>) -> R,
) -> Result<R, i_slint_core::platform::PlatformError> {
use i_slint_core::api::GraphicsAPI;
self.opengl_context.borrow().ensure_current()?;
let api = GraphicsAPI::NativeOpenGL {
get_proc_address: &|name| self.opengl_context.borrow().get_proc_address(name),
};
Ok(callback(Some(api)))
}
#[cfg(target_family = "wasm")]
fn with_graphics_api<R>(
&self,
callback: impl FnOnce(Option<i_slint_core::api::GraphicsAPI<'_>>) -> R,
) -> Result<R, i_slint_core::platform::PlatformError> {
use i_slint_core::api::GraphicsAPI;
let id =
self.html_canvas.borrow().as_ref().map_or_else(|| String::new(), |canvas| canvas.id());
let api = GraphicsAPI::WebGL { canvas_element_id: &id, context_type: "webgl2" };
Ok(callback(Some(api)))
}
/// This function is called by the renderers when the surface needs to be resized, typically
/// in response to the windowing system notifying of a change in the window system.
/// For most implementations this is a no-op, with the exception for wayland for example.
fn resize(
&self,
width: NonZeroU32,
height: NonZeroU32,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.opengl_context.borrow().resize(width, height)
}
}
impl FemtoVGRenderer<OpenGLBackend> {
/// Creates a new renderer that renders using OpenGL. An implementation of the OpenGLInterface
/// trait needs to supplied.
pub fn new(
#[cfg(not(target_arch = "wasm32"))] opengl_context: impl OpenGLInterface + 'static,
#[cfg(target_arch = "wasm32")] html_canvas: web_sys::HtmlCanvasElement,
) -> Result<Self, PlatformError> {
use super::FemtoVGRendererExt;
let this = Self::new_suspended();
this.graphics_backend.set_opengl_context(
&this,
#[cfg(not(target_arch = "wasm32"))]
opengl_context,
#[cfg(target_arch = "wasm32")]
html_canvas,
)?;
Ok(this)
}
}