From b9a9576207ddfc7afd89da30b7cfc7ca0d7e335c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 6 Jan 2023 23:29:38 +0100 Subject: Remove `iced_glow`, `glyph-brush`, and `wgpu_glyph` dependencies --- Cargo.toml | 11 +- examples/integration/.gitignore | 2 + examples/integration/Cargo.toml | 22 + examples/integration/README.md | 36 ++ examples/integration/index.html | 21 + examples/integration/src/controls.rs | 112 +++++ examples/integration/src/main.rs | 289 ++++++++++++ examples/integration/src/scene.rs | 102 +++++ examples/integration/src/shader/frag.wgsl | 4 + examples/integration/src/shader/vert.wgsl | 6 + examples/integration_opengl/Cargo.toml | 12 - examples/integration_opengl/README.md | 16 - examples/integration_opengl/src/controls.rs | 101 ----- examples/integration_opengl/src/main.rs | 187 -------- examples/integration_opengl/src/scene.rs | 102 ----- examples/integration_wgpu/.gitignore | 2 - examples/integration_wgpu/Cargo.toml | 22 - examples/integration_wgpu/README.md | 36 -- examples/integration_wgpu/index.html | 21 - examples/integration_wgpu/src/controls.rs | 112 ----- examples/integration_wgpu/src/main.rs | 289 ------------ examples/integration_wgpu/src/scene.rs | 102 ----- examples/integration_wgpu/src/shader/frag.wgsl | 4 - examples/integration_wgpu/src/shader/vert.wgsl | 6 - glow/Cargo.toml | 51 --- glow/README.md | 51 --- glow/src/backend.rs | 280 ------------ glow/src/image.rs | 254 ----------- glow/src/image/storage.rs | 78 ---- glow/src/lib.rs | 53 --- glow/src/program.rs | 133 ------ glow/src/quad.rs | 74 --- glow/src/quad/compatibility.rs | 349 --------------- glow/src/quad/core.rs | 244 ---------- glow/src/settings.rs | 61 --- glow/src/shader/common/gradient.frag | 59 --- glow/src/shader/common/gradient.vert | 9 - glow/src/shader/common/image.frag | 22 - glow/src/shader/common/image.vert | 9 - glow/src/shader/common/solid.frag | 18 - glow/src/shader/common/solid.vert | 11 - glow/src/shader/compatibility/quad.frag | 83 ---- glow/src/shader/compatibility/quad.vert | 46 -- glow/src/shader/core/quad.frag | 95 ---- glow/src/shader/core/quad.vert | 52 --- glow/src/text.rs | 257 ----------- glow/src/triangle.rs | 595 ------------------------- glow/src/window.rs | 4 - glow/src/window/compositor.rs | 111 ----- graphics/Cargo.toml | 7 - graphics/src/font.rs | 35 -- graphics/src/font/source.rs | 45 -- graphics/src/lib.rs | 1 - wgpu/Cargo.toml | 4 - wgpu/src/backend.rs | 86 +--- wgpu/src/text.rs | 264 +---------- 56 files changed, 621 insertions(+), 4437 deletions(-) create mode 100644 examples/integration/.gitignore create mode 100644 examples/integration/Cargo.toml create mode 100644 examples/integration/README.md create mode 100644 examples/integration/index.html create mode 100644 examples/integration/src/controls.rs create mode 100644 examples/integration/src/main.rs create mode 100644 examples/integration/src/scene.rs create mode 100644 examples/integration/src/shader/frag.wgsl create mode 100644 examples/integration/src/shader/vert.wgsl delete mode 100644 examples/integration_opengl/Cargo.toml delete mode 100644 examples/integration_opengl/README.md delete mode 100644 examples/integration_opengl/src/controls.rs delete mode 100644 examples/integration_opengl/src/main.rs delete mode 100644 examples/integration_opengl/src/scene.rs delete mode 100644 examples/integration_wgpu/.gitignore delete mode 100644 examples/integration_wgpu/Cargo.toml delete mode 100644 examples/integration_wgpu/README.md delete mode 100644 examples/integration_wgpu/index.html delete mode 100644 examples/integration_wgpu/src/controls.rs delete mode 100644 examples/integration_wgpu/src/main.rs delete mode 100644 examples/integration_wgpu/src/scene.rs delete mode 100644 examples/integration_wgpu/src/shader/frag.wgsl delete mode 100644 examples/integration_wgpu/src/shader/vert.wgsl delete mode 100644 glow/Cargo.toml delete mode 100644 glow/README.md delete mode 100644 glow/src/backend.rs delete mode 100644 glow/src/image.rs delete mode 100644 glow/src/image/storage.rs delete mode 100644 glow/src/lib.rs delete mode 100644 glow/src/program.rs delete mode 100644 glow/src/quad.rs delete mode 100644 glow/src/quad/compatibility.rs delete mode 100644 glow/src/quad/core.rs delete mode 100644 glow/src/settings.rs delete mode 100644 glow/src/shader/common/gradient.frag delete mode 100644 glow/src/shader/common/gradient.vert delete mode 100644 glow/src/shader/common/image.frag delete mode 100644 glow/src/shader/common/image.vert delete mode 100644 glow/src/shader/common/solid.frag delete mode 100644 glow/src/shader/common/solid.vert delete mode 100644 glow/src/shader/compatibility/quad.frag delete mode 100644 glow/src/shader/compatibility/quad.vert delete mode 100644 glow/src/shader/core/quad.frag delete mode 100644 glow/src/shader/core/quad.vert delete mode 100644 glow/src/text.rs delete mode 100644 glow/src/triangle.rs delete mode 100644 glow/src/window.rs delete mode 100644 glow/src/window/compositor.rs delete mode 100644 graphics/src/font.rs delete mode 100644 graphics/src/font/source.rs diff --git a/Cargo.toml b/Cargo.toml index d26ec2b6..42c9488c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,19 +14,15 @@ categories = ["gui"] [features] default = ["wgpu"] # Enables the `Image` widget -image = ["iced_wgpu?/image", "iced_glow?/image", "image_rs"] +image = ["iced_wgpu?/image", "image_rs"] # Enables the `Svg` widget -svg = ["iced_wgpu?/svg", "iced_glow?/svg"] +svg = ["iced_wgpu?/svg"] # Enables the `Canvas` widget canvas = ["iced_graphics/canvas"] # Enables the `QRCode` widget qr_code = ["iced_graphics/qr_code"] # Enables the `iced_wgpu` renderer wgpu = ["iced_wgpu"] -# Enables using system fonts -default_system_font = ["iced_wgpu?/default_system_font", "iced_glow?/default_system_font"] -# Enables the `iced_glow` renderer. Overrides `iced_wgpu` -glow = ["iced_glow", "iced_glutin"] # Enables a debug view in native platforms (press F12) debug = ["iced_winit/debug"] # Enables `tokio` as the `executor::Default` on native platforms @@ -44,7 +40,6 @@ chrome-trace = [ "iced_winit/chrome-trace", "iced_glutin?/trace", "iced_wgpu?/tracing", - "iced_glow?/tracing", ] [badges] @@ -55,7 +50,6 @@ members = [ "core", "futures", "graphics", - "glow", "glutin", "lazy", "native", @@ -72,7 +66,6 @@ iced_native = { version = "0.9", path = "native" } iced_graphics = { version = "0.7", path = "graphics" } iced_winit = { version = "0.8", path = "winit", features = ["application"] } iced_glutin = { version = "0.7", path = "glutin", optional = true } -iced_glow = { version = "0.7", path = "glow", optional = true } thiserror = "1.0" [dependencies.image_rs] diff --git a/examples/integration/.gitignore b/examples/integration/.gitignore new file mode 100644 index 00000000..e188dc28 --- /dev/null +++ b/examples/integration/.gitignore @@ -0,0 +1,2 @@ +*.wasm +*.js diff --git a/examples/integration/Cargo.toml b/examples/integration/Cargo.toml new file mode 100644 index 00000000..200306aa --- /dev/null +++ b/examples/integration/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "integration" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2021" +publish = false + +[dependencies] +iced_winit = { path = "../../winit" } +iced_wgpu = { path = "../../wgpu", features = ["webgl"] } +env_logger = "0.8" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +console_error_panic_hook = "0.1.7" +console_log = "0.2.0" +log = "0.4" +wasm-bindgen = "0.2" +web-sys = { version = "0.3", features = ["Element", "HtmlCanvasElement", "Window", "Document"] } +# This dependency a little bit quirky, it is deep in the tree and without `js` feature it +# refuses to work with `wasm32-unknown-unknown target`. Unfortunately, we need this patch +# to make it work +getrandom = { version = "0.2", features = ["js"] } diff --git a/examples/integration/README.md b/examples/integration/README.md new file mode 100644 index 00000000..ece9ba1e --- /dev/null +++ b/examples/integration/README.md @@ -0,0 +1,36 @@ +## `wgpu` integration + +A demonstration of how to integrate Iced in an existing [`wgpu`] application. + +The __[`main`]__ file contains all the code of the example. + +
+ + + +
+ +You can run it with `cargo run`: +``` +cargo run --package integration_wgpu +``` + +### How to run this example with WebGL backend +NOTE: Currently, WebGL backend is is still experimental, so expect bugs. + +```sh +# 0. Install prerequisites +cargo install wasm-bindgen-cli https +# 1. cd to the current folder +# 2. Compile wasm module +cargo build -p integration_wgpu --target wasm32-unknown-unknown +# 3. Invoke wasm-bindgen +wasm-bindgen ../../target/wasm32-unknown-unknown/debug/integration_wgpu.wasm --out-dir . --target web --no-typescript +# 4. run http server +http +# 5. Open 127.0.0.1:8000 in browser +``` + + +[`main`]: src/main.rs +[`wgpu`]: https://github.com/gfx-rs/wgpu diff --git a/examples/integration/index.html b/examples/integration/index.html new file mode 100644 index 00000000..920bc4a0 --- /dev/null +++ b/examples/integration/index.html @@ -0,0 +1,21 @@ + + + + + Iced - wgpu + WebGL integration + + +

integration_wgpu

+ + + + + diff --git a/examples/integration/src/controls.rs b/examples/integration/src/controls.rs new file mode 100644 index 00000000..533cb6e2 --- /dev/null +++ b/examples/integration/src/controls.rs @@ -0,0 +1,112 @@ +use iced_wgpu::Renderer; +use iced_winit::widget::{slider, text_input, Column, Row, Text}; +use iced_winit::{Alignment, Color, Command, Element, Length, Program}; + +pub struct Controls { + background_color: Color, + text: String, +} + +#[derive(Debug, Clone)] +pub enum Message { + BackgroundColorChanged(Color), + TextChanged(String), +} + +impl Controls { + pub fn new() -> Controls { + Controls { + background_color: Color::BLACK, + text: Default::default(), + } + } + + pub fn background_color(&self) -> Color { + self.background_color + } +} + +impl Program for Controls { + type Renderer = Renderer; + type Message = Message; + + fn update(&mut self, message: Message) -> Command { + match message { + Message::BackgroundColorChanged(color) => { + self.background_color = color; + } + Message::TextChanged(text) => { + self.text = text; + } + } + + Command::none() + } + + fn view(&self) -> Element { + let background_color = self.background_color; + let text = &self.text; + + let sliders = Row::new() + .width(500) + .spacing(20) + .push( + slider(0.0..=1.0, background_color.r, move |r| { + Message::BackgroundColorChanged(Color { + r, + ..background_color + }) + }) + .step(0.01), + ) + .push( + slider(0.0..=1.0, background_color.g, move |g| { + Message::BackgroundColorChanged(Color { + g, + ..background_color + }) + }) + .step(0.01), + ) + .push( + slider(0.0..=1.0, background_color.b, move |b| { + Message::BackgroundColorChanged(Color { + b, + ..background_color + }) + }) + .step(0.01), + ); + + Row::new() + .width(Length::Fill) + .height(Length::Fill) + .align_items(Alignment::End) + .push( + Column::new() + .width(Length::Fill) + .align_items(Alignment::End) + .push( + Column::new() + .padding(10) + .spacing(10) + .push( + Text::new("Background color") + .style(Color::WHITE), + ) + .push(sliders) + .push( + Text::new(format!("{background_color:?}")) + .size(14) + .style(Color::WHITE), + ) + .push(text_input( + "Placeholder", + text, + Message::TextChanged, + )), + ), + ) + .into() + } +} diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs new file mode 100644 index 00000000..2a56b6fa --- /dev/null +++ b/examples/integration/src/main.rs @@ -0,0 +1,289 @@ +mod controls; +mod scene; + +use controls::Controls; +use scene::Scene; + +use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport}; +use iced_winit::{ + conversion, futures, program, renderer, winit, Clipboard, Color, Debug, + Size, +}; + +use winit::{ + dpi::PhysicalPosition, + event::{Event, ModifiersState, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, +}; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::JsCast; +#[cfg(target_arch = "wasm32")] +use web_sys::HtmlCanvasElement; +#[cfg(target_arch = "wasm32")] +use winit::platform::web::WindowBuilderExtWebSys; + +pub fn main() { + #[cfg(target_arch = "wasm32")] + let canvas_element = { + console_log::init_with_level(log::Level::Debug) + .expect("could not initialize logger"); + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + + web_sys::window() + .and_then(|win| win.document()) + .and_then(|doc| doc.get_element_by_id("iced_canvas")) + .and_then(|element| element.dyn_into::().ok()) + .expect("Canvas with id `iced_canvas` is missing") + }; + #[cfg(not(target_arch = "wasm32"))] + env_logger::init(); + + // Initialize winit + let event_loop = EventLoop::new(); + + #[cfg(target_arch = "wasm32")] + let window = winit::window::WindowBuilder::new() + .with_canvas(Some(canvas_element)) + .build(&event_loop) + .expect("Failed to build winit window"); + + #[cfg(not(target_arch = "wasm32"))] + let window = winit::window::Window::new(&event_loop).unwrap(); + + let physical_size = window.inner_size(); + let mut viewport = Viewport::with_physical_size( + Size::new(physical_size.width, physical_size.height), + window.scale_factor(), + ); + let mut cursor_position = PhysicalPosition::new(-1.0, -1.0); + let mut modifiers = ModifiersState::default(); + let mut clipboard = Clipboard::connect(&window); + + // Initialize wgpu + + #[cfg(target_arch = "wasm32")] + let default_backend = wgpu::Backends::GL; + #[cfg(not(target_arch = "wasm32"))] + let default_backend = wgpu::Backends::PRIMARY; + + let backend = + wgpu::util::backend_bits_from_env().unwrap_or(default_backend); + + let instance = wgpu::Instance::new(backend); + let surface = unsafe { instance.create_surface(&window) }; + + let (format, (device, queue)) = futures::executor::block_on(async { + let adapter = wgpu::util::initialize_adapter_from_env_or_default( + &instance, + backend, + Some(&surface), + ) + .await + .expect("No suitable GPU adapters found on the system!"); + + let adapter_features = adapter.features(); + + #[cfg(target_arch = "wasm32")] + let needed_limits = wgpu::Limits::downlevel_webgl2_defaults() + .using_resolution(adapter.limits()); + + #[cfg(not(target_arch = "wasm32"))] + let needed_limits = wgpu::Limits::default(); + + ( + surface + .get_supported_formats(&adapter) + .first() + .copied() + .expect("Get preferred format"), + adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + features: adapter_features & wgpu::Features::default(), + limits: needed_limits, + }, + None, + ) + .await + .expect("Request device"), + ) + }); + + surface.configure( + &device, + &wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format, + width: physical_size.width, + height: physical_size.height, + present_mode: wgpu::PresentMode::AutoVsync, + alpha_mode: wgpu::CompositeAlphaMode::Auto, + }, + ); + + let mut resized = false; + + // Initialize staging belt + let mut staging_belt = wgpu::util::StagingBelt::new(5 * 1024); + + // Initialize scene and GUI controls + let scene = Scene::new(&device, format); + let controls = Controls::new(); + + // Initialize iced + let mut debug = Debug::new(); + let mut renderer = + Renderer::new(Backend::new(&device, Settings::default(), format)); + + let mut state = program::State::new( + controls, + viewport.logical_size(), + &mut renderer, + &mut debug, + ); + + // Run event loop + event_loop.run(move |event, _, control_flow| { + // You should change this if you want to render continuosly + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { event, .. } => { + match event { + WindowEvent::CursorMoved { position, .. } => { + cursor_position = position; + } + WindowEvent::ModifiersChanged(new_modifiers) => { + modifiers = new_modifiers; + } + WindowEvent::Resized(_) => { + resized = true; + } + WindowEvent::CloseRequested => { + *control_flow = ControlFlow::Exit; + } + _ => {} + } + + // Map window event to iced event + if let Some(event) = iced_winit::conversion::window_event( + &event, + window.scale_factor(), + modifiers, + ) { + state.queue_event(event); + } + } + Event::MainEventsCleared => { + // If there are events pending + if !state.is_queue_empty() { + // We update iced + let _ = state.update( + viewport.logical_size(), + conversion::cursor_position( + cursor_position, + viewport.scale_factor(), + ), + &mut renderer, + &iced_wgpu::Theme::Dark, + &renderer::Style { text_color: Color::WHITE }, + &mut clipboard, + &mut debug, + ); + + // and request a redraw + window.request_redraw(); + } + } + Event::RedrawRequested(_) => { + if resized { + let size = window.inner_size(); + + viewport = Viewport::with_physical_size( + Size::new(size.width, size.height), + window.scale_factor(), + ); + + surface.configure( + &device, + &wgpu::SurfaceConfiguration { + format, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::AutoVsync, + alpha_mode: wgpu::CompositeAlphaMode::Auto + }, + ); + + resized = false; + } + + match surface.get_current_texture() { + Ok(frame) => { + let mut encoder = device.create_command_encoder( + &wgpu::CommandEncoderDescriptor { label: None }, + ); + + let program = state.program(); + + let view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default()); + + { + // We clear the frame + let mut render_pass = scene.clear( + &view, + &mut encoder, + program.background_color(), + ); + + // Draw the scene + scene.draw(&mut render_pass); + } + + // And then iced on top + renderer.with_primitives(|backend, primitive| { + backend.present( + &device, + &mut staging_belt, + &mut encoder, + &view, + primitive, + &viewport, + &debug.overlay(), + ); + }); + + // Then we submit the work + staging_belt.finish(); + queue.submit(Some(encoder.finish())); + frame.present(); + + // Update the mouse cursor + window.set_cursor_icon( + iced_winit::conversion::mouse_interaction( + state.mouse_interaction(), + ), + ); + + // And recall staging buffers + staging_belt.recall(); + + } + Err(error) => match error { + wgpu::SurfaceError::OutOfMemory => { + panic!("Swapchain error: {error}. Rendering cannot continue.") + } + _ => { + // Try rendering again next frame. + window.request_redraw(); + } + }, + } + } + _ => {} + } + }) +} diff --git a/examples/integration/src/scene.rs b/examples/integration/src/scene.rs new file mode 100644 index 00000000..3e41fbda --- /dev/null +++ b/examples/integration/src/scene.rs @@ -0,0 +1,102 @@ +use iced_wgpu::wgpu; +use iced_winit::Color; + +pub struct Scene { + pipeline: wgpu::RenderPipeline, +} + +impl Scene { + pub fn new( + device: &wgpu::Device, + texture_format: wgpu::TextureFormat, + ) -> Scene { + let pipeline = build_pipeline(device, texture_format); + + Scene { pipeline } + } + + pub fn clear<'a>( + &self, + target: &'a wgpu::TextureView, + encoder: &'a mut wgpu::CommandEncoder, + background_color: Color, + ) -> wgpu::RenderPass<'a> { + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear({ + let [r, g, b, a] = background_color.into_linear(); + + wgpu::Color { + r: r as f64, + g: g as f64, + b: b as f64, + a: a as f64, + } + }), + store: true, + }, + })], + depth_stencil_attachment: None, + }) + } + + pub fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { + render_pass.set_pipeline(&self.pipeline); + render_pass.draw(0..3, 0..1); + } +} + +fn build_pipeline( + device: &wgpu::Device, + texture_format: wgpu::TextureFormat, +) -> wgpu::RenderPipeline { + let (vs_module, fs_module) = ( + device.create_shader_module(wgpu::include_wgsl!("shader/vert.wgsl")), + device.create_shader_module(wgpu::include_wgsl!("shader/frag.wgsl")), + ); + + let pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + push_constant_ranges: &[], + bind_group_layouts: &[], + }); + + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &vs_module, + entry_point: "main", + buffers: &[], + }, + fragment: Some(wgpu::FragmentState { + module: &fs_module, + entry_point: "main", + targets: &[Some(wgpu::ColorTargetState { + format: texture_format, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent::REPLACE, + alpha: wgpu::BlendComponent::REPLACE, + }), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + front_face: wgpu::FrontFace::Ccw, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + }) +} diff --git a/examples/integration/src/shader/frag.wgsl b/examples/integration/src/shader/frag.wgsl new file mode 100644 index 00000000..cf27bb56 --- /dev/null +++ b/examples/integration/src/shader/frag.wgsl @@ -0,0 +1,4 @@ +@fragment +fn main() -> @location(0) vec4 { + return vec4(1.0, 0.0, 0.0, 1.0); +} diff --git a/examples/integration/src/shader/vert.wgsl b/examples/integration/src/shader/vert.wgsl new file mode 100644 index 00000000..e353e6ba --- /dev/null +++ b/examples/integration/src/shader/vert.wgsl @@ -0,0 +1,6 @@ +@vertex +fn main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4 { + let x = f32(1 - i32(in_vertex_index)) * 0.5; + let y = f32(1 - i32(in_vertex_index & 1u) * 2) * 0.5; + return vec4(x, y, 0.0, 1.0); +} diff --git a/examples/integration_opengl/Cargo.toml b/examples/integration_opengl/Cargo.toml deleted file mode 100644 index 6dac999c..00000000 --- a/examples/integration_opengl/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "integration_opengl" -version = "0.1.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2021" -publish = false - -[dependencies] -iced_glutin = { path = "../../glutin" } -iced_glow = { path = "../../glow" } -iced_winit = { path = "../../winit" } -env_logger = "0.8" diff --git a/examples/integration_opengl/README.md b/examples/integration_opengl/README.md deleted file mode 100644 index b7c2c074..00000000 --- a/examples/integration_opengl/README.md +++ /dev/null @@ -1,16 +0,0 @@ -## OpenGL integration - -A demonstration of how to integrate Iced in an existing graphical OpenGL application. - -The __[`main`]__ file contains all the code of the example. - -
- image host -
- -You can run it with `cargo run`: -``` -cargo run --package integration_opengl -``` - -[`main`]: src/main.rs diff --git a/examples/integration_opengl/src/controls.rs b/examples/integration_opengl/src/controls.rs deleted file mode 100644 index c3648f44..00000000 --- a/examples/integration_opengl/src/controls.rs +++ /dev/null @@ -1,101 +0,0 @@ -use iced_glow::Renderer; -use iced_glutin::widget::Slider; -use iced_glutin::widget::{Column, Row, Text}; -use iced_glutin::{Alignment, Color, Command, Element, Length, Program}; - -pub struct Controls { - background_color: Color, -} - -#[derive(Debug, Clone)] -pub enum Message { - BackgroundColorChanged(Color), -} - -impl Controls { - pub fn new() -> Controls { - Controls { - background_color: Color::BLACK, - } - } - - pub fn background_color(&self) -> Color { - self.background_color - } -} - -impl Program for Controls { - type Renderer = Renderer; - type Message = Message; - - fn update(&mut self, message: Message) -> Command { - match message { - Message::BackgroundColorChanged(color) => { - self.background_color = color; - } - } - - Command::none() - } - - fn view(&self) -> Element { - let background_color = self.background_color; - - let sliders = Row::new() - .width(500) - .spacing(20) - .push( - Slider::new(0.0..=1.0, background_color.r, move |r| { - Message::BackgroundColorChanged(Color { - r, - ..background_color - }) - }) - .step(0.01), - ) - .push( - Slider::new(0.0..=1.0, background_color.g, move |g| { - Message::BackgroundColorChanged(Color { - g, - ..background_color - }) - }) - .step(0.01), - ) - .push( - Slider::new(0.0..=1.0, background_color.b, move |b| { - Message::BackgroundColorChanged(Color { - b, - ..background_color - }) - }) - .step(0.01), - ); - - Row::new() - .width(Length::Fill) - .height(Length::Fill) - .align_items(Alignment::End) - .push( - Column::new() - .width(Length::Fill) - .align_items(Alignment::End) - .push( - Column::new() - .padding(10) - .spacing(10) - .push( - Text::new("Background color") - .style(Color::WHITE), - ) - .push(sliders) - .push( - Text::new(format!("{background_color:?}")) - .size(14) - .style(Color::WHITE), - ), - ), - ) - .into() - } -} diff --git a/examples/integration_opengl/src/main.rs b/examples/integration_opengl/src/main.rs deleted file mode 100644 index f161c8a0..00000000 --- a/examples/integration_opengl/src/main.rs +++ /dev/null @@ -1,187 +0,0 @@ -mod controls; -mod scene; - -use controls::Controls; -use scene::Scene; - -use glow::*; -use glutin::dpi::PhysicalPosition; -use glutin::event::{Event, ModifiersState, WindowEvent}; -use glutin::event_loop::ControlFlow; -use iced_glow::glow; -use iced_glow::{Backend, Renderer, Settings, Viewport}; -use iced_glutin::conversion; -use iced_glutin::glutin; -use iced_glutin::renderer; -use iced_glutin::{program, Clipboard, Color, Debug, Size}; - -pub fn main() { - env_logger::init(); - let (gl, event_loop, windowed_context, shader_version) = { - let el = glutin::event_loop::EventLoop::new(); - - let wb = glutin::window::WindowBuilder::new() - .with_title("OpenGL integration example") - .with_inner_size(glutin::dpi::LogicalSize::new(1024.0, 768.0)); - - let windowed_context = glutin::ContextBuilder::new() - .with_vsync(true) - .build_windowed(wb, &el) - .unwrap(); - - unsafe { - let windowed_context = windowed_context.make_current().unwrap(); - - let gl = glow::Context::from_loader_function(|s| { - windowed_context.get_proc_address(s) as *const _ - }); - - // Enable auto-conversion from/to sRGB - gl.enable(glow::FRAMEBUFFER_SRGB); - - // Enable alpha blending - gl.enable(glow::BLEND); - gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA); - - // Disable multisampling by default - gl.disable(glow::MULTISAMPLE); - - (gl, el, windowed_context, "#version 410") - } - }; - - let physical_size = windowed_context.window().inner_size(); - let mut viewport = Viewport::with_physical_size( - Size::new(physical_size.width, physical_size.height), - windowed_context.window().scale_factor(), - ); - - let mut cursor_position = PhysicalPosition::new(-1.0, -1.0); - let mut modifiers = ModifiersState::default(); - let mut clipboard = Clipboard::connect(windowed_context.window()); - - let mut renderer = Renderer::new(Backend::new(&gl, Settings::default())); - - let mut debug = Debug::new(); - - let controls = Controls::new(); - let mut state = program::State::new( - controls, - viewport.logical_size(), - &mut renderer, - &mut debug, - ); - let mut resized = false; - - let scene = Scene::new(&gl, shader_version); - - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - match event { - Event::WindowEvent { event, .. } => { - match event { - WindowEvent::CursorMoved { position, .. } => { - cursor_position = position; - } - WindowEvent::ModifiersChanged(new_modifiers) => { - modifiers = new_modifiers; - } - WindowEvent::Resized(physical_size) => { - viewport = Viewport::with_physical_size( - Size::new( - physical_size.width, - physical_size.height, - ), - windowed_context.window().scale_factor(), - ); - - resized = true; - } - WindowEvent::CloseRequested => { - scene.cleanup(&gl); - *control_flow = ControlFlow::Exit - } - _ => (), - } - - // Map window event to iced event - if let Some(event) = iced_winit::conversion::window_event( - &event, - windowed_context.window().scale_factor(), - modifiers, - ) { - state.queue_event(event); - } - } - Event::MainEventsCleared => { - // If there are events pending - if !state.is_queue_empty() { - // We update iced - let _ = state.update( - viewport.logical_size(), - conversion::cursor_position( - cursor_position, - viewport.scale_factor(), - ), - &mut renderer, - &iced_glow::Theme::Dark, - &renderer::Style { - text_color: Color::WHITE, - }, - &mut clipboard, - &mut debug, - ); - - // and request a redraw - windowed_context.window().request_redraw(); - } - } - Event::RedrawRequested(_) => { - if resized { - let size = windowed_context.window().inner_size(); - - unsafe { - gl.viewport( - 0, - 0, - size.width as i32, - size.height as i32, - ); - } - - resized = false; - } - - let program = state.program(); - { - // We clear the frame - scene.clear(&gl, program.background_color()); - - // Draw the scene - scene.draw(&gl); - } - - // And then iced on top - renderer.with_primitives(|backend, primitive| { - backend.present( - &gl, - primitive, - &viewport, - &debug.overlay(), - ); - }); - - // Update the mouse cursor - windowed_context.window().set_cursor_icon( - iced_winit::conversion::mouse_interaction( - state.mouse_interaction(), - ), - ); - - windowed_context.swap_buffers().unwrap(); - } - _ => (), - } - }); -} diff --git a/examples/integration_opengl/src/scene.rs b/examples/integration_opengl/src/scene.rs deleted file mode 100644 index c1d05b65..00000000 --- a/examples/integration_opengl/src/scene.rs +++ /dev/null @@ -1,102 +0,0 @@ -use glow::*; -use iced_glow::glow; -use iced_glow::Color; - -pub struct Scene { - program: glow::Program, - vertex_array: glow::VertexArray, -} - -impl Scene { - pub fn new(gl: &glow::Context, shader_version: &str) -> Self { - unsafe { - let vertex_array = gl - .create_vertex_array() - .expect("Cannot create vertex array"); - gl.bind_vertex_array(Some(vertex_array)); - - let program = gl.create_program().expect("Cannot create program"); - - let (vertex_shader_source, fragment_shader_source) = ( - r#"const vec2 verts[3] = vec2[3]( - vec2(0.5f, 1.0f), - vec2(0.0f, 0.0f), - vec2(1.0f, 0.0f) - ); - out vec2 vert; - void main() { - vert = verts[gl_VertexID]; - gl_Position = vec4(vert - 0.5, 0.0, 1.0); - }"#, - r#"precision highp float; - in vec2 vert; - out vec4 color; - void main() { - color = vec4(vert, 0.5, 1.0); - }"#, - ); - - let shader_sources = [ - (glow::VERTEX_SHADER, vertex_shader_source), - (glow::FRAGMENT_SHADER, fragment_shader_source), - ]; - - let mut shaders = Vec::with_capacity(shader_sources.len()); - - for (shader_type, shader_source) in shader_sources.iter() { - let shader = gl - .create_shader(*shader_type) - .expect("Cannot create shader"); - gl.shader_source( - shader, - &format!("{shader_version}\n{shader_source}"), - ); - gl.compile_shader(shader); - if !gl.get_shader_compile_status(shader) { - panic!("{}", gl.get_shader_info_log(shader)); - } - gl.attach_shader(program, shader); - shaders.push(shader); - } - - gl.link_program(program); - if !gl.get_program_link_status(program) { - panic!("{}", gl.get_program_info_log(program)); - } - - for shader in shaders { - gl.detach_shader(program, shader); - gl.delete_shader(shader); - } - - gl.use_program(Some(program)); - Self { - program, - vertex_array, - } - } - } - - pub fn clear(&self, gl: &glow::Context, background_color: Color) { - let [r, g, b, a] = background_color.into_linear(); - unsafe { - gl.clear_color(r, g, b, a); - gl.clear(glow::COLOR_BUFFER_BIT); - } - } - - pub fn draw(&self, gl: &glow::Context) { - unsafe { - gl.bind_vertex_array(Some(self.vertex_array)); - gl.use_program(Some(self.program)); - gl.draw_arrays(glow::TRIANGLES, 0, 3); - } - } - - pub fn cleanup(&self, gl: &glow::Context) { - unsafe { - gl.delete_program(self.program); - gl.delete_vertex_array(self.vertex_array); - } - } -} diff --git a/examples/integration_wgpu/.gitignore b/examples/integration_wgpu/.gitignore deleted file mode 100644 index e188dc28..00000000 --- a/examples/integration_wgpu/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.wasm -*.js diff --git a/examples/integration_wgpu/Cargo.toml b/examples/integration_wgpu/Cargo.toml deleted file mode 100644 index eaa1df7e..00000000 --- a/examples/integration_wgpu/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "integration_wgpu" -version = "0.1.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2021" -publish = false - -[dependencies] -iced_winit = { path = "../../winit" } -iced_wgpu = { path = "../../wgpu", features = ["webgl"] } -env_logger = "0.8" - -[target.'cfg(target_arch = "wasm32")'.dependencies] -console_error_panic_hook = "0.1.7" -console_log = "0.2.0" -log = "0.4" -wasm-bindgen = "0.2" -web-sys = { version = "0.3", features = ["Element", "HtmlCanvasElement", "Window", "Document"] } -# This dependency a little bit quirky, it is deep in the tree and without `js` feature it -# refuses to work with `wasm32-unknown-unknown target`. Unfortunately, we need this patch -# to make it work -getrandom = { version = "0.2", features = ["js"] } diff --git a/examples/integration_wgpu/README.md b/examples/integration_wgpu/README.md deleted file mode 100644 index ece9ba1e..00000000 --- a/examples/integration_wgpu/README.md +++ /dev/null @@ -1,36 +0,0 @@ -## `wgpu` integration - -A demonstration of how to integrate Iced in an existing [`wgpu`] application. - -The __[`main`]__ file contains all the code of the example. - - - -You can run it with `cargo run`: -``` -cargo run --package integration_wgpu -``` - -### How to run this example with WebGL backend -NOTE: Currently, WebGL backend is is still experimental, so expect bugs. - -```sh -# 0. Install prerequisites -cargo install wasm-bindgen-cli https -# 1. cd to the current folder -# 2. Compile wasm module -cargo build -p integration_wgpu --target wasm32-unknown-unknown -# 3. Invoke wasm-bindgen -wasm-bindgen ../../target/wasm32-unknown-unknown/debug/integration_wgpu.wasm --out-dir . --target web --no-typescript -# 4. run http server -http -# 5. Open 127.0.0.1:8000 in browser -``` - - -[`main`]: src/main.rs -[`wgpu`]: https://github.com/gfx-rs/wgpu diff --git a/examples/integration_wgpu/index.html b/examples/integration_wgpu/index.html deleted file mode 100644 index 461e67a4..00000000 --- a/examples/integration_wgpu/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - Iced - wgpu + WebGL integration - - -

integration_wgpu

- - - - - diff --git a/examples/integration_wgpu/src/controls.rs b/examples/integration_wgpu/src/controls.rs deleted file mode 100644 index 533cb6e2..00000000 --- a/examples/integration_wgpu/src/controls.rs +++ /dev/null @@ -1,112 +0,0 @@ -use iced_wgpu::Renderer; -use iced_winit::widget::{slider, text_input, Column, Row, Text}; -use iced_winit::{Alignment, Color, Command, Element, Length, Program}; - -pub struct Controls { - background_color: Color, - text: String, -} - -#[derive(Debug, Clone)] -pub enum Message { - BackgroundColorChanged(Color), - TextChanged(String), -} - -impl Controls { - pub fn new() -> Controls { - Controls { - background_color: Color::BLACK, - text: Default::default(), - } - } - - pub fn background_color(&self) -> Color { - self.background_color - } -} - -impl Program for Controls { - type Renderer = Renderer; - type Message = Message; - - fn update(&mut self, message: Message) -> Command { - match message { - Message::BackgroundColorChanged(color) => { - self.background_color = color; - } - Message::TextChanged(text) => { - self.text = text; - } - } - - Command::none() - } - - fn view(&self) -> Element { - let background_color = self.background_color; - let text = &self.text; - - let sliders = Row::new() - .width(500) - .spacing(20) - .push( - slider(0.0..=1.0, background_color.r, move |r| { - Message::BackgroundColorChanged(Color { - r, - ..background_color - }) - }) - .step(0.01), - ) - .push( - slider(0.0..=1.0, background_color.g, move |g| { - Message::BackgroundColorChanged(Color { - g, - ..background_color - }) - }) - .step(0.01), - ) - .push( - slider(0.0..=1.0, background_color.b, move |b| { - Message::BackgroundColorChanged(Color { - b, - ..background_color - }) - }) - .step(0.01), - ); - - Row::new() - .width(Length::Fill) - .height(Length::Fill) - .align_items(Alignment::End) - .push( - Column::new() - .width(Length::Fill) - .align_items(Alignment::End) - .push( - Column::new() - .padding(10) - .spacing(10) - .push( - Text::new("Background color") - .style(Color::WHITE), - ) - .push(sliders) - .push( - Text::new(format!("{background_color:?}")) - .size(14) - .style(Color::WHITE), - ) - .push(text_input( - "Placeholder", - text, - Message::TextChanged, - )), - ), - ) - .into() - } -} diff --git a/examples/integration_wgpu/src/main.rs b/examples/integration_wgpu/src/main.rs deleted file mode 100644 index 2a56b6fa..00000000 --- a/examples/integration_wgpu/src/main.rs +++ /dev/null @@ -1,289 +0,0 @@ -mod controls; -mod scene; - -use controls::Controls; -use scene::Scene; - -use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport}; -use iced_winit::{ - conversion, futures, program, renderer, winit, Clipboard, Color, Debug, - Size, -}; - -use winit::{ - dpi::PhysicalPosition, - event::{Event, ModifiersState, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, -}; - -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::JsCast; -#[cfg(target_arch = "wasm32")] -use web_sys::HtmlCanvasElement; -#[cfg(target_arch = "wasm32")] -use winit::platform::web::WindowBuilderExtWebSys; - -pub fn main() { - #[cfg(target_arch = "wasm32")] - let canvas_element = { - console_log::init_with_level(log::Level::Debug) - .expect("could not initialize logger"); - std::panic::set_hook(Box::new(console_error_panic_hook::hook)); - - web_sys::window() - .and_then(|win| win.document()) - .and_then(|doc| doc.get_element_by_id("iced_canvas")) - .and_then(|element| element.dyn_into::().ok()) - .expect("Canvas with id `iced_canvas` is missing") - }; - #[cfg(not(target_arch = "wasm32"))] - env_logger::init(); - - // Initialize winit - let event_loop = EventLoop::new(); - - #[cfg(target_arch = "wasm32")] - let window = winit::window::WindowBuilder::new() - .with_canvas(Some(canvas_element)) - .build(&event_loop) - .expect("Failed to build winit window"); - - #[cfg(not(target_arch = "wasm32"))] - let window = winit::window::Window::new(&event_loop).unwrap(); - - let physical_size = window.inner_size(); - let mut viewport = Viewport::with_physical_size( - Size::new(physical_size.width, physical_size.height), - window.scale_factor(), - ); - let mut cursor_position = PhysicalPosition::new(-1.0, -1.0); - let mut modifiers = ModifiersState::default(); - let mut clipboard = Clipboard::connect(&window); - - // Initialize wgpu - - #[cfg(target_arch = "wasm32")] - let default_backend = wgpu::Backends::GL; - #[cfg(not(target_arch = "wasm32"))] - let default_backend = wgpu::Backends::PRIMARY; - - let backend = - wgpu::util::backend_bits_from_env().unwrap_or(default_backend); - - let instance = wgpu::Instance::new(backend); - let surface = unsafe { instance.create_surface(&window) }; - - let (format, (device, queue)) = futures::executor::block_on(async { - let adapter = wgpu::util::initialize_adapter_from_env_or_default( - &instance, - backend, - Some(&surface), - ) - .await - .expect("No suitable GPU adapters found on the system!"); - - let adapter_features = adapter.features(); - - #[cfg(target_arch = "wasm32")] - let needed_limits = wgpu::Limits::downlevel_webgl2_defaults() - .using_resolution(adapter.limits()); - - #[cfg(not(target_arch = "wasm32"))] - let needed_limits = wgpu::Limits::default(); - - ( - surface - .get_supported_formats(&adapter) - .first() - .copied() - .expect("Get preferred format"), - adapter - .request_device( - &wgpu::DeviceDescriptor { - label: None, - features: adapter_features & wgpu::Features::default(), - limits: needed_limits, - }, - None, - ) - .await - .expect("Request device"), - ) - }); - - surface.configure( - &device, - &wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format, - width: physical_size.width, - height: physical_size.height, - present_mode: wgpu::PresentMode::AutoVsync, - alpha_mode: wgpu::CompositeAlphaMode::Auto, - }, - ); - - let mut resized = false; - - // Initialize staging belt - let mut staging_belt = wgpu::util::StagingBelt::new(5 * 1024); - - // Initialize scene and GUI controls - let scene = Scene::new(&device, format); - let controls = Controls::new(); - - // Initialize iced - let mut debug = Debug::new(); - let mut renderer = - Renderer::new(Backend::new(&device, Settings::default(), format)); - - let mut state = program::State::new( - controls, - viewport.logical_size(), - &mut renderer, - &mut debug, - ); - - // Run event loop - event_loop.run(move |event, _, control_flow| { - // You should change this if you want to render continuosly - *control_flow = ControlFlow::Wait; - - match event { - Event::WindowEvent { event, .. } => { - match event { - WindowEvent::CursorMoved { position, .. } => { - cursor_position = position; - } - WindowEvent::ModifiersChanged(new_modifiers) => { - modifiers = new_modifiers; - } - WindowEvent::Resized(_) => { - resized = true; - } - WindowEvent::CloseRequested => { - *control_flow = ControlFlow::Exit; - } - _ => {} - } - - // Map window event to iced event - if let Some(event) = iced_winit::conversion::window_event( - &event, - window.scale_factor(), - modifiers, - ) { - state.queue_event(event); - } - } - Event::MainEventsCleared => { - // If there are events pending - if !state.is_queue_empty() { - // We update iced - let _ = state.update( - viewport.logical_size(), - conversion::cursor_position( - cursor_position, - viewport.scale_factor(), - ), - &mut renderer, - &iced_wgpu::Theme::Dark, - &renderer::Style { text_color: Color::WHITE }, - &mut clipboard, - &mut debug, - ); - - // and request a redraw - window.request_redraw(); - } - } - Event::RedrawRequested(_) => { - if resized { - let size = window.inner_size(); - - viewport = Viewport::with_physical_size( - Size::new(size.width, size.height), - window.scale_factor(), - ); - - surface.configure( - &device, - &wgpu::SurfaceConfiguration { - format, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - width: size.width, - height: size.height, - present_mode: wgpu::PresentMode::AutoVsync, - alpha_mode: wgpu::CompositeAlphaMode::Auto - }, - ); - - resized = false; - } - - match surface.get_current_texture() { - Ok(frame) => { - let mut encoder = device.create_command_encoder( - &wgpu::CommandEncoderDescriptor { label: None }, - ); - - let program = state.program(); - - let view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default()); - - { - // We clear the frame - let mut render_pass = scene.clear( - &view, - &mut encoder, - program.background_color(), - ); - - // Draw the scene - scene.draw(&mut render_pass); - } - - // And then iced on top - renderer.with_primitives(|backend, primitive| { - backend.present( - &device, - &mut staging_belt, - &mut encoder, - &view, - primitive, - &viewport, - &debug.overlay(), - ); - }); - - // Then we submit the work - staging_belt.finish(); - queue.submit(Some(encoder.finish())); - frame.present(); - - // Update the mouse cursor - window.set_cursor_icon( - iced_winit::conversion::mouse_interaction( - state.mouse_interaction(), - ), - ); - - // And recall staging buffers - staging_belt.recall(); - - } - Err(error) => match error { - wgpu::SurfaceError::OutOfMemory => { - panic!("Swapchain error: {error}. Rendering cannot continue.") - } - _ => { - // Try rendering again next frame. - window.request_redraw(); - } - }, - } - } - _ => {} - } - }) -} diff --git a/examples/integration_wgpu/src/scene.rs b/examples/integration_wgpu/src/scene.rs deleted file mode 100644 index 3e41fbda..00000000 --- a/examples/integration_wgpu/src/scene.rs +++ /dev/null @@ -1,102 +0,0 @@ -use iced_wgpu::wgpu; -use iced_winit::Color; - -pub struct Scene { - pipeline: wgpu::RenderPipeline, -} - -impl Scene { - pub fn new( - device: &wgpu::Device, - texture_format: wgpu::TextureFormat, - ) -> Scene { - let pipeline = build_pipeline(device, texture_format); - - Scene { pipeline } - } - - pub fn clear<'a>( - &self, - target: &'a wgpu::TextureView, - encoder: &'a mut wgpu::CommandEncoder, - background_color: Color, - ) -> wgpu::RenderPass<'a> { - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: target, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear({ - let [r, g, b, a] = background_color.into_linear(); - - wgpu::Color { - r: r as f64, - g: g as f64, - b: b as f64, - a: a as f64, - } - }), - store: true, - }, - })], - depth_stencil_attachment: None, - }) - } - - pub fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { - render_pass.set_pipeline(&self.pipeline); - render_pass.draw(0..3, 0..1); - } -} - -fn build_pipeline( - device: &wgpu::Device, - texture_format: wgpu::TextureFormat, -) -> wgpu::RenderPipeline { - let (vs_module, fs_module) = ( - device.create_shader_module(wgpu::include_wgsl!("shader/vert.wgsl")), - device.create_shader_module(wgpu::include_wgsl!("shader/frag.wgsl")), - ); - - let pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: None, - push_constant_ranges: &[], - bind_group_layouts: &[], - }); - - device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: None, - layout: Some(&pipeline_layout), - vertex: wgpu::VertexState { - module: &vs_module, - entry_point: "main", - buffers: &[], - }, - fragment: Some(wgpu::FragmentState { - module: &fs_module, - entry_point: "main", - targets: &[Some(wgpu::ColorTargetState { - format: texture_format, - blend: Some(wgpu::BlendState { - color: wgpu::BlendComponent::REPLACE, - alpha: wgpu::BlendComponent::REPLACE, - }), - write_mask: wgpu::ColorWrites::ALL, - })], - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - front_face: wgpu::FrontFace::Ccw, - ..Default::default() - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - }) -} diff --git a/examples/integration_wgpu/src/shader/frag.wgsl b/examples/integration_wgpu/src/shader/frag.wgsl deleted file mode 100644 index cf27bb56..00000000 --- a/examples/integration_wgpu/src/shader/frag.wgsl +++ /dev/null @@ -1,4 +0,0 @@ -@fragment -fn main() -> @location(0) vec4 { - return vec4(1.0, 0.0, 0.0, 1.0); -} diff --git a/examples/integration_wgpu/src/shader/vert.wgsl b/examples/integration_wgpu/src/shader/vert.wgsl deleted file mode 100644 index e353e6ba..00000000 --- a/examples/integration_wgpu/src/shader/vert.wgsl +++ /dev/null @@ -1,6 +0,0 @@ -@vertex -fn main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4 { - let x = f32(1 - i32(in_vertex_index)) * 0.5; - let y = f32(1 - i32(in_vertex_index & 1u) * 2) * 0.5; - return vec4(x, y, 0.0, 1.0); -} diff --git a/glow/Cargo.toml b/glow/Cargo.toml deleted file mode 100644 index 1a848ab7..00000000 --- a/glow/Cargo.toml +++ /dev/null @@ -1,51 +0,0 @@ -[package] -name = "iced_glow" -version = "0.7.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2021" -description = "A glow renderer for iced" -license = "MIT AND OFL-1.1" -repository = "https://github.com/iced-rs/iced" - -[features] -svg = ["iced_graphics/svg"] -image = ["iced_graphics/image"] -png = ["iced_graphics/png"] -jpeg = ["iced_graphics/jpeg"] -jpeg_rayon = ["iced_graphics/jpeg_rayon"] -gif = ["iced_graphics/gif"] -webp = ["iced_graphics/webp"] -pnm = ["iced_graphics/pnm"] -ico = ["iced_graphics/ico"] -bmp = ["iced_graphics/bmp"] -hdr = ["iced_graphics/hdr"] -dds = ["iced_graphics/dds"] -farbfeld = ["iced_graphics/farbfeld"] -canvas = ["iced_graphics/canvas"] -qr_code = ["iced_graphics/qr_code"] -default_system_font = ["iced_graphics/font-source"] - -[dependencies] -glow = "0.11.1" -glow_glyph = "0.5.0" -glyph_brush = "0.7" -euclid = "0.22" -bytemuck = "1.4" -log = "0.4" - -[dependencies.iced_native] -version = "0.9" -path = "../native" - -[dependencies.iced_graphics] -version = "0.7" -path = "../graphics" -features = ["font-fallback", "font-icons", "opengl"] - -[dependencies.tracing] -version = "0.1.6" -optional = true - -[package.metadata.docs.rs] -rustdoc-args = ["--cfg", "docsrs"] -all-features = true diff --git a/glow/README.md b/glow/README.md deleted file mode 100644 index 95c9d62a..00000000 --- a/glow/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# `iced_glow` -[![Documentation](https://docs.rs/iced_glow/badge.svg)][documentation] -[![Crates.io](https://img.shields.io/crates/v/iced_glow.svg)](https://crates.io/crates/iced_glow) -[![License](https://img.shields.io/crates/l/iced_glow.svg)](https://github.com/iced-rs/iced/blob/master/LICENSE) -[![Discord Server](https://img.shields.io/discord/628993209984614400?label=&labelColor=6A7EC2&logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/3xZJ65GAhd) - -`iced_glow` is a [`glow`] renderer for [`iced_native`]. This renderer supports OpenGL 3.0+ and OpenGL ES 2.0. - -This renderer is mostly used as a fallback for hardware that doesn't support [`wgpu`] (Vulkan, Metal or DX12). - -Currently, `iced_glow` supports the following primitives: -- Text, which is rendered using [`glow_glyph`]. No shaping at all. -- Quads or rectangles, with rounded borders and a solid background color. -- Clip areas, useful to implement scrollables or hide overflowing content. -- Meshes of triangles, useful to draw geometry freely. - -

- The native target -

- -[documentation]: https://docs.rs/iced_glow -[`iced_native`]: ../native -[`glow`]: https://github.com/grovesNL/glow -[`wgpu`]: https://github.com/gfx-rs/wgpu -[`glow_glyph`]: https://github.com/hecrj/glow_glyph - -## Installation -Add `iced_glow` as a dependency in your `Cargo.toml`: - -```toml -iced_glow = "0.7" -``` - -__Iced moves fast and the `master` branch can contain breaking changes!__ If -you want to learn about a specific release, check out [the release list]. - -[the release list]: https://github.com/iced-rs/iced/releases - -## Current limitations - -The current implementation is quite naive, it uses: - -- A different pipeline/shader for each primitive -- A very simplistic layer model: every `Clip` primitive will generate new layers -- _Many_ render passes instead of preparing everything upfront -- A glyph cache that is trimmed incorrectly when there are multiple layers (a [`glyph_brush`] limitation) - -Some of these issues are already being worked on! If you want to help, [get in touch!] - -[get in touch!]: ../CONTRIBUTING.md -[`glyph_brush`]: https://github.com/alexheretic/glyph-brush diff --git a/glow/src/backend.rs b/glow/src/backend.rs deleted file mode 100644 index 36a34eda..00000000 --- a/glow/src/backend.rs +++ /dev/null @@ -1,280 +0,0 @@ -#[cfg(any(feature = "image", feature = "svg"))] -use crate::image; -use crate::quad; -use crate::text; -use crate::{program, triangle}; -use crate::{Settings, Transformation, Viewport}; - -use iced_graphics::backend; -use iced_graphics::font; -use iced_graphics::{Layer, Primitive}; -use iced_native::alignment; -use iced_native::{Font, Size}; - -/// A [`glow`] graphics backend for [`iced`]. -/// -/// [`glow`]: https://github.com/grovesNL/glow -/// [`iced`]: https://github.com/iced-rs/iced -#[derive(Debug)] -pub struct Backend { - #[cfg(any(feature = "image", feature = "svg"))] - image_pipeline: image::Pipeline, - quad_pipeline: quad::Pipeline, - text_pipeline: text::Pipeline, - triangle_pipeline: triangle::Pipeline, - default_text_size: f32, -} - -impl Backend { - /// Creates a new [`Backend`]. - pub fn new(gl: &glow::Context, settings: Settings) -> Self { - let text_pipeline = text::Pipeline::new( - gl, - settings.default_font, - settings.text_multithreading, - ); - - let shader_version = program::Version::new(gl); - - #[cfg(any(feature = "image", feature = "svg"))] - let image_pipeline = image::Pipeline::new(gl, &shader_version); - let quad_pipeline = quad::Pipeline::new(gl, &shader_version); - let triangle_pipeline = triangle::Pipeline::new(gl, &shader_version); - - Self { - #[cfg(any(feature = "image", feature = "svg"))] - image_pipeline, - quad_pipeline, - text_pipeline, - triangle_pipeline, - default_text_size: settings.default_text_size, - } - } - - /// Draws the provided primitives in the default framebuffer. - /// - /// The text provided as overlay will be rendered on top of the primitives. - /// This is useful for rendering debug information. - pub fn present>( - &mut self, - gl: &glow::Context, - primitives: &[Primitive], - viewport: &Viewport, - overlay_text: &[T], - ) { - let viewport_size = viewport.physical_size(); - let scale_factor = viewport.scale_factor() as f32; - let projection = viewport.projection(); - - let mut layers = Layer::generate(primitives, viewport); - layers.push(Layer::overlay(overlay_text, viewport)); - - for layer in layers { - self.flush( - gl, - scale_factor, - projection, - &layer, - viewport_size.height, - ); - } - - #[cfg(any(feature = "image", feature = "svg"))] - self.image_pipeline.trim_cache(gl); - } - - fn flush( - &mut self, - gl: &glow::Context, - scale_factor: f32, - transformation: Transformation, - layer: &Layer<'_>, - target_height: u32, - ) { - let mut bounds = (layer.bounds * scale_factor).snap(); - - if bounds.width < 1 || bounds.height < 1 { - return; - } - - bounds.height = bounds.height.min(target_height); - - if !layer.quads.is_empty() { - self.quad_pipeline.draw( - gl, - target_height, - &layer.quads, - transformation, - scale_factor, - bounds, - ); - } - - if !layer.meshes.is_empty() { - let scaled = transformation - * Transformation::scale(scale_factor, scale_factor); - - self.triangle_pipeline.draw( - &layer.meshes, - gl, - target_height, - scaled, - scale_factor, - ); - } - - #[cfg(any(feature = "image", feature = "svg"))] - if !layer.images.is_empty() { - let scaled = transformation - * Transformation::scale(scale_factor, scale_factor); - - self.image_pipeline.draw( - gl, - target_height, - scaled, - scale_factor, - &layer.images, - bounds, - ); - } - - if !layer.text.is_empty() { - for text in layer.text.iter() { - // Target physical coordinates directly to avoid blurry text - let text = glow_glyph::Section { - // TODO: We `round` here to avoid rerasterizing text when - // its position changes slightly. This can make text feel a - // bit "jumpy". We may be able to do better once we improve - // our text rendering/caching pipeline. - screen_position: ( - (text.bounds.x * scale_factor).round(), - (text.bounds.y * scale_factor).round(), - ), - // TODO: Fix precision issues with some scale factors. - // - // The `ceil` here can cause some words to render on the - // same line when they should not. - // - // Ideally, `wgpu_glyph` should be able to compute layout - // using logical positions, and then apply the proper - // scaling when rendering. This would ensure that both - // measuring and rendering follow the same layout rules. - bounds: ( - (text.bounds.width * scale_factor).ceil(), - (text.bounds.height * scale_factor).ceil(), - ), - text: vec![glow_glyph::Text { - text: text.content, - scale: glow_glyph::ab_glyph::PxScale { - x: text.size * scale_factor, - y: text.size * scale_factor, - }, - font_id: self.text_pipeline.find_font(text.font), - extra: glow_glyph::Extra { - color: text.color, - z: 0.0, - }, - }], - layout: glow_glyph::Layout::default() - .h_align(match text.horizontal_alignment { - alignment::Horizontal::Left => { - glow_glyph::HorizontalAlign::Left - } - alignment::Horizontal::Center => { - glow_glyph::HorizontalAlign::Center - } - alignment::Horizontal::Right => { - glow_glyph::HorizontalAlign::Right - } - }) - .v_align(match text.vertical_alignment { - alignment::Vertical::Top => { - glow_glyph::VerticalAlign::Top - } - alignment::Vertical::Center => { - glow_glyph::VerticalAlign::Center - } - alignment::Vertical::Bottom => { - glow_glyph::VerticalAlign::Bottom - } - }), - }; - - self.text_pipeline.queue(text); - } - - self.text_pipeline.draw_queued( - gl, - transformation, - glow_glyph::Region { - x: bounds.x, - y: target_height - (bounds.y + bounds.height), - width: bounds.width, - height: bounds.height, - }, - ); - } - } -} - -impl iced_graphics::Backend for Backend { - fn trim_measurements(&mut self) { - self.text_pipeline.trim_measurement_cache() - } -} - -impl backend::Text for Backend { - const ICON_FONT: Font = font::ICONS; - const CHECKMARK_ICON: char = font::CHECKMARK_ICON; - const ARROW_DOWN_ICON: char = font::ARROW_DOWN_ICON; - - fn default_size(&self) -> f32 { - self.default_text_size - } - - fn measure( - &self, - contents: &str, - size: f32, - font: Font, - bounds: Size, - ) -> (f32, f32) { - self.text_pipeline.measure(contents, size, font, bounds) - } - - fn hit_test( - &self, - contents: &str, - size: f32, - font: Font, - bounds: Size, - point: iced_native::Point, - nearest_only: bool, - ) -> Option { - self.text_pipeline.hit_test( - contents, - size, - font, - bounds, - point, - nearest_only, - ) - } -} - -#[cfg(feature = "image")] -impl backend::Image for Backend { - fn dimensions(&self, handle: &iced_native::image::Handle) -> Size { - self.image_pipeline.dimensions(handle) - } -} - -#[cfg(feature = "svg")] -impl backend::Svg for Backend { - fn viewport_dimensions( - &self, - handle: &iced_native::svg::Handle, - ) -> Size { - self.image_pipeline.viewport_dimensions(handle) - } -} diff --git a/glow/src/image.rs b/glow/src/image.rs deleted file mode 100644 index d3a25b5b..00000000 --- a/glow/src/image.rs +++ /dev/null @@ -1,254 +0,0 @@ -mod storage; - -use storage::Storage; - -pub use iced_graphics::triangle::{Mesh2D, Vertex2D}; - -use crate::program::{self, Shader}; -use crate::Transformation; - -#[cfg(feature = "image")] -use iced_graphics::image::raster; - -#[cfg(feature = "svg")] -use iced_graphics::image::vector; - -use iced_graphics::layer; -use iced_graphics::Rectangle; -use iced_graphics::Size; - -use glow::HasContext; - -use std::cell::RefCell; - -#[cfg(feature = "tracing")] -use tracing::info_span; - -#[derive(Debug)] -pub(crate) struct Pipeline { - program: ::Program, - vertex_array: ::VertexArray, - vertex_buffer: ::Buffer, - transform_location: ::UniformLocation, - storage: Storage, - #[cfg(feature = "image")] - raster_cache: RefCell>, - #[cfg(feature = "svg")] - vector_cache: RefCell>, -} - -impl Pipeline { - pub fn new( - gl: &glow::Context, - shader_version: &program::Version, - ) -> Pipeline { - let program = unsafe { - let vertex_shader = Shader::vertex( - gl, - shader_version, - include_str!("shader/common/image.vert"), - ); - let fragment_shader = Shader::fragment( - gl, - shader_version, - include_str!("shader/common/image.frag"), - ); - - program::create( - gl, - &[vertex_shader, fragment_shader], - &[(0, "i_Position")], - ) - }; - - let transform_location = - unsafe { gl.get_uniform_location(program, "u_Transform") } - .expect("Get transform location"); - - unsafe { - gl.use_program(Some(program)); - - let transform: [f32; 16] = Transformation::identity().into(); - gl.uniform_matrix_4_f32_slice( - Some(&transform_location), - false, - &transform, - ); - - gl.use_program(None); - } - - let vertex_buffer = - unsafe { gl.create_buffer().expect("Create vertex buffer") }; - let vertex_array = - unsafe { gl.create_vertex_array().expect("Create vertex array") }; - - unsafe { - gl.bind_vertex_array(Some(vertex_array)); - gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer)); - - let vertices = &[0u8, 0, 1, 0, 0, 1, 1, 1]; - gl.buffer_data_size( - glow::ARRAY_BUFFER, - vertices.len() as i32, - glow::STATIC_DRAW, - ); - gl.buffer_sub_data_u8_slice( - glow::ARRAY_BUFFER, - 0, - bytemuck::cast_slice(vertices), - ); - - gl.enable_vertex_attrib_array(0); - gl.vertex_attrib_pointer_f32( - 0, - 2, - glow::UNSIGNED_BYTE, - false, - 0, - 0, - ); - - gl.bind_buffer(glow::ARRAY_BUFFER, None); - gl.bind_vertex_array(None); - } - - Pipeline { - program, - vertex_array, - vertex_buffer, - transform_location, - storage: Storage::default(), - #[cfg(feature = "image")] - raster_cache: RefCell::new(raster::Cache::default()), - #[cfg(feature = "svg")] - vector_cache: RefCell::new(vector::Cache::default()), - } - } - - #[cfg(feature = "image")] - pub fn dimensions(&self, handle: &iced_native::image::Handle) -> Size { - self.raster_cache.borrow_mut().load(handle).dimensions() - } - - #[cfg(feature = "svg")] - pub fn viewport_dimensions( - &self, - handle: &iced_native::svg::Handle, - ) -> Size { - let mut cache = self.vector_cache.borrow_mut(); - let svg = cache.load(handle); - - svg.viewport_dimensions() - } - - pub fn draw( - &mut self, - mut gl: &glow::Context, - target_height: u32, - transformation: Transformation, - _scale_factor: f32, - images: &[layer::Image], - layer_bounds: Rectangle, - ) { - #[cfg(feature = "tracing")] - let _ = info_span!("Glow::Image", "DRAW").entered(); - - unsafe { - gl.use_program(Some(self.program)); - gl.bind_vertex_array(Some(self.vertex_array)); - gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer)); - gl.enable(glow::SCISSOR_TEST); - } - - #[cfg(feature = "image")] - let mut raster_cache = self.raster_cache.borrow_mut(); - - #[cfg(feature = "svg")] - let mut vector_cache = self.vector_cache.borrow_mut(); - - for image in images { - let (entry, bounds) = match &image { - #[cfg(feature = "image")] - layer::Image::Raster { handle, bounds } => ( - raster_cache.upload(handle, &mut gl, &mut self.storage), - bounds, - ), - #[cfg(not(feature = "image"))] - layer::Image::Raster { handle: _, bounds } => (None, bounds), - - #[cfg(feature = "svg")] - layer::Image::Vector { - handle, - color, - bounds, - } => { - let size = [bounds.width, bounds.height]; - ( - vector_cache.upload( - handle, - *color, - size, - _scale_factor, - &mut gl, - &mut self.storage, - ), - bounds, - ) - } - - #[cfg(not(feature = "svg"))] - layer::Image::Vector { bounds, .. } => (None, bounds), - }; - - unsafe { - gl.scissor( - layer_bounds.x as i32, - (target_height - (layer_bounds.y + layer_bounds.height)) - as i32, - layer_bounds.width as i32, - layer_bounds.height as i32, - ); - - if let Some(storage::Entry { texture, .. }) = entry { - gl.bind_texture(glow::TEXTURE_2D, Some(*texture)) - } else { - continue; - } - - let translate = Transformation::translate(bounds.x, bounds.y); - let scale = Transformation::scale(bounds.width, bounds.height); - let transformation = transformation * translate * scale; - let matrix: [f32; 16] = transformation.into(); - gl.uniform_matrix_4_f32_slice( - Some(&self.transform_location), - false, - &matrix, - ); - - gl.draw_arrays(glow::TRIANGLE_STRIP, 0, 4); - - gl.bind_texture(glow::TEXTURE_2D, None); - } - } - - unsafe { - gl.bind_buffer(glow::ARRAY_BUFFER, None); - gl.bind_vertex_array(None); - gl.use_program(None); - gl.disable(glow::SCISSOR_TEST); - } - } - - pub fn trim_cache(&mut self, mut gl: &glow::Context) { - #[cfg(feature = "image")] - self.raster_cache - .borrow_mut() - .trim(&mut self.storage, &mut gl); - - #[cfg(feature = "svg")] - self.vector_cache - .borrow_mut() - .trim(&mut self.storage, &mut gl); - } -} diff --git a/glow/src/image/storage.rs b/glow/src/image/storage.rs deleted file mode 100644 index 9bc20641..00000000 --- a/glow/src/image/storage.rs +++ /dev/null @@ -1,78 +0,0 @@ -use iced_graphics::image; -use iced_graphics::Size; - -use glow::HasContext; - -#[derive(Debug, Default)] -pub struct Storage; - -impl image::Storage for Storage { - type Entry = Entry; - type State<'a> = &'a glow::Context; - - fn upload( - &mut self, - width: u32, - height: u32, - data: &[u8], - gl: &mut &glow::Context, - ) -> Option { - unsafe { - let texture = gl.create_texture().expect("create texture"); - gl.bind_texture(glow::TEXTURE_2D, Some(texture)); - gl.tex_image_2d( - glow::TEXTURE_2D, - 0, - glow::SRGB8_ALPHA8 as i32, - width as i32, - height as i32, - 0, - glow::RGBA, - glow::UNSIGNED_BYTE, - Some(data), - ); - gl.tex_parameter_i32( - glow::TEXTURE_2D, - glow::TEXTURE_WRAP_S, - glow::CLAMP_TO_EDGE as _, - ); - gl.tex_parameter_i32( - glow::TEXTURE_2D, - glow::TEXTURE_WRAP_T, - glow::CLAMP_TO_EDGE as _, - ); - gl.tex_parameter_i32( - glow::TEXTURE_2D, - glow::TEXTURE_MIN_FILTER, - glow::LINEAR as _, - ); - gl.tex_parameter_i32( - glow::TEXTURE_2D, - glow::TEXTURE_MAG_FILTER, - glow::LINEAR as _, - ); - gl.bind_texture(glow::TEXTURE_2D, None); - - Some(Entry { - size: Size::new(width, height), - texture, - }) - } - } - - fn remove(&mut self, entry: &Entry, gl: &mut &glow::Context) { - unsafe { gl.delete_texture(entry.texture) } - } -} - -#[derive(Debug)] -pub struct Entry { - size: Size, - pub(super) texture: glow::NativeTexture, -} - -impl image::storage::Entry for Entry { - fn size(&self) -> Size { - self.size - } -} diff --git a/glow/src/lib.rs b/glow/src/lib.rs deleted file mode 100644 index 9e7de0d9..00000000 --- a/glow/src/lib.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! A [`glow`] renderer for [`iced_native`]. -//! -//! ![The native path of the Iced ecosystem](https://github.com/iced-rs/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/native.png?raw=true) -//! -//! [`glow`]: https://github.com/grovesNL/glow -//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.8/native -#![doc( - html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" -)] -#![deny( - missing_debug_implementations, - missing_docs, - unused_results, - clippy::extra_unused_lifetimes, - clippy::from_over_into, - clippy::needless_borrow, - clippy::new_without_default, - clippy::useless_conversion -)] -#![forbid(rust_2018_idioms)] -#![allow(clippy::inherent_to_string, clippy::type_complexity)] -#![cfg_attr(docsrs, feature(doc_cfg))] - -pub use glow; - -mod backend; -#[cfg(any(feature = "image", feature = "svg"))] -mod image; -mod program; -mod quad; -mod text; -mod triangle; - -pub mod settings; -pub mod window; - -pub use backend::Backend; -pub use settings::Settings; - -pub(crate) use iced_graphics::Transformation; - -pub use iced_graphics::{Error, Viewport}; -pub use iced_native::Theme; - -pub use iced_native::alignment; -pub use iced_native::{Alignment, Background, Color, Command, Length, Vector}; - -/// A [`glow`] graphics renderer for [`iced`]. -/// -/// [`glow`]: https://github.com/grovesNL/glow -/// [`iced`]: https://github.com/iced-rs/iced -pub type Renderer = - iced_graphics::Renderer; diff --git a/glow/src/program.rs b/glow/src/program.rs deleted file mode 100644 index 95437fcd..00000000 --- a/glow/src/program.rs +++ /dev/null @@ -1,133 +0,0 @@ -use glow::HasContext; - -/// The [`Version`] of a `Program`. -pub struct Version { - vertex: String, - fragment: String, -} - -impl Version { - pub fn new(gl: &glow::Context) -> Version { - let version = gl.version(); - - let (vertex, fragment) = match ( - version.major, - version.minor, - version.is_embedded, - ) { - // OpenGL 3.0+ - (3, 0 | 1 | 2, false) => ( - format!("#version 1{}0\n#extension GL_ARB_explicit_attrib_location : enable", version.minor + 3), - format!( - "#version 1{}0\n#extension GL_ARB_explicit_attrib_location : enable\n#define HIGHER_THAN_300 1", - version.minor + 3 - ), - ), - // OpenGL 3.3+ - (3 | 4, _, false) => ( - format!("#version {}{}0\n#extension GL_ARB_explicit_attrib_location : enable", version.major, version.minor), - format!( - "#version {}{}0\n#extension GL_ARB_explicit_attrib_location : enable\n#define HIGHER_THAN_300 1", - version.major, version.minor - ), - ), - // OpenGL ES 3.0+ - (3, _, true) => ( - format!("#version 3{}0 es", version.minor), - format!( - "#version 3{}0 es\n#define HIGHER_THAN_300 1", - version.minor - ), - ), - // OpenGL ES 2.0+ - (2, _, true) => ( - String::from( - "#version 100\n#define in attribute\n#define out varying", - ), - String::from("#version 100\n#define in varying"), - ), - // OpenGL 2.1 - (2, _, false) => ( - String::from( - "#version 120\n#define in attribute\n#define out varying", - ), - String::from("#version 120\n#define in varying"), - ), - // OpenGL 1.1+ - _ => panic!("Incompatible context version: {version:?}"), - }; - - log::info!("Shader directive: {}", vertex.lines().next().unwrap()); - - Version { vertex, fragment } - } -} - -pub struct Shader(::Shader); - -impl Shader { - fn compile(gl: &glow::Context, stage: u32, content: &str) -> Shader { - unsafe { - let shader = gl.create_shader(stage).expect("Cannot create shader"); - - gl.shader_source(shader, content); - gl.compile_shader(shader); - - if !gl.get_shader_compile_status(shader) { - panic!("{}", gl.get_shader_info_log(shader)); - } - - Shader(shader) - } - } - - /// Creates a vertex [`Shader`]. - pub fn vertex( - gl: &glow::Context, - version: &Version, - content: &'static str, - ) -> Self { - let content = format!("{}\n{}", version.vertex, content); - - Shader::compile(gl, glow::VERTEX_SHADER, &content) - } - - /// Creates a fragment [`Shader`]. - pub fn fragment( - gl: &glow::Context, - version: &Version, - content: &'static str, - ) -> Self { - let content = format!("{}\n{}", version.fragment, content); - - Shader::compile(gl, glow::FRAGMENT_SHADER, &content) - } -} - -pub unsafe fn create( - gl: &glow::Context, - shaders: &[Shader], - attributes: &[(u32, &str)], -) -> ::Program { - let program = gl.create_program().expect("Cannot create program"); - - for shader in shaders { - gl.attach_shader(program, shader.0); - } - - for (i, name) in attributes { - gl.bind_attrib_location(program, *i, name); - } - - gl.link_program(program); - if !gl.get_program_link_status(program) { - panic!("{}", gl.get_program_info_log(program)); - } - - for shader in shaders { - gl.detach_shader(program, shader.0); - gl.delete_shader(shader.0); - } - - program -} diff --git a/glow/src/quad.rs b/glow/src/quad.rs deleted file mode 100644 index 67d9a098..00000000 --- a/glow/src/quad.rs +++ /dev/null @@ -1,74 +0,0 @@ -mod compatibility; -mod core; - -use crate::program; -use crate::Transformation; -use glow::HasContext; -use iced_graphics::layer; -use iced_native::Rectangle; - -#[cfg(feature = "tracing")] -use tracing::info_span; - -#[derive(Debug)] -pub enum Pipeline { - Core(core::Pipeline), - Compatibility(compatibility::Pipeline), -} - -impl Pipeline { - pub fn new( - gl: &glow::Context, - shader_version: &program::Version, - ) -> Pipeline { - let gl_version = gl.version(); - - // OpenGL 3.0+ and OpenGL ES 3.0+ have instancing (which is what separates `core` from `compatibility`) - if gl_version.major >= 3 { - log::info!("Mode: core"); - Pipeline::Core(core::Pipeline::new(gl, shader_version)) - } else { - log::info!("Mode: compatibility"); - Pipeline::Compatibility(compatibility::Pipeline::new( - gl, - shader_version, - )) - } - } - - pub fn draw( - &mut self, - gl: &glow::Context, - target_height: u32, - instances: &[layer::Quad], - transformation: Transformation, - scale: f32, - bounds: Rectangle, - ) { - #[cfg(feature = "tracing")] - let _ = info_span!("Glow::Quad", "DRAW").enter(); - - match self { - Pipeline::Core(pipeline) => { - pipeline.draw( - gl, - target_height, - instances, - transformation, - scale, - bounds, - ); - } - Pipeline::Compatibility(pipeline) => { - pipeline.draw( - gl, - target_height, - instances, - transformation, - scale, - bounds, - ); - } - } - } -} diff --git a/glow/src/quad/compatibility.rs b/glow/src/quad/compatibility.rs deleted file mode 100644 index e909162c..00000000 --- a/glow/src/quad/compatibility.rs +++ /dev/null @@ -1,349 +0,0 @@ -use crate::program::{self, Shader}; -use crate::Transformation; -use glow::HasContext; -use iced_graphics::layer; -use iced_native::Rectangle; - -// Only change `MAX_QUADS`, otherwise you could cause problems -// by splitting a triangle into different render passes. -const MAX_QUADS: usize = 100_000; -const MAX_VERTICES: usize = MAX_QUADS * 4; -const MAX_INDICES: usize = MAX_QUADS * 6; - -#[derive(Debug)] -pub struct Pipeline { - program: ::Program, - vertex_array: ::VertexArray, - vertex_buffer: ::Buffer, - index_buffer: ::Buffer, - transform_location: ::UniformLocation, - scale_location: ::UniformLocation, - screen_height_location: ::UniformLocation, - current_transform: Transformation, - current_scale: f32, - current_target_height: u32, -} - -impl Pipeline { - pub fn new( - gl: &glow::Context, - shader_version: &program::Version, - ) -> Pipeline { - let program = unsafe { - let vertex_shader = Shader::vertex( - gl, - shader_version, - include_str!("../shader/compatibility/quad.vert"), - ); - let fragment_shader = Shader::fragment( - gl, - shader_version, - include_str!("../shader/compatibility/quad.frag"), - ); - - program::create( - gl, - &[vertex_shader, fragment_shader], - &[ - (0, "i_Pos"), - (1, "i_Scale"), - (2, "i_Color"), - (3, "i_BorderColor"), - (4, "i_BorderRadius"), - (5, "i_BorderWidth"), - ], - ) - }; - - let transform_location = - unsafe { gl.get_uniform_location(program, "u_Transform") } - .expect("Get transform location"); - - let scale_location = - unsafe { gl.get_uniform_location(program, "u_Scale") } - .expect("Get scale location"); - - let screen_height_location = - unsafe { gl.get_uniform_location(program, "u_ScreenHeight") } - .expect("Get target height location"); - - unsafe { - gl.use_program(Some(program)); - - gl.uniform_matrix_4_f32_slice( - Some(&transform_location), - false, - Transformation::identity().as_ref(), - ); - - gl.uniform_1_f32(Some(&scale_location), 1.0); - gl.uniform_1_f32(Some(&screen_height_location), 0.0); - - gl.use_program(None); - } - - let (vertex_array, vertex_buffer, index_buffer) = - unsafe { create_buffers(gl, MAX_VERTICES) }; - - Pipeline { - program, - vertex_array, - vertex_buffer, - index_buffer, - transform_location, - scale_location, - screen_height_location, - current_transform: Transformation::identity(), - current_scale: 1.0, - current_target_height: 0, - } - } - - pub fn draw( - &mut self, - gl: &glow::Context, - target_height: u32, - instances: &[layer::Quad], - transformation: Transformation, - scale: f32, - bounds: Rectangle, - ) { - // TODO: Remove this allocation (probably by changing the shader and removing the need of two `position`) - let vertices: Vec = - instances.iter().flat_map(Vertex::from_quad).collect(); - - // TODO: Remove this allocation (or allocate only when needed) - let indices: Vec = (0..instances.len().min(MAX_QUADS) as i32) - .flat_map(|i| { - [i * 4, 1 + i * 4, 2 + i * 4, 2 + i * 4, 1 + i * 4, 3 + i * 4] - }) - .cycle() - .take(instances.len() * 6) - .collect(); - - unsafe { - gl.enable(glow::SCISSOR_TEST); - gl.scissor( - bounds.x as i32, - (target_height - (bounds.y + bounds.height)) as i32, - bounds.width as i32, - bounds.height as i32, - ); - - gl.use_program(Some(self.program)); - gl.bind_vertex_array(Some(self.vertex_array)); - gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer)); - gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.index_buffer)); - } - - if transformation != self.current_transform { - unsafe { - gl.uniform_matrix_4_f32_slice( - Some(&self.transform_location), - false, - transformation.as_ref(), - ); - - self.current_transform = transformation; - } - } - - if scale != self.current_scale { - unsafe { - gl.uniform_1_f32(Some(&self.scale_location), scale); - } - - self.current_scale = scale; - } - - if target_height != self.current_target_height { - unsafe { - gl.uniform_1_f32( - Some(&self.screen_height_location), - target_height as f32, - ); - } - - self.current_target_height = target_height; - } - - let passes = vertices - .chunks(MAX_VERTICES) - .zip(indices.chunks(MAX_INDICES)); - - for (vertices, indices) in passes { - unsafe { - gl.buffer_sub_data_u8_slice( - glow::ARRAY_BUFFER, - 0, - bytemuck::cast_slice(vertices), - ); - - gl.buffer_sub_data_u8_slice( - glow::ELEMENT_ARRAY_BUFFER, - 0, - bytemuck::cast_slice(indices), - ); - - gl.draw_elements( - glow::TRIANGLES, - indices.len() as i32, - glow::UNSIGNED_INT, - 0, - ); - } - } - - unsafe { - gl.bind_vertex_array(None); - gl.use_program(None); - gl.disable(glow::SCISSOR_TEST); - } - } -} - -unsafe fn create_buffers( - gl: &glow::Context, - size: usize, -) -> ( - ::VertexArray, - ::Buffer, - ::Buffer, -) { - let vertex_array = gl.create_vertex_array().expect("Create vertex array"); - let vertex_buffer = gl.create_buffer().expect("Create vertex buffer"); - let index_buffer = gl.create_buffer().expect("Create index buffer"); - - gl.bind_vertex_array(Some(vertex_array)); - - gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(index_buffer)); - gl.buffer_data_size( - glow::ELEMENT_ARRAY_BUFFER, - 12 * size as i32, - glow::DYNAMIC_DRAW, - ); - - gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer)); - gl.buffer_data_size( - glow::ARRAY_BUFFER, - (size * Vertex::SIZE) as i32, - glow::DYNAMIC_DRAW, - ); - - let stride = Vertex::SIZE as i32; - - gl.enable_vertex_attrib_array(0); - gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, stride, 0); - - gl.enable_vertex_attrib_array(1); - gl.vertex_attrib_pointer_f32(1, 2, glow::FLOAT, false, stride, 4 * 2); - - gl.enable_vertex_attrib_array(2); - gl.vertex_attrib_pointer_f32(2, 4, glow::FLOAT, false, stride, 4 * (2 + 2)); - - gl.enable_vertex_attrib_array(3); - gl.vertex_attrib_pointer_f32( - 3, - 4, - glow::FLOAT, - false, - stride, - 4 * (2 + 2 + 4), - ); - - gl.enable_vertex_attrib_array(4); - gl.vertex_attrib_pointer_f32( - 4, - 4, - glow::FLOAT, - false, - stride, - 4 * (2 + 2 + 4 + 4), - ); - - gl.enable_vertex_attrib_array(5); - gl.vertex_attrib_pointer_f32( - 5, - 1, - glow::FLOAT, - false, - stride, - 4 * (2 + 2 + 4 + 4 + 4), - ); - - gl.enable_vertex_attrib_array(6); - gl.vertex_attrib_pointer_f32( - 6, - 2, - glow::FLOAT, - false, - stride, - 4 * (2 + 2 + 4 + 4 + 4 + 1), - ); - - gl.bind_vertex_array(None); - gl.bind_buffer(glow::ARRAY_BUFFER, None); - gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); - - (vertex_array, vertex_buffer, index_buffer) -} - -/// The vertex of a colored rectangle with a border. -/// -/// This type can be directly uploaded to GPU memory. -#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] -#[repr(C)] -pub struct Vertex { - /// The position of the [`Vertex`]. - pub position: [f32; 2], - - /// The size of the [`Vertex`]. - pub size: [f32; 2], - - /// The color of the [`Vertex`], in __linear RGB__. - pub color: [f32; 4], - - /// The border color of the [`Vertex`], in __linear RGB__. - pub border_color: [f32; 4], - - /// The border radius of the [`Vertex`]. - pub border_radius: [f32; 4], - - /// The border width of the [`Vertex`]. - pub border_width: f32, - - /// The __quad__ position of the [`Vertex`]. - pub q_position: [f32; 2], -} - -impl Vertex { - const SIZE: usize = std::mem::size_of::(); - - fn from_quad(quad: &layer::Quad) -> [Vertex; 4] { - let base = Vertex { - position: quad.position, - size: quad.size, - color: quad.color, - border_color: quad.color, - border_radius: quad.border_radius, - border_width: quad.border_width, - q_position: [0.0, 0.0], - }; - - [ - base, - Self { - q_position: [0.0, 1.0], - ..base - }, - Self { - q_position: [1.0, 0.0], - ..base - }, - Self { - q_position: [1.0, 1.0], - ..base - }, - ] - } -} diff --git a/glow/src/quad/core.rs b/glow/src/quad/core.rs deleted file mode 100644 index 89036530..00000000 --- a/glow/src/quad/core.rs +++ /dev/null @@ -1,244 +0,0 @@ -use crate::program::{self, Shader}; -use crate::Transformation; -use glow::HasContext; -use iced_graphics::layer; -use iced_native::Rectangle; - -const MAX_INSTANCES: usize = 100_000; - -#[derive(Debug)] -pub struct Pipeline { - program: ::Program, - vertex_array: ::VertexArray, - instances: ::Buffer, - transform_location: ::UniformLocation, - scale_location: ::UniformLocation, - screen_height_location: ::UniformLocation, - current_transform: Transformation, - current_scale: f32, - current_target_height: u32, -} - -impl Pipeline { - pub fn new( - gl: &glow::Context, - shader_version: &program::Version, - ) -> Pipeline { - let program = unsafe { - let vertex_shader = Shader::vertex( - gl, - shader_version, - include_str!("../shader/core/quad.vert"), - ); - let fragment_shader = Shader::fragment( - gl, - shader_version, - include_str!("../shader/core/quad.frag"), - ); - - program::create( - gl, - &[vertex_shader, fragment_shader], - &[ - (0, "i_Pos"), - (1, "i_Scale"), - (2, "i_Color"), - (3, "i_BorderColor"), - (4, "i_BorderRadius"), - (5, "i_BorderWidth"), - ], - ) - }; - - let transform_location = - unsafe { gl.get_uniform_location(program, "u_Transform") } - .expect("Get transform location"); - - let scale_location = - unsafe { gl.get_uniform_location(program, "u_Scale") } - .expect("Get scale location"); - - let screen_height_location = - unsafe { gl.get_uniform_location(program, "u_ScreenHeight") } - .expect("Get target height location"); - - unsafe { - gl.use_program(Some(program)); - - gl.uniform_matrix_4_f32_slice( - Some(&transform_location), - false, - Transformation::identity().as_ref(), - ); - - gl.uniform_1_f32(Some(&scale_location), 1.0); - gl.uniform_1_f32(Some(&screen_height_location), 0.0); - - gl.use_program(None); - } - - let (vertex_array, instances) = - unsafe { create_instance_buffer(gl, MAX_INSTANCES) }; - - Pipeline { - program, - vertex_array, - instances, - transform_location, - scale_location, - screen_height_location, - current_transform: Transformation::identity(), - current_scale: 1.0, - current_target_height: 0, - } - } - - pub fn draw( - &mut self, - gl: &glow::Context, - target_height: u32, - instances: &[layer::Quad], - transformation: Transformation, - scale: f32, - bounds: Rectangle, - ) { - unsafe { - gl.enable(glow::SCISSOR_TEST); - gl.scissor( - bounds.x as i32, - (target_height - (bounds.y + bounds.height)) as i32, - bounds.width as i32, - bounds.height as i32, - ); - - gl.use_program(Some(self.program)); - gl.bind_vertex_array(Some(self.vertex_array)); - gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.instances)); - } - - if transformation != self.current_transform { - unsafe { - gl.uniform_matrix_4_f32_slice( - Some(&self.transform_location), - false, - transformation.as_ref(), - ); - - self.current_transform = transformation; - } - } - - if scale != self.current_scale { - unsafe { - gl.uniform_1_f32(Some(&self.scale_location), scale); - } - - self.current_scale = scale; - } - - if target_height != self.current_target_height { - unsafe { - gl.uniform_1_f32( - Some(&self.screen_height_location), - target_height as f32, - ); - } - - self.current_target_height = target_height; - } - - for instances in instances.chunks(MAX_INSTANCES) { - unsafe { - gl.buffer_sub_data_u8_slice( - glow::ARRAY_BUFFER, - 0, - bytemuck::cast_slice(instances), - ); - - gl.draw_arrays_instanced( - glow::TRIANGLE_STRIP, - 0, - 4, - instances.len() as i32, - ); - } - } - - unsafe { - gl.bind_vertex_array(None); - gl.use_program(None); - gl.disable(glow::SCISSOR_TEST); - } - } -} - -unsafe fn create_instance_buffer( - gl: &glow::Context, - size: usize, -) -> ( - ::VertexArray, - ::Buffer, -) { - let vertex_array = gl.create_vertex_array().expect("Create vertex array"); - let buffer = gl.create_buffer().expect("Create instance buffer"); - - gl.bind_vertex_array(Some(vertex_array)); - gl.bind_buffer(glow::ARRAY_BUFFER, Some(buffer)); - gl.buffer_data_size( - glow::ARRAY_BUFFER, - (size * std::mem::size_of::()) as i32, - glow::DYNAMIC_DRAW, - ); - - let stride = std::mem::size_of::() as i32; - - gl.enable_vertex_attrib_array(0); - gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, stride, 0); - gl.vertex_attrib_divisor(0, 1); - - gl.enable_vertex_attrib_array(1); - gl.vertex_attrib_pointer_f32(1, 2, glow::FLOAT, false, stride, 4 * 2); - gl.vertex_attrib_divisor(1, 1); - - gl.enable_vertex_attrib_array(2); - gl.vertex_attrib_pointer_f32(2, 4, glow::FLOAT, false, stride, 4 * (2 + 2)); - gl.vertex_attrib_divisor(2, 1); - - gl.enable_vertex_attrib_array(3); - gl.vertex_attrib_pointer_f32( - 3, - 4, - glow::FLOAT, - false, - stride, - 4 * (2 + 2 + 4), - ); - gl.vertex_attrib_divisor(3, 1); - - gl.enable_vertex_attrib_array(4); - gl.vertex_attrib_pointer_f32( - 4, - 4, - glow::FLOAT, - false, - stride, - 4 * (2 + 2 + 4 + 4), - ); - gl.vertex_attrib_divisor(4, 1); - - gl.enable_vertex_attrib_array(5); - gl.vertex_attrib_pointer_f32( - 5, - 1, - glow::FLOAT, - false, - stride, - 4 * (2 + 2 + 4 + 4 + 4), - ); - gl.vertex_attrib_divisor(5, 1); - - gl.bind_vertex_array(None); - gl.bind_buffer(glow::ARRAY_BUFFER, None); - - (vertex_array, buffer) -} diff --git a/glow/src/settings.rs b/glow/src/settings.rs deleted file mode 100644 index 6aaa0d55..00000000 --- a/glow/src/settings.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! Configure a renderer. -pub use iced_graphics::Antialiasing; - -/// The settings of a [`Backend`]. -/// -/// [`Backend`]: crate::Backend -#[derive(Clone, Copy, PartialEq)] -pub struct Settings { - /// The bytes of the font that will be used by default. - /// - /// If `None` is provided, a default system font will be chosen. - pub default_font: Option<&'static [u8]>, - - /// The default size of text. - /// - /// By default, it will be set to `20.0`. - pub default_text_size: f32, - - /// If enabled, spread text workload in multiple threads when multiple cores - /// are available. - /// - /// By default, it is disabled. - pub text_multithreading: bool, - - /// The antialiasing strategy that will be used for triangle primitives. - /// - /// By default, it is `None`. - pub antialiasing: Option, -} - -impl Default for Settings { - fn default() -> Settings { - Settings { - default_font: None, - default_text_size: 20.0, - text_multithreading: false, - antialiasing: None, - } - } -} - -impl std::fmt::Debug for Settings { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Settings") - // Instead of printing the font bytes, we simply show a `bool` indicating if using a default font or not. - .field("default_font", &self.default_font.is_some()) - .field("default_text_size", &self.default_text_size) - .field("text_multithreading", &self.text_multithreading) - .field("antialiasing", &self.antialiasing) - .finish() - } -} - -impl Settings { - /// Creates new [`Settings`] using environment configuration. - /// - /// Currently, this is equivalent to calling [`Settings::default`]. - pub fn from_env() -> Self { - Self::default() - } -} diff --git a/glow/src/shader/common/gradient.frag b/glow/src/shader/common/gradient.frag deleted file mode 100644 index 9af0cb6e..00000000 --- a/glow/src/shader/common/gradient.frag +++ /dev/null @@ -1,59 +0,0 @@ -#ifdef GL_ES -#ifdef GL_FRAGMENT_PRECISION_HIGH -precision highp float; -#else -precision mediump float; -#endif -#endif - -#ifdef HIGHER_THAN_300 -layout (location = 0) out vec4 fragColor; -#define gl_FragColor fragColor -#endif - -in vec2 raw_position; - -uniform vec4 gradient_direction; -uniform int color_stops_size; -// GLSL does not support dynamically sized arrays without SSBOs so this is capped to 16 stops -//stored as color(vec4) -> offset(vec4) sequentially; -uniform vec4 color_stops[32]; - -//TODO: rewrite without branching to make ALUs happy -void main() { - vec2 start = gradient_direction.xy; - vec2 end = gradient_direction.zw; - vec2 gradient_vec = vec2(end - start); - vec2 current_vec = vec2(raw_position.xy - start); - vec2 unit = normalize(gradient_vec); - float coord_offset = dot(unit, current_vec) / length(gradient_vec); - //if a gradient has a start/end stop that is identical, the mesh will have a transparent fill - gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); - - float min_offset = color_stops[1].x; - float max_offset = color_stops[color_stops_size - 1].x; - - for (int i = 0; i < color_stops_size - 2; i += 2) { - float curr_offset = color_stops[i+1].x; - float next_offset = color_stops[i+3].x; - - if (coord_offset <= min_offset) { - //current coordinate is before the first defined offset, set it to the start color - gl_FragColor = color_stops[0]; - } - - if (curr_offset <= coord_offset && coord_offset <= next_offset) { - //current fragment is between the current offset processing & the next one, interpolate colors - gl_FragColor = mix(color_stops[i], color_stops[i+2], smoothstep( - curr_offset, - next_offset, - coord_offset - )); - } - - if (coord_offset >= max_offset) { - //current coordinate is before the last defined offset, set it to the last color - gl_FragColor = color_stops[color_stops_size - 2]; - } - } -} diff --git a/glow/src/shader/common/gradient.vert b/glow/src/shader/common/gradient.vert deleted file mode 100644 index fe505997..00000000 --- a/glow/src/shader/common/gradient.vert +++ /dev/null @@ -1,9 +0,0 @@ -uniform mat4 u_Transform; - -in vec2 i_Position; -out vec2 raw_position; - -void main() { - gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0); - raw_position = i_Position; -} diff --git a/glow/src/shader/common/image.frag b/glow/src/shader/common/image.frag deleted file mode 100644 index 5e05abdf..00000000 --- a/glow/src/shader/common/image.frag +++ /dev/null @@ -1,22 +0,0 @@ -#ifdef GL_ES -#ifdef GL_FRAGMENT_PRECISION_HIGH -precision highp float; -#else -precision mediump float; -#endif -#endif - -uniform sampler2D tex; -in vec2 tex_pos; - -#ifdef HIGHER_THAN_300 -out vec4 fragColor; -#define gl_FragColor fragColor -#endif -#ifdef GL_ES -#define texture texture2D -#endif - -void main() { - gl_FragColor = texture(tex, tex_pos); -} diff --git a/glow/src/shader/common/image.vert b/glow/src/shader/common/image.vert deleted file mode 100644 index 93e541f2..00000000 --- a/glow/src/shader/common/image.vert +++ /dev/null @@ -1,9 +0,0 @@ -uniform mat4 u_Transform; - -in vec2 i_Position; -out vec2 tex_pos; - -void main() { - gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0); - tex_pos = i_Position; -} diff --git a/glow/src/shader/common/solid.frag b/glow/src/shader/common/solid.frag deleted file mode 100644 index 174ffdd3..00000000 --- a/glow/src/shader/common/solid.frag +++ /dev/null @@ -1,18 +0,0 @@ -#ifdef GL_ES -#ifdef GL_FRAGMENT_PRECISION_HIGH -precision highp float; -#else -precision mediump float; -#endif -#endif - -#ifdef HIGHER_THAN_300 -out vec4 fragColor; -#define gl_FragColor fragColor -#endif - -in vec4 v_Color; - -void main() { - gl_FragColor = v_Color; -} diff --git a/glow/src/shader/common/solid.vert b/glow/src/shader/common/solid.vert deleted file mode 100644 index 59ed88e5..00000000 --- a/glow/src/shader/common/solid.vert +++ /dev/null @@ -1,11 +0,0 @@ -uniform mat4 u_Transform; - -in vec2 i_Position; -in vec4 i_Color; - -out vec4 v_Color; - -void main() { - gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0); - v_Color = i_Color; -} diff --git a/glow/src/shader/compatibility/quad.frag b/glow/src/shader/compatibility/quad.frag deleted file mode 100644 index bb9d8122..00000000 --- a/glow/src/shader/compatibility/quad.frag +++ /dev/null @@ -1,83 +0,0 @@ -#ifdef GL_ES -#ifdef GL_FRAGMENT_PRECISION_HIGH -precision highp float; -#else -precision mediump float; -#endif -#endif - -uniform float u_ScreenHeight; - -varying vec4 v_Color; -varying vec4 v_BorderColor; -varying vec2 v_Pos; -varying vec2 v_Scale; -varying vec4 v_BorderRadius; -varying float v_BorderWidth; - -float _distance(vec2 frag_coord, vec2 position, vec2 size, float radius) -{ - // TODO: Try SDF approach: https://www.shadertoy.com/view/wd3XRN - vec2 inner_size = size - vec2(radius, radius) * 2.0; - vec2 top_left = position + vec2(radius, radius); - vec2 bottom_right = top_left + inner_size; - - vec2 top_left_distance = top_left - frag_coord; - vec2 bottom_right_distance = frag_coord - bottom_right; - - vec2 distance = vec2( - max(max(top_left_distance.x, bottom_right_distance.x), 0.0), - max(max(top_left_distance.y, bottom_right_distance.y), 0.0) - ); - - return sqrt(distance.x * distance.x + distance.y * distance.y); -} - -float selectBorderRadius(vec4 radi, vec2 position, vec2 center) -{ - float rx = radi.x; - float ry = radi.y; - rx = position.x > center.x ? radi.y : radi.x; - ry = position.x > center.x ? radi.z : radi.w; - rx = position.y > center.y ? ry : rx; - return rx; -} - -void main() { - vec2 fragCoord = vec2(gl_FragCoord.x, u_ScreenHeight - gl_FragCoord.y); - - float border_radius = selectBorderRadius( - v_BorderRadius, - fragCoord, - (v_Pos + v_Scale * 0.5).xy - ); - - float internal_border = max(border_radius - v_BorderWidth, 0.0); - - float internal_distance = _distance( - fragCoord, - v_Pos + vec2(v_BorderWidth), - v_Scale - vec2(v_BorderWidth * 2.0), - internal_border - ); - - float border_mix = smoothstep( - max(internal_border - 0.5, 0.0), - internal_border + 0.5, - internal_distance - ); - - vec4 mixed_color = mix(v_Color, v_BorderColor, border_mix); - - float d = _distance( - fragCoord, - v_Pos, - v_Scale, - border_radius - ); - - float radius_alpha = - 1.0 - smoothstep(max(border_radius - 0.5, 0.0), border_radius + 0.5, d); - - gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha); -} diff --git a/glow/src/shader/compatibility/quad.vert b/glow/src/shader/compatibility/quad.vert deleted file mode 100644 index 89931f06..00000000 --- a/glow/src/shader/compatibility/quad.vert +++ /dev/null @@ -1,46 +0,0 @@ -uniform mat4 u_Transform; -uniform float u_Scale; - -attribute vec2 i_Pos; -attribute vec2 i_Scale; -attribute vec4 i_Color; -attribute vec4 i_BorderColor; -attribute vec4 i_BorderRadius; -attribute float i_BorderWidth; -attribute vec2 q_Pos; - -varying vec4 v_Color; -varying vec4 v_BorderColor; -varying vec2 v_Pos; -varying vec2 v_Scale; -varying vec4 v_BorderRadius; -varying float v_BorderWidth; - - -void main() { - vec2 p_Pos = i_Pos * u_Scale; - vec2 p_Scale = i_Scale * u_Scale; - - vec4 i_BorderRadius = vec4( - min(i_BorderRadius.x, min(i_Scale.x, i_Scale.y) / 2.0), - min(i_BorderRadius.y, min(i_Scale.x, i_Scale.y) / 2.0), - min(i_BorderRadius.z, min(i_Scale.x, i_Scale.y) / 2.0), - min(i_BorderRadius.w, min(i_Scale.x, i_Scale.y) / 2.0) - ); - - mat4 i_Transform = mat4( - vec4(p_Scale.x + 1.0, 0.0, 0.0, 0.0), - vec4(0.0, p_Scale.y + 1.0, 0.0, 0.0), - vec4(0.0, 0.0, 1.0, 0.0), - vec4(p_Pos - vec2(0.5, 0.5), 0.0, 1.0) - ); - - v_Color = i_Color; - v_BorderColor = i_BorderColor; - v_Pos = p_Pos; - v_Scale = p_Scale; - v_BorderRadius = i_BorderRadius * u_Scale; - v_BorderWidth = i_BorderWidth * u_Scale; - - gl_Position = u_Transform * i_Transform * vec4(q_Pos, 0.0, 1.0); -} diff --git a/glow/src/shader/core/quad.frag b/glow/src/shader/core/quad.frag deleted file mode 100644 index 71147aa5..00000000 --- a/glow/src/shader/core/quad.frag +++ /dev/null @@ -1,95 +0,0 @@ -#ifdef GL_ES -#ifdef GL_FRAGMENT_PRECISION_HIGH -precision highp float; -#else -precision mediump float; -#endif -#endif - -#ifdef HIGHER_THAN_300 -out vec4 fragColor; -#define gl_FragColor fragColor -#endif - -uniform float u_ScreenHeight; - -in vec4 v_Color; -in vec4 v_BorderColor; -in vec2 v_Pos; -in vec2 v_Scale; -in vec4 v_BorderRadius; -in float v_BorderWidth; - -float fDistance(vec2 frag_coord, vec2 position, vec2 size, float radius) -{ - // TODO: Try SDF approach: https://www.shadertoy.com/view/wd3XRN - vec2 inner_size = size - vec2(radius, radius) * 2.0; - vec2 top_left = position + vec2(radius, radius); - vec2 bottom_right = top_left + inner_size; - - vec2 top_left_distance = top_left - frag_coord; - vec2 bottom_right_distance = frag_coord - bottom_right; - - vec2 distance = vec2( - max(max(top_left_distance.x, bottom_right_distance.x), 0.0), - max(max(top_left_distance.y, bottom_right_distance.y), 0.0) - ); - - return sqrt(distance.x * distance.x + distance.y * distance.y); -} - -float selectBorderRadius(vec4 radi, vec2 position, vec2 center) -{ - float rx = radi.x; - float ry = radi.y; - rx = position.x > center.x ? radi.y : radi.x; - ry = position.x > center.x ? radi.z : radi.w; - rx = position.y > center.y ? ry : rx; - return rx; -} - -void main() { - vec4 mixed_color; - - vec2 fragCoord = vec2(gl_FragCoord.x, u_ScreenHeight - gl_FragCoord.y); - - float border_radius = selectBorderRadius( - v_BorderRadius, - fragCoord, - (v_Pos + v_Scale * 0.5).xy - ); - - // TODO: Remove branching (?) - if(v_BorderWidth > 0.0) { - float internal_border = max(border_radius - v_BorderWidth, 0.0); - - float internal_distance = fDistance( - fragCoord, - v_Pos + vec2(v_BorderWidth), - v_Scale - vec2(v_BorderWidth * 2.0), - internal_border - ); - - float border_mix = smoothstep( - max(internal_border - 0.5, 0.0), - internal_border + 0.5, - internal_distance - ); - - mixed_color = mix(v_Color, v_BorderColor, border_mix); - } else { - mixed_color = v_Color; - } - - float d = fDistance( - fragCoord, - v_Pos, - v_Scale, - border_radius - ); - - float radius_alpha = - 1.0 - smoothstep(max(border_radius - 0.5, 0.0), border_radius + 0.5, d); - - gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha); -} diff --git a/glow/src/shader/core/quad.vert b/glow/src/shader/core/quad.vert deleted file mode 100644 index 17c3e641..00000000 --- a/glow/src/shader/core/quad.vert +++ /dev/null @@ -1,52 +0,0 @@ -uniform mat4 u_Transform; -uniform float u_Scale; - -in vec2 i_Pos; -in vec2 i_Scale; -in vec4 i_Color; -in vec4 i_BorderColor; -in vec4 i_BorderRadius; -in float i_BorderWidth; - -out vec4 v_Color; -out vec4 v_BorderColor; -out vec2 v_Pos; -out vec2 v_Scale; -out vec4 v_BorderRadius; -out float v_BorderWidth; - -vec2 positions[4] = vec2[]( - vec2(0.0, 0.0), - vec2(0.0, 1.0), - vec2(1.0, 0.0), - vec2(1.0, 1.0) -); - -void main() { - vec2 q_Pos = positions[gl_VertexID]; - vec2 p_Pos = i_Pos * u_Scale; - vec2 p_Scale = i_Scale * u_Scale; - - vec4 i_BorderRadius = vec4( - min(i_BorderRadius.x, min(i_Scale.x, i_Scale.y) / 2.0), - min(i_BorderRadius.y, min(i_Scale.x, i_Scale.y) / 2.0), - min(i_BorderRadius.z, min(i_Scale.x, i_Scale.y) / 2.0), - min(i_BorderRadius.w, min(i_Scale.x, i_Scale.y) / 2.0) - ); - - mat4 i_Transform = mat4( - vec4(p_Scale.x + 1.0, 0.0, 0.0, 0.0), - vec4(0.0, p_Scale.y + 1.0, 0.0, 0.0), - vec4(0.0, 0.0, 1.0, 0.0), - vec4(p_Pos - vec2(0.5, 0.5), 0.0, 1.0) - ); - - v_Color = i_Color; - v_BorderColor = i_BorderColor; - v_Pos = p_Pos; - v_Scale = p_Scale; - v_BorderRadius = i_BorderRadius * u_Scale; - v_BorderWidth = i_BorderWidth * u_Scale; - - gl_Position = u_Transform * i_Transform * vec4(q_Pos, 0.0, 1.0); -} diff --git a/glow/src/text.rs b/glow/src/text.rs deleted file mode 100644 index 37ccdece..00000000 --- a/glow/src/text.rs +++ /dev/null @@ -1,257 +0,0 @@ -use crate::Transformation; - -use iced_graphics::font; - -use glow_glyph::ab_glyph; -use std::{cell::RefCell, collections::HashMap}; - -pub use iced_native::text::Hit; - -#[derive(Debug)] -pub struct Pipeline { - draw_brush: RefCell, - draw_font_map: RefCell>, - measure_brush: RefCell>, -} - -impl Pipeline { - pub fn new( - gl: &glow::Context, - default_font: Option<&[u8]>, - multithreading: bool, - ) -> Self { - let default_font = default_font.map(|slice| slice.to_vec()); - - // TODO: Font customization - #[cfg(feature = "default_system_font")] - let default_font = { - default_font.or_else(|| { - font::Source::new() - .load(&[font::Family::SansSerif, font::Family::Serif]) - .ok() - }) - }; - - let default_font = - default_font.unwrap_or_else(|| font::FALLBACK.to_vec()); - - let font = ab_glyph::FontArc::try_from_vec(default_font) - .unwrap_or_else(|_| { - log::warn!( - "System font failed to load. Falling back to \ - embedded font..." - ); - - ab_glyph::FontArc::try_from_slice(font::FALLBACK) - .expect("Load fallback font") - }); - - let draw_brush_builder = - glow_glyph::GlyphBrushBuilder::using_font(font.clone()) - .initial_cache_size((2048, 2048)) - .draw_cache_multithread(multithreading); - - #[cfg(target_arch = "wasm32")] - let draw_brush_builder = draw_brush_builder.draw_cache_align_4x4(true); - - let draw_brush = draw_brush_builder.build(gl); - - let measure_brush = - glyph_brush::GlyphBrushBuilder::using_font(font).build(); - - Pipeline { - draw_brush: RefCell::new(draw_brush), - draw_font_map: RefCell::new(HashMap::new()), - measure_brush: RefCell::new(measure_brush), - } - } - - pub fn queue(&mut self, section: glow_glyph::Section<'_>) { - self.draw_brush.borrow_mut().queue(section); - } - - pub fn draw_queued( - &mut self, - gl: &glow::Context, - transformation: Transformation, - region: glow_glyph::Region, - ) { - self.draw_brush - .borrow_mut() - .draw_queued_with_transform_and_scissoring( - gl, - transformation.into(), - region, - ) - .expect("Draw text"); - } - - pub fn measure( - &self, - content: &str, - size: f32, - font: iced_native::Font, - bounds: iced_native::Size, - ) -> (f32, f32) { - use glow_glyph::GlyphCruncher; - - let glow_glyph::FontId(font_id) = self.find_font(font); - - let section = glow_glyph::Section { - bounds: (bounds.width, bounds.height), - text: vec![glow_glyph::Text { - text: content, - scale: size.into(), - font_id: glow_glyph::FontId(font_id), - extra: glow_glyph::Extra::default(), - }], - ..Default::default() - }; - - if let Some(bounds) = - self.measure_brush.borrow_mut().glyph_bounds(section) - { - (bounds.width().ceil(), bounds.height().ceil()) - } else { - (0.0, 0.0) - } - } - - pub fn hit_test( - &self, - content: &str, - size: f32, - font: iced_native::Font, - bounds: iced_native::Size, - point: iced_native::Point, - nearest_only: bool, - ) -> Option { - use glow_glyph::GlyphCruncher; - - let glow_glyph::FontId(font_id) = self.find_font(font); - - let section = glow_glyph::Section { - bounds: (bounds.width, bounds.height), - text: vec![glow_glyph::Text { - text: content, - scale: size.into(), - font_id: glow_glyph::FontId(font_id), - extra: glow_glyph::Extra::default(), - }], - ..Default::default() - }; - - let mut mb = self.measure_brush.borrow_mut(); - - // The underlying type is FontArc, so clones are cheap. - use ab_glyph::{Font, ScaleFont}; - let font = mb.fonts()[font_id].clone().into_scaled(size); - - // Implements an iterator over the glyph bounding boxes. - let bounds = mb.glyphs(section).map( - |glow_glyph::SectionGlyph { - byte_index, glyph, .. - }| { - ( - *byte_index, - iced_native::Rectangle::new( - iced_native::Point::new( - glyph.position.x - font.h_side_bearing(glyph.id), - glyph.position.y - font.ascent(), - ), - iced_native::Size::new( - font.h_advance(glyph.id), - font.ascent() - font.descent(), - ), - ), - ) - }, - ); - - // Implements computation of the character index based on the byte index - // within the input string. - let char_index = |byte_index| { - let mut b_count = 0; - for (i, utf8_len) in - content.chars().map(|c| c.len_utf8()).enumerate() - { - if byte_index < (b_count + utf8_len) { - return i; - } - b_count += utf8_len; - } - - byte_index - }; - - if !nearest_only { - for (idx, bounds) in bounds.clone() { - if bounds.contains(point) { - return Some(Hit::CharOffset(char_index(idx))); - } - } - } - - let nearest = bounds - .map(|(index, bounds)| (index, bounds.center())) - .min_by(|(_, center_a), (_, center_b)| { - center_a - .distance(point) - .partial_cmp(¢er_b.distance(point)) - .unwrap_or(std::cmp::Ordering::Greater) - }); - - nearest.map(|(idx, center)| { - Hit::NearestCharOffset(char_index(idx), point - center) - }) - } - - pub fn trim_measurement_cache(&mut self) { - // TODO: We should probably use a `GlyphCalculator` for this. However, - // it uses a lifetimed `GlyphCalculatorGuard` with side-effects on drop. - // This makes stuff quite inconvenient. A manual method for trimming the - // cache would make our lives easier. - loop { - let action = self - .measure_brush - .borrow_mut() - .process_queued(|_, _| {}, |_| {}); - - match action { - Ok(_) => break, - Err(glyph_brush::BrushError::TextureTooSmall { suggested }) => { - let (width, height) = suggested; - - self.measure_brush - .borrow_mut() - .resize_texture(width, height); - } - } - } - } - - pub fn find_font(&self, font: iced_native::Font) -> glow_glyph::FontId { - match font { - iced_native::Font::Default => glow_glyph::FontId(0), - iced_native::Font::External { name, bytes } => { - if let Some(font_id) = self.draw_font_map.borrow().get(name) { - return *font_id; - } - - let font = ab_glyph::FontArc::try_from_slice(bytes) - .expect("Load font"); - - let _ = self.measure_brush.borrow_mut().add_font(font.clone()); - - let font_id = self.draw_brush.borrow_mut().add_font(font); - - let _ = self - .draw_font_map - .borrow_mut() - .insert(String::from(name), font_id); - - font_id - } - } - } -} diff --git a/glow/src/triangle.rs b/glow/src/triangle.rs deleted file mode 100644 index 42c88455..00000000 --- a/glow/src/triangle.rs +++ /dev/null @@ -1,595 +0,0 @@ -//! Draw meshes of triangles. -use crate::program; -use crate::Transformation; - -use iced_graphics::gradient::Gradient; -use iced_graphics::layer::mesh::{self, Mesh}; -use iced_graphics::triangle::{ColoredVertex2D, Vertex2D}; - -use glow::HasContext; -use std::marker::PhantomData; - -#[cfg(feature = "tracing")] -use tracing::info_span; - -const DEFAULT_VERTICES: usize = 1_000; -const DEFAULT_INDICES: usize = 1_000; - -#[derive(Debug)] -pub(crate) struct Pipeline { - indices: Buffer, - solid: solid::Program, - gradient: gradient::Program, -} - -impl Pipeline { - pub fn new(gl: &glow::Context, shader_version: &program::Version) -> Self { - let mut indices = unsafe { - Buffer::new( - gl, - glow::ELEMENT_ARRAY_BUFFER, - glow::DYNAMIC_DRAW, - DEFAULT_INDICES, - ) - }; - - let solid = solid::Program::new(gl, shader_version); - let gradient = gradient::Program::new(gl, shader_version); - - unsafe { - gl.bind_vertex_array(Some(solid.vertex_array)); - indices.bind(gl, 0); - - gl.bind_vertex_array(Some(gradient.vertex_array)); - indices.bind(gl, 0); - - gl.bind_vertex_array(None); - } - - Self { - indices, - solid, - gradient, - } - } - - pub fn draw( - &mut self, - meshes: &[Mesh<'_>], - gl: &glow::Context, - target_height: u32, - transformation: Transformation, - scale_factor: f32, - ) { - #[cfg(feature = "tracing")] - let _ = info_span!("Glow::Triangle", "DRAW").enter(); - - unsafe { - gl.enable(glow::MULTISAMPLE); - gl.enable(glow::SCISSOR_TEST); - } - - // Count the total amount of vertices & indices we need to handle - let count = mesh::attribute_count_of(meshes); - - // Then we ensure the current attribute buffers are big enough, resizing if necessary - unsafe { - self.indices.bind(gl, count.indices); - } - - // We upload all the vertices and indices upfront - let mut solid_vertex_offset = 0; - let mut gradient_vertex_offset = 0; - let mut index_offset = 0; - - for mesh in meshes { - let indices = mesh.indices(); - - unsafe { - gl.buffer_sub_data_u8_slice( - glow::ELEMENT_ARRAY_BUFFER, - (index_offset * std::mem::size_of::()) as i32, - bytemuck::cast_slice(indices), - ); - - index_offset += indices.len(); - } - - match mesh { - Mesh::Solid { buffers, .. } => { - unsafe { - self.solid.vertices.bind(gl, count.solid_vertices); - - gl.buffer_sub_data_u8_slice( - glow::ARRAY_BUFFER, - (solid_vertex_offset - * std::mem::size_of::()) - as i32, - bytemuck::cast_slice(&buffers.vertices), - ); - } - - solid_vertex_offset += buffers.vertices.len(); - } - Mesh::Gradient { buffers, .. } => { - unsafe { - self.gradient - .vertices - .bind(gl, count.gradient_vertices); - - gl.buffer_sub_data_u8_slice( - glow::ARRAY_BUFFER, - (gradient_vertex_offset - * std::mem::size_of::()) - as i32, - bytemuck::cast_slice(&buffers.vertices), - ); - } - - gradient_vertex_offset += buffers.vertices.len(); - } - } - } - - // Then we draw each mesh using offsets - let mut last_solid_vertex = 0; - let mut last_gradient_vertex = 0; - let mut last_index = 0; - - for mesh in meshes { - let indices = mesh.indices(); - let origin = mesh.origin(); - - let transform = - transformation * Transformation::translate(origin.x, origin.y); - - let clip_bounds = (mesh.clip_bounds() * scale_factor).snap(); - - unsafe { - gl.scissor( - clip_bounds.x as i32, - (target_height - (clip_bounds.y + clip_bounds.height)) - as i32, - clip_bounds.width as i32, - clip_bounds.height as i32, - ); - } - - match mesh { - Mesh::Solid { buffers, .. } => unsafe { - gl.use_program(Some(self.solid.program)); - gl.bind_vertex_array(Some(self.solid.vertex_array)); - - if transform != self.solid.uniforms.transform { - gl.uniform_matrix_4_f32_slice( - Some(&self.solid.uniforms.transform_location), - false, - transform.as_ref(), - ); - - self.solid.uniforms.transform = transform; - } - - gl.draw_elements_base_vertex( - glow::TRIANGLES, - indices.len() as i32, - glow::UNSIGNED_INT, - (last_index * std::mem::size_of::()) as i32, - last_solid_vertex as i32, - ); - - last_solid_vertex += buffers.vertices.len(); - }, - Mesh::Gradient { - buffers, gradient, .. - } => unsafe { - gl.use_program(Some(self.gradient.program)); - gl.bind_vertex_array(Some(self.gradient.vertex_array)); - - if transform != self.gradient.uniforms.transform { - gl.uniform_matrix_4_f32_slice( - Some(&self.gradient.uniforms.locations.transform), - false, - transform.as_ref(), - ); - - self.gradient.uniforms.transform = transform; - } - - if &self.gradient.uniforms.gradient != *gradient { - match gradient { - Gradient::Linear(linear) => { - gl.uniform_4_f32( - Some( - &self - .gradient - .uniforms - .locations - .gradient_direction, - ), - linear.start.x, - linear.start.y, - linear.end.x, - linear.end.y, - ); - - gl.uniform_1_i32( - Some( - &self - .gradient - .uniforms - .locations - .color_stops_size, - ), - (linear.color_stops.len() * 2) as i32, - ); - - let mut stops = [0.0; 128]; - - for (index, stop) in linear - .color_stops - .iter() - .enumerate() - .take(16) - { - let [r, g, b, a] = stop.color.into_linear(); - - stops[index * 8] = r; - stops[(index * 8) + 1] = g; - stops[(index * 8) + 2] = b; - stops[(index * 8) + 3] = a; - stops[(index * 8) + 4] = stop.offset; - stops[(index * 8) + 5] = 0.; - stops[(index * 8) + 6] = 0.; - stops[(index * 8) + 7] = 0.; - } - - gl.uniform_4_f32_slice( - Some( - &self - .gradient - .uniforms - .locations - .color_stops, - ), - &stops, - ); - } - } - - self.gradient.uniforms.gradient = (*gradient).clone(); - } - - gl.draw_elements_base_vertex( - glow::TRIANGLES, - indices.len() as i32, - glow::UNSIGNED_INT, - (last_index * std::mem::size_of::()) as i32, - last_gradient_vertex as i32, - ); - - last_gradient_vertex += buffers.vertices.len(); - }, - } - - last_index += indices.len(); - } - - unsafe { - gl.bind_vertex_array(None); - gl.disable(glow::SCISSOR_TEST); - gl.disable(glow::MULTISAMPLE); - } - } -} - -#[derive(Debug)] -pub struct Buffer { - raw: ::Buffer, - target: u32, - usage: u32, - size: usize, - phantom: PhantomData, -} - -impl Buffer { - pub unsafe fn new( - gl: &glow::Context, - target: u32, - usage: u32, - size: usize, - ) -> Self { - let raw = gl.create_buffer().expect("Create buffer"); - - let mut buffer = Buffer { - raw, - target, - usage, - size: 0, - phantom: PhantomData, - }; - - buffer.bind(gl, size); - - buffer - } - - pub unsafe fn bind(&mut self, gl: &glow::Context, size: usize) { - gl.bind_buffer(self.target, Some(self.raw)); - - if self.size < size { - gl.buffer_data_size( - self.target, - (size * std::mem::size_of::()) as i32, - self.usage, - ); - - self.size = size; - } - } -} - -mod solid { - use crate::program; - use crate::triangle; - use glow::{Context, HasContext, NativeProgram}; - use iced_graphics::triangle::ColoredVertex2D; - use iced_graphics::Transformation; - - #[derive(Debug)] - pub struct Program { - pub program: ::Program, - pub vertex_array: ::VertexArray, - pub vertices: triangle::Buffer, - pub uniforms: Uniforms, - } - - impl Program { - pub fn new(gl: &Context, shader_version: &program::Version) -> Self { - let program = unsafe { - let vertex_shader = program::Shader::vertex( - gl, - shader_version, - include_str!("shader/common/solid.vert"), - ); - - let fragment_shader = program::Shader::fragment( - gl, - shader_version, - include_str!("shader/common/solid.frag"), - ); - - program::create( - gl, - &[vertex_shader, fragment_shader], - &[(0, "i_Position"), (1, "i_Color")], - ) - }; - - let vertex_array = unsafe { - gl.create_vertex_array().expect("Create vertex array") - }; - - let vertices = unsafe { - triangle::Buffer::new( - gl, - glow::ARRAY_BUFFER, - glow::DYNAMIC_DRAW, - super::DEFAULT_VERTICES, - ) - }; - - unsafe { - gl.bind_vertex_array(Some(vertex_array)); - - let stride = std::mem::size_of::() as i32; - - gl.enable_vertex_attrib_array(0); - gl.vertex_attrib_pointer_f32( - 0, - 2, - glow::FLOAT, - false, - stride, - 0, - ); - - gl.enable_vertex_attrib_array(1); - gl.vertex_attrib_pointer_f32( - 1, - 4, - glow::FLOAT, - false, - stride, - 4 * 2, - ); - - gl.bind_vertex_array(None); - }; - - Self { - program, - vertex_array, - vertices, - uniforms: Uniforms::new(gl, program), - } - } - } - - #[derive(Debug)] - pub struct Uniforms { - pub transform: Transformation, - pub transform_location: ::UniformLocation, - } - - impl Uniforms { - fn new(gl: &Context, program: NativeProgram) -> Self { - let transform = Transformation::identity(); - let transform_location = - unsafe { gl.get_uniform_location(program, "u_Transform") } - .expect("Solid - Get u_Transform."); - - unsafe { - gl.use_program(Some(program)); - - gl.uniform_matrix_4_f32_slice( - Some(&transform_location), - false, - transform.as_ref(), - ); - - gl.use_program(None); - } - - Self { - transform, - transform_location, - } - } - } -} - -mod gradient { - use crate::program; - use crate::triangle; - use glow::{Context, HasContext, NativeProgram}; - use iced_graphics::gradient::{self, Gradient}; - use iced_graphics::triangle::Vertex2D; - use iced_graphics::Transformation; - - #[derive(Debug)] - pub struct Program { - pub program: ::Program, - pub vertex_array: ::VertexArray, - pub vertices: triangle::Buffer, - pub uniforms: Uniforms, - } - - impl Program { - pub fn new(gl: &Context, shader_version: &program::Version) -> Self { - let program = unsafe { - let vertex_shader = program::Shader::vertex( - gl, - shader_version, - include_str!("shader/common/gradient.vert"), - ); - - let fragment_shader = program::Shader::fragment( - gl, - shader_version, - include_str!("shader/common/gradient.frag"), - ); - - program::create( - gl, - &[vertex_shader, fragment_shader], - &[(0, "i_Position")], - ) - }; - - let vertex_array = unsafe { - gl.create_vertex_array().expect("Create vertex array") - }; - - let vertices = unsafe { - triangle::Buffer::new( - gl, - glow::ARRAY_BUFFER, - glow::DYNAMIC_DRAW, - super::DEFAULT_VERTICES, - ) - }; - - unsafe { - gl.bind_vertex_array(Some(vertex_array)); - - let stride = std::mem::size_of::() as i32; - - gl.enable_vertex_attrib_array(0); - gl.vertex_attrib_pointer_f32( - 0, - 2, - glow::FLOAT, - false, - stride, - 0, - ); - - gl.bind_vertex_array(None); - }; - - Self { - program, - vertex_array, - vertices, - uniforms: Uniforms::new(gl, program), - } - } - } - - #[derive(Debug)] - pub struct Uniforms { - pub gradient: Gradient, - pub transform: Transformation, - pub locations: Locations, - } - - #[derive(Debug)] - pub struct Locations { - pub gradient_direction: ::UniformLocation, - pub color_stops_size: ::UniformLocation, - //currently the maximum number of stops is 16 due to lack of SSBO in GL2.1 - pub color_stops: ::UniformLocation, - pub transform: ::UniformLocation, - } - - impl Uniforms { - fn new(gl: &Context, program: NativeProgram) -> Self { - let gradient_direction = unsafe { - gl.get_uniform_location(program, "gradient_direction") - } - .expect("Gradient - Get gradient_direction."); - - let color_stops_size = - unsafe { gl.get_uniform_location(program, "color_stops_size") } - .expect("Gradient - Get color_stops_size."); - - let color_stops = unsafe { - gl.get_uniform_location(program, "color_stops") - .expect("Gradient - Get color_stops.") - }; - - let transform = Transformation::identity(); - let transform_location = - unsafe { gl.get_uniform_location(program, "u_Transform") } - .expect("Solid - Get u_Transform."); - - unsafe { - gl.use_program(Some(program)); - - gl.uniform_matrix_4_f32_slice( - Some(&transform_location), - false, - transform.as_ref(), - ); - - gl.use_program(None); - } - - Self { - gradient: Gradient::Linear(gradient::Linear { - start: Default::default(), - end: Default::default(), - color_stops: vec![], - }), - transform: Transformation::identity(), - locations: Locations { - gradient_direction, - color_stops_size, - color_stops, - transform: transform_location, - }, - } - } - } -} diff --git a/glow/src/window.rs b/glow/src/window.rs deleted file mode 100644 index aac5fb9e..00000000 --- a/glow/src/window.rs +++ /dev/null @@ -1,4 +0,0 @@ -//! Display rendering results on windows. -mod compositor; - -pub use compositor::Compositor; diff --git a/glow/src/window/compositor.rs b/glow/src/window/compositor.rs deleted file mode 100644 index 20756032..00000000 --- a/glow/src/window/compositor.rs +++ /dev/null @@ -1,111 +0,0 @@ -use crate::{Backend, Color, Error, Renderer, Settings, Viewport}; - -use glow::HasContext; -use iced_graphics::{compositor, Antialiasing, Size}; - -use core::ffi::c_void; -use std::marker::PhantomData; - -/// A window graphics backend for iced powered by `glow`. -#[allow(missing_debug_implementations)] -pub struct Compositor { - gl: glow::Context, - theme: PhantomData, -} - -impl iced_graphics::window::GLCompositor for Compositor { - type Settings = Settings; - type Renderer = Renderer; - - unsafe fn new( - settings: Self::Settings, - loader_function: impl FnMut(&str) -> *const c_void, - ) -> Result<(Self, Self::Renderer), Error> { - let gl = glow::Context::from_loader_function(loader_function); - - log::info!("{:#?}", settings); - - let version = gl.version(); - log::info!( - "OpenGL version: {:?} (Embedded: {})", - version, - version.is_embedded - ); - - let renderer = gl.get_parameter_string(glow::RENDERER); - log::info!("Renderer: {}", renderer); - - // Enable auto-conversion from/to sRGB - gl.enable(glow::FRAMEBUFFER_SRGB); - - // Enable alpha blending - gl.enable(glow::BLEND); - gl.blend_func_separate( - glow::SRC_ALPHA, - glow::ONE_MINUS_SRC_ALPHA, - glow::ONE, - glow::ONE_MINUS_SRC_ALPHA, - ); - - // Disable multisampling by default - gl.disable(glow::MULTISAMPLE); - - let renderer = Renderer::new(Backend::new(&gl, settings)); - - Ok(( - Self { - gl, - theme: PhantomData, - }, - renderer, - )) - } - - fn sample_count(settings: &Settings) -> u32 { - settings - .antialiasing - .map(Antialiasing::sample_count) - .unwrap_or(0) - } - - fn resize_viewport(&mut self, physical_size: Size) { - unsafe { - self.gl.viewport( - 0, - 0, - physical_size.width as i32, - physical_size.height as i32, - ); - } - } - - fn fetch_information(&self) -> compositor::Information { - let adapter = unsafe { self.gl.get_parameter_string(glow::RENDERER) }; - - compositor::Information { - backend: format!("{:?}", self.gl.version()), - adapter, - } - } - - fn present>( - &mut self, - renderer: &mut Self::Renderer, - viewport: &Viewport, - color: Color, - overlay: &[T], - ) { - let gl = &self.gl; - - let [r, g, b, a] = color.into_linear(); - - unsafe { - gl.clear_color(r, g, b, a); - gl.clear(glow::COLOR_BUFFER_BIT); - } - - renderer.with_primitives(|backend, primitive| { - backend.present(gl, primitive, viewport, overlay); - }); - } -} diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 13ab61d8..19d6af0c 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -26,9 +26,6 @@ dds = ["image_rs/dds"] farbfeld = ["image_rs/farbfeld"] canvas = ["lyon"] qr_code = ["qrcode", "canvas"] -font-source = ["font-kit"] -font-fallback = [] -font-icons = [] opengl = [] image_rs = ["kamadak-exif"] @@ -60,10 +57,6 @@ version = "0.12" optional = true default-features = false -[dependencies.font-kit] -version = "0.10" -optional = true - [dependencies.image_rs] version = "0.24" package = "image" diff --git a/graphics/src/font.rs b/graphics/src/font.rs deleted file mode 100644 index d55d0faf..00000000 --- a/graphics/src/font.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Find system fonts or use the built-in ones. -#[cfg(feature = "font-source")] -mod source; - -#[cfg(feature = "font-source")] -#[cfg_attr(docsrs, doc(cfg(feature = "font-source")))] -pub use source::Source; - -#[cfg(feature = "font-source")] -#[cfg_attr(docsrs, doc(cfg(feature = "font-source")))] -pub use font_kit::{ - error::SelectionError as LoadError, family_name::FamilyName as Family, -}; - -/// A built-in fallback font, for convenience. -#[cfg(feature = "font-fallback")] -#[cfg_attr(docsrs, doc(cfg(feature = "font-fallback")))] -pub const FALLBACK: &[u8] = include_bytes!("../fonts/Lato-Regular.ttf"); - -/// A built-in icon font, for convenience. -#[cfg(feature = "font-icons")] -#[cfg_attr(docsrs, doc(cfg(feature = "font-icons")))] -pub const ICONS: iced_native::Font = iced_native::Font::External { - name: "iced_wgpu icons", - bytes: include_bytes!("../fonts/Icons.ttf"), -}; - -/// The `char` representing a ✔ icon in the built-in [`ICONS`] font. -#[cfg(feature = "font-icons")] -#[cfg_attr(docsrs, doc(cfg(feature = "font-icons")))] -pub const CHECKMARK_ICON: char = '\u{F00C}'; - -/// The `char` representing a ▼ icon in the built-in [`ICONS`] font. -#[cfg(feature = "font-icons")] -pub const ARROW_DOWN_ICON: char = '\u{E800}'; diff --git a/graphics/src/font/source.rs b/graphics/src/font/source.rs deleted file mode 100644 index c0b50e1d..00000000 --- a/graphics/src/font/source.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::font::{Family, LoadError}; - -/// A font source that can find and load system fonts. -#[allow(missing_debug_implementations)] -pub struct Source { - raw: font_kit::source::SystemSource, -} - -impl Source { - /// Creates a new [`Source`]. - pub fn new() -> Self { - Source { - raw: font_kit::source::SystemSource::new(), - } - } - - /// Finds and loads a font matching the set of provided family priorities. - pub fn load(&self, families: &[Family]) -> Result, LoadError> { - let font = self.raw.select_best_match( - families, - &font_kit::properties::Properties::default(), - )?; - - match font { - font_kit::handle::Handle::Path { path, .. } => { - use std::io::Read; - - let mut buf = Vec::new(); - let mut reader = std::fs::File::open(path).expect("Read font"); - let _ = reader.read_to_end(&mut buf); - - Ok(buf) - } - font_kit::handle::Handle::Memory { bytes, .. } => { - Ok(bytes.as_ref().clone()) - } - } - } -} - -impl Default for Source { - fn default() -> Self { - Self::new() - } -} diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index d39dd90c..41bef2c3 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -28,7 +28,6 @@ mod transformation; mod viewport; pub mod backend; -pub mod font; pub mod gradient; pub mod image; pub mod layer; diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index f1e22cf6..5badeae6 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -23,14 +23,11 @@ dds = ["iced_graphics/dds"] farbfeld = ["iced_graphics/farbfeld"] canvas = ["iced_graphics/canvas"] qr_code = ["iced_graphics/qr_code"] -default_system_font = ["iced_graphics/font-source"] spirv = ["wgpu/spirv"] webgl = ["wgpu/webgl"] [dependencies] wgpu = "0.14" -wgpu_glyph = "0.18" -glyph_brush = "0.7" raw-window-handle = "0.5" log = "0.4" guillotiere = "0.6" @@ -48,7 +45,6 @@ path = "../native" [dependencies.iced_graphics] version = "0.7" path = "../graphics" -features = ["font-fallback", "font-icons"] [dependencies.tracing] version = "0.1.6" diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 6a299425..e9e23e80 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -4,10 +4,8 @@ use crate::triangle; use crate::{Settings, Transformation}; use iced_graphics::backend; -use iced_graphics::font; use iced_graphics::layer::Layer; use iced_graphics::{Primitive, Viewport}; -use iced_native::alignment; use iced_native::{Font, Size}; #[cfg(feature = "tracing")] @@ -173,83 +171,11 @@ impl Backend { } if !layer.text.is_empty() { - for text in layer.text.iter() { - // Target physical coordinates directly to avoid blurry text - let text = wgpu_glyph::Section { - // TODO: We `round` here to avoid rerasterizing text when - // its position changes slightly. This can make text feel a - // bit "jumpy". We may be able to do better once we improve - // our text rendering/caching pipeline. - screen_position: ( - (text.bounds.x * scale_factor).round(), - (text.bounds.y * scale_factor).round(), - ), - // TODO: Fix precision issues with some scale factors. - // - // The `ceil` here can cause some words to render on the - // same line when they should not. - // - // Ideally, `wgpu_glyph` should be able to compute layout - // using logical positions, and then apply the proper - // scaling when rendering. This would ensure that both - // measuring and rendering follow the same layout rules. - bounds: ( - (text.bounds.width * scale_factor).ceil(), - (text.bounds.height * scale_factor).ceil(), - ), - text: vec![wgpu_glyph::Text { - text: text.content, - scale: wgpu_glyph::ab_glyph::PxScale { - x: text.size * scale_factor, - y: text.size * scale_factor, - }, - font_id: self.text_pipeline.find_font(text.font), - extra: wgpu_glyph::Extra { - color: text.color, - z: 0.0, - }, - }], - layout: wgpu_glyph::Layout::default() - .h_align(match text.horizontal_alignment { - alignment::Horizontal::Left => { - wgpu_glyph::HorizontalAlign::Left - } - alignment::Horizontal::Center => { - wgpu_glyph::HorizontalAlign::Center - } - alignment::Horizontal::Right => { - wgpu_glyph::HorizontalAlign::Right - } - }) - .v_align(match text.vertical_alignment { - alignment::Vertical::Top => { - wgpu_glyph::VerticalAlign::Top - } - alignment::Vertical::Center => { - wgpu_glyph::VerticalAlign::Center - } - alignment::Vertical::Bottom => { - wgpu_glyph::VerticalAlign::Bottom - } - }), - }; - - self.text_pipeline.queue(text); + for _text in layer.text.iter() { + // TODO: Queue text sections } - self.text_pipeline.draw_queued( - device, - staging_belt, - encoder, - target, - transformation, - wgpu_glyph::Region { - x: bounds.x, - y: bounds.y, - width: bounds.width, - height: bounds.height, - }, - ); + // TODO: Draw queued } } } @@ -261,9 +187,9 @@ impl iced_graphics::Backend for Backend { } impl backend::Text for Backend { - const ICON_FONT: Font = font::ICONS; - const CHECKMARK_ICON: char = font::CHECKMARK_ICON; - const ARROW_DOWN_ICON: char = font::ARROW_DOWN_ICON; + const ICON_FONT: Font = Font::Default; // TODO + const CHECKMARK_ICON: char = '✓'; + const ARROW_DOWN_ICON: char = '▼'; fn default_size(&self) -> f32 { self.default_text_size diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index e17b84c1..125e6be0 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,265 +1,39 @@ -use crate::Transformation; - -use iced_graphics::font; - -use std::{cell::RefCell, collections::HashMap}; -use wgpu_glyph::ab_glyph; - pub use iced_native::text::Hit; #[derive(Debug)] -pub struct Pipeline { - draw_brush: RefCell>, - draw_font_map: RefCell>, - measure_brush: RefCell>, -} +pub struct Pipeline; impl Pipeline { pub fn new( - device: &wgpu::Device, - format: wgpu::TextureFormat, - default_font: Option<&[u8]>, - multithreading: bool, + _device: &wgpu::Device, + _format: wgpu::TextureFormat, + _default_font: Option<&[u8]>, + _multithreading: bool, ) -> Self { - let default_font = default_font.map(|slice| slice.to_vec()); - - // TODO: Font customization - #[cfg(not(target_os = "ios"))] - #[cfg(feature = "default_system_font")] - let default_font = { - default_font.or_else(|| { - font::Source::new() - .load(&[font::Family::SansSerif, font::Family::Serif]) - .ok() - }) - }; - - let default_font = - default_font.unwrap_or_else(|| font::FALLBACK.to_vec()); - - let font = ab_glyph::FontArc::try_from_vec(default_font) - .unwrap_or_else(|_| { - log::warn!( - "System font failed to load. Falling back to \ - embedded font..." - ); - - ab_glyph::FontArc::try_from_slice(font::FALLBACK) - .expect("Load fallback font") - }); - - let draw_brush_builder = - wgpu_glyph::GlyphBrushBuilder::using_font(font.clone()) - .initial_cache_size((2048, 2048)) - .draw_cache_multithread(multithreading); - - #[cfg(target_arch = "wasm32")] - let draw_brush_builder = draw_brush_builder.draw_cache_align_4x4(true); - - let draw_brush = draw_brush_builder.build(device, format); - - let measure_brush = - glyph_brush::GlyphBrushBuilder::using_font(font).build(); - - Pipeline { - draw_brush: RefCell::new(draw_brush), - draw_font_map: RefCell::new(HashMap::new()), - measure_brush: RefCell::new(measure_brush), - } - } - - pub fn queue(&mut self, section: wgpu_glyph::Section<'_>) { - self.draw_brush.borrow_mut().queue(section); - } - - pub fn draw_queued( - &mut self, - device: &wgpu::Device, - staging_belt: &mut wgpu::util::StagingBelt, - encoder: &mut wgpu::CommandEncoder, - target: &wgpu::TextureView, - transformation: Transformation, - region: wgpu_glyph::Region, - ) { - self.draw_brush - .borrow_mut() - .draw_queued_with_transform_and_scissoring( - device, - staging_belt, - encoder, - target, - transformation.into(), - region, - ) - .expect("Draw text"); + Pipeline } pub fn measure( &self, - content: &str, - size: f32, - font: iced_native::Font, - bounds: iced_native::Size, + _content: &str, + _size: f32, + _font: iced_native::Font, + _bounds: iced_native::Size, ) -> (f32, f32) { - use wgpu_glyph::GlyphCruncher; - - let wgpu_glyph::FontId(font_id) = self.find_font(font); - - let section = wgpu_glyph::Section { - bounds: (bounds.width, bounds.height), - text: vec![wgpu_glyph::Text { - text: content, - scale: size.into(), - font_id: wgpu_glyph::FontId(font_id), - extra: wgpu_glyph::Extra::default(), - }], - ..Default::default() - }; - - if let Some(bounds) = - self.measure_brush.borrow_mut().glyph_bounds(section) - { - (bounds.width().ceil(), bounds.height().ceil()) - } else { - (0.0, 0.0) - } + (0.0, 0.0) } pub fn hit_test( &self, - content: &str, - size: f32, - font: iced_native::Font, - bounds: iced_native::Size, - point: iced_native::Point, - nearest_only: bool, + _content: &str, + _size: f32, + _font: iced_native::Font, + _bounds: iced_native::Size, + _point: iced_native::Point, + _nearest_only: bool, ) -> Option { - use wgpu_glyph::GlyphCruncher; - - let wgpu_glyph::FontId(font_id) = self.find_font(font); - - let section = wgpu_glyph::Section { - bounds: (bounds.width, bounds.height), - text: vec![wgpu_glyph::Text { - text: content, - scale: size.into(), - font_id: wgpu_glyph::FontId(font_id), - extra: wgpu_glyph::Extra::default(), - }], - ..Default::default() - }; - - let mut mb = self.measure_brush.borrow_mut(); - - // The underlying type is FontArc, so clones are cheap. - use wgpu_glyph::ab_glyph::{Font, ScaleFont}; - let font = mb.fonts()[font_id].clone().into_scaled(size); - - // Implements an iterator over the glyph bounding boxes. - let bounds = mb.glyphs(section).map( - |wgpu_glyph::SectionGlyph { - byte_index, glyph, .. - }| { - ( - *byte_index, - iced_native::Rectangle::new( - iced_native::Point::new( - glyph.position.x - font.h_side_bearing(glyph.id), - glyph.position.y - font.ascent(), - ), - iced_native::Size::new( - font.h_advance(glyph.id), - font.ascent() - font.descent(), - ), - ), - ) - }, - ); - - // Implements computation of the character index based on the byte index - // within the input string. - let char_index = |byte_index| { - let mut b_count = 0; - for (i, utf8_len) in - content.chars().map(|c| c.len_utf8()).enumerate() - { - if byte_index < (b_count + utf8_len) { - return i; - } - b_count += utf8_len; - } - - byte_index - }; - - if !nearest_only { - for (idx, bounds) in bounds.clone() { - if bounds.contains(point) { - return Some(Hit::CharOffset(char_index(idx))); - } - } - } - - let nearest = bounds - .map(|(index, bounds)| (index, bounds.center())) - .min_by(|(_, center_a), (_, center_b)| { - center_a - .distance(point) - .partial_cmp(¢er_b.distance(point)) - .unwrap_or(std::cmp::Ordering::Greater) - }); - - nearest.map(|(idx, center)| { - Hit::NearestCharOffset(char_index(idx), point - center) - }) + None } - pub fn trim_measurement_cache(&mut self) { - // TODO: We should probably use a `GlyphCalculator` for this. However, - // it uses a lifetimed `GlyphCalculatorGuard` with side-effects on drop. - // This makes stuff quite inconvenient. A manual method for trimming the - // cache would make our lives easier. - loop { - let action = self - .measure_brush - .borrow_mut() - .process_queued(|_, _| {}, |_| {}); - - match action { - Ok(_) => break, - Err(glyph_brush::BrushError::TextureTooSmall { suggested }) => { - let (width, height) = suggested; - - self.measure_brush - .borrow_mut() - .resize_texture(width, height); - } - } - } - } - - pub fn find_font(&self, font: iced_native::Font) -> wgpu_glyph::FontId { - match font { - iced_native::Font::Default => wgpu_glyph::FontId(0), - iced_native::Font::External { name, bytes } => { - if let Some(font_id) = self.draw_font_map.borrow().get(name) { - return *font_id; - } - - let font = ab_glyph::FontArc::try_from_slice(bytes) - .expect("Load font"); - - let _ = self.measure_brush.borrow_mut().add_font(font.clone()); - - let font_id = self.draw_brush.borrow_mut().add_font(font); - - let _ = self - .draw_font_map - .borrow_mut() - .insert(String::from(name), font_id); - - font_id - } - } - } + pub fn trim_measurement_cache(&mut self) {} } -- cgit From baf51a8fcffc78e4ca20f7dcbba18ca3655f2840 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 31 Jan 2023 06:29:21 +0100 Subject: Draft `glyphon` implementation of text pipeline for `iced_wgpu` --- examples/integration/src/main.rs | 9 ++- graphics/src/layer.rs | 9 ++- graphics/src/layer/text.rs | 4 +- src/settings.rs | 4 +- wgpu/Cargo.toml | 5 ++ wgpu/src/backend.rs | 19 +++-- wgpu/src/text.rs | 160 ++++++++++++++++++++++++++++++++++++--- wgpu/src/window/compositor.rs | 3 +- 8 files changed, 187 insertions(+), 26 deletions(-) diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index 2a56b6fa..69f6961e 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -134,8 +134,12 @@ pub fn main() { // Initialize iced let mut debug = Debug::new(); - let mut renderer = - Renderer::new(Backend::new(&device, Settings::default(), format)); + let mut renderer = Renderer::new(Backend::new( + &device, + &queue, + Settings::default(), + format, + )); let mut state = program::State::new( controls, @@ -247,6 +251,7 @@ pub fn main() { renderer.with_primitives(|backend, primitive| { backend.present( &device, + &queue, &mut staging_belt, &mut encoder, &view, diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs index 1d453caa..cc8f299c 100644 --- a/graphics/src/layer.rs +++ b/graphics/src/layer.rs @@ -12,7 +12,8 @@ pub use text::Text; use crate::alignment; use crate::{ - Background, Font, Point, Primitive, Rectangle, Size, Vector, Viewport, + Background, Color, Font, Point, Primitive, Rectangle, Size, Vector, + Viewport, }; /// A group of primitives that should be clipped together. @@ -60,7 +61,7 @@ impl<'a> Layer<'a> { Point::new(11.0, 11.0 + 25.0 * i as f32), Size::INFINITY, ), - color: [0.9, 0.9, 0.9, 1.0], + color: Color::new(0.9, 0.9, 0.9, 1.0), size: 20.0, font: Font::Default, horizontal_alignment: alignment::Horizontal::Left, @@ -71,7 +72,7 @@ impl<'a> Layer<'a> { overlay.text.push(Text { bounds: text.bounds + Vector::new(-1.0, -1.0), - color: [0.0, 0.0, 0.0, 1.0], + color: Color::BLACK, ..text }); } @@ -136,7 +137,7 @@ impl<'a> Layer<'a> { content, bounds: *bounds + translation, size: *size, - color: color.into_linear(), + color: *color, font: *font, horizontal_alignment: *horizontal_alignment, vertical_alignment: *vertical_alignment, diff --git a/graphics/src/layer/text.rs b/graphics/src/layer/text.rs index 74f7a676..38d62616 100644 --- a/graphics/src/layer/text.rs +++ b/graphics/src/layer/text.rs @@ -1,4 +1,4 @@ -use crate::{alignment, Font, Rectangle}; +use crate::{alignment, Color, Font, Rectangle}; /// A paragraph of text. #[derive(Debug, Clone, Copy)] @@ -10,7 +10,7 @@ pub struct Text<'a> { pub bounds: Rectangle, /// The color of the [`Text`], in __linear RGB_. - pub color: [f32; 4], + pub color: Color, /// The size of the [`Text`]. pub size: f32, diff --git a/src/settings.rs b/src/settings.rs index 0eb3e62d..266ad404 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -28,7 +28,7 @@ pub struct Settings { /// The text size that will be used by default. /// - /// The default value is `20.0`. + /// The default value is `16.0`. pub default_text_size: f32, /// If enabled, spread text workload in multiple threads when multiple cores @@ -97,7 +97,7 @@ where window: Default::default(), flags: Default::default(), default_font: Default::default(), - default_text_size: 20.0, + default_text_size: 16.0, text_multithreading: false, antialiasing: false, exit_on_close_request: true, diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 5badeae6..d29c1129 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -33,6 +33,7 @@ log = "0.4" guillotiere = "0.6" futures = "0.3" bitflags = "1.2" +once_cell = "1.0" [dependencies.bytemuck] version = "1.9" @@ -46,6 +47,10 @@ path = "../native" version = "0.7" path = "../graphics" +[dependencies.glyphon] +version = "0.2" +path = "../../glyphon" + [dependencies.tracing] version = "0.1.6" optional = true diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index e9e23e80..77785760 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -18,7 +18,7 @@ use crate::image; /// /// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs /// [`iced`]: https://github.com/iced-rs/iced -#[derive(Debug)] +#[allow(missing_debug_implementations)] pub struct Backend { quad_pipeline: quad::Pipeline, text_pipeline: text::Pipeline, @@ -34,11 +34,13 @@ impl Backend { /// Creates a new [`Backend`]. pub fn new( device: &wgpu::Device, + queue: &wgpu::Queue, settings: Settings, format: wgpu::TextureFormat, ) -> Self { let text_pipeline = text::Pipeline::new( device, + queue, format, settings.default_font, settings.text_multithreading, @@ -70,6 +72,7 @@ impl Backend { pub fn present>( &mut self, device: &wgpu::Device, + queue: &wgpu::Queue, staging_belt: &mut wgpu::util::StagingBelt, encoder: &mut wgpu::CommandEncoder, frame: &wgpu::TextureView, @@ -91,6 +94,7 @@ impl Backend { for layer in layers { self.flush( device, + queue, scale_factor, transformation, &layer, @@ -108,6 +112,7 @@ impl Backend { fn flush( &mut self, device: &wgpu::Device, + queue: &wgpu::Queue, scale_factor: f32, transformation: Transformation, layer: &Layer<'_>, @@ -171,11 +176,15 @@ impl Backend { } if !layer.text.is_empty() { - for _text in layer.text.iter() { - // TODO: Queue text sections - } + self.text_pipeline.prepare( + device, + queue, + &layer.text, + scale_factor, + target_size, + ); - // TODO: Draw queued + self.text_pipeline.render(encoder, target); } } } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 125e6be0..ccb627cd 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,26 +1,166 @@ pub use iced_native::text::Hit; -#[derive(Debug)] -pub struct Pipeline; +use iced_graphics::layer::Text; +use iced_native::{Font, Size}; + +#[allow(missing_debug_implementations)] +pub struct Pipeline { + renderer: glyphon::TextRenderer, + atlas: glyphon::TextAtlas, + cache: glyphon::SwashCache<'static>, +} + +// TODO: Share with `iced_graphics` +static FONT_SYSTEM: once_cell::sync::Lazy = + once_cell::sync::Lazy::new(glyphon::FontSystem::new); impl Pipeline { pub fn new( - _device: &wgpu::Device, - _format: wgpu::TextureFormat, + device: &wgpu::Device, + queue: &wgpu::Queue, + format: wgpu::TextureFormat, _default_font: Option<&[u8]>, _multithreading: bool, ) -> Self { - Pipeline + Pipeline { + renderer: glyphon::TextRenderer::new(device, queue), + atlas: glyphon::TextAtlas::new(device, queue, format), + cache: glyphon::SwashCache::new(&FONT_SYSTEM), + } + } + + pub fn prepare( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + sections: &[Text<'_>], + scale_factor: f32, + target_size: Size, + ) { + let buffers: Vec<_> = sections + .iter() + .map(|section| { + let metrics = glyphon::Metrics::new( + (section.size * scale_factor) as i32, + (section.size * 1.2 * scale_factor) as i32, + ); + + let mut buffer = glyphon::Buffer::new(&FONT_SYSTEM, metrics); + + buffer.set_size( + (section.bounds.width * scale_factor).ceil() as i32, + (section.bounds.height * scale_factor).ceil() as i32, + ); + + buffer.set_text( + section.content, + glyphon::Attrs::new() + .color({ + let [r, g, b, a] = section.color.into_rgba8(); + glyphon::Color::rgba(r, g, b, a) + }) + .family(match section.font { + Font::Default => glyphon::Family::SansSerif, + Font::External { name, .. } => { + glyphon::Family::Name(name) + } + }), + ); + + buffer.shape_until_scroll(); + + buffer + }) + .collect(); + + let text_areas: Vec<_> = sections + .iter() + .zip(buffers.iter()) + .map(|(section, buffer)| glyphon::TextArea { + buffer, + left: (section.bounds.x * scale_factor) as i32, + top: (section.bounds.y * scale_factor) as i32, + bounds: glyphon::TextBounds { + left: (section.bounds.x * scale_factor) as i32, + top: (section.bounds.y * scale_factor) as i32, + right: ((section.bounds.x + section.bounds.width) + * scale_factor) as i32, + bottom: ((section.bounds.y + section.bounds.height) + * scale_factor) as i32, + }, + }) + .collect(); + + self.renderer + .prepare( + device, + queue, + &mut self.atlas, + glyphon::Resolution { + width: target_size.width, + height: target_size.height, + }, + &text_areas, + glyphon::Color::rgb(0, 0, 0), + &mut self.cache, + ) + .expect("Prepare text sections"); + } + + pub fn render( + &mut self, + encoder: &mut wgpu::CommandEncoder, + target: &wgpu::TextureView, + ) { + let mut render_pass = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: true, + }, + })], + depth_stencil_attachment: None, + }); + + self.renderer + .render(&self.atlas, &mut render_pass) + .expect("Render text"); } pub fn measure( &self, - _content: &str, - _size: f32, - _font: iced_native::Font, - _bounds: iced_native::Size, + content: &str, + size: f32, + font: Font, + bounds: Size, ) -> (f32, f32) { - (0.0, 0.0) + let attrs = match font { + Font::Default => glyphon::Attrs::new(), + Font::External { name, .. } => glyphon::Attrs { + family: glyphon::Family::Name(name), + ..glyphon::Attrs::new() + }, + }; + + let mut paragraph = + glyphon::BufferLine::new(content, glyphon::AttrsList::new(attrs)); + + // TODO: Cache layout + let layout = paragraph.layout( + &FONT_SYSTEM, + size as i32, + bounds.width as i32, + glyphon::Wrap::Word, + ); + + ( + layout.iter().fold(0.0, |max, line| line.w.max(max)), + size * 1.2 * layout.len() as f32, + ) } pub fn hit_test( diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 6d0c36f6..50231f7c 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -114,7 +114,7 @@ impl Compositor { /// Creates a new rendering [`Backend`] for this [`Compositor`]. pub fn create_backend(&self) -> Backend { - Backend::new(&self.device, self.settings, self.format) + Backend::new(&self.device, &self.queue, self.settings, self.format) } } @@ -227,6 +227,7 @@ impl iced_graphics::window::Compositor for Compositor { renderer.with_primitives(|backend, primitives| { backend.present( &self.device, + &self.queue, &mut self.staging_belt, &mut encoder, view, -- cgit From ba258f8fbcf72eaabefe193b6fbee6484b44e569 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 Feb 2023 03:24:14 +0100 Subject: Implement support for multiple text layers in `iced_wgpu` --- wgpu/src/backend.rs | 3 +++ wgpu/src/text.rs | 45 ++++++++++++++++++++++++++++++++------------- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 77785760..9bf464b3 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -105,6 +105,8 @@ impl Backend { ); } + self.text_pipeline.end_frame(); + #[cfg(any(feature = "image", feature = "svg"))] self.image_pipeline.trim_cache(device, encoder); } @@ -180,6 +182,7 @@ impl Backend { device, queue, &layer.text, + layer.bounds, scale_factor, target_size, ); diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index ccb627cd..2b18299f 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,13 +1,14 @@ pub use iced_native::text::Hit; use iced_graphics::layer::Text; -use iced_native::{Font, Size}; +use iced_native::{Font, Rectangle, Size}; #[allow(missing_debug_implementations)] pub struct Pipeline { - renderer: glyphon::TextRenderer, + renderers: Vec, atlas: glyphon::TextAtlas, cache: glyphon::SwashCache<'static>, + layer: usize, } // TODO: Share with `iced_graphics` @@ -23,9 +24,10 @@ impl Pipeline { _multithreading: bool, ) -> Self { Pipeline { - renderer: glyphon::TextRenderer::new(device, queue), + renderers: Vec::new(), atlas: glyphon::TextAtlas::new(device, queue, format), cache: glyphon::SwashCache::new(&FONT_SYSTEM), + layer: 0, } } @@ -34,9 +36,17 @@ impl Pipeline { device: &wgpu::Device, queue: &wgpu::Queue, sections: &[Text<'_>], + bounds: Rectangle, scale_factor: f32, target_size: Size, ) { + if self.renderers.len() <= self.layer { + self.renderers + .push(glyphon::TextRenderer::new(device, queue)); + } + + let renderer = &mut self.renderers[self.layer]; + let buffers: Vec<_> = sections .iter() .map(|section| { @@ -73,6 +83,13 @@ impl Pipeline { }) .collect(); + let bounds = glyphon::TextBounds { + left: (bounds.x * scale_factor) as i32, + top: (bounds.y * scale_factor) as i32, + right: ((bounds.x + bounds.width) * scale_factor) as i32, + bottom: ((bounds.y + bounds.height) * scale_factor) as i32, + }; + let text_areas: Vec<_> = sections .iter() .zip(buffers.iter()) @@ -80,18 +97,11 @@ impl Pipeline { buffer, left: (section.bounds.x * scale_factor) as i32, top: (section.bounds.y * scale_factor) as i32, - bounds: glyphon::TextBounds { - left: (section.bounds.x * scale_factor) as i32, - top: (section.bounds.y * scale_factor) as i32, - right: ((section.bounds.x + section.bounds.width) - * scale_factor) as i32, - bottom: ((section.bounds.y + section.bounds.height) - * scale_factor) as i32, - }, + bounds, }) .collect(); - self.renderer + renderer .prepare( device, queue, @@ -126,9 +136,18 @@ impl Pipeline { depth_stencil_attachment: None, }); - self.renderer + let renderer = &mut self.renderers[self.layer]; + + renderer .render(&self.atlas, &mut render_pass) .expect("Render text"); + + self.layer += 1; + } + + pub fn end_frame(&mut self) { + self.renderers.truncate(self.layer); + self.layer = 0; } pub fn measure( -- cgit From 98a16fd670d501a0072fee13b023be686ff30cf8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 Feb 2023 03:39:15 +0100 Subject: Implement proper text alignment support in `iced_wgpu` --- wgpu/src/text.rs | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 2b18299f..19d0782a 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,6 +1,7 @@ pub use iced_native::text::Hit; use iced_graphics::layer::Text; +use iced_native::alignment; use iced_native::{Font, Rectangle, Size}; #[allow(missing_debug_implementations)] @@ -93,11 +94,37 @@ impl Pipeline { let text_areas: Vec<_> = sections .iter() .zip(buffers.iter()) - .map(|(section, buffer)| glyphon::TextArea { - buffer, - left: (section.bounds.x * scale_factor) as i32, - top: (section.bounds.y * scale_factor) as i32, - bounds, + .map(|(section, buffer)| { + let x = section.bounds.x * scale_factor; + let y = section.bounds.y * scale_factor; + + let max_width = buffer + .layout_runs() + .fold(0.0f32, |max, run| max.max(run.line_w)); + + let total_height = buffer.visible_lines() as f32 + * section.size + * 1.2 + * scale_factor; + + let left = match section.horizontal_alignment { + alignment::Horizontal::Left => x, + alignment::Horizontal::Center => x - max_width / 2.0, + alignment::Horizontal::Right => x - max_width, + }; + + let top = match section.vertical_alignment { + alignment::Vertical::Top => y, + alignment::Vertical::Center => y - total_height / 2.0, + alignment::Vertical::Bottom => y - total_height, + }; + + glyphon::TextArea { + buffer, + left: left as i32, + top: top as i32, + bounds, + } }) .collect(); -- cgit From c68edb3278fdfa3c5ea6b29b9e2213fed56d4e66 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 Feb 2023 04:05:51 +0100 Subject: Fix `TextInput` line height --- native/src/widget/text_input.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index ee0473ea..5e198b8f 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -387,7 +387,11 @@ where let text_size = size.unwrap_or_else(|| renderer.default_size()); let padding = padding.fit(Size::ZERO, limits.max()); - let limits = limits.width(width).pad(padding).height(text_size); + + let limits = limits + .width(width) + .pad(padding) + .height(text_size as f32 * 1.2); let mut text = layout::Node::new(limits.resolve(Size::ZERO)); text.move_to(Point::new(padding.left, padding.top)); -- cgit From bb27982009d89a1cf4874697f7c17ff0af71d93d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 Feb 2023 04:06:29 +0100 Subject: Convert sRGB to linear RGB for text in `iced_wgpu` --- wgpu/src/text.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 19d0782a..3f828da1 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -67,8 +67,14 @@ impl Pipeline { section.content, glyphon::Attrs::new() .color({ - let [r, g, b, a] = section.color.into_rgba8(); - glyphon::Color::rgba(r, g, b, a) + let [r, g, b, a] = section.color.into_linear(); + + glyphon::Color::rgba( + (r * 255.0) as u8, + (g * 255.0) as u8, + (b * 255.0) as u8, + (a * 255.0) as u8, + ) }) .family(match section.font { Font::Default => glyphon::Family::SansSerif, -- cgit From 032e860f13a562719faf128238abe7ffb7f2a610 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 Feb 2023 04:14:11 +0100 Subject: Fix `PickList` line height --- native/src/widget/pick_list.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 17528db4..a128d1ae 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -384,7 +384,7 @@ where let size = { let intrinsic = - Size::new(max_width + text_size + padding.left, text_size); + Size::new(max_width + text_size + padding.left, text_size * 1.2); limits.resolve(intrinsic).pad(padding) }; @@ -620,12 +620,12 @@ pub fn draw<'a, T, Renderer>( color: style.handle_color, bounds: Rectangle { x: bounds.x + bounds.width - padding.horizontal(), - y: bounds.center_y() - size / 2.0, - height: size, + y: bounds.center_y(), + height: size * 1.2, ..bounds }, horizontal_alignment: alignment::Horizontal::Right, - vertical_alignment: alignment::Vertical::Top, + vertical_alignment: alignment::Vertical::Center, }); } @@ -645,12 +645,12 @@ pub fn draw<'a, T, Renderer>( }, bounds: Rectangle { x: bounds.x + padding.left, - y: bounds.center_y() - text_size / 2.0, + y: bounds.center_y(), width: bounds.width - padding.horizontal(), - height: text_size, + height: text_size * 1.2, }, horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, + vertical_alignment: alignment::Vertical::Center, }); } } -- cgit From 1d0c44fb255f5fbf5a03e7c737e40ab66d39de9e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 2 Feb 2023 01:24:27 +0100 Subject: Implement basic text caching in `iced_wgpu` --- core/src/font.rs | 16 +++++ wgpu/Cargo.toml | 2 + wgpu/src/text.rs | 201 ++++++++++++++++++++++++++++++++++++++----------------- 3 files changed, 159 insertions(+), 60 deletions(-) diff --git a/core/src/font.rs b/core/src/font.rs index 3f9ad2b5..130e378e 100644 --- a/core/src/font.rs +++ b/core/src/font.rs @@ -1,3 +1,5 @@ +use std::hash::{Hash, Hasher}; + /// A font. #[derive(Debug, Clone, Copy)] pub enum Font { @@ -22,3 +24,17 @@ impl Default for Font { Font::Default } } + +impl Hash for Font { + fn hash(&self, hasher: &mut H) { + match self { + Self::Default => { + 0.hash(hasher); + } + Self::External { name, .. } => { + 1.hash(hasher); + name.hash(hasher); + } + } + } +} diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index d29c1129..1a94c6a3 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -34,6 +34,8 @@ guillotiere = "0.6" futures = "0.3" bitflags = "1.2" once_cell = "1.0" +rustc-hash = "1.1" +twox-hash = "1.6" [dependencies.bytemuck] version = "1.9" diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 3f828da1..a1d621d4 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -2,16 +2,107 @@ pub use iced_native::text::Hit; use iced_graphics::layer::Text; use iced_native::alignment; -use iced_native::{Font, Rectangle, Size}; +use iced_native::{Color, Font, Rectangle, Size}; + +use rustc_hash::{FxHashMap, FxHashSet}; +use std::cell::RefCell; +use std::hash::{BuildHasher, Hash, Hasher}; +use twox_hash::RandomXxHashBuilder64; #[allow(missing_debug_implementations)] pub struct Pipeline { renderers: Vec, atlas: glyphon::TextAtlas, cache: glyphon::SwashCache<'static>, + measurement_cache: RefCell, + render_cache: Cache, layer: usize, } +struct Cache { + entries: FxHashMap>, + recently_used: FxHashSet, + hasher: RandomXxHashBuilder64, +} + +impl Cache { + fn new() -> Self { + Self { + entries: FxHashMap::default(), + recently_used: FxHashSet::default(), + hasher: RandomXxHashBuilder64::default(), + } + } + + fn get(&self, key: &KeyHash) -> Option<&glyphon::Buffer<'static>> { + self.entries.get(key) + } + + fn allocate( + &mut self, + key: Key<'_>, + ) -> (KeyHash, &mut glyphon::Buffer<'static>) { + let hash = { + let mut hasher = self.hasher.build_hasher(); + + key.content.hash(&mut hasher); + (key.size as i32).hash(&mut hasher); + key.font.hash(&mut hasher); + (key.bounds.width as i32).hash(&mut hasher); + (key.bounds.height as i32).hash(&mut hasher); + key.color.into_rgba8().hash(&mut hasher); + + hasher.finish() + }; + + if !self.entries.contains_key(&hash) { + let metrics = + glyphon::Metrics::new(key.size as i32, (key.size * 1.2) as i32); + + let mut buffer = glyphon::Buffer::new(&FONT_SYSTEM, metrics); + + buffer.set_size(key.bounds.width as i32, key.bounds.height as i32); + buffer.set_text( + key.content, + glyphon::Attrs::new().family(to_family(key.font)).color({ + let [r, g, b, a] = key.color.into_linear(); + + glyphon::Color::rgba( + (r * 255.0) as u8, + (g * 255.0) as u8, + (b * 255.0) as u8, + (a * 255.0) as u8, + ) + }), + ); + + let _ = self.entries.insert(hash, buffer); + } + + let _ = self.recently_used.insert(hash); + + (hash, self.entries.get_mut(&hash).unwrap()) + } + + fn trim(&mut self) { + self.entries + .retain(|key, _| self.recently_used.contains(key)); + + self.recently_used.clear(); + } +} + +#[derive(Debug, Clone, Copy)] +struct Key<'a> { + content: &'a str, + size: f32, + font: Font, + bounds: Size, + color: Color, +} + +type KeyHash = u64; + // TODO: Share with `iced_graphics` static FONT_SYSTEM: once_cell::sync::Lazy = once_cell::sync::Lazy::new(glyphon::FontSystem::new); @@ -28,6 +119,8 @@ impl Pipeline { renderers: Vec::new(), atlas: glyphon::TextAtlas::new(device, queue, format), cache: glyphon::SwashCache::new(&FONT_SYSTEM), + measurement_cache: RefCell::new(Cache::new()), + render_cache: Cache::new(), layer: 0, } } @@ -48,48 +141,29 @@ impl Pipeline { let renderer = &mut self.renderers[self.layer]; - let buffers: Vec<_> = sections + let keys: Vec<_> = sections .iter() .map(|section| { - let metrics = glyphon::Metrics::new( - (section.size * scale_factor) as i32, - (section.size * 1.2 * scale_factor) as i32, - ); - - let mut buffer = glyphon::Buffer::new(&FONT_SYSTEM, metrics); - - buffer.set_size( - (section.bounds.width * scale_factor).ceil() as i32, - (section.bounds.height * scale_factor).ceil() as i32, - ); - - buffer.set_text( - section.content, - glyphon::Attrs::new() - .color({ - let [r, g, b, a] = section.color.into_linear(); - - glyphon::Color::rgba( - (r * 255.0) as u8, - (g * 255.0) as u8, - (b * 255.0) as u8, - (a * 255.0) as u8, - ) - }) - .family(match section.font { - Font::Default => glyphon::Family::SansSerif, - Font::External { name, .. } => { - glyphon::Family::Name(name) - } - }), - ); - - buffer.shape_until_scroll(); - - buffer + let (key, _) = self.render_cache.allocate(Key { + content: section.content, + size: section.size * scale_factor, + font: section.font, + bounds: Size { + width: section.bounds.width * scale_factor, + height: section.bounds.height * scale_factor, + }, + color: section.color, + }); + + key }) .collect(); + let buffers: Vec<_> = keys + .iter() + .map(|key| self.render_cache.get(key).expect("Get cached buffer")) + .collect(); + let bounds = glyphon::TextBounds { left: (bounds.x * scale_factor) as i32, top: (bounds.y * scale_factor) as i32, @@ -190,29 +264,27 @@ impl Pipeline { font: Font, bounds: Size, ) -> (f32, f32) { - let attrs = match font { - Font::Default => glyphon::Attrs::new(), - Font::External { name, .. } => glyphon::Attrs { - family: glyphon::Family::Name(name), - ..glyphon::Attrs::new() + let mut measurement_cache = self.measurement_cache.borrow_mut(); + + let (_, paragraph) = measurement_cache.allocate(Key { + content, + size: size, + font, + bounds: Size { + width: bounds.width, + height: f32::INFINITY, }, - }; + color: Color::BLACK, + }); + + let (total_lines, max_width) = paragraph + .layout_runs() + .enumerate() + .fold((0, 0.0), |(_, max), (i, buffer)| { + (i + 1, buffer.line_w.max(max)) + }); - let mut paragraph = - glyphon::BufferLine::new(content, glyphon::AttrsList::new(attrs)); - - // TODO: Cache layout - let layout = paragraph.layout( - &FONT_SYSTEM, - size as i32, - bounds.width as i32, - glyphon::Wrap::Word, - ); - - ( - layout.iter().fold(0.0, |max, line| line.w.max(max)), - size * 1.2 * layout.len() as f32, - ) + (max_width, size * 1.2 * total_lines as f32) } pub fn hit_test( @@ -227,5 +299,14 @@ impl Pipeline { None } - pub fn trim_measurement_cache(&mut self) {} + pub fn trim_measurement_cache(&mut self) { + self.measurement_cache.borrow_mut().trim(); + } +} + +fn to_family(font: Font) -> glyphon::Family<'static> { + match font { + Font::Default => glyphon::Family::SansSerif, + Font::External { name, .. } => glyphon::Family::Name(name), + } } -- cgit From 02fc7e6e89e0a098a2a8cae8490f36d3bdf8126c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 2 Feb 2023 01:30:49 +0100 Subject: Trim text `render_cache` after rendering in `iced_wgpu` --- wgpu/src/text.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index a1d621d4..cca00435 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -254,6 +254,8 @@ impl Pipeline { pub fn end_frame(&mut self) { self.renderers.truncate(self.layer); + self.render_cache.trim(); + self.layer = 0; } -- cgit From 6b707711469c7298bb363029a0c3d12a7834278c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 2 Feb 2023 01:34:25 +0100 Subject: Avoid unnecessary `Vec` allocation in text pipeline --- wgpu/src/text.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index cca00435..1e2bf5c2 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -159,11 +159,6 @@ impl Pipeline { }) .collect(); - let buffers: Vec<_> = keys - .iter() - .map(|key| self.render_cache.get(key).expect("Get cached buffer")) - .collect(); - let bounds = glyphon::TextBounds { left: (bounds.x * scale_factor) as i32, top: (bounds.y * scale_factor) as i32, @@ -173,8 +168,11 @@ impl Pipeline { let text_areas: Vec<_> = sections .iter() - .zip(buffers.iter()) - .map(|(section, buffer)| { + .zip(keys.iter()) + .map(|(section, key)| { + let buffer = + self.render_cache.get(key).expect("Get cached buffer"); + let x = section.bounds.x * scale_factor; let y = section.bounds.y * scale_factor; -- cgit From c8e8b1a7ba47ab13cef2dc7f300fbfcefe1e8a48 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 2 Feb 2023 01:51:08 +0100 Subject: Use `bounds` directly for `measure` in text pipeline --- wgpu/src/text.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 1e2bf5c2..026a6d27 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -270,10 +270,7 @@ impl Pipeline { content, size: size, font, - bounds: Size { - width: bounds.width, - height: f32::INFINITY, - }, + bounds, color: Color::BLACK, }); -- cgit From 0a324f0aebdee06c9cce8ef107b44b847dace05b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 2 Feb 2023 02:02:41 +0100 Subject: Implement `hit_test` for `text::Pipeline` in `iced_wgpu` --- wgpu/src/text.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 026a6d27..41787136 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -286,14 +286,26 @@ impl Pipeline { pub fn hit_test( &self, - _content: &str, - _size: f32, - _font: iced_native::Font, - _bounds: iced_native::Size, - _point: iced_native::Point, + content: &str, + size: f32, + font: iced_native::Font, + bounds: iced_native::Size, + point: iced_native::Point, _nearest_only: bool, ) -> Option { - None + let mut measurement_cache = self.measurement_cache.borrow_mut(); + + let (_, paragraph) = measurement_cache.allocate(Key { + content, + size: size, + font, + bounds, + color: Color::BLACK, + }); + + let cursor = paragraph.hit(point.x as i32, point.y as i32)?; + + Some(Hit::CharOffset(cursor.index)) } pub fn trim_measurement_cache(&mut self) { -- cgit From a7580e0696a1a0ba76a89db3f78bc99ba3fbb361 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Feb 2023 06:50:38 +0100 Subject: Count `layout_runs` instead of using `visible_lines` in `text::Pipeline::prepare` --- wgpu/src/text.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 41787136..fa7fd5df 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -176,14 +176,15 @@ impl Pipeline { let x = section.bounds.x * scale_factor; let y = section.bounds.y * scale_factor; - let max_width = buffer + let (total_lines, max_width) = buffer .layout_runs() - .fold(0.0f32, |max, run| max.max(run.line_w)); + .enumerate() + .fold((0, 0.0), |(_, max), (i, buffer)| { + (i + 1, buffer.line_w.max(max)) + }); - let total_height = buffer.visible_lines() as f32 - * section.size - * 1.2 - * scale_factor; + let total_height = + total_lines as f32 * section.size * 1.2 * scale_factor; let left = match section.horizontal_alignment { alignment::Horizontal::Left => x, -- cgit From b29de28d1f0f608f8029c93d154cfd1b0f8b8cbb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Feb 2023 07:33:33 +0100 Subject: Overhaul `Font` type to allow font family selection --- core/src/font.rs | 53 +++++++++------------ examples/todos/fonts/icons.ttf | Bin 5596 -> 5732 bytes examples/todos/src/main.rs | 5 +- graphics/fonts/Icons.ttf | Bin 5032 -> 0 bytes graphics/fonts/Lato-Regular.ttf | Bin 75136 -> 0 bytes graphics/fonts/OFL.txt | 93 ------------------------------------- graphics/src/backend.rs | 3 ++ graphics/src/layer.rs | 2 +- graphics/src/renderer.rs | 4 ++ graphics/src/widget/canvas/text.rs | 2 +- native/src/overlay/menu.rs | 12 ++--- native/src/renderer/null.rs | 8 +++- native/src/text.rs | 5 +- native/src/widget/checkbox.rs | 13 +++--- native/src/widget/pick_list.rs | 25 +++++----- native/src/widget/radio.rs | 11 +++-- native/src/widget/text.rs | 20 +++++--- native/src/widget/text_input.rs | 39 +++++++++------- native/src/widget/toggler.rs | 13 +++--- src/application.rs | 1 - src/settings.rs | 18 ++----- wgpu/fonts/Iced-Icons.ttf | Bin 0 -> 5108 bytes wgpu/src/backend.rs | 21 ++++----- wgpu/src/lib.rs | 4 +- wgpu/src/settings.rs | 37 +++------------ wgpu/src/text.rs | 10 ++-- 26 files changed, 145 insertions(+), 254 deletions(-) delete mode 100644 graphics/fonts/Icons.ttf delete mode 100644 graphics/fonts/Lato-Regular.ttf delete mode 100644 graphics/fonts/OFL.txt create mode 100644 wgpu/fonts/Iced-Icons.ttf diff --git a/core/src/font.rs b/core/src/font.rs index 130e378e..1f774a70 100644 --- a/core/src/font.rs +++ b/core/src/font.rs @@ -1,40 +1,29 @@ -use std::hash::{Hash, Hasher}; +use std::hash::Hash; /// A font. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Font { - /// The default font. - /// - /// This is normally a font configured in a renderer or loaded from the - /// system. - Default, + /// The name of a font family of choice. + Name(&'static str), - /// An external font. - External { - /// The name of the external font - name: &'static str, + /// Serif fonts represent the formal text style for a script. + Serif, - /// The bytes of the external font - bytes: &'static [u8], - }, -} + /// Glyphs in sans-serif fonts, as the term is used in CSS, are generally low + /// contrast and have stroke endings that are plain — without any flaring, + /// cross stroke, or other ornamentation. + SansSerif, -impl Default for Font { - fn default() -> Font { - Font::Default - } -} + /// Glyphs in cursive fonts generally use a more informal script style, and + /// the result looks more like handwritten pen or brush writing than printed + /// letterwork. + Cursive, + + /// Fantasy fonts are primarily decorative or expressive fonts that contain + /// decorative or expressive representations of characters. + Fantasy, -impl Hash for Font { - fn hash(&self, hasher: &mut H) { - match self { - Self::Default => { - 0.hash(hasher); - } - Self::External { name, .. } => { - 1.hash(hasher); - name.hash(hasher); - } - } - } + /// The sole criterion of a monospace font is that all glyphs have the same + /// fixed width. + Monospace, } diff --git a/examples/todos/fonts/icons.ttf b/examples/todos/fonts/icons.ttf index 4498299d..7b65fd36 100644 Binary files a/examples/todos/fonts/icons.ttf and b/examples/todos/fonts/icons.ttf differ diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 6408f09c..5509cf0e 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -466,10 +466,7 @@ fn empty_message(message: &str) -> Element<'_, Message> { } // Fonts -const ICONS: Font = Font::External { - name: "Icons", - bytes: include_bytes!("../../todos/fonts/icons.ttf"), -}; +const ICONS: Font = Font::Name("Iced-Todos-Icons"); fn icon(unicode: char) -> Text<'static> { text(unicode.to_string()) diff --git a/graphics/fonts/Icons.ttf b/graphics/fonts/Icons.ttf deleted file mode 100644 index 5e455b69..00000000 Binary files a/graphics/fonts/Icons.ttf and /dev/null differ diff --git a/graphics/fonts/Lato-Regular.ttf b/graphics/fonts/Lato-Regular.ttf deleted file mode 100644 index 33eba8b1..00000000 Binary files a/graphics/fonts/Lato-Regular.ttf and /dev/null differ diff --git a/graphics/fonts/OFL.txt b/graphics/fonts/OFL.txt deleted file mode 100644 index dfca0da4..00000000 --- a/graphics/fonts/OFL.txt +++ /dev/null @@ -1,93 +0,0 @@ -Copyright (c) 2010-2014 by tyPoland Lukasz Dziedzic (team@latofonts.com) with Reserved Font Name "Lato" - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index 256b7ab5..7bbdee95 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -31,6 +31,9 @@ pub trait Text { /// [`ICON_FONT`]: Self::ICON_FONT const ARROW_DOWN_ICON: char; + /// Returns the default [`Font`]. + fn default_font(&self) -> Font; + /// Returns the default size of text. fn default_size(&self) -> f32; diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs index cc8f299c..f6eb2fdd 100644 --- a/graphics/src/layer.rs +++ b/graphics/src/layer.rs @@ -63,7 +63,7 @@ impl<'a> Layer<'a> { ), color: Color::new(0.9, 0.9, 0.9, 1.0), size: 20.0, - font: Font::Default, + font: Font::Monospace, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, }; diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 34b6eb1d..7ad53dec 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -130,6 +130,10 @@ where const CHECKMARK_ICON: char = B::CHECKMARK_ICON; const ARROW_DOWN_ICON: char = B::ARROW_DOWN_ICON; + fn default_font(&self) -> Self::Font { + self.backend().default_font() + } + fn default_size(&self) -> f32 { self.backend().default_size() } diff --git a/graphics/src/widget/canvas/text.rs b/graphics/src/widget/canvas/text.rs index 056f8204..8c0b2dfb 100644 --- a/graphics/src/widget/canvas/text.rs +++ b/graphics/src/widget/canvas/text.rs @@ -34,7 +34,7 @@ impl Default for Text { position: Point::ORIGIN, color: Color::BLACK, size: 16.0, - font: Font::Default, + font: Font::SansSerif, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, } diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index 50f741ef..9c3a8a44 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -31,7 +31,7 @@ where width: f32, padding: Padding, text_size: Option, - font: Renderer::Font, + font: Option, style: ::Style, } @@ -58,7 +58,7 @@ where width: 0.0, padding: Padding::ZERO, text_size: None, - font: Default::default(), + font: None, style: Default::default(), } } @@ -82,8 +82,8 @@ where } /// Sets the font of the [`Menu`]. - pub fn font(mut self, font: Renderer::Font) -> Self { - self.font = font; + pub fn font(mut self, font: impl Into) -> Self { + self.font = Some(font.into()); self } @@ -311,7 +311,7 @@ where last_selection: &'a mut Option, padding: Padding, text_size: Option, - font: Renderer::Font, + font: Option, style: ::Style, } @@ -491,7 +491,7 @@ where ..bounds }, size: text_size, - font: self.font.clone(), + font: self.font.unwrap_or_else(|| renderer.default_font()), color: if is_selected { appearance.selected_text_color } else { diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs index 9376d540..50d7d6d6 100644 --- a/native/src/renderer/null.rs +++ b/native/src/renderer/null.rs @@ -40,12 +40,16 @@ impl Renderer for Null { impl text::Renderer for Null { type Font = Font; - const ICON_FONT: Font = Font::Default; + const ICON_FONT: Font = Font::SansSerif; const CHECKMARK_ICON: char = '0'; const ARROW_DOWN_ICON: char = '0'; + fn default_font(&self) -> Self::Font { + Font::SansSerif + } + fn default_size(&self) -> f32 { - 20.0 + 16.0 } fn measure( diff --git a/native/src/text.rs b/native/src/text.rs index 55c3cfd3..b7915a55 100644 --- a/native/src/text.rs +++ b/native/src/text.rs @@ -57,7 +57,7 @@ impl Hit { /// A renderer capable of measuring and drawing [`Text`]. pub trait Renderer: crate::Renderer { /// The font type used. - type Font: Default + Clone; + type Font: Copy; /// The icon font of the backend. const ICON_FONT: Self::Font; @@ -72,6 +72,9 @@ pub trait Renderer: crate::Renderer { /// [`ICON_FONT`]: Self::ICON_FONT const ARROW_DOWN_ICON: char; + /// Returns the default [`Font`]. + fn default_font(&self) -> Self::Font; + /// Returns the default size of [`Text`]. fn default_size(&self) -> f32; diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 9b69e574..138c458c 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -55,7 +55,7 @@ where size: f32, spacing: f32, text_size: Option, - font: Renderer::Font, + font: Option, icon: Icon, style: ::Style, } @@ -91,7 +91,7 @@ where size: Self::DEFAULT_SIZE, spacing: Self::DEFAULT_SPACING, text_size: None, - font: Renderer::Font::default(), + font: None, icon: Icon { font: Renderer::ICON_FONT, code_point: Renderer::CHECKMARK_ICON, @@ -128,8 +128,8 @@ where /// Sets the [`Font`] of the text of the [`Checkbox`]. /// /// [`Font`]: crate::text::Renderer::Font - pub fn font(mut self, font: Renderer::Font) -> Self { - self.font = font; + pub fn font(mut self, font: impl Into) -> Self { + self.font = Some(font.into()); self } @@ -175,7 +175,7 @@ where .push(Row::new().width(self.size).height(self.size)) .push( Text::new(&self.label) - .font(self.font.clone()) + .font(self.font.unwrap_or_else(|| renderer.default_font())) .width(self.width) .size( self.text_size @@ -288,6 +288,7 @@ where { let label_layout = children.next().unwrap(); + let font = self.font.unwrap_or_else(|| renderer.default_font()); widget::text::draw( renderer, @@ -295,7 +296,7 @@ where label_layout, &self.label, self.text_size, - self.font.clone(), + font, widget::text::Appearance { color: custom_style.text_color, }, diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index a128d1ae..c1ff0004 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -35,7 +35,7 @@ where width: Length, padding: Padding, text_size: Option, - font: Renderer::Font, + font: Option, handle: Handle, style: ::Style, } @@ -70,7 +70,7 @@ where width: Length::Shrink, padding: Self::DEFAULT_PADDING, text_size: None, - font: Default::default(), + font: None, handle: Default::default(), style: Default::default(), } @@ -101,8 +101,8 @@ where } /// Sets the font of the [`PickList`]. - pub fn font(mut self, font: Renderer::Font) -> Self { - self.font = font; + pub fn font(mut self, font: impl Into) -> Self { + self.font = Some(font.into()); self } @@ -163,7 +163,7 @@ where self.width, self.padding, self.text_size, - &self.font, + self.font.unwrap_or_else(|| renderer.default_font()), self.placeholder.as_deref(), &self.options, ) @@ -212,6 +212,7 @@ where cursor_position: Point, _viewport: &Rectangle, ) { + let font = self.font.unwrap_or_else(|| renderer.default_font()); draw( renderer, theme, @@ -219,7 +220,7 @@ where cursor_position, self.padding, self.text_size, - &self.font, + font, self.placeholder.as_deref(), self.selected.as_ref(), &self.handle, @@ -232,7 +233,7 @@ where &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, - _renderer: &Renderer, + renderer: &Renderer, ) -> Option> { let state = tree.state.downcast_mut::>(); @@ -241,7 +242,7 @@ where state, self.padding, self.text_size, - self.font.clone(), + self.font.unwrap_or_else(|| renderer.default_font()), &self.options, self.style.clone(), ) @@ -343,7 +344,7 @@ pub fn layout( width: Length, padding: Padding, text_size: Option, - font: &Renderer::Font, + font: Renderer::Font, placeholder: Option<&str>, options: &[T], ) -> layout::Node @@ -362,7 +363,7 @@ where let (width, _) = renderer.measure( label, text_size, - font.clone(), + font, Size::new(f32::INFINITY, f32::INFINITY), ); @@ -560,7 +561,7 @@ pub fn draw<'a, T, Renderer>( cursor_position: Point, padding: Padding, text_size: Option, - font: &Renderer::Font, + font: Renderer::Font, placeholder: Option<&str>, selected: Option<&T>, handle: &Handle, @@ -637,7 +638,7 @@ pub fn draw<'a, T, Renderer>( renderer.fill_text(Text { content: label, size: text_size, - font: font.clone(), + font, color: if is_selected { style.text_color } else { diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index 9daddfbc..bd803910 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -53,7 +53,7 @@ where size: f32, spacing: f32, text_size: Option, - font: Renderer::Font, + font: Option, style: ::Style, } @@ -95,7 +95,7 @@ where size: Self::DEFAULT_SIZE, spacing: Self::DEFAULT_SPACING, //15 text_size: None, - font: Default::default(), + font: None, style: Default::default(), } } @@ -125,8 +125,8 @@ where } /// Sets the text font of the [`Radio`] button. - pub fn font(mut self, font: Renderer::Font) -> Self { - self.font = font; + pub fn font(mut self, font: impl Into) -> Self { + self.font = Some(font.into()); self } @@ -268,6 +268,7 @@ where { let label_layout = children.next().unwrap(); + let font = self.font.unwrap_or(renderer.default_font()); widget::text::draw( renderer, @@ -275,7 +276,7 @@ where label_layout, &self.label, self.text_size, - self.font.clone(), + font, widget::text::Appearance { color: custom_style.text_color, }, diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs index 3fee48f2..235a027e 100644 --- a/native/src/widget/text.rs +++ b/native/src/widget/text.rs @@ -37,7 +37,7 @@ where height: Length, horizontal_alignment: alignment::Horizontal, vertical_alignment: alignment::Vertical, - font: Renderer::Font, + font: Option, style: ::Style, } @@ -51,7 +51,7 @@ where Text { content: content.into(), size: None, - font: Default::default(), + font: None, width: Length::Shrink, height: Length::Shrink, horizontal_alignment: alignment::Horizontal::Left, @@ -70,7 +70,7 @@ where /// /// [`Font`]: crate::text::Renderer::Font pub fn font(mut self, font: impl Into) -> Self { - self.font = font.into(); + self.font = Some(font.into()); self } @@ -138,8 +138,12 @@ where let bounds = limits.max(); - let (width, height) = - renderer.measure(&self.content, size, self.font.clone(), bounds); + let (width, height) = renderer.measure( + &self.content, + size, + self.font.unwrap_or_else(|| renderer.default_font()), + bounds, + ); let size = limits.resolve(Size::new(width, height)); @@ -156,13 +160,15 @@ where _cursor_position: Point, _viewport: &Rectangle, ) { + let font = self.font.unwrap_or_else(|| renderer.default_font()); + draw( renderer, style, layout, &self.content, self.size, - self.font.clone(), + font, theme.appearance(self.style), self.horizontal_alignment, self.vertical_alignment, @@ -242,7 +248,7 @@ where height: self.height, horizontal_alignment: self.horizontal_alignment, vertical_alignment: self.vertical_alignment, - font: self.font.clone(), + font: self.font, style: self.style, } } diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 5e198b8f..e6b70db2 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -61,7 +61,7 @@ where placeholder: String, value: Value, is_secure: bool, - font: Renderer::Font, + font: Option, width: Length, padding: Padding, size: Option, @@ -92,7 +92,7 @@ where placeholder: String::from(placeholder), value: Value::new(value), is_secure: false, - font: Default::default(), + font: None, width: Length::Fill, padding: Padding::new(5.0), size: None, @@ -129,7 +129,7 @@ where /// /// [`Font`]: text::Renderer::Font pub fn font(mut self, font: Renderer::Font) -> Self { - self.font = font; + self.font = Some(font); self } /// Sets the width of the [`TextInput`]. @@ -179,6 +179,8 @@ where cursor_position: Point, value: Option<&Value>, ) { + let font = self.font.unwrap_or(renderer.default_font()); + draw( renderer, theme, @@ -188,7 +190,7 @@ where value.unwrap_or(&self.value), &self.placeholder, self.size, - &self.font, + font, self.is_secure, &self.style, ) @@ -258,7 +260,7 @@ where shell, &mut self.value, self.size, - &self.font, + self.font.unwrap_or(renderer.default_font()), self.is_secure, self.on_change.as_ref(), self.on_paste.as_deref(), @@ -277,6 +279,8 @@ where cursor_position: Point, _viewport: &Rectangle, ) { + let font = self.font.unwrap_or(renderer.default_font()); + draw( renderer, theme, @@ -286,7 +290,7 @@ where &self.value, &self.placeholder, self.size, - &self.font, + font, self.is_secure, &self.style, ) @@ -410,7 +414,7 @@ pub fn update<'a, Message, Renderer>( shell: &mut Shell<'_, Message>, value: &mut Value, size: Option, - font: &Renderer::Font, + font: Renderer::Font, is_secure: bool, on_change: &dyn Fn(String) -> Message, on_paste: Option<&dyn Fn(String) -> Message>, @@ -459,7 +463,7 @@ where find_cursor_position( renderer, text_layout.bounds(), - font.clone(), + font, size, &value, state, @@ -487,7 +491,7 @@ where let position = find_cursor_position( renderer, text_layout.bounds(), - font.clone(), + font, size, value, state, @@ -536,7 +540,7 @@ where let position = find_cursor_position( renderer, text_layout.bounds(), - font.clone(), + font, size, &value, state, @@ -816,7 +820,7 @@ pub fn draw( value: &Value, placeholder: &str, size: Option, - font: &Renderer::Font, + font: Renderer::Font, is_secure: bool, style: &::Style, ) where @@ -862,7 +866,7 @@ pub fn draw( value, size, position, - font.clone(), + font, ); let is_cursor_visible = ((focus.now - focus.updated_at) @@ -903,7 +907,7 @@ pub fn draw( value, size, left, - font.clone(), + font, ); let (right_position, right_offset) = @@ -913,7 +917,7 @@ pub fn draw( value, size, right, - font.clone(), + font, ); let width = right_position - left_position; @@ -948,7 +952,7 @@ pub fn draw( let text_width = renderer.measure_width( if text.is_empty() { placeholder } else { &text }, size, - font.clone(), + font, ); let render = |renderer: &mut Renderer| { @@ -963,7 +967,7 @@ pub fn draw( } else { theme.value_color(style) }, - font: font.clone(), + font: font, bounds: Rectangle { y: text_bounds.center_y(), width: f32::INFINITY, @@ -1195,8 +1199,7 @@ where { let size = size.unwrap_or_else(|| renderer.default_size()); - let offset = - offset(renderer, text_bounds, font.clone(), size, value, state); + let offset = offset(renderer, text_bounds, font, size, value, state); renderer .hit_test( diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs index a434af65..495406db 100644 --- a/native/src/widget/toggler.rs +++ b/native/src/widget/toggler.rs @@ -42,7 +42,7 @@ where text_size: Option, text_alignment: alignment::Horizontal, spacing: f32, - font: Renderer::Font, + font: Option, style: ::Style, } @@ -79,7 +79,7 @@ where text_size: None, text_alignment: alignment::Horizontal::Left, spacing: 0.0, - font: Renderer::Font::default(), + font: None, style: Default::default(), } } @@ -117,8 +117,8 @@ where /// Sets the [`Font`] of the text of the [`Toggler`] /// /// [`Font`]: crate::text::Renderer::Font - pub fn font(mut self, font: Renderer::Font) -> Self { - self.font = font; + pub fn font(mut self, font: impl Into) -> Self { + self.font = Some(font.into()); self } @@ -160,7 +160,7 @@ where row = row.push( Text::new(label) .horizontal_alignment(self.text_alignment) - .font(self.font.clone()) + .font(self.font.unwrap_or_else(|| renderer.default_font())) .width(self.width) .size( self.text_size @@ -236,6 +236,7 @@ where if let Some(label) = &self.label { let label_layout = children.next().unwrap(); + let font = self.font.unwrap_or_else(|| renderer.default_font()); crate::widget::text::draw( renderer, @@ -243,7 +244,7 @@ where label_layout, label, self.text_size, - self.font.clone(), + font, Default::default(), self.text_alignment, alignment::Vertical::Center, diff --git a/src/application.rs b/src/application.rs index 1db5c93f..9a1c1855 100644 --- a/src/application.rs +++ b/src/application.rs @@ -197,7 +197,6 @@ pub trait Application: Sized { let renderer_settings = crate::renderer::Settings { default_font: settings.default_font, default_text_size: settings.default_text_size, - text_multithreading: settings.text_multithreading, antialiasing: if settings.antialiasing { Some(crate::renderer::settings::Antialiasing::MSAAx4) } else { diff --git a/src/settings.rs b/src/settings.rs index 266ad404..ce07b0dd 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,5 +1,6 @@ //! Configure your application. use crate::window; +use crate::Font; /// The settings of an application. #[derive(Debug, Clone)] @@ -20,23 +21,16 @@ pub struct Settings { /// [`Application`]: crate::Application pub flags: Flags, - /// The bytes of the font that will be used by default. + /// The default [`Font`] to be used. /// - /// If `None` is provided, a default system font will be chosen. - // TODO: Add `name` for web compatibility - pub default_font: Option<&'static [u8]>, + /// By default, it uses [`Font::SansSerif`]. + pub default_font: Font, /// The text size that will be used by default. /// /// The default value is `16.0`. pub default_text_size: f32, - /// If enabled, spread text workload in multiple threads when multiple cores - /// are available. - /// - /// By default, it is disabled. - pub text_multithreading: bool, - /// If set to true, the renderer will try to perform antialiasing for some /// primitives. /// @@ -79,7 +73,6 @@ impl Settings { window: default_settings.window, default_font: default_settings.default_font, default_text_size: default_settings.default_text_size, - text_multithreading: default_settings.text_multithreading, antialiasing: default_settings.antialiasing, exit_on_close_request: default_settings.exit_on_close_request, try_opengles_first: default_settings.try_opengles_first, @@ -96,9 +89,8 @@ where id: None, window: Default::default(), flags: Default::default(), - default_font: Default::default(), + default_font: Font::SansSerif, default_text_size: 16.0, - text_multithreading: false, antialiasing: false, exit_on_close_request: true, try_opengles_first: false, diff --git a/wgpu/fonts/Iced-Icons.ttf b/wgpu/fonts/Iced-Icons.ttf new file mode 100644 index 00000000..7112f086 Binary files /dev/null and b/wgpu/fonts/Iced-Icons.ttf differ diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 9bf464b3..874edb96 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -27,6 +27,7 @@ pub struct Backend { #[cfg(any(feature = "image", feature = "svg"))] image_pipeline: image::Pipeline, + default_font: Font, default_text_size: f32, } @@ -38,14 +39,7 @@ impl Backend { settings: Settings, format: wgpu::TextureFormat, ) -> Self { - let text_pipeline = text::Pipeline::new( - device, - queue, - format, - settings.default_font, - settings.text_multithreading, - ); - + let text_pipeline = text::Pipeline::new(device, queue, format); let quad_pipeline = quad::Pipeline::new(device, format); let triangle_pipeline = triangle::Pipeline::new(device, format, settings.antialiasing); @@ -61,6 +55,7 @@ impl Backend { #[cfg(any(feature = "image", feature = "svg"))] image_pipeline, + default_font: settings.default_font, default_text_size: settings.default_text_size, } } @@ -199,9 +194,13 @@ impl iced_graphics::Backend for Backend { } impl backend::Text for Backend { - const ICON_FONT: Font = Font::Default; // TODO - const CHECKMARK_ICON: char = '✓'; - const ARROW_DOWN_ICON: char = '▼'; + const ICON_FONT: Font = Font::Name("Iced-Icons"); + const CHECKMARK_ICON: char = '\u{e800}'; + const ARROW_DOWN_ICON: char = '\u{f00c}'; + + fn default_font(&self) -> Font { + self.default_font + } fn default_size(&self) -> f32 { self.default_text_size diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 1a293681..6d6d3fd6 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -47,7 +47,9 @@ mod quad; mod text; mod triangle; -pub use iced_graphics::{Antialiasing, Color, Error, Primitive, Viewport}; +pub use iced_graphics::{ + Antialiasing, Color, Error, Font, Primitive, Viewport, +}; pub use iced_native::Theme; pub use wgpu; diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index 5ef79499..bd9cf473 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -1,12 +1,12 @@ //! Configure a renderer. -use std::fmt; - pub use crate::Antialiasing; +use crate::Font; + /// The settings of a [`Backend`]. /// /// [`Backend`]: crate::Backend -#[derive(Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct Settings { /// The present mode of the [`Backend`]. /// @@ -16,42 +16,20 @@ pub struct Settings { /// The internal graphics backend to use. pub internal_backend: wgpu::Backends, - /// The bytes of the font that will be used by default. - /// - /// If `None` is provided, a default system font will be chosen. - pub default_font: Option<&'static [u8]>, + /// The default [`Font`] to use. + pub default_font: Font, /// The default size of text. /// /// By default, it will be set to `16.0`. pub default_text_size: f32, - /// If enabled, spread text workload in multiple threads when multiple cores - /// are available. - /// - /// By default, it is disabled. - pub text_multithreading: bool, - /// The antialiasing strategy that will be used for triangle primitives. /// /// By default, it is `None`. pub antialiasing: Option, } -impl fmt::Debug for Settings { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Settings") - .field("present_mode", &self.present_mode) - .field("internal_backend", &self.internal_backend) - // Instead of printing the font bytes, we simply show a `bool` indicating if using a default font or not. - .field("default_font", &self.default_font.is_some()) - .field("default_text_size", &self.default_text_size) - .field("text_multithreading", &self.text_multithreading) - .field("antialiasing", &self.antialiasing) - .finish() - } -} - impl Settings { /// Creates new [`Settings`] using environment configuration. /// @@ -81,9 +59,8 @@ impl Default for Settings { Settings { present_mode: wgpu::PresentMode::AutoVsync, internal_backend: wgpu::Backends::all(), - default_font: None, - default_text_size: 20.0, - text_multithreading: false, + default_font: Font::SansSerif, + default_text_size: 16.0, antialiasing: None, } } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index fa7fd5df..083d9d2b 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -112,8 +112,6 @@ impl Pipeline { device: &wgpu::Device, queue: &wgpu::Queue, format: wgpu::TextureFormat, - _default_font: Option<&[u8]>, - _multithreading: bool, ) -> Self { Pipeline { renderers: Vec::new(), @@ -316,7 +314,11 @@ impl Pipeline { fn to_family(font: Font) -> glyphon::Family<'static> { match font { - Font::Default => glyphon::Family::SansSerif, - Font::External { name, .. } => glyphon::Family::Name(name), + Font::Name(name) => glyphon::Family::Name(name), + Font::SansSerif => glyphon::Family::SansSerif, + Font::Serif => glyphon::Family::Serif, + Font::Cursive => glyphon::Family::Cursive, + Font::Fantasy => glyphon::Family::Fantasy, + Font::Monospace => glyphon::Family::Monospace, } } -- cgit From 238154af4ac8dda7f12dd90aa7be106e933bcb30 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Feb 2023 11:12:15 +0100 Subject: Implement `font::load` command in `iced_native` --- examples/todos/src/main.rs | 15 +- graphics/src/backend.rs | 5 + graphics/src/renderer.rs | 5 + native/src/command/action.rs | 16 ++ native/src/font.rs | 19 ++ native/src/lib.rs | 6 +- native/src/program.rs | 3 +- native/src/renderer/null.rs | 4 + native/src/text.rs | 7 +- src/lib.rs | 6 +- wgpu/Cargo.toml | 1 + wgpu/src/backend.rs | 6 + wgpu/src/text.rs | 470 ++++++++++++++++++++++++------------------- winit/src/application.rs | 10 + 14 files changed, 358 insertions(+), 215 deletions(-) create mode 100644 native/src/font.rs diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 5509cf0e..5df4e968 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -1,5 +1,6 @@ use iced::alignment::{self, Alignment}; use iced::event::{self, Event}; +use iced::font::{self, Font}; use iced::keyboard; use iced::subscription; use iced::theme::{self, Theme}; @@ -9,7 +10,7 @@ use iced::widget::{ }; use iced::window; use iced::{Application, Element}; -use iced::{Color, Command, Font, Length, Settings, Subscription}; +use iced::{Color, Command, Length, Settings, Subscription}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; @@ -44,6 +45,7 @@ struct State { #[derive(Debug, Clone)] enum Message { Loaded(Result), + FontLoaded(Result<(), font::Error>), Saved(Result<(), SaveError>), InputChanged(String), CreateTask, @@ -61,7 +63,11 @@ impl Application for Todos { fn new(_flags: ()) -> (Todos, Command) { ( Todos::Loading, - Command::perform(SavedState::load(), Message::Loaded), + Command::batch(vec![ + font::load(include_bytes!("../fonts/icons.ttf").as_slice()) + .map(Message::FontLoaded), + Command::perform(SavedState::load(), Message::Loaded), + ]), ) } @@ -384,7 +390,7 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element { let tasks_left = tasks.iter().filter(|task| !task.completed).count(); let filter_button = |label, filter, current_filter| { - let label = text(label).size(16); + let label = text(label); let button = button(label).style(if filter == current_filter { theme::Button::Primary @@ -401,8 +407,7 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element { tasks_left, if tasks_left == 1 { "task" } else { "tasks" } )) - .width(Length::Fill) - .size(16), + .width(Length::Fill), row![ filter_button("All", Filter::All, current_filter), filter_button("Active", Filter::Active, current_filter), diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index 7bbdee95..8658cffe 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -4,6 +4,8 @@ use iced_native::svg; use iced_native::text; use iced_native::{Font, Point, Size}; +use std::borrow::Cow; + /// The graphics backend of a [`Renderer`]. /// /// [`Renderer`]: crate::Renderer @@ -64,6 +66,9 @@ pub trait Text { point: Point, nearest_only: bool, ) -> Option; + + /// Loads a [`Font`] from its bytes. + fn load_font(&mut self, font: Cow<'static, [u8]>); } /// A graphics backend that supports image rendering. diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 7ad53dec..b052c094 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -10,6 +10,7 @@ use iced_native::{Background, Color, Element, Font, Point, Rectangle, Size}; pub use iced_native::renderer::Style; +use std::borrow::Cow; use std::marker::PhantomData; /// A backend-agnostic renderer that supports all the built-in widgets. @@ -167,6 +168,10 @@ where ) } + fn load_font(&mut self, bytes: Cow<'static, [u8]>) { + self.backend.load_font(bytes); + } + fn fill_text(&mut self, text: Text<'_, Self::Font>) { self.primitives.push(Primitive::Text { content: text.content.to_string(), diff --git a/native/src/command/action.rs b/native/src/command/action.rs index a51b8c21..d1589c05 100644 --- a/native/src/command/action.rs +++ b/native/src/command/action.rs @@ -1,10 +1,12 @@ use crate::clipboard; +use crate::font; use crate::system; use crate::widget; use crate::window; use iced_futures::MaybeSend; +use std::borrow::Cow; use std::fmt; /// An action that a [`Command`] can perform. @@ -27,6 +29,15 @@ pub enum Action { /// Run a widget action. Widget(widget::Action), + + /// Load a font from its bytes. + LoadFont { + /// The bytes of the font to load. + bytes: Cow<'static, [u8]>, + + /// The message to produce when the font has been loaded. + tagger: Box) -> T>, + }, } impl Action { @@ -49,6 +60,10 @@ impl Action { Self::Window(window) => Action::Window(window.map(f)), Self::System(system) => Action::System(system.map(f)), Self::Widget(widget) => Action::Widget(widget.map(f)), + Self::LoadFont { bytes, tagger } => Action::LoadFont { + bytes, + tagger: Box::new(move |result| f(tagger(result))), + }, } } } @@ -63,6 +78,7 @@ impl fmt::Debug for Action { Self::Window(action) => write!(f, "Action::Window({action:?})"), Self::System(action) => write!(f, "Action::System({action:?})"), Self::Widget(_action) => write!(f, "Action::Widget"), + Self::LoadFont { .. } => write!(f, "Action::LoadFont"), } } } diff --git a/native/src/font.rs b/native/src/font.rs new file mode 100644 index 00000000..6840a25f --- /dev/null +++ b/native/src/font.rs @@ -0,0 +1,19 @@ +//! Load and use fonts. +pub use iced_core::Font; + +use crate::command::{self, Command}; +use std::borrow::Cow; + +/// An error while loading a font. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Error {} + +/// Load a font from its bytes. +pub fn load( + bytes: impl Into>, +) -> Command> { + Command::single(command::Action::LoadFont { + bytes: bytes.into(), + tagger: Box::new(std::convert::identity), + }) +} diff --git a/native/src/lib.rs b/native/src/lib.rs index ebdc8490..27b6fc0d 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -47,6 +47,7 @@ pub mod clipboard; pub mod command; pub mod event; +pub mod font; pub mod image; pub mod keyboard; pub mod layout; @@ -80,8 +81,8 @@ mod debug; pub use iced_core::alignment; pub use iced_core::time; pub use iced_core::{ - color, Alignment, Background, Color, ContentFit, Font, Length, Padding, - Pixels, Point, Rectangle, Size, Vector, + color, Alignment, Background, Color, ContentFit, Length, Padding, Pixels, + Point, Rectangle, Size, Vector, }; pub use iced_futures::{executor, futures}; pub use iced_style::application; @@ -95,6 +96,7 @@ pub use command::Command; pub use debug::Debug; pub use element::Element; pub use event::Event; +pub use font::Font; pub use hasher::Hasher; pub use layout::Layout; pub use overlay::Overlay; diff --git a/native/src/program.rs b/native/src/program.rs index c71c237f..25cab332 100644 --- a/native/src/program.rs +++ b/native/src/program.rs @@ -1,4 +1,5 @@ //! Build interactive programs using The Elm Architecture. +use crate::text; use crate::{Command, Element, Renderer}; mod state; @@ -8,7 +9,7 @@ pub use state::State; /// The core of a user interface application following The Elm Architecture. pub trait Program: Sized { /// The graphics backend to use to draw the [`Program`]. - type Renderer: Renderer; + type Renderer: Renderer + text::Renderer; /// The type of __messages__ your [`Program`] will produce. type Message: std::fmt::Debug + Send; diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs index 50d7d6d6..150ee786 100644 --- a/native/src/renderer/null.rs +++ b/native/src/renderer/null.rs @@ -2,6 +2,8 @@ use crate::renderer::{self, Renderer}; use crate::text::{self, Text}; use crate::{Background, Font, Point, Rectangle, Size, Theme, Vector}; +use std::borrow::Cow; + /// A renderer that does nothing. /// /// It can be useful if you are writing tests! @@ -52,6 +54,8 @@ impl text::Renderer for Null { 16.0 } + fn load_font(&mut self, _font: Cow<'static, [u8]>) {} + fn measure( &self, _content: &str, diff --git a/native/src/text.rs b/native/src/text.rs index b7915a55..1bbd36cc 100644 --- a/native/src/text.rs +++ b/native/src/text.rs @@ -2,6 +2,8 @@ use crate::alignment; use crate::{Color, Point, Rectangle, Size, Vector}; +use std::borrow::Cow; + /// A paragraph. #[derive(Debug, Clone, Copy)] pub struct Text<'a, Font> { @@ -72,7 +74,7 @@ pub trait Renderer: crate::Renderer { /// [`ICON_FONT`]: Self::ICON_FONT const ARROW_DOWN_ICON: char; - /// Returns the default [`Font`]. + /// Returns the default [`Self::Font`]. fn default_font(&self) -> Self::Font; /// Returns the default size of [`Text`]. @@ -112,6 +114,9 @@ pub trait Renderer: crate::Renderer { nearest_only: bool, ) -> Option; + /// Loads a [`Self::Font`] from its bytes. + fn load_font(&mut self, font: Cow<'static, [u8]>); + /// Draws the given [`Text`]. fn fill_text(&mut self, text: Text<'_, Self::Font>); } diff --git a/src/lib.rs b/src/lib.rs index 318852f9..17c5ab97 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -196,6 +196,7 @@ use iced_glow as renderer; pub use iced_native::theme; pub use runtime::event; +pub use runtime::font; pub use runtime::subscription; pub use application::Application; @@ -203,6 +204,7 @@ pub use element::Element; pub use error::Error; pub use event::Event; pub use executor::Executor; +pub use font::Font; pub use renderer::Renderer; pub use result::Result; pub use sandbox::Sandbox; @@ -213,8 +215,8 @@ pub use theme::Theme; pub use runtime::alignment; pub use runtime::futures; pub use runtime::{ - color, Alignment, Background, Color, Command, ContentFit, Font, Length, - Padding, Point, Rectangle, Size, Vector, + color, Alignment, Background, Color, Command, ContentFit, Length, Padding, + Point, Rectangle, Size, Vector, }; #[cfg(feature = "system")] diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 1a94c6a3..dffbbab0 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -36,6 +36,7 @@ bitflags = "1.2" once_cell = "1.0" rustc-hash = "1.1" twox-hash = "1.6" +ouroboros = "0.15" [dependencies.bytemuck] version = "1.9" diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 874edb96..5a275c8a 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -14,6 +14,8 @@ use tracing::info_span; #[cfg(any(feature = "image", feature = "svg"))] use crate::image; +use std::borrow::Cow; + /// A [`wgpu`] graphics backend for [`iced`]. /// /// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs @@ -234,6 +236,10 @@ impl backend::Text for Backend { nearest_only, ) } + + fn load_font(&mut self, font: Cow<'static, [u8]>) { + self.text_pipeline.load_font(font); + } } #[cfg(feature = "image")] diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 083d9d2b..cdfcd576 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -5,108 +5,37 @@ use iced_native::alignment; use iced_native::{Color, Font, Rectangle, Size}; use rustc_hash::{FxHashMap, FxHashSet}; +use std::borrow::Cow; use std::cell::RefCell; use std::hash::{BuildHasher, Hash, Hasher}; +use std::sync::Arc; use twox_hash::RandomXxHashBuilder64; #[allow(missing_debug_implementations)] pub struct Pipeline { + system: Option, renderers: Vec, atlas: glyphon::TextAtlas, - cache: glyphon::SwashCache<'static>, - measurement_cache: RefCell, - render_cache: Cache, layer: usize, } -struct Cache { - entries: FxHashMap>, - recently_used: FxHashSet, - hasher: RandomXxHashBuilder64, -} - -impl Cache { - fn new() -> Self { - Self { - entries: FxHashMap::default(), - recently_used: FxHashSet::default(), - hasher: RandomXxHashBuilder64::default(), - } - } - - fn get(&self, key: &KeyHash) -> Option<&glyphon::Buffer<'static>> { - self.entries.get(key) - } - - fn allocate( - &mut self, - key: Key<'_>, - ) -> (KeyHash, &mut glyphon::Buffer<'static>) { - let hash = { - let mut hasher = self.hasher.build_hasher(); - - key.content.hash(&mut hasher); - (key.size as i32).hash(&mut hasher); - key.font.hash(&mut hasher); - (key.bounds.width as i32).hash(&mut hasher); - (key.bounds.height as i32).hash(&mut hasher); - key.color.into_rgba8().hash(&mut hasher); - - hasher.finish() - }; +#[ouroboros::self_referencing] +struct System { + fonts: glyphon::FontSystem, - if !self.entries.contains_key(&hash) { - let metrics = - glyphon::Metrics::new(key.size as i32, (key.size * 1.2) as i32); + #[borrows(fonts)] + #[not_covariant] + cache: glyphon::SwashCache<'this>, - let mut buffer = glyphon::Buffer::new(&FONT_SYSTEM, metrics); + #[borrows(fonts)] + #[not_covariant] + measurement_cache: RefCell>, - buffer.set_size(key.bounds.width as i32, key.bounds.height as i32); - buffer.set_text( - key.content, - glyphon::Attrs::new().family(to_family(key.font)).color({ - let [r, g, b, a] = key.color.into_linear(); - - glyphon::Color::rgba( - (r * 255.0) as u8, - (g * 255.0) as u8, - (b * 255.0) as u8, - (a * 255.0) as u8, - ) - }), - ); - - let _ = self.entries.insert(hash, buffer); - } - - let _ = self.recently_used.insert(hash); - - (hash, self.entries.get_mut(&hash).unwrap()) - } - - fn trim(&mut self) { - self.entries - .retain(|key, _| self.recently_used.contains(key)); - - self.recently_used.clear(); - } + #[borrows(fonts)] + #[not_covariant] + render_cache: Cache<'this>, } -#[derive(Debug, Clone, Copy)] -struct Key<'a> { - content: &'a str, - size: f32, - font: Font, - bounds: Size, - color: Color, -} - -type KeyHash = u64; - -// TODO: Share with `iced_graphics` -static FONT_SYSTEM: once_cell::sync::Lazy = - once_cell::sync::Lazy::new(glyphon::FontSystem::new); - impl Pipeline { pub fn new( device: &wgpu::Device, @@ -114,15 +43,41 @@ impl Pipeline { format: wgpu::TextureFormat, ) -> Self { Pipeline { + system: Some( + SystemBuilder { + fonts: glyphon::FontSystem::new(), + cache_builder: |fonts| glyphon::SwashCache::new(fonts), + measurement_cache_builder: |_| RefCell::new(Cache::new()), + render_cache_builder: |_| Cache::new(), + } + .build(), + ), renderers: Vec::new(), atlas: glyphon::TextAtlas::new(device, queue, format), - cache: glyphon::SwashCache::new(&FONT_SYSTEM), - measurement_cache: RefCell::new(Cache::new()), - render_cache: Cache::new(), layer: 0, } } + pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { + let heads = self.system.take().unwrap().into_heads(); + + let (locale, mut db) = heads.fonts.into_locale_and_db(); + + db.load_font_source(glyphon::fontdb::Source::Binary(Arc::new( + bytes.to_owned(), + ))); + + self.system = Some( + SystemBuilder { + fonts: glyphon::FontSystem::new_with_locale_and_db(locale, db), + cache_builder: |fonts| glyphon::SwashCache::new(fonts), + measurement_cache_builder: |_| RefCell::new(Cache::new()), + render_cache_builder: |_| Cache::new(), + } + .build(), + ); + } + pub fn prepare( &mut self, device: &wgpu::Device, @@ -132,93 +87,100 @@ impl Pipeline { scale_factor: f32, target_size: Size, ) { - if self.renderers.len() <= self.layer { - self.renderers - .push(glyphon::TextRenderer::new(device, queue)); - } - - let renderer = &mut self.renderers[self.layer]; - - let keys: Vec<_> = sections - .iter() - .map(|section| { - let (key, _) = self.render_cache.allocate(Key { - content: section.content, - size: section.size * scale_factor, - font: section.font, - bounds: Size { - width: section.bounds.width * scale_factor, - height: section.bounds.height * scale_factor, + self.system.as_mut().unwrap().with_mut(|fields| { + if self.renderers.len() <= self.layer { + self.renderers + .push(glyphon::TextRenderer::new(device, queue)); + } + + let renderer = &mut self.renderers[self.layer]; + + let keys: Vec<_> = sections + .iter() + .map(|section| { + let (key, _) = fields.render_cache.allocate( + fields.fonts, + Key { + content: section.content, + size: section.size * scale_factor, + font: section.font, + bounds: Size { + width: section.bounds.width * scale_factor, + height: section.bounds.height * scale_factor, + }, + color: section.color, + }, + ); + + key + }) + .collect(); + + let bounds = glyphon::TextBounds { + left: (bounds.x * scale_factor) as i32, + top: (bounds.y * scale_factor) as i32, + right: ((bounds.x + bounds.width) * scale_factor) as i32, + bottom: ((bounds.y + bounds.height) * scale_factor) as i32, + }; + + let text_areas: Vec<_> = sections + .iter() + .zip(keys.iter()) + .map(|(section, key)| { + let buffer = fields + .render_cache + .get(key) + .expect("Get cached buffer"); + + let x = section.bounds.x * scale_factor; + let y = section.bounds.y * scale_factor; + + let (total_lines, max_width) = buffer + .layout_runs() + .enumerate() + .fold((0, 0.0), |(_, max), (i, buffer)| { + (i + 1, buffer.line_w.max(max)) + }); + + let total_height = + total_lines as f32 * section.size * 1.2 * scale_factor; + + let left = match section.horizontal_alignment { + alignment::Horizontal::Left => x, + alignment::Horizontal::Center => x - max_width / 2.0, + alignment::Horizontal::Right => x - max_width, + }; + + let top = match section.vertical_alignment { + alignment::Vertical::Top => y, + alignment::Vertical::Center => y - total_height / 2.0, + alignment::Vertical::Bottom => y - total_height, + }; + + glyphon::TextArea { + buffer, + left: left as i32, + top: top as i32, + bounds, + } + }) + .collect(); + + renderer + .prepare( + device, + queue, + &mut self.atlas, + glyphon::Resolution { + width: target_size.width, + height: target_size.height, }, - color: section.color, - }); - - key - }) - .collect(); - - let bounds = glyphon::TextBounds { - left: (bounds.x * scale_factor) as i32, - top: (bounds.y * scale_factor) as i32, - right: ((bounds.x + bounds.width) * scale_factor) as i32, - bottom: ((bounds.y + bounds.height) * scale_factor) as i32, - }; - - let text_areas: Vec<_> = sections - .iter() - .zip(keys.iter()) - .map(|(section, key)| { - let buffer = - self.render_cache.get(key).expect("Get cached buffer"); - - let x = section.bounds.x * scale_factor; - let y = section.bounds.y * scale_factor; - - let (total_lines, max_width) = buffer - .layout_runs() - .enumerate() - .fold((0, 0.0), |(_, max), (i, buffer)| { - (i + 1, buffer.line_w.max(max)) - }); - - let total_height = - total_lines as f32 * section.size * 1.2 * scale_factor; - - let left = match section.horizontal_alignment { - alignment::Horizontal::Left => x, - alignment::Horizontal::Center => x - max_width / 2.0, - alignment::Horizontal::Right => x - max_width, - }; - - let top = match section.vertical_alignment { - alignment::Vertical::Top => y, - alignment::Vertical::Center => y - total_height / 2.0, - alignment::Vertical::Bottom => y - total_height, - }; - - glyphon::TextArea { - buffer, - left: left as i32, - top: top as i32, - bounds, - } - }) - .collect(); - - renderer - .prepare( - device, - queue, - &mut self.atlas, - glyphon::Resolution { - width: target_size.width, - height: target_size.height, - }, - &text_areas, - glyphon::Color::rgb(0, 0, 0), - &mut self.cache, - ) - .expect("Prepare text sections"); + &text_areas, + glyphon::Color::rgb(0, 0, 0), + fields.cache, + ) + .expect("Prepare text sections"); + }); } pub fn render( @@ -251,7 +213,10 @@ impl Pipeline { pub fn end_frame(&mut self) { self.renderers.truncate(self.layer); - self.render_cache.trim(); + self.system + .as_mut() + .unwrap() + .with_render_cache_mut(|cache| cache.trim()); self.layer = 0; } @@ -263,24 +228,29 @@ impl Pipeline { font: Font, bounds: Size, ) -> (f32, f32) { - let mut measurement_cache = self.measurement_cache.borrow_mut(); - - let (_, paragraph) = measurement_cache.allocate(Key { - content, - size: size, - font, - bounds, - color: Color::BLACK, - }); + self.system.as_ref().unwrap().with(|fields| { + let mut measurement_cache = fields.measurement_cache.borrow_mut(); + + let (_, paragraph) = measurement_cache.allocate( + fields.fonts, + Key { + content, + size: size, + font, + bounds, + color: Color::BLACK, + }, + ); - let (total_lines, max_width) = paragraph - .layout_runs() - .enumerate() - .fold((0, 0.0), |(_, max), (i, buffer)| { - (i + 1, buffer.line_w.max(max)) - }); + let (total_lines, max_width) = paragraph + .layout_runs() + .enumerate() + .fold((0, 0.0), |(_, max), (i, buffer)| { + (i + 1, buffer.line_w.max(max)) + }); - (max_width, size * 1.2 * total_lines as f32) + (max_width, size * 1.2 * total_lines as f32) + }) } pub fn hit_test( @@ -292,23 +262,31 @@ impl Pipeline { point: iced_native::Point, _nearest_only: bool, ) -> Option { - let mut measurement_cache = self.measurement_cache.borrow_mut(); - - let (_, paragraph) = measurement_cache.allocate(Key { - content, - size: size, - font, - bounds, - color: Color::BLACK, - }); + self.system.as_ref().unwrap().with(|fields| { + let mut measurement_cache = fields.measurement_cache.borrow_mut(); + + let (_, paragraph) = measurement_cache.allocate( + fields.fonts, + Key { + content, + size: size, + font, + bounds, + color: Color::BLACK, + }, + ); - let cursor = paragraph.hit(point.x as i32, point.y as i32)?; + let cursor = paragraph.hit(point.x as i32, point.y as i32)?; - Some(Hit::CharOffset(cursor.index)) + Some(Hit::CharOffset(cursor.index)) + }) } pub fn trim_measurement_cache(&mut self) { - self.measurement_cache.borrow_mut().trim(); + self.system + .as_mut() + .unwrap() + .with_measurement_cache_mut(|cache| cache.borrow_mut().trim()); } } @@ -322,3 +300,87 @@ fn to_family(font: Font) -> glyphon::Family<'static> { Font::Monospace => glyphon::Family::Monospace, } } + +struct Cache<'a> { + entries: FxHashMap>, + recently_used: FxHashSet, + hasher: RandomXxHashBuilder64, +} + +impl<'a> Cache<'a> { + fn new() -> Self { + Self { + entries: FxHashMap::default(), + recently_used: FxHashSet::default(), + hasher: RandomXxHashBuilder64::default(), + } + } + + fn get(&self, key: &KeyHash) -> Option<&glyphon::Buffer<'a>> { + self.entries.get(key) + } + + fn allocate( + &mut self, + fonts: &'a glyphon::FontSystem, + key: Key<'_>, + ) -> (KeyHash, &mut glyphon::Buffer<'a>) { + let hash = { + let mut hasher = self.hasher.build_hasher(); + + key.content.hash(&mut hasher); + (key.size as i32).hash(&mut hasher); + key.font.hash(&mut hasher); + (key.bounds.width as i32).hash(&mut hasher); + (key.bounds.height as i32).hash(&mut hasher); + key.color.into_rgba8().hash(&mut hasher); + + hasher.finish() + }; + + if !self.entries.contains_key(&hash) { + let metrics = + glyphon::Metrics::new(key.size as i32, (key.size * 1.2) as i32); + let mut buffer = glyphon::Buffer::new(&fonts, metrics); + + buffer.set_size(key.bounds.width as i32, key.bounds.height as i32); + buffer.set_text( + key.content, + glyphon::Attrs::new().family(to_family(key.font)).color({ + let [r, g, b, a] = key.color.into_linear(); + + glyphon::Color::rgba( + (r * 255.0) as u8, + (g * 255.0) as u8, + (b * 255.0) as u8, + (a * 255.0) as u8, + ) + }), + ); + + let _ = self.entries.insert(hash, buffer); + } + + let _ = self.recently_used.insert(hash); + + (hash, self.entries.get_mut(&hash).unwrap()) + } + + fn trim(&mut self) { + self.entries + .retain(|key, _| self.recently_used.contains(key)); + + self.recently_used.clear(); + } +} + +#[derive(Debug, Clone, Copy)] +struct Key<'a> { + content: &'a str, + size: f32, + font: Font, + bounds: Size, + color: Color, +} + +type KeyHash = u64; diff --git a/winit/src/application.rs b/winit/src/application.rs index 9781a453..889becad 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -851,6 +851,16 @@ pub fn run_command( current_cache = user_interface.into_cache(); *cache = current_cache; } + command::Action::LoadFont { bytes, tagger } => { + use crate::text::Renderer; + + // TODO: Error handling (?) + renderer.load_font(bytes); + + proxy + .send_event(tagger(Ok(()))) + .expect("Send message to event loop"); + } } } } -- cgit From 5a82fc654e2933c4c93dac5393685861feb07b1f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Feb 2023 11:21:35 +0100 Subject: Use floating coordinates directly in `text::Pipeline` --- wgpu/Cargo.toml | 3 ++- wgpu/src/text.rs | 13 ++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index dffbbab0..280ce29b 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -52,7 +52,8 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" -path = "../../glyphon" +git = "https://github.com/hecrj/glyphon.git" +rev = "bffca9b958af11d1bfd0c0d1a281fc8799cc5a5b" [dependencies.tracing] version = "0.1.6" diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index cdfcd576..2ca3f0d8 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -276,7 +276,7 @@ impl Pipeline { }, ); - let cursor = paragraph.hit(point.x as i32, point.y as i32)?; + let cursor = paragraph.hit(point.x, point.y)?; Some(Hit::CharOffset(cursor.index)) }) @@ -329,21 +329,20 @@ impl<'a> Cache<'a> { let mut hasher = self.hasher.build_hasher(); key.content.hash(&mut hasher); - (key.size as i32).hash(&mut hasher); + key.size.to_bits().hash(&mut hasher); key.font.hash(&mut hasher); - (key.bounds.width as i32).hash(&mut hasher); - (key.bounds.height as i32).hash(&mut hasher); + key.bounds.width.to_bits().hash(&mut hasher); + key.bounds.height.to_bits().hash(&mut hasher); key.color.into_rgba8().hash(&mut hasher); hasher.finish() }; if !self.entries.contains_key(&hash) { - let metrics = - glyphon::Metrics::new(key.size as i32, (key.size * 1.2) as i32); + let metrics = glyphon::Metrics::new(key.size, key.size * 1.2); let mut buffer = glyphon::Buffer::new(&fonts, metrics); - buffer.set_size(key.bounds.width as i32, key.bounds.height as i32); + buffer.set_size(key.bounds.width, key.bounds.height); buffer.set_text( key.content, glyphon::Attrs::new().family(to_family(key.font)).color({ -- cgit From d2825360a75600bb6b4097737c987e2d9e05da6a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Feb 2023 11:35:12 +0100 Subject: Load `Iced-Icons.ttf` font in `text::Pipeline::new` --- wgpu/Cargo.toml | 2 +- wgpu/src/backend.rs | 4 ++-- wgpu/src/text.rs | 8 +++++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 280ce29b..1ab3d93f 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -53,7 +53,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "bffca9b958af11d1bfd0c0d1a281fc8799cc5a5b" +rev = "4b5e5106f05332dc324bae2095468845a61f36b9" [dependencies.tracing] version = "0.1.6" diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 5a275c8a..f19959bd 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -197,8 +197,8 @@ impl iced_graphics::Backend for Backend { impl backend::Text for Backend { const ICON_FONT: Font = Font::Name("Iced-Icons"); - const CHECKMARK_ICON: char = '\u{e800}'; - const ARROW_DOWN_ICON: char = '\u{f00c}'; + const CHECKMARK_ICON: char = '\u{f00c}'; + const ARROW_DOWN_ICON: char = '\u{e800}'; fn default_font(&self) -> Font { self.default_font diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 2ca3f0d8..4af57af3 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -45,7 +45,13 @@ impl Pipeline { Pipeline { system: Some( SystemBuilder { - fonts: glyphon::FontSystem::new(), + fonts: glyphon::FontSystem::new_with_fonts( + [glyphon::fontdb::Source::Binary(Arc::new( + include_bytes!("../fonts/Iced-Icons.ttf") + .as_slice(), + ))] + .into_iter(), + ), cache_builder: |fonts| glyphon::SwashCache::new(fonts), measurement_cache_builder: |_| RefCell::new(Cache::new()), render_cache_builder: |_| Cache::new(), -- cgit From 17470bf7d36ee164311020b9d8c79662ac49c166 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Feb 2023 12:35:10 +0100 Subject: Fix `clippy` lints :tada: --- native/src/widget/text_input.rs | 2 +- wgpu/src/text.rs | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index e6b70db2..0656be62 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -967,7 +967,7 @@ pub fn draw( } else { theme.value_color(style) }, - font: font, + font, bounds: Rectangle { y: text_bounds.center_y(), width: f32::INFINITY, diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 4af57af3..967596f2 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -7,6 +7,7 @@ use iced_native::{Color, Font, Rectangle, Size}; use rustc_hash::{FxHashMap, FxHashSet}; use std::borrow::Cow; use std::cell::RefCell; +use std::collections::hash_map; use std::hash::{BuildHasher, Hash, Hasher}; use std::sync::Arc; use twox_hash::RandomXxHashBuilder64; @@ -70,7 +71,7 @@ impl Pipeline { let (locale, mut db) = heads.fonts.into_locale_and_db(); db.load_font_source(glyphon::fontdb::Source::Binary(Arc::new( - bytes.to_owned(), + bytes.into_owned(), ))); self.system = Some( @@ -241,7 +242,7 @@ impl Pipeline { fields.fonts, Key { content, - size: size, + size, font, bounds, color: Color::BLACK, @@ -275,7 +276,7 @@ impl Pipeline { fields.fonts, Key { content, - size: size, + size, font, bounds, color: Color::BLACK, @@ -344,9 +345,9 @@ impl<'a> Cache<'a> { hasher.finish() }; - if !self.entries.contains_key(&hash) { + if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) { let metrics = glyphon::Metrics::new(key.size, key.size * 1.2); - let mut buffer = glyphon::Buffer::new(&fonts, metrics); + let mut buffer = glyphon::Buffer::new(fonts, metrics); buffer.set_size(key.bounds.width, key.bounds.height); buffer.set_text( @@ -363,7 +364,7 @@ impl<'a> Cache<'a> { }), ); - let _ = self.entries.insert(hash, buffer); + let _ = entry.insert(buffer); } let _ = self.recently_used.insert(hash); -- cgit From da4182099db703d59006ca72de4cb4d54c9d7855 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Feb 2023 12:42:02 +0100 Subject: Disable `std` feature for `twox-hash` to fix Wasm build --- wgpu/Cargo.toml | 9 ++++++++- wgpu/src/text.rs | 11 ++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 1ab3d93f..0d9fb75b 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -35,9 +35,16 @@ futures = "0.3" bitflags = "1.2" once_cell = "1.0" rustc-hash = "1.1" -twox-hash = "1.6" ouroboros = "0.15" +[dependencies.twox-hash] +version = "1.6" +default-features = false + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.twox-hash] +version = "1.6.1" +features = ["std"] + [dependencies.bytemuck] version = "1.9" features = ["derive"] diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 967596f2..6c88acc3 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -10,7 +10,6 @@ use std::cell::RefCell; use std::collections::hash_map; use std::hash::{BuildHasher, Hash, Hasher}; use std::sync::Arc; -use twox_hash::RandomXxHashBuilder64; #[allow(missing_debug_implementations)] pub struct Pipeline { @@ -311,15 +310,21 @@ fn to_family(font: Font) -> glyphon::Family<'static> { struct Cache<'a> { entries: FxHashMap>, recently_used: FxHashSet, - hasher: RandomXxHashBuilder64, + hasher: HashBuilder, } +#[cfg(not(target_arch = "wasm32"))] +type HashBuilder = twox_hash::RandomXxHashBuilder64; + +#[cfg(target_arch = "wasm32")] +type HashBuilder = std::hash::BuildHasherDefault; + impl<'a> Cache<'a> { fn new() -> Self { Self { entries: FxHashMap::default(), recently_used: FxHashSet::default(), - hasher: RandomXxHashBuilder64::default(), + hasher: HashBuilder::default(), } } -- cgit From b4dd9b6d93479f28304ba23fec59816d6fcec564 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Feb 2023 13:24:11 +0100 Subject: Update `glyphon` fork (sRGB support) --- wgpu/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 0d9fb75b..da8cdf9f 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -60,7 +60,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "4b5e5106f05332dc324bae2095468845a61f36b9" +rev = "304dcaf8443bec7194891fdef3df468a6eaeb7d9" [dependencies.tracing] version = "0.1.6" -- cgit From de7fc637cf72eb14da17dac12510b41e8e3c4a71 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Feb 2023 14:44:42 +0100 Subject: Fix `test` workflow --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 38b81842..390e7bf3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,4 +38,4 @@ jobs: - name: Check compilation of `todos` example run: cargo build --package todos --target wasm32-unknown-unknown - name: Check compilation of `integration_wgpu` example - run: cargo build --package integration_wgpu --target wasm32-unknown-unknown + run: cargo build --package integration --target wasm32-unknown-unknown -- cgit From a2ab9e939502ff36fd51115d9828fcdcd7bc104d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Feb 2023 16:41:18 +0100 Subject: Use `Pixels` for `Text::size` --- native/src/overlay/menu.rs | 3 ++- native/src/widget/checkbox.rs | 3 +-- native/src/widget/pick_list.rs | 6 +++--- native/src/widget/radio.rs | 3 +-- native/src/widget/text.rs | 8 +++----- native/src/widget/text_input.rs | 18 ++++++++---------- native/src/widget/toggler.rs | 3 +-- 7 files changed, 19 insertions(+), 25 deletions(-) diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index 9c3a8a44..4ccccbf7 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -450,7 +450,8 @@ where let text_size = self.text_size.unwrap_or_else(|| renderer.default_size()); - let option_height = (text_size + self.padding.vertical()) as usize; + let option_height = + (text_size + f32::from(self.padding.vertical())) as usize; let offset = viewport.y - bounds.y; let start = (offset / option_height as f32) as usize; diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 138c458c..0e21e995 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -288,7 +288,6 @@ where { let label_layout = children.next().unwrap(); - let font = self.font.unwrap_or_else(|| renderer.default_font()); widget::text::draw( renderer, @@ -296,7 +295,7 @@ where label_layout, &self.label, self.text_size, - font, + self.font, widget::text::Appearance { color: custom_style.text_color, }, diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index c1ff0004..b4cda748 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -163,7 +163,7 @@ where self.width, self.padding, self.text_size, - self.font.unwrap_or_else(|| renderer.default_font()), + self.font, self.placeholder.as_deref(), &self.options, ) @@ -344,7 +344,7 @@ pub fn layout( width: Length, padding: Padding, text_size: Option, - font: Renderer::Font, + font: Option, placeholder: Option<&str>, options: &[T], ) -> layout::Node @@ -363,7 +363,7 @@ where let (width, _) = renderer.measure( label, text_size, - font, + font.unwrap_or_else(|| renderer.default_font()), Size::new(f32::INFINITY, f32::INFINITY), ); diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index bd803910..5f60eaef 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -268,7 +268,6 @@ where { let label_layout = children.next().unwrap(); - let font = self.font.unwrap_or(renderer.default_font()); widget::text::draw( renderer, @@ -276,7 +275,7 @@ where label_layout, &self.label, self.text_size, - font, + self.font, widget::text::Appearance { color: custom_style.text_color, }, diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs index 235a027e..aede754a 100644 --- a/native/src/widget/text.rs +++ b/native/src/widget/text.rs @@ -160,15 +160,13 @@ where _cursor_position: Point, _viewport: &Rectangle, ) { - let font = self.font.unwrap_or_else(|| renderer.default_font()); - draw( renderer, style, layout, &self.content, self.size, - font, + self.font, theme.appearance(self.style), self.horizontal_alignment, self.vertical_alignment, @@ -192,7 +190,7 @@ pub fn draw( layout: Layout<'_>, content: &str, size: Option, - font: Renderer::Font, + font: Option, appearance: Appearance, horizontal_alignment: alignment::Horizontal, vertical_alignment: alignment::Vertical, @@ -218,7 +216,7 @@ pub fn draw( size: size.unwrap_or_else(|| renderer.default_size()), bounds: Rectangle { x, y, ..bounds }, color: appearance.color.unwrap_or(style.text_color), - font, + font: font.unwrap_or_else(|| renderer.default_font()), horizontal_alignment, vertical_alignment, }); diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 0656be62..f71b4503 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -179,8 +179,6 @@ where cursor_position: Point, value: Option<&Value>, ) { - let font = self.font.unwrap_or(renderer.default_font()); - draw( renderer, theme, @@ -190,7 +188,7 @@ where value.unwrap_or(&self.value), &self.placeholder, self.size, - font, + self.font, self.is_secure, &self.style, ) @@ -260,7 +258,7 @@ where shell, &mut self.value, self.size, - self.font.unwrap_or(renderer.default_font()), + self.font, self.is_secure, self.on_change.as_ref(), self.on_paste.as_deref(), @@ -279,8 +277,6 @@ where cursor_position: Point, _viewport: &Rectangle, ) { - let font = self.font.unwrap_or(renderer.default_font()); - draw( renderer, theme, @@ -290,7 +286,7 @@ where &self.value, &self.placeholder, self.size, - font, + self.font, self.is_secure, &self.style, ) @@ -414,7 +410,7 @@ pub fn update<'a, Message, Renderer>( shell: &mut Shell<'_, Message>, value: &mut Value, size: Option, - font: Renderer::Font, + font: Option, is_secure: bool, on_change: &dyn Fn(String) -> Message, on_paste: Option<&dyn Fn(String) -> Message>, @@ -820,7 +816,7 @@ pub fn draw( value: &Value, placeholder: &str, size: Option, - font: Renderer::Font, + font: Option, is_secure: bool, style: &::Style, ) where @@ -854,6 +850,7 @@ pub fn draw( ); let text = value.to_string(); + let font = font.unwrap_or_else(|| renderer.default_font()); let size = size.unwrap_or_else(|| renderer.default_size()); let (cursor, offset) = if let Some(focus) = &state.is_focused { @@ -1188,7 +1185,7 @@ where fn find_cursor_position( renderer: &Renderer, text_bounds: Rectangle, - font: Renderer::Font, + font: Option, size: Option, value: &Value, state: &State, @@ -1197,6 +1194,7 @@ fn find_cursor_position( where Renderer: text::Renderer, { + let font = font.unwrap_or_else(|| renderer.default_font()); let size = size.unwrap_or_else(|| renderer.default_size()); let offset = offset(renderer, text_bounds, font, size, value, state); diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs index 495406db..d9c80ebe 100644 --- a/native/src/widget/toggler.rs +++ b/native/src/widget/toggler.rs @@ -236,7 +236,6 @@ where if let Some(label) = &self.label { let label_layout = children.next().unwrap(); - let font = self.font.unwrap_or_else(|| renderer.default_font()); crate::widget::text::draw( renderer, @@ -244,7 +243,7 @@ where label_layout, label, self.text_size, - font, + self.font, Default::default(), self.text_alignment, alignment::Vertical::Center, -- cgit From eb3cd3a321db48a4a4555575f022e9a0ed85063b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 5 Feb 2023 18:24:58 +0100 Subject: Fix `overlay::Menu` line height --- native/src/overlay/menu.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index 4ccccbf7..0509f7bc 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -344,7 +344,7 @@ where let size = { let intrinsic = Size::new( 0.0, - (text_size + self.padding.vertical()) + (text_size * 1.2 + self.padding.vertical()) * self.options.len() as f32, ); @@ -386,7 +386,7 @@ where *self.hovered_option = Some( ((cursor_position.y - bounds.y) - / (text_size + self.padding.vertical())) + / (text_size * 1.2 + self.padding.vertical())) as usize, ); } @@ -401,7 +401,7 @@ where *self.hovered_option = Some( ((cursor_position.y - bounds.y) - / (text_size + self.padding.vertical())) + / (text_size * 1.2 + self.padding.vertical())) as usize, ); @@ -451,7 +451,7 @@ where let text_size = self.text_size.unwrap_or_else(|| renderer.default_size()); let option_height = - (text_size + f32::from(self.padding.vertical())) as usize; + (text_size * 1.2 + f32::from(self.padding.vertical())) as usize; let offset = viewport.y - bounds.y; let start = (offset / option_height as f32) as usize; @@ -468,7 +468,7 @@ where x: bounds.x, y: bounds.y + (option_height * i) as f32, width: bounds.width, - height: text_size + self.padding.vertical(), + height: text_size * 1.2 + self.padding.vertical(), }; if is_selected { -- cgit From 32309f0140efb4ea92e3e35e3adc5c740909f196 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 5 Feb 2023 18:26:09 +0100 Subject: Introduce `Weight` enum to `font` --- core/src/font.rs | 16 ++++++++++++++++ core/src/lib.rs | 2 +- native/src/font.rs | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/core/src/font.rs b/core/src/font.rs index 1f774a70..b67c08af 100644 --- a/core/src/font.rs +++ b/core/src/font.rs @@ -1,3 +1,4 @@ +//! Load and use fonts. use std::hash::Hash; /// A font. @@ -27,3 +28,18 @@ pub enum Font { /// fixed width. Monospace, } + +/// The weight of some text. +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Weight { + Thin, + ExtraLight, + Light, + Normal, + Medium, + Semibold, + Bold, + ExtraBold, + Black, +} diff --git a/core/src/lib.rs b/core/src/lib.rs index d3596b4d..d7314851 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -25,6 +25,7 @@ #![forbid(unsafe_code, rust_2018_idioms)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] pub mod alignment; +pub mod font; pub mod keyboard; pub mod mouse; pub mod time; @@ -32,7 +33,6 @@ pub mod time; mod background; mod color; mod content_fit; -mod font; mod length; mod padding; mod pixels; diff --git a/native/src/font.rs b/native/src/font.rs index 6840a25f..15359694 100644 --- a/native/src/font.rs +++ b/native/src/font.rs @@ -1,5 +1,5 @@ //! Load and use fonts. -pub use iced_core::Font; +pub use iced_core::font::*; use crate::command::{self, Command}; use std::borrow::Cow; -- cgit From 6cf4a10906e777645b6bf47faf0e7b8f3c36549e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 5 Feb 2023 18:28:46 +0100 Subject: Stop reusing `SwashCache` in `text::Pipeline` `SwashCache` can't be easily trimmed and it's not really getting us anything since `glyphon` already caches using a glyph atlas anyways. --- wgpu/src/text.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 6c88acc3..59a840ca 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -23,10 +23,6 @@ pub struct Pipeline { struct System { fonts: glyphon::FontSystem, - #[borrows(fonts)] - #[not_covariant] - cache: glyphon::SwashCache<'this>, - #[borrows(fonts)] #[not_covariant] measurement_cache: RefCell>, @@ -52,7 +48,6 @@ impl Pipeline { ))] .into_iter(), ), - cache_builder: |fonts| glyphon::SwashCache::new(fonts), measurement_cache_builder: |_| RefCell::new(Cache::new()), render_cache_builder: |_| Cache::new(), } @@ -76,7 +71,6 @@ impl Pipeline { self.system = Some( SystemBuilder { fonts: glyphon::FontSystem::new_with_locale_and_db(locale, db), - cache_builder: |fonts| glyphon::SwashCache::new(fonts), measurement_cache_builder: |_| RefCell::new(Cache::new()), render_cache_builder: |_| Cache::new(), } @@ -183,7 +177,7 @@ impl Pipeline { }, &text_areas, glyphon::Color::rgb(0, 0, 0), - fields.cache, + &mut glyphon::SwashCache::new(fields.fonts), ) .expect("Prepare text sections"); }); -- cgit From f37b87fbabf09296ad7fea695baded25020d5fbc Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 5 Feb 2023 18:30:11 +0100 Subject: Avoid allocating `text_areas` in `text::Pipeline` --- wgpu/Cargo.toml | 2 +- wgpu/src/text.rs | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index da8cdf9f..b3fbfc00 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -60,7 +60,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "304dcaf8443bec7194891fdef3df468a6eaeb7d9" +rev = "3c2acb9dea5b9fcb0fa650b3c73b3a3242c62f4a" [dependencies.tracing] version = "0.1.6" diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 59a840ca..e8c3e385 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -123,10 +123,8 @@ impl Pipeline { bottom: ((bounds.y + bounds.height) * scale_factor) as i32, }; - let text_areas: Vec<_> = sections - .iter() - .zip(keys.iter()) - .map(|(section, key)| { + let text_areas = + sections.iter().zip(keys.iter()).map(|(section, key)| { let buffer = fields .render_cache .get(key) @@ -163,8 +161,7 @@ impl Pipeline { top: top as i32, bounds, } - }) - .collect(); + }); renderer .prepare( @@ -175,7 +172,7 @@ impl Pipeline { width: target_size.width, height: target_size.height, }, - &text_areas, + text_areas, glyphon::Color::rgb(0, 0, 0), &mut glyphon::SwashCache::new(fields.fonts), ) -- cgit From 15d257a52a994f0110d775eb49ca7e6100b0e1a8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 5 Feb 2023 18:30:32 +0100 Subject: Stop truncating the `renderers` in `text::Pipeline` We avoid recreating buffers this way, and the amount of layers should stay relatively low anyways. --- wgpu/src/text.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index e8c3e385..16eea234 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -209,7 +209,6 @@ impl Pipeline { } pub fn end_frame(&mut self) { - self.renderers.truncate(self.layer); self.system .as_mut() .unwrap() -- cgit From dd80772da9ce89230d5a96c96923837f9887befa Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 5 Feb 2023 18:31:53 +0100 Subject: Set a minimum `height` for `Buffer` of `size * 1.2` This avoids text from misteriously disappearing, even if the user uses a `height` that isn't enough to fit the text. --- wgpu/src/text.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 16eea234..f1171b5f 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -344,7 +344,10 @@ impl<'a> Cache<'a> { let metrics = glyphon::Metrics::new(key.size, key.size * 1.2); let mut buffer = glyphon::Buffer::new(fonts, metrics); - buffer.set_size(key.bounds.width, key.bounds.height); + buffer.set_size( + key.bounds.width, + key.bounds.height.max(key.size * 1.2), + ); buffer.set_text( key.content, glyphon::Attrs::new().family(to_family(key.font)).color({ -- cgit From 7d4c63d4119c505801daf138be44e65599e748d0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 5 Feb 2023 18:33:09 +0100 Subject: Set `Attrs::monospaced` if `Font::Monospace` is selected --- wgpu/src/text.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index f1171b5f..6e7e60d8 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -350,16 +350,19 @@ impl<'a> Cache<'a> { ); buffer.set_text( key.content, - glyphon::Attrs::new().family(to_family(key.font)).color({ - let [r, g, b, a] = key.color.into_linear(); - - glyphon::Color::rgba( - (r * 255.0) as u8, - (g * 255.0) as u8, - (b * 255.0) as u8, - (a * 255.0) as u8, - ) - }), + glyphon::Attrs::new() + .family(to_family(key.font)) + .color({ + let [r, g, b, a] = key.color.into_linear(); + + glyphon::Color::rgba( + (r * 255.0) as u8, + (g * 255.0) as u8, + (b * 255.0) as u8, + (a * 255.0) as u8, + ) + }) + .monospaced(matches!(key.font, Font::Monospace)), ); let _ = entry.insert(buffer); -- cgit From c8befa8d95890eb22972f487e08974e962028eda Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 5 Feb 2023 18:35:23 +0100 Subject: Fix useless `f32` conversions and please `clippy` --- native/src/widget/text_input.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index f71b4503..f51416e1 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -385,13 +385,8 @@ where Renderer: text::Renderer, { let text_size = size.unwrap_or_else(|| renderer.default_size()); - let padding = padding.fit(Size::ZERO, limits.max()); - - let limits = limits - .width(width) - .pad(padding) - .height(text_size as f32 * 1.2); + let limits = limits.width(width).pad(padding).height(text_size * 1.2); let mut text = layout::Node::new(limits.resolve(Size::ZERO)); text.move_to(Point::new(padding.left, padding.top)); -- cgit From 680ea5dcca37ecf71ab2e5194e907d9414715d22 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 6 Feb 2023 22:21:27 +0100 Subject: Refactor `quad::Pipeline` to `prepare` and `render` architecture --- wgpu/src/backend.rs | 31 ++++++-- wgpu/src/buffer.rs | 86 ++++++++++++++++++++ wgpu/src/quad.rs | 225 +++++++++++++++++++++++++++------------------------- 3 files changed, 230 insertions(+), 112 deletions(-) diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index f19959bd..2c35936c 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -88,13 +88,14 @@ impl Backend { let mut layers = Layer::generate(primitives, viewport); layers.push(Layer::overlay(overlay_text, viewport)); - for layer in layers { + for (i, layer) in layers.iter().enumerate() { self.flush( device, queue, scale_factor, transformation, &layer, + i, staging_belt, encoder, frame, @@ -102,6 +103,7 @@ impl Backend { ); } + self.quad_pipeline.end_frame(); self.text_pipeline.end_frame(); #[cfg(any(feature = "image", feature = "svg"))] @@ -115,6 +117,7 @@ impl Backend { scale_factor: f32, transformation: Transformation, layer: &Layer<'_>, + layer_index: usize, staging_belt: &mut wgpu::util::StagingBelt, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, @@ -127,16 +130,32 @@ impl Backend { } if !layer.quads.is_empty() { - self.quad_pipeline.draw( + self.quad_pipeline.prepare( device, - staging_belt, - encoder, + queue, &layer.quads, transformation, scale_factor, - bounds, - target, ); + + let mut render_pass = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("iced_wgpu::quad render pass"), + color_attachments: &[Some( + wgpu::RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: true, + }, + }, + )], + depth_stencil_attachment: None, + }); + + self.quad_pipeline + .render(layer_index, bounds, &mut render_pass); } if !layer.meshes.is_empty() { diff --git a/wgpu/src/buffer.rs b/wgpu/src/buffer.rs index 7c092d0b..c210dd4e 100644 --- a/wgpu/src/buffer.rs +++ b/wgpu/src/buffer.rs @@ -1,3 +1,89 @@ //! Utilities for buffer operations. pub mod dynamic; pub mod r#static; + +use std::marker::PhantomData; +use std::ops::RangeBounds; + +#[derive(Debug)] +pub struct Buffer { + label: &'static str, + size: u64, + usage: wgpu::BufferUsages, + raw: wgpu::Buffer, + type_: PhantomData, +} + +impl Buffer { + pub fn new( + device: &wgpu::Device, + label: &'static str, + amount: usize, + usage: wgpu::BufferUsages, + ) -> Self { + let size = next_copy_size::(amount); + + let raw = device.create_buffer(&wgpu::BufferDescriptor { + label: Some(label), + size, + usage, + mapped_at_creation: false, + }); + + Self { + label, + size, + usage, + raw, + type_: PhantomData, + } + } + + pub fn resize(&mut self, device: &wgpu::Device, new_count: usize) -> bool { + let new_size = (std::mem::size_of::() * new_count) as u64; + + if self.size < new_size { + self.raw = device.create_buffer(&wgpu::BufferDescriptor { + label: Some(self.label), + size: new_size, + usage: self.usage, + mapped_at_creation: false, + }); + + self.size = new_size; + + true + } else { + false + } + } + + pub fn write( + &self, + queue: &wgpu::Queue, + offset_count: usize, + contents: &[T], + ) { + queue.write_buffer( + &self.raw, + (std::mem::size_of::() * offset_count) as u64, + bytemuck::cast_slice(contents), + ); + } + + pub fn slice( + &self, + bounds: impl RangeBounds, + ) -> wgpu::BufferSlice<'_> { + self.raw.slice(bounds) + } +} + +fn next_copy_size(amount: usize) -> u64 { + let align_mask = wgpu::COPY_BUFFER_ALIGNMENT - 1; + + (((std::mem::size_of::() * amount).next_power_of_two() as u64 + + align_mask) + & !align_mask) + .max(wgpu::COPY_BUFFER_ALIGNMENT) +} diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 2f5fcc6b..ebbe7a9d 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -1,3 +1,4 @@ +use crate::buffer::Buffer; use crate::Transformation; use iced_graphics::layer; use iced_native::Rectangle; @@ -12,11 +13,11 @@ use tracing::info_span; #[derive(Debug)] pub struct Pipeline { pipeline: wgpu::RenderPipeline, - constants: wgpu::BindGroup, - constants_buffer: wgpu::Buffer, + constant_layout: wgpu::BindGroupLayout, vertices: wgpu::Buffer, indices: wgpu::Buffer, - instances: wgpu::Buffer, + layers: Vec, + current_layer: usize, } impl Pipeline { @@ -38,22 +39,6 @@ impl Pipeline { }], }); - let constants_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("iced_wgpu::quad uniforms buffer"), - size: mem::size_of::() as wgpu::BufferAddress, - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - - let constants = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu::quad uniforms bind group"), - layout: &constant_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: constants_buffer.as_entire_binding(), - }], - }); - let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("iced_wgpu::quad pipeline layout"), @@ -148,118 +133,146 @@ impl Pipeline { usage: wgpu::BufferUsages::INDEX, }); - let instances = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("iced_wgpu::quad instance buffer"), - size: mem::size_of::() as u64 * MAX_INSTANCES as u64, - usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - Pipeline { pipeline, - constants, - constants_buffer, + constant_layout, vertices, indices, - instances, + layers: Vec::new(), + current_layer: 0, } } - pub fn draw( + pub fn prepare( &mut self, device: &wgpu::Device, - staging_belt: &mut wgpu::util::StagingBelt, - encoder: &mut wgpu::CommandEncoder, + queue: &wgpu::Queue, instances: &[layer::Quad], transformation: Transformation, scale: f32, - bounds: Rectangle, - target: &wgpu::TextureView, ) { - #[cfg(feature = "tracing")] - let _ = info_span!("Wgpu::Quad", "DRAW").entered(); + if self.layers.len() <= self.current_layer { + self.layers.push(Layer::new(device, &self.constant_layout)); + } - let uniforms = Uniforms::new(transformation, scale); + let layer = &mut self.layers[self.current_layer]; + layer.prepare(device, queue, instances, transformation, scale); - { - let mut constants_buffer = staging_belt.write_buffer( - encoder, - &self.constants_buffer, - 0, - wgpu::BufferSize::new(mem::size_of::() as u64) - .unwrap(), - device, + self.current_layer += 1; + } + + pub fn render<'a>( + &'a self, + layer: usize, + bounds: Rectangle, + render_pass: &mut wgpu::RenderPass<'a>, + ) { + if let Some(layer) = self.layers.get(layer) { + render_pass.set_pipeline(&self.pipeline); + + render_pass.set_scissor_rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ); + + render_pass.set_index_buffer( + self.indices.slice(..), + wgpu::IndexFormat::Uint16, ); + render_pass.set_vertex_buffer(0, self.vertices.slice(..)); - constants_buffer.copy_from_slice(bytemuck::bytes_of(&uniforms)); + layer.draw(render_pass); } + } - let mut i = 0; - let total = instances.len(); + pub fn end_frame(&mut self) { + self.current_layer = 0; + } +} - while i < total { - let end = (i + MAX_INSTANCES).min(total); - let amount = end - i; +#[derive(Debug)] +struct Layer { + constants: wgpu::BindGroup, + constants_buffer: wgpu::Buffer, + instances: Buffer, + instance_count: usize, +} - let instance_bytes = bytemuck::cast_slice(&instances[i..end]); +impl Layer { + pub fn new( + device: &wgpu::Device, + constant_layout: &wgpu::BindGroupLayout, + ) -> Self { + let constants_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("iced_wgpu::quad uniforms buffer"), + size: mem::size_of::() as wgpu::BufferAddress, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); - let mut instance_buffer = staging_belt.write_buffer( - encoder, - &self.instances, - 0, - wgpu::BufferSize::new(instance_bytes.len() as u64).unwrap(), - device, - ); + let constants = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("iced_wgpu::quad uniforms bind group"), + layout: &constant_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: constants_buffer.as_entire_binding(), + }], + }); - instance_buffer.copy_from_slice(instance_bytes); - - #[cfg(feature = "tracing")] - let _ = info_span!("Wgpu::Quad", "BEGIN_RENDER_PASS").enter(); - - { - let mut render_pass = - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("iced_wgpu::quad render pass"), - color_attachments: &[Some( - wgpu::RenderPassColorAttachment { - view: target, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: true, - }, - }, - )], - depth_stencil_attachment: None, - }); - - render_pass.set_pipeline(&self.pipeline); - render_pass.set_bind_group(0, &self.constants, &[]); - render_pass.set_index_buffer( - self.indices.slice(..), - wgpu::IndexFormat::Uint16, - ); - render_pass.set_vertex_buffer(0, self.vertices.slice(..)); - render_pass.set_vertex_buffer(1, self.instances.slice(..)); - - render_pass.set_scissor_rect( - bounds.x, - bounds.y, - bounds.width, - // TODO: Address anti-aliasing adjustments properly - bounds.height, - ); - - render_pass.draw_indexed( - 0..QUAD_INDICES.len() as u32, - 0, - 0..amount as u32, - ); - } - - i += MAX_INSTANCES; + let instances = Buffer::new( + device, + "iced_wgpu::quad instance buffer", + MAX_INSTANCES, + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + ); + + Self { + constants, + constants_buffer, + instances, + instance_count: 0, } } + + pub fn prepare( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + instances: &[layer::Quad], + transformation: Transformation, + scale: f32, + ) { + #[cfg(feature = "tracing")] + let _ = info_span!("Wgpu::Quad", "PREPARE").entered(); + + let uniforms = Uniforms::new(transformation, scale); + + queue.write_buffer( + &self.constants_buffer, + 0, + bytemuck::bytes_of(&uniforms), + ); + + let _ = self.instances.resize(device, instances.len()); + self.instances.write(queue, 0, instances); + self.instance_count = instances.len(); + } + + pub fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { + #[cfg(feature = "tracing")] + let _ = info_span!("Wgpu::Quad", "DRAW").entered(); + + render_pass.set_bind_group(0, &self.constants, &[]); + render_pass.set_vertex_buffer(1, self.instances.slice(..)); + + render_pass.draw_indexed( + 0..QUAD_INDICES.len() as u32, + 0, + 0..self.instance_count as u32, + ); + } } #[repr(C)] -- cgit From a970f34cb4968bfe913c1f77b5087f44f29aba8a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 7 Feb 2023 18:35:05 +0100 Subject: Apply `ceil` to text bounds when drawing --- wgpu/src/text.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 6e7e60d8..95994a4e 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -105,8 +105,10 @@ impl Pipeline { size: section.size * scale_factor, font: section.font, bounds: Size { - width: section.bounds.width * scale_factor, - height: section.bounds.height * scale_factor, + width: (section.bounds.width * scale_factor) + .ceil(), + height: (section.bounds.height * scale_factor) + .ceil(), }, color: section.color, }, -- cgit From 77b59496b0a138510938aca8367572e08019ddb6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 7 Feb 2023 19:26:41 +0100 Subject: Fix rendering order for `quad::Pipeline` --- wgpu/src/backend.rs | 7 ++----- wgpu/src/quad.rs | 22 +++++++++++++--------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 2c35936c..395d28d5 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -88,14 +88,13 @@ impl Backend { let mut layers = Layer::generate(primitives, viewport); layers.push(Layer::overlay(overlay_text, viewport)); - for (i, layer) in layers.iter().enumerate() { + for layer in layers { self.flush( device, queue, scale_factor, transformation, &layer, - i, staging_belt, encoder, frame, @@ -117,7 +116,6 @@ impl Backend { scale_factor: f32, transformation: Transformation, layer: &Layer<'_>, - layer_index: usize, staging_belt: &mut wgpu::util::StagingBelt, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, @@ -154,8 +152,7 @@ impl Backend { depth_stencil_attachment: None, }); - self.quad_pipeline - .render(layer_index, bounds, &mut render_pass); + self.quad_pipeline.render(bounds, &mut render_pass); } if !layer.meshes.is_empty() { diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index ebbe7a9d..e4173b12 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -17,7 +17,8 @@ pub struct Pipeline { vertices: wgpu::Buffer, indices: wgpu::Buffer, layers: Vec, - current_layer: usize, + prepare_layer: usize, + render_layer: usize, } impl Pipeline { @@ -139,7 +140,8 @@ impl Pipeline { vertices, indices, layers: Vec::new(), - current_layer: 0, + prepare_layer: 0, + render_layer: 0, } } @@ -151,23 +153,22 @@ impl Pipeline { transformation: Transformation, scale: f32, ) { - if self.layers.len() <= self.current_layer { + if self.layers.len() <= self.prepare_layer { self.layers.push(Layer::new(device, &self.constant_layout)); } - let layer = &mut self.layers[self.current_layer]; + let layer = &mut self.layers[self.prepare_layer]; layer.prepare(device, queue, instances, transformation, scale); - self.current_layer += 1; + self.prepare_layer += 1; } pub fn render<'a>( - &'a self, - layer: usize, + &'a mut self, bounds: Rectangle, render_pass: &mut wgpu::RenderPass<'a>, ) { - if let Some(layer) = self.layers.get(layer) { + if let Some(layer) = self.layers.get(self.render_layer) { render_pass.set_pipeline(&self.pipeline); render_pass.set_scissor_rect( @@ -184,11 +185,14 @@ impl Pipeline { render_pass.set_vertex_buffer(0, self.vertices.slice(..)); layer.draw(render_pass); + + self.render_layer += 1; } } pub fn end_frame(&mut self) { - self.current_layer = 0; + self.prepare_layer = 0; + self.render_layer = 0; } } -- cgit From 34c963f7b39e3f16b55665a978948ead5b869f0f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 7 Feb 2023 20:08:26 +0100 Subject: Remove unnecessary borrow in `quad::Pipeline` --- wgpu/src/quad.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index e4173b12..94bcec92 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -218,7 +218,7 @@ impl Layer { let constants = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("iced_wgpu::quad uniforms bind group"), - layout: &constant_layout, + layout: constant_layout, entries: &[wgpu::BindGroupEntry { binding: 0, resource: constants_buffer.as_entire_binding(), -- cgit From 363966ee9e7aa81a3679eaea776d2c867aa6c3b0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 7 Feb 2023 22:53:08 +0100 Subject: Refactor `image::Pipeline` into `prepare` and `render` architecture --- wgpu/src/backend.rs | 26 ++++- wgpu/src/image.rs | 259 ++++++++++++++++++++++++++---------------------- wgpu/src/image/atlas.rs | 49 +++++---- wgpu/src/lib.rs | 3 +- 4 files changed, 190 insertions(+), 147 deletions(-) diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 395d28d5..90a511ef 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -106,7 +106,7 @@ impl Backend { self.text_pipeline.end_frame(); #[cfg(any(feature = "image", feature = "svg"))] - self.image_pipeline.trim_cache(device, encoder); + self.image_pipeline.end_frame(device, queue, encoder); } fn flush( @@ -177,16 +177,32 @@ impl Backend { let scaled = transformation * Transformation::scale(scale_factor, scale_factor); - self.image_pipeline.draw( + self.image_pipeline.prepare( device, - staging_belt, + queue, encoder, &layer.images, scaled, - bounds, - target, scale_factor, ); + + let mut render_pass = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("iced_wgpu::image render pass"), + color_attachments: &[Some( + wgpu::RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: true, + }, + }, + )], + depth_stencil_attachment: None, + }); + + self.image_pipeline.render(bounds, &mut render_pass); } } diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index a5e63b17..f6c39f1a 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -6,7 +6,7 @@ use iced_graphics::image::raster; #[cfg(feature = "svg")] use iced_graphics::image::vector; -use crate::Transformation; +use crate::{Buffer, Transformation}; use atlas::Atlas; use iced_graphics::layer; @@ -34,15 +34,108 @@ pub struct Pipeline { vector_cache: RefCell>, pipeline: wgpu::RenderPipeline, - uniforms: wgpu::Buffer, vertices: wgpu::Buffer, indices: wgpu::Buffer, - instances: wgpu::Buffer, - constants: wgpu::BindGroup, + sampler: wgpu::Sampler, texture: wgpu::BindGroup, texture_version: usize, - texture_layout: wgpu::BindGroupLayout, texture_atlas: Atlas, + texture_layout: wgpu::BindGroupLayout, + constant_layout: wgpu::BindGroupLayout, + + layers: Vec, + prepare_layer: usize, + render_layer: usize, +} + +#[derive(Debug)] +struct Layer { + uniforms: wgpu::Buffer, + constants: wgpu::BindGroup, + instances: Buffer, + instance_count: usize, +} + +impl Layer { + fn new( + device: &wgpu::Device, + constant_layout: &wgpu::BindGroupLayout, + sampler: &wgpu::Sampler, + ) -> Self { + let uniforms = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("iced_wgpu::image uniforms buffer"), + size: mem::size_of::() as u64, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + let constants = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("iced_wgpu::image constants bind group"), + layout: &constant_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer( + wgpu::BufferBinding { + buffer: &uniforms, + offset: 0, + size: None, + }, + ), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + ], + }); + + let instances = Buffer::new( + device, + "iced_wgpu::image instance buffer", + Instance::INITIAL, + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + ); + + Self { + uniforms, + constants, + instances, + instance_count: 0, + } + } + + fn prepare( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + instances: &[Instance], + transformation: Transformation, + ) { + queue.write_buffer( + &self.uniforms, + 0, + bytemuck::bytes_of(&Uniforms { + transform: transformation.into(), + }), + ); + + let _ = self.instances.resize(device, instances.len()); + self.instances.write(queue, 0, instances); + + self.instance_count = instances.len(); + } + + fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { + render_pass.set_bind_group(0, &self.constants, &[]); + render_pass.set_vertex_buffer(1, self.instances.slice(..)); + + render_pass.draw_indexed( + 0..QUAD_INDICES.len() as u32, + 0, + 0..self.instance_count as u32, + ); + } } impl Pipeline { @@ -86,35 +179,6 @@ impl Pipeline { ], }); - let uniforms_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("iced_wgpu::image uniforms buffer"), - size: mem::size_of::() as u64, - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - - let constant_bind_group = - device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu::image constants bind group"), - layout: &constant_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer( - wgpu::BufferBinding { - buffer: &uniforms_buffer, - offset: 0, - size: None, - }, - ), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&sampler), - }, - ], - }); - let texture_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("iced_wgpu::image texture atlas layout"), @@ -225,13 +289,6 @@ impl Pipeline { usage: wgpu::BufferUsages::INDEX, }); - let instances = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("iced_wgpu::image instance buffer"), - size: mem::size_of::() as u64 * Instance::MAX as u64, - usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - let texture_atlas = Atlas::new(device); let texture = device.create_bind_group(&wgpu::BindGroupDescriptor { @@ -253,15 +310,18 @@ impl Pipeline { vector_cache: RefCell::new(vector::Cache::default()), pipeline, - uniforms: uniforms_buffer, vertices, indices, - instances, - constants: constant_bind_group, + sampler, texture, texture_version: texture_atlas.layer_count(), - texture_layout, texture_atlas, + texture_layout, + constant_layout, + + layers: Vec::new(), + prepare_layer: 0, + render_layer: 0, } } @@ -281,17 +341,18 @@ impl Pipeline { svg.viewport_dimensions() } - pub fn draw( + pub fn prepare( &mut self, device: &wgpu::Device, - staging_belt: &mut wgpu::util::StagingBelt, + queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, images: &[layer::Image], transformation: Transformation, - bounds: Rectangle, - target: &wgpu::TextureView, _scale: f32, ) { + #[cfg(feature = "tracing")] + let _ = info_span!("Wgpu::Image", "PREPARE").entered(); + #[cfg(feature = "tracing")] let _ = info_span!("Wgpu::Image", "DRAW").entered(); @@ -309,7 +370,7 @@ impl Pipeline { layer::Image::Raster { handle, bounds } => { if let Some(atlas_entry) = raster_cache.upload( handle, - &mut (device, encoder), + &mut (device, queue, encoder), &mut self.texture_atlas, ) { add_instances( @@ -336,7 +397,7 @@ impl Pipeline { *color, size, _scale, - &mut (device, encoder), + &mut (device, queue, encoder), &mut self.texture_atlas, ) { add_instances( @@ -376,68 +437,27 @@ impl Pipeline { self.texture_version = texture_version; } - { - let mut uniforms_buffer = staging_belt.write_buffer( - encoder, - &self.uniforms, - 0, - wgpu::BufferSize::new(mem::size_of::() as u64) - .unwrap(), + if self.layers.len() <= self.prepare_layer { + self.layers.push(Layer::new( device, - ); - - uniforms_buffer.copy_from_slice(bytemuck::bytes_of(&Uniforms { - transform: transformation.into(), - })); + &self.constant_layout, + &self.sampler, + )); } - let mut i = 0; - let total = instances.len(); - - while i < total { - let end = (i + Instance::MAX).min(total); - let amount = end - i; - - let mut instances_buffer = staging_belt.write_buffer( - encoder, - &self.instances, - 0, - wgpu::BufferSize::new( - (amount * std::mem::size_of::()) as u64, - ) - .unwrap(), - device, - ); - - instances_buffer.copy_from_slice(bytemuck::cast_slice( - &instances[i..i + amount], - )); + let layer = &mut self.layers[self.prepare_layer]; + layer.prepare(device, queue, instances, transformation); - let mut render_pass = - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("iced_wgpu::image render pass"), - color_attachments: &[Some( - wgpu::RenderPassColorAttachment { - view: target, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: true, - }, - }, - )], - depth_stencil_attachment: None, - }); + self.prepare_layer += 1; + } + pub fn render<'a>( + &'a mut self, + bounds: Rectangle, + render_pass: &mut wgpu::RenderPass<'a>, + ) { + if let Some(layer) = self.layers.get(self.render_layer) { render_pass.set_pipeline(&self.pipeline); - render_pass.set_bind_group(0, &self.constants, &[]); - render_pass.set_bind_group(1, &self.texture, &[]); - render_pass.set_index_buffer( - self.indices.slice(..), - wgpu::IndexFormat::Uint16, - ); - render_pass.set_vertex_buffer(0, self.vertices.slice(..)); - render_pass.set_vertex_buffer(1, self.instances.slice(..)); render_pass.set_scissor_rect( bounds.x, @@ -446,30 +466,37 @@ impl Pipeline { bounds.height, ); - render_pass.draw_indexed( - 0..QUAD_INDICES.len() as u32, - 0, - 0..amount as u32, + render_pass.set_bind_group(1, &self.texture, &[]); + render_pass.set_index_buffer( + self.indices.slice(..), + wgpu::IndexFormat::Uint16, ); + render_pass.set_vertex_buffer(0, self.vertices.slice(..)); - i += Instance::MAX; + layer.render(render_pass); + + self.render_layer += 1; } } - pub fn trim_cache( + pub fn end_frame( &mut self, device: &wgpu::Device, + queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, ) { #[cfg(feature = "image")] self.raster_cache .borrow_mut() - .trim(&mut self.texture_atlas, &mut (device, encoder)); + .trim(&mut self.texture_atlas, &mut (device, queue, encoder)); #[cfg(feature = "svg")] self.vector_cache .borrow_mut() - .trim(&mut self.texture_atlas, &mut (device, encoder)); + .trim(&mut self.texture_atlas, &mut (device, queue, encoder)); + + self.prepare_layer = 0; + self.render_layer = 0; } } @@ -507,7 +534,7 @@ struct Instance { } impl Instance { - pub const MAX: usize = 1_000; + pub const INITIAL: usize = 1_000; } #[repr(C)] diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index eafe2f96..7df67abd 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -185,13 +185,13 @@ impl Atlas { fn upload_allocation( &mut self, - buffer: &wgpu::Buffer, + data: &[u8], image_width: u32, image_height: u32, padding: u32, offset: usize, allocation: &Allocation, - encoder: &mut wgpu::CommandEncoder, + queue: &wgpu::Queue, ) { let (x, y) = allocation.position(); let Size { width, height } = allocation.size(); @@ -203,15 +203,7 @@ impl Atlas { depth_or_array_layers: 1, }; - encoder.copy_buffer_to_texture( - wgpu::ImageCopyBuffer { - buffer, - layout: wgpu::ImageDataLayout { - offset: offset as u64, - bytes_per_row: NonZeroU32::new(4 * image_width + padding), - rows_per_image: NonZeroU32::new(image_height), - }, - }, + queue.write_texture( wgpu::ImageCopyTexture { texture: &self.texture, mip_level: 0, @@ -222,6 +214,12 @@ impl Atlas { }, aspect: wgpu::TextureAspect::default(), }, + data, + wgpu::ImageDataLayout { + offset: offset as u64, + bytes_per_row: NonZeroU32::new(4 * image_width + padding), + rows_per_image: NonZeroU32::new(image_height), + }, extent, ); } @@ -301,17 +299,19 @@ impl Atlas { impl image::Storage for Atlas { type Entry = Entry; - type State<'a> = (&'a wgpu::Device, &'a mut wgpu::CommandEncoder); + type State<'a> = ( + &'a wgpu::Device, + &'a wgpu::Queue, + &'a mut wgpu::CommandEncoder, + ); fn upload( &mut self, width: u32, height: u32, data: &[u8], - (device, encoder): &mut Self::State<'_>, + (device, queue, encoder): &mut Self::State<'_>, ) -> Option { - use wgpu::util::DeviceExt; - let entry = { let current_size = self.layers.len(); let entry = self.allocate(width, height)?; @@ -344,17 +344,16 @@ impl image::Storage for Atlas { ) } - let buffer = - device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("iced_wgpu::image staging buffer"), - contents: &padded_data, - usage: wgpu::BufferUsages::COPY_SRC, - }); - match &entry { Entry::Contiguous(allocation) => { self.upload_allocation( - &buffer, width, height, padding, 0, allocation, encoder, + &padded_data, + width, + height, + padding, + 0, + allocation, + queue, ); } Entry::Fragmented { fragments, .. } => { @@ -363,13 +362,13 @@ impl image::Storage for Atlas { let offset = (y * padded_width as u32 + 4 * x) as usize; self.upload_allocation( - &buffer, + &padded_data, width, height, padding, offset, &fragment.allocation, - encoder, + queue, ); } } diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 6d6d3fd6..9da40572 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -56,7 +56,8 @@ pub use wgpu; pub use backend::Backend; pub use settings::Settings; -pub(crate) use iced_graphics::Transformation; +use crate::buffer::Buffer; +use iced_graphics::Transformation; #[cfg(any(feature = "image", feature = "svg"))] mod image; -- cgit From 23ed352e83dcb8a13acdac1cd4c7e2a9df492ebd Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 7 Feb 2023 22:53:41 +0100 Subject: Fix needless borrows in `image::Pipeline` --- wgpu/src/image.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index f6c39f1a..5d1ae8d7 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -71,7 +71,7 @@ impl Layer { let constants = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("iced_wgpu::image constants bind group"), - layout: &constant_layout, + layout: constant_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, @@ -85,7 +85,7 @@ impl Layer { }, wgpu::BindGroupEntry { binding: 1, - resource: wgpu::BindingResource::Sampler(&sampler), + resource: wgpu::BindingResource::Sampler(sampler), }, ], }); -- cgit From b8c1809ea101cece6943432fd3597f785c39af09 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 7 Feb 2023 23:55:16 +0100 Subject: Refactor `triangle::Pipeline` into `prepare` and `render` architecture And get rid of the staging belt! :tada: --- examples/integration/src/main.rs | 9 - wgpu/src/backend.rs | 17 +- wgpu/src/buffer/dynamic.rs | 23 +- wgpu/src/buffer/static.rs | 21 +- wgpu/src/quad.rs | 7 +- wgpu/src/triangle.rs | 561 +++++++++++++++++++++++---------------- wgpu/src/window/compositor.rs | 11 - 7 files changed, 358 insertions(+), 291 deletions(-) diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index 69f6961e..5af30ab1 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -125,9 +125,6 @@ pub fn main() { let mut resized = false; - // Initialize staging belt - let mut staging_belt = wgpu::util::StagingBelt::new(5 * 1024); - // Initialize scene and GUI controls let scene = Scene::new(&device, format); let controls = Controls::new(); @@ -252,7 +249,6 @@ pub fn main() { backend.present( &device, &queue, - &mut staging_belt, &mut encoder, &view, primitive, @@ -262,7 +258,6 @@ pub fn main() { }); // Then we submit the work - staging_belt.finish(); queue.submit(Some(encoder.finish())); frame.present(); @@ -272,10 +267,6 @@ pub fn main() { state.mouse_interaction(), ), ); - - // And recall staging buffers - staging_belt.recall(); - } Err(error) => match error { wgpu::SurfaceError::OutOfMemory => { diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 90a511ef..8c4c0daa 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -70,7 +70,6 @@ impl Backend { &mut self, device: &wgpu::Device, queue: &wgpu::Queue, - staging_belt: &mut wgpu::util::StagingBelt, encoder: &mut wgpu::CommandEncoder, frame: &wgpu::TextureView, primitives: &[Primitive], @@ -95,7 +94,6 @@ impl Backend { scale_factor, transformation, &layer, - staging_belt, encoder, frame, target_size, @@ -104,6 +102,7 @@ impl Backend { self.quad_pipeline.end_frame(); self.text_pipeline.end_frame(); + self.triangle_pipeline.end_frame(); #[cfg(any(feature = "image", feature = "svg"))] self.image_pipeline.end_frame(device, queue, encoder); @@ -116,7 +115,6 @@ impl Backend { scale_factor: f32, transformation: Transformation, layer: &Layer<'_>, - staging_belt: &mut wgpu::util::StagingBelt, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, target_size: Size, @@ -159,15 +157,20 @@ impl Backend { let scaled = transformation * Transformation::scale(scale_factor, scale_factor); - self.triangle_pipeline.draw( + self.triangle_pipeline.prepare( + device, + queue, + &layer.meshes, + scaled, + ); + + self.triangle_pipeline.render( device, - staging_belt, encoder, target, target_size, - scaled, - scale_factor, &layer.meshes, + scale_factor, ); } diff --git a/wgpu/src/buffer/dynamic.rs b/wgpu/src/buffer/dynamic.rs index 88289b98..43fc47ac 100644 --- a/wgpu/src/buffer/dynamic.rs +++ b/wgpu/src/buffer/dynamic.rs @@ -112,25 +112,8 @@ impl Buffer { } /// Write the contents of this dynamic buffer to the GPU via staging belt command. - pub fn write( - &mut self, - device: &wgpu::Device, - staging_belt: &mut wgpu::util::StagingBelt, - encoder: &mut wgpu::CommandEncoder, - ) { - let size = self.cpu.get_ref().len(); - - if let Some(buffer_size) = wgpu::BufferSize::new(size as u64) { - let mut buffer = staging_belt.write_buffer( - encoder, - &self.gpu, - 0, - buffer_size, - device, - ); - - buffer.copy_from_slice(self.cpu.get_ref()); - } + pub fn write(&mut self, queue: &wgpu::Queue) { + queue.write_buffer(&self.gpu, 0, self.cpu.get_ref()); } // Gets the aligned offset at the given index from the CPU buffer. @@ -184,7 +167,7 @@ impl Internal { } /// Returns bytearray of aligned CPU buffer. - pub(super) fn get_ref(&self) -> &Vec { + pub(super) fn get_ref(&self) -> &[u8] { match self { Internal::Uniform(buf) => buf.as_ref(), #[cfg(not(target_arch = "wasm32"))] diff --git a/wgpu/src/buffer/static.rs b/wgpu/src/buffer/static.rs index ef87422f..7ae7edd2 100644 --- a/wgpu/src/buffer/static.rs +++ b/wgpu/src/buffer/static.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use std::mem; //128 triangles/indices -const DEFAULT_STATIC_BUFFER_COUNT: wgpu::BufferAddress = 128; +const DEFAULT_STATIC_BUFFER_COUNT: wgpu::BufferAddress = 1_000; /// A generic buffer struct useful for items which have no alignment requirements /// (e.g. Vertex, Index buffers) & no dynamic offsets. @@ -71,28 +71,15 @@ impl Buffer { /// Returns the size of the written bytes. pub fn write( &mut self, - device: &wgpu::Device, - staging_belt: &mut wgpu::util::StagingBelt, - encoder: &mut wgpu::CommandEncoder, + queue: &wgpu::Queue, offset: u64, content: &[T], ) -> u64 { let bytes = bytemuck::cast_slice(content); let bytes_size = bytes.len() as u64; - if let Some(buffer_size) = wgpu::BufferSize::new(bytes_size) { - let mut buffer = staging_belt.write_buffer( - encoder, - &self.gpu, - offset, - buffer_size, - device, - ); - - buffer.copy_from_slice(bytes); - - self.offsets.push(offset); - } + queue.write_buffer(&self.gpu, offset, bytes); + self.offsets.push(offset); bytes_size } diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 94bcec92..c1aa49c4 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -1,5 +1,4 @@ -use crate::buffer::Buffer; -use crate::Transformation; +use crate::{Buffer, Transformation}; use iced_graphics::layer; use iced_native::Rectangle; @@ -228,7 +227,7 @@ impl Layer { let instances = Buffer::new( device, "iced_wgpu::quad instance buffer", - MAX_INSTANCES, + INITIAL_INSTANCES, wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, ); @@ -302,7 +301,7 @@ const QUAD_VERTS: [Vertex; 4] = [ }, ]; -const MAX_INSTANCES: usize = 100_000; +const INITIAL_INSTANCES: usize = 10_000; #[repr(C)] #[derive(Debug, Clone, Copy, Zeroable, Pod)] diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index efdd214b..572af1e8 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -14,50 +14,56 @@ use tracing::info_span; #[derive(Debug)] pub struct Pipeline { blit: Option, - index_buffer: Buffer, - index_strides: Vec, solid: solid::Pipeline, /// Gradients are currently not supported on WASM targets due to their need of storage buffers. #[cfg(not(target_arch = "wasm32"))] gradient: gradient::Pipeline, + + layers: Vec, + prepare_layer: usize, + render_layer: usize, } -impl Pipeline { - pub fn new( +#[derive(Debug)] +struct Layer { + index_buffer: Buffer, + index_strides: Vec, + solid: solid::Layer, + + #[cfg(not(target_arch = "wasm32"))] + gradient: gradient::Layer, +} + +impl Layer { + fn new( device: &wgpu::Device, - format: wgpu::TextureFormat, - antialiasing: Option, - ) -> Pipeline { - Pipeline { - blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)), + solid: &solid::Pipeline, + #[cfg(not(target_arch = "wasm32"))] gradient: &gradient::Pipeline, + ) -> Self { + Self { index_buffer: Buffer::new( device, "iced_wgpu::triangle vertex buffer", wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, ), index_strides: Vec::new(), - solid: solid::Pipeline::new(device, format, antialiasing), + solid: solid::Layer::new(device, &solid.constants_layout), #[cfg(not(target_arch = "wasm32"))] - gradient: gradient::Pipeline::new(device, format, antialiasing), + gradient: gradient::Layer::new(device, &gradient.constants_layout), } } - pub fn draw( + fn prepare( &mut self, device: &wgpu::Device, - staging_belt: &mut wgpu::util::StagingBelt, - encoder: &mut wgpu::CommandEncoder, - target: &wgpu::TextureView, - target_size: Size, - transformation: Transformation, - scale_factor: f32, + queue: &wgpu::Queue, + solid: &solid::Pipeline, + #[cfg(not(target_arch = "wasm32"))] gradient: &gradient::Pipeline, meshes: &[Mesh<'_>], + transformation: Transformation, ) { - #[cfg(feature = "tracing")] - let _ = info_span!("Wgpu::Triangle", "DRAW").entered(); - // Count the total amount of vertices & indices we need to handle let count = mesh::attribute_count_of(meshes); @@ -75,6 +81,7 @@ impl Pipeline { .resize(device, count.gradient_vertices); // Prepare dynamic buffers & data store for writing + self.index_buffer.clear(); self.index_strides.clear(); self.solid.vertices.clear(); self.solid.uniforms.clear(); @@ -99,13 +106,8 @@ impl Pipeline { let transform = transformation * Transformation::translate(origin.x, origin.y); - let new_index_offset = self.index_buffer.write( - device, - staging_belt, - encoder, - index_offset, - indices, - ); + let new_index_offset = + self.index_buffer.write(queue, index_offset, indices); index_offset += new_index_offset; self.index_strides.push(indices.len() as u32); @@ -116,9 +118,7 @@ impl Pipeline { self.solid.uniforms.push(&solid::Uniforms::new(transform)); let written_bytes = self.solid.vertices.write( - device, - staging_belt, - encoder, + queue, solid_vertex_offset, &buffers.vertices, ); @@ -130,9 +130,7 @@ impl Pipeline { buffers, gradient, .. } => { let written_bytes = self.gradient.vertices.write( - device, - staging_belt, - encoder, + queue, gradient_vertex_offset, &buffers.vertices, ); @@ -196,14 +194,14 @@ impl Pipeline { let uniforms_resized = self.solid.uniforms.resize(device); if uniforms_resized { - self.solid.bind_group = solid::Pipeline::bind_group( + self.solid.constants = solid::Layer::bind_group( device, self.solid.uniforms.raw(), - &self.solid.bind_group_layout, + &solid.constants_layout, ) } - self.solid.uniforms.write(device, staging_belt, encoder); + self.solid.uniforms.write(queue); } #[cfg(not(target_arch = "wasm32"))] @@ -218,22 +216,169 @@ impl Pipeline { let storage_resized = self.gradient.storage.resize(device); if uniforms_resized || storage_resized { - self.gradient.bind_group = gradient::Pipeline::bind_group( + self.gradient.constants = gradient::Layer::bind_group( device, self.gradient.uniforms.raw(), self.gradient.storage.raw(), - &self.gradient.bind_group_layout, + &gradient.constants_layout, ); } // Write to GPU - self.gradient.uniforms.write(device, staging_belt, encoder); - self.gradient.storage.write(device, staging_belt, encoder); + self.gradient.uniforms.write(queue); + self.gradient.storage.write(queue); // Cleanup self.gradient.color_stop_offset = 0; self.gradient.color_stops_pending_write.color_stops.clear(); } + } + + fn render<'a>( + &'a mut self, + solid: &'a solid::Pipeline, + #[cfg(not(target_arch = "wasm32"))] gradient: &'a gradient::Pipeline, + meshes: &[Mesh<'_>], + scale_factor: f32, + render_pass: &mut wgpu::RenderPass<'a>, + ) { + let mut num_solids = 0; + #[cfg(not(target_arch = "wasm32"))] + let mut num_gradients = 0; + let mut last_is_solid = None; + + for (index, mesh) in meshes.iter().enumerate() { + let clip_bounds = (mesh.clip_bounds() * scale_factor).snap(); + + render_pass.set_scissor_rect( + clip_bounds.x, + clip_bounds.y, + clip_bounds.width, + clip_bounds.height, + ); + + match mesh { + Mesh::Solid { .. } => { + if !last_is_solid.unwrap_or(false) { + render_pass.set_pipeline(&solid.pipeline); + + last_is_solid = Some(true); + } + + render_pass.set_bind_group( + 0, + &self.solid.constants, + &[self.solid.uniforms.offset_at_index(num_solids)], + ); + + render_pass.set_vertex_buffer( + 0, + self.solid.vertices.slice_from_index(num_solids), + ); + + num_solids += 1; + } + #[cfg(not(target_arch = "wasm32"))] + Mesh::Gradient { .. } => { + if last_is_solid.unwrap_or(true) { + render_pass.set_pipeline(&gradient.pipeline); + + last_is_solid = Some(false); + } + + render_pass.set_bind_group( + 0, + &self.gradient.constants, + &[self + .gradient + .uniforms + .offset_at_index(num_gradients)], + ); + + render_pass.set_vertex_buffer( + 0, + self.gradient.vertices.slice_from_index(num_gradients), + ); + + num_gradients += 1; + } + #[cfg(target_arch = "wasm32")] + Mesh::Gradient { .. } => {} + }; + + render_pass.set_index_buffer( + self.index_buffer.slice_from_index(index), + wgpu::IndexFormat::Uint32, + ); + + render_pass.draw_indexed(0..self.index_strides[index], 0, 0..1); + } + } +} + +impl Pipeline { + pub fn new( + device: &wgpu::Device, + format: wgpu::TextureFormat, + antialiasing: Option, + ) -> Pipeline { + Pipeline { + blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)), + solid: solid::Pipeline::new(device, format, antialiasing), + + #[cfg(not(target_arch = "wasm32"))] + gradient: gradient::Pipeline::new(device, format, antialiasing), + + layers: Vec::new(), + prepare_layer: 0, + render_layer: 0, + } + } + + pub fn prepare( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + meshes: &[Mesh<'_>], + transformation: Transformation, + ) { + #[cfg(feature = "tracing")] + let _ = info_span!("Wgpu::Triangle", "PREPARE").entered(); + + if self.layers.len() <= self.prepare_layer { + self.layers.push(Layer::new( + device, + &self.solid, + #[cfg(not(target_arch = "wasm32"))] + &self.gradient, + )); + } + + let layer = &mut self.layers[self.prepare_layer]; + layer.prepare( + device, + queue, + &self.solid, + #[cfg(not(target_arch = "wasm32"))] + &self.gradient, + meshes, + transformation, + ); + + self.prepare_layer += 1; + } + + pub fn render( + &mut self, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + target: &wgpu::TextureView, + target_size: Size, + meshes: &[Mesh<'_>], + scale_factor: f32, + ) { + #[cfg(feature = "tracing")] + let _ = info_span!("Wgpu::Triangle", "DRAW").entered(); // Configure render pass { @@ -268,87 +413,29 @@ impl Pipeline { depth_stencil_attachment: None, }); - let mut num_solids = 0; - #[cfg(not(target_arch = "wasm32"))] - let mut num_gradients = 0; - let mut last_is_solid = None; - - for (index, mesh) in meshes.iter().enumerate() { - let clip_bounds = (mesh.clip_bounds() * scale_factor).snap(); - - render_pass.set_scissor_rect( - clip_bounds.x, - clip_bounds.y, - clip_bounds.width, - clip_bounds.height, - ); - - match mesh { - Mesh::Solid { .. } => { - if !last_is_solid.unwrap_or(false) { - render_pass.set_pipeline(&self.solid.pipeline); - - last_is_solid = Some(true); - } - - render_pass.set_bind_group( - 0, - &self.solid.bind_group, - &[self.solid.uniforms.offset_at_index(num_solids)], - ); - - render_pass.set_vertex_buffer( - 0, - self.solid.vertices.slice_from_index(num_solids), - ); - - num_solids += 1; - } - #[cfg(not(target_arch = "wasm32"))] - Mesh::Gradient { .. } => { - if last_is_solid.unwrap_or(true) { - render_pass.set_pipeline(&self.gradient.pipeline); - - last_is_solid = Some(false); - } - - render_pass.set_bind_group( - 0, - &self.gradient.bind_group, - &[self - .gradient - .uniforms - .offset_at_index(num_gradients)], - ); - - render_pass.set_vertex_buffer( - 0, - self.gradient - .vertices - .slice_from_index(num_gradients), - ); - - num_gradients += 1; - } - #[cfg(target_arch = "wasm32")] - Mesh::Gradient { .. } => {} - }; - - render_pass.set_index_buffer( - self.index_buffer.slice_from_index(index), - wgpu::IndexFormat::Uint32, - ); + let layer = &mut self.layers[self.render_layer]; - render_pass.draw_indexed(0..self.index_strides[index], 0, 0..1); - } + layer.render( + &self.solid, + #[cfg(not(target_arch = "wasm32"))] + &self.gradient, + meshes, + scale_factor, + &mut render_pass, + ); } - self.index_buffer.clear(); + self.render_layer += 1; if let Some(blit) = &mut self.blit { blit.draw(encoder, target); } } + + pub fn end_frame(&mut self) { + self.prepare_layer = 0; + self.render_layer = 0; + } } fn fragment_target( @@ -390,10 +477,62 @@ mod solid { #[derive(Debug)] pub struct Pipeline { pub pipeline: wgpu::RenderPipeline, + pub constants_layout: wgpu::BindGroupLayout, + } + + #[derive(Debug)] + pub struct Layer { pub vertices: Buffer, pub uniforms: dynamic::Buffer, - pub bind_group_layout: wgpu::BindGroupLayout, - pub bind_group: wgpu::BindGroup, + pub constants: wgpu::BindGroup, + } + + impl Layer { + pub fn new( + device: &wgpu::Device, + constants_layout: &wgpu::BindGroupLayout, + ) -> Self { + let vertices = Buffer::new( + device, + "iced_wgpu::triangle::solid vertex buffer", + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + ); + + let uniforms = dynamic::Buffer::uniform( + device, + "iced_wgpu::triangle::solid uniforms", + ); + + let constants = + Self::bind_group(device, uniforms.raw(), constants_layout); + + Self { + vertices, + uniforms, + constants, + } + } + + pub fn bind_group( + device: &wgpu::Device, + buffer: &wgpu::Buffer, + layout: &wgpu::BindGroupLayout, + ) -> wgpu::BindGroup { + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("iced_wgpu::triangle::solid bind group"), + layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer( + wgpu::BufferBinding { + buffer, + offset: 0, + size: Some(Uniforms::min_size()), + }, + ), + }], + }) + } } #[derive(Debug, Clone, Copy, ShaderType)] @@ -416,18 +555,7 @@ mod solid { format: wgpu::TextureFormat, antialiasing: Option, ) -> Self { - let vertices = Buffer::new( - device, - "iced_wgpu::triangle::solid vertex buffer", - wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - ); - - let uniforms = dynamic::Buffer::uniform( - device, - "iced_wgpu::triangle::solid uniforms", - ); - - let bind_group_layout = device.create_bind_group_layout( + let constants_layout = device.create_bind_group_layout( &wgpu::BindGroupLayoutDescriptor { label: Some("iced_wgpu::triangle::solid bind group layout"), entries: &[wgpu::BindGroupLayoutEntry { @@ -443,13 +571,10 @@ mod solid { }, ); - let bind_group = - Self::bind_group(device, uniforms.raw(), &bind_group_layout); - let layout = device.create_pipeline_layout( &wgpu::PipelineLayoutDescriptor { label: Some("iced_wgpu::triangle::solid pipeline layout"), - bind_group_layouts: &[&bind_group_layout], + bind_group_layouts: &[&constants_layout], push_constant_ranges: &[], }, ); @@ -501,33 +626,9 @@ mod solid { Self { pipeline, - vertices, - uniforms, - bind_group_layout, - bind_group, + constants_layout, } } - - pub fn bind_group( - device: &wgpu::Device, - buffer: &wgpu::Buffer, - layout: &wgpu::BindGroupLayout, - ) -> wgpu::BindGroup { - device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu::triangle::solid bind group"), - layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer( - wgpu::BufferBinding { - buffer, - offset: 0, - size: Some(Uniforms::min_size()), - }, - ), - }], - }) - } } } @@ -545,15 +646,90 @@ mod gradient { #[derive(Debug)] pub struct Pipeline { pub pipeline: wgpu::RenderPipeline, + pub constants_layout: wgpu::BindGroupLayout, + } + + #[derive(Debug)] + pub struct Layer { pub vertices: Buffer, pub uniforms: dynamic::Buffer, pub storage: dynamic::Buffer, + pub constants: wgpu::BindGroup, pub color_stop_offset: i32, //Need to store these and then write them all at once //or else they will be padded to 256 and cause gaps in the storage buffer pub color_stops_pending_write: Storage, - pub bind_group_layout: wgpu::BindGroupLayout, - pub bind_group: wgpu::BindGroup, + } + + impl Layer { + pub fn new( + device: &wgpu::Device, + constants_layout: &wgpu::BindGroupLayout, + ) -> Self { + let vertices = Buffer::new( + device, + "iced_wgpu::triangle::gradient vertex buffer", + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + ); + + let uniforms = dynamic::Buffer::uniform( + device, + "iced_wgpu::triangle::gradient uniforms", + ); + + // Note: with a WASM target storage buffers are not supported. Will need to use UBOs & static + // sized array (eg like the 32-sized array on OpenGL side right now) to make gradients work + let storage = dynamic::Buffer::storage( + device, + "iced_wgpu::triangle::gradient storage", + ); + + let constants = Self::bind_group( + device, + uniforms.raw(), + storage.raw(), + constants_layout, + ); + + Self { + vertices, + uniforms, + storage, + constants, + color_stop_offset: 0, + color_stops_pending_write: Storage { + color_stops: vec![], + }, + } + } + + pub fn bind_group( + device: &wgpu::Device, + uniform_buffer: &wgpu::Buffer, + storage_buffer: &wgpu::Buffer, + layout: &wgpu::BindGroupLayout, + ) -> wgpu::BindGroup { + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("iced_wgpu::triangle::gradient bind group"), + layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer( + wgpu::BufferBinding { + buffer: uniform_buffer, + offset: 0, + size: Some(Uniforms::min_size()), + }, + ), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: storage_buffer.as_entire_binding(), + }, + ], + }) + } } #[derive(Debug, ShaderType)] @@ -584,25 +760,7 @@ mod gradient { format: wgpu::TextureFormat, antialiasing: Option, ) -> Self { - let vertices = Buffer::new( - device, - "iced_wgpu::triangle::gradient vertex buffer", - wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - ); - - let uniforms = dynamic::Buffer::uniform( - device, - "iced_wgpu::triangle::gradient uniforms", - ); - - //Note: with a WASM target storage buffers are not supported. Will need to use UBOs & static - // sized array (eg like the 32-sized array on OpenGL side right now) to make gradients work - let storage = dynamic::Buffer::storage( - device, - "iced_wgpu::triangle::gradient storage", - ); - - let bind_group_layout = device.create_bind_group_layout( + let constants_layout = device.create_bind_group_layout( &wgpu::BindGroupLayoutDescriptor { label: Some( "iced_wgpu::triangle::gradient bind group layout", @@ -634,19 +792,12 @@ mod gradient { }, ); - let bind_group = Pipeline::bind_group( - device, - uniforms.raw(), - storage.raw(), - &bind_group_layout, - ); - let layout = device.create_pipeline_layout( &wgpu::PipelineLayoutDescriptor { label: Some( "iced_wgpu::triangle::gradient pipeline layout", ), - bind_group_layouts: &[&bind_group_layout], + bind_group_layouts: &[&constants_layout], push_constant_ranges: &[], }, ); @@ -694,44 +845,8 @@ mod gradient { Self { pipeline, - vertices, - uniforms, - storage, - color_stop_offset: 0, - color_stops_pending_write: Storage { - color_stops: vec![], - }, - bind_group_layout, - bind_group, + constants_layout, } } - - pub fn bind_group( - device: &wgpu::Device, - uniform_buffer: &wgpu::Buffer, - storage_buffer: &wgpu::Buffer, - layout: &wgpu::BindGroupLayout, - ) -> wgpu::BindGroup { - device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu::triangle::gradient bind group"), - layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer( - wgpu::BufferBinding { - buffer: uniform_buffer, - offset: 0, - size: Some(Uniforms::min_size()), - }, - ), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: storage_buffer.as_entire_binding(), - }, - ], - }) - } } } diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 50231f7c..6e1acc06 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -16,14 +16,11 @@ pub struct Compositor { adapter: wgpu::Adapter, device: wgpu::Device, queue: wgpu::Queue, - staging_belt: wgpu::util::StagingBelt, format: wgpu::TextureFormat, theme: PhantomData, } impl Compositor { - const CHUNK_SIZE: u64 = 10 * 1024; - /// Requests a new [`Compositor`] with the given [`Settings`]. /// /// Returns `None` if no compatible graphics adapter could be found. @@ -98,15 +95,12 @@ impl Compositor { .next() .await?; - let staging_belt = wgpu::util::StagingBelt::new(Self::CHUNK_SIZE); - Some(Compositor { instance, settings, adapter, device, queue, - staging_belt, format, theme: PhantomData, }) @@ -228,7 +222,6 @@ impl iced_graphics::window::Compositor for Compositor { backend.present( &self.device, &self.queue, - &mut self.staging_belt, &mut encoder, view, primitives, @@ -238,13 +231,9 @@ impl iced_graphics::window::Compositor for Compositor { }); // Submit work - self.staging_belt.finish(); let _submission = self.queue.submit(Some(encoder.finish())); frame.present(); - // Recall staging buffers - self.staging_belt.recall(); - Ok(()) } Err(error) => match error { -- cgit From 730d6a07564d014c470e02f233394ec98325d463 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 Feb 2023 00:47:16 +0100 Subject: Reuse a `RenderPass` as much as possible in `iced_wgpu` --- examples/integration/src/main.rs | 1 + wgpu/Cargo.toml | 5 +- wgpu/src/backend.rs | 261 +++++++++++++++++++++++++-------------- wgpu/src/image.rs | 10 +- wgpu/src/quad.rs | 10 +- wgpu/src/text.rs | 40 ++---- wgpu/src/triangle.rs | 10 +- wgpu/src/window/compositor.rs | 29 +---- 8 files changed, 196 insertions(+), 170 deletions(-) diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index 5af30ab1..a7a90ced 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -250,6 +250,7 @@ pub fn main() { &device, &queue, &mut encoder, + None, &view, primitive, &viewport, diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index b3fbfc00..7a6b75f0 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -59,8 +59,9 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" -git = "https://github.com/hecrj/glyphon.git" -rev = "3c2acb9dea5b9fcb0fa650b3c73b3a3242c62f4a" +# git = "https://github.com/hecrj/glyphon.git" +# rev = "3c2acb9dea5b9fcb0fa650b3c73b3a3242c62f4a" +path = "../../glyphon" [dependencies.tracing] version = "0.1.6" diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 8c4c0daa..9d1e3d1c 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -5,8 +5,7 @@ use crate::{Settings, Transformation}; use iced_graphics::backend; use iced_graphics::layer::Layer; -use iced_graphics::{Primitive, Viewport}; -use iced_native::{Font, Size}; +use iced_graphics::{Color, Font, Primitive, Size, Viewport}; #[cfg(feature = "tracing")] use tracing::info_span; @@ -71,6 +70,7 @@ impl Backend { device: &wgpu::Device, queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, + clear_color: Option, frame: &wgpu::TextureView, primitives: &[Primitive], viewport: &Viewport, @@ -87,18 +87,25 @@ impl Backend { let mut layers = Layer::generate(primitives, viewport); layers.push(Layer::overlay(overlay_text, viewport)); - for layer in layers { - self.flush( - device, - queue, - scale_factor, - transformation, - &layer, - encoder, - frame, - target_size, - ); - } + self.prepare( + device, + queue, + encoder, + scale_factor, + transformation, + target_size, + &layers, + ); + + self.render( + device, + encoder, + frame, + clear_color, + scale_factor, + target_size, + &layers, + ); self.quad_pipeline.end_frame(); self.text_pipeline.end_frame(); @@ -108,90 +115,153 @@ impl Backend { self.image_pipeline.end_frame(device, queue, encoder); } - fn flush( + fn prepare( &mut self, device: &wgpu::Device, queue: &wgpu::Queue, + _encoder: &mut wgpu::CommandEncoder, scale_factor: f32, transformation: Transformation, - layer: &Layer<'_>, - encoder: &mut wgpu::CommandEncoder, - target: &wgpu::TextureView, target_size: Size, + layers: &[Layer<'_>], ) { - let bounds = (layer.bounds * scale_factor).snap(); + for layer in layers { + let bounds = (layer.bounds * scale_factor).snap(); - if bounds.width < 1 || bounds.height < 1 { - return; - } + if bounds.width < 1 || bounds.height < 1 { + return; + } - if !layer.quads.is_empty() { - self.quad_pipeline.prepare( - device, - queue, - &layer.quads, - transformation, - scale_factor, - ); - - let mut render_pass = - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("iced_wgpu::quad render pass"), - color_attachments: &[Some( - wgpu::RenderPassColorAttachment { - view: target, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: true, - }, - }, - )], - depth_stencil_attachment: None, - }); + if !layer.quads.is_empty() { + self.quad_pipeline.prepare( + device, + queue, + &layer.quads, + transformation, + scale_factor, + ); + } - self.quad_pipeline.render(bounds, &mut render_pass); - } + if !layer.meshes.is_empty() { + let scaled = transformation + * Transformation::scale(scale_factor, scale_factor); + + self.triangle_pipeline.prepare( + device, + queue, + &layer.meshes, + scaled, + ); + } - if !layer.meshes.is_empty() { - let scaled = transformation - * Transformation::scale(scale_factor, scale_factor); - - self.triangle_pipeline.prepare( - device, - queue, - &layer.meshes, - scaled, - ); - - self.triangle_pipeline.render( - device, - encoder, - target, - target_size, - &layer.meshes, - scale_factor, - ); + #[cfg(any(feature = "image", feature = "svg"))] + { + if !layer.images.is_empty() { + let scaled = transformation + * Transformation::scale(scale_factor, scale_factor); + + self.image_pipeline.prepare( + device, + queue, + _encoder, + &layer.images, + scaled, + scale_factor, + ); + } + } + + if !layer.text.is_empty() { + self.text_pipeline.prepare( + device, + queue, + &layer.text, + layer.bounds, + scale_factor, + target_size, + ); + } } + } + fn render( + &mut self, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + target: &wgpu::TextureView, + clear_color: Option, + scale_factor: f32, + target_size: Size, + layers: &[Layer<'_>], + ) { + use std::mem::ManuallyDrop; + + let mut quad_layer = 0; + let mut triangle_layer = 0; #[cfg(any(feature = "image", feature = "svg"))] - { - if !layer.images.is_empty() { - let scaled = transformation - * Transformation::scale(scale_factor, scale_factor); + let mut image_layer = 0; + let mut text_layer = 0; + + let mut render_pass = ManuallyDrop::new(encoder.begin_render_pass( + &wgpu::RenderPassDescriptor { + label: Some("iced_wgpu::quad render pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: wgpu::Operations { + load: match clear_color { + Some(background_color) => wgpu::LoadOp::Clear({ + let [r, g, b, a] = + background_color.into_linear(); + + wgpu::Color { + r: f64::from(r), + g: f64::from(g), + b: f64::from(b), + a: f64::from(a), + } + }), + None => wgpu::LoadOp::Load, + }, + store: true, + }, + })], + depth_stencil_attachment: None, + }, + )); + + for layer in layers { + let bounds = (layer.bounds * scale_factor).snap(); - self.image_pipeline.prepare( + if bounds.width < 1 || bounds.height < 1 { + return; + } + + if !layer.quads.is_empty() { + self.quad_pipeline + .render(quad_layer, bounds, &mut render_pass); + + quad_layer += 1; + } + + if !layer.meshes.is_empty() { + let _ = ManuallyDrop::into_inner(render_pass); + + self.triangle_pipeline.render( device, - queue, encoder, - &layer.images, - scaled, + target, + triangle_layer, + target_size, + &layer.meshes, scale_factor, ); - let mut render_pass = - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("iced_wgpu::image render pass"), + triangle_layer += 1; + + render_pass = ManuallyDrop::new(encoder.begin_render_pass( + &wgpu::RenderPassDescriptor { + label: Some("iced_wgpu::quad render pass"), color_attachments: &[Some( wgpu::RenderPassColorAttachment { view: target, @@ -203,24 +273,31 @@ impl Backend { }, )], depth_stencil_attachment: None, - }); + }, + )); + } - self.image_pipeline.render(bounds, &mut render_pass); + #[cfg(any(feature = "image", feature = "svg"))] + { + if !layer.images.is_empty() { + self.image_pipeline.render( + image_layer, + bounds, + &mut render_pass, + ); + + image_layer += 1; + } } - } - if !layer.text.is_empty() { - self.text_pipeline.prepare( - device, - queue, - &layer.text, - layer.bounds, - scale_factor, - target_size, - ); - - self.text_pipeline.render(encoder, target); + if !layer.text.is_empty() { + self.text_pipeline.render(text_layer, &mut render_pass); + + text_layer += 1; + } } + + let _ = ManuallyDrop::into_inner(render_pass); } } diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 5d1ae8d7..db05d2ff 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -45,7 +45,6 @@ pub struct Pipeline { layers: Vec, prepare_layer: usize, - render_layer: usize, } #[derive(Debug)] @@ -321,7 +320,6 @@ impl Pipeline { layers: Vec::new(), prepare_layer: 0, - render_layer: 0, } } @@ -452,11 +450,12 @@ impl Pipeline { } pub fn render<'a>( - &'a mut self, + &'a self, + layer: usize, bounds: Rectangle, render_pass: &mut wgpu::RenderPass<'a>, ) { - if let Some(layer) = self.layers.get(self.render_layer) { + if let Some(layer) = self.layers.get(layer) { render_pass.set_pipeline(&self.pipeline); render_pass.set_scissor_rect( @@ -474,8 +473,6 @@ impl Pipeline { render_pass.set_vertex_buffer(0, self.vertices.slice(..)); layer.render(render_pass); - - self.render_layer += 1; } } @@ -496,7 +493,6 @@ impl Pipeline { .trim(&mut self.texture_atlas, &mut (device, queue, encoder)); self.prepare_layer = 0; - self.render_layer = 0; } } diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index c1aa49c4..246cc5e1 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -17,7 +17,6 @@ pub struct Pipeline { indices: wgpu::Buffer, layers: Vec, prepare_layer: usize, - render_layer: usize, } impl Pipeline { @@ -140,7 +139,6 @@ impl Pipeline { indices, layers: Vec::new(), prepare_layer: 0, - render_layer: 0, } } @@ -163,11 +161,12 @@ impl Pipeline { } pub fn render<'a>( - &'a mut self, + &'a self, + layer: usize, bounds: Rectangle, render_pass: &mut wgpu::RenderPass<'a>, ) { - if let Some(layer) = self.layers.get(self.render_layer) { + if let Some(layer) = self.layers.get(layer) { render_pass.set_pipeline(&self.pipeline); render_pass.set_scissor_rect( @@ -184,14 +183,11 @@ impl Pipeline { render_pass.set_vertex_buffer(0, self.vertices.slice(..)); layer.draw(render_pass); - - self.render_layer += 1; } } pub fn end_frame(&mut self) { self.prepare_layer = 0; - self.render_layer = 0; } } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 95994a4e..73708fd8 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -16,7 +16,7 @@ pub struct Pipeline { system: Option, renderers: Vec, atlas: glyphon::TextAtlas, - layer: usize, + prepare_layer: usize, } #[ouroboros::self_referencing] @@ -55,7 +55,7 @@ impl Pipeline { ), renderers: Vec::new(), atlas: glyphon::TextAtlas::new(device, queue, format), - layer: 0, + prepare_layer: 0, } } @@ -88,12 +88,12 @@ impl Pipeline { target_size: Size, ) { self.system.as_mut().unwrap().with_mut(|fields| { - if self.renderers.len() <= self.layer { + if self.renderers.len() <= self.prepare_layer { self.renderers .push(glyphon::TextRenderer::new(device, queue)); } - let renderer = &mut self.renderers[self.layer]; + let renderer = &mut self.renderers[self.prepare_layer]; let keys: Vec<_> = sections .iter() @@ -179,35 +179,21 @@ impl Pipeline { &mut glyphon::SwashCache::new(fields.fonts), ) .expect("Prepare text sections"); + + self.prepare_layer += 1; }); } - pub fn render( - &mut self, - encoder: &mut wgpu::CommandEncoder, - target: &wgpu::TextureView, + pub fn render<'a>( + &'a self, + layer: usize, + render_pass: &mut wgpu::RenderPass<'a>, ) { - let mut render_pass = - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: target, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: true, - }, - })], - depth_stencil_attachment: None, - }); - - let renderer = &mut self.renderers[self.layer]; + let renderer = &self.renderers[layer]; renderer - .render(&self.atlas, &mut render_pass) + .render(&self.atlas, render_pass) .expect("Render text"); - - self.layer += 1; } pub fn end_frame(&mut self) { @@ -216,7 +202,7 @@ impl Pipeline { .unwrap() .with_render_cache_mut(|cache| cache.trim()); - self.layer = 0; + self.prepare_layer = 0; } pub fn measure( diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 572af1e8..1b537bf4 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -22,7 +22,6 @@ pub struct Pipeline { layers: Vec, prepare_layer: usize, - render_layer: usize, } #[derive(Debug)] @@ -235,7 +234,7 @@ impl Layer { } fn render<'a>( - &'a mut self, + &'a self, solid: &'a solid::Pipeline, #[cfg(not(target_arch = "wasm32"))] gradient: &'a gradient::Pipeline, meshes: &[Mesh<'_>], @@ -331,7 +330,6 @@ impl Pipeline { layers: Vec::new(), prepare_layer: 0, - render_layer: 0, } } @@ -373,6 +371,7 @@ impl Pipeline { device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, + layer: usize, target_size: Size, meshes: &[Mesh<'_>], scale_factor: f32, @@ -413,7 +412,7 @@ impl Pipeline { depth_stencil_attachment: None, }); - let layer = &mut self.layers[self.render_layer]; + let layer = &mut self.layers[layer]; layer.render( &self.solid, @@ -425,8 +424,6 @@ impl Pipeline { ); } - self.render_layer += 1; - if let Some(blit) = &mut self.blit { blit.draw(encoder, target); } @@ -434,7 +431,6 @@ impl Pipeline { pub fn end_frame(&mut self) { self.prepare_layer = 0; - self.render_layer = 0; } } diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 6e1acc06..365cb603 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -190,39 +190,12 @@ impl iced_graphics::window::Compositor for Compositor { .texture .create_view(&wgpu::TextureViewDescriptor::default()); - let _ = - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some( - "iced_wgpu::window::Compositor render pass", - ), - color_attachments: &[Some( - wgpu::RenderPassColorAttachment { - view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear({ - let [r, g, b, a] = - background_color.into_linear(); - - wgpu::Color { - r: f64::from(r), - g: f64::from(g), - b: f64::from(b), - a: f64::from(a), - } - }), - store: true, - }, - }, - )], - depth_stencil_attachment: None, - }); - renderer.with_primitives(|backend, primitives| { backend.present( &self.device, &self.queue, &mut encoder, + Some(background_color), view, primitives, viewport, -- cgit From 21886d7e9cde17904719f1642f0b3af9791102ad Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 Feb 2023 00:48:38 +0100 Subject: Use my GitHub fork of `glyphon` --- wgpu/Cargo.toml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 7a6b75f0..46115a3c 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -59,9 +59,8 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" -# git = "https://github.com/hecrj/glyphon.git" -# rev = "3c2acb9dea5b9fcb0fa650b3c73b3a3242c62f4a" -path = "../../glyphon" +git = "https://github.com/hecrj/glyphon.git" +rev = "ccf19c67e8a4564263626bc6b86b6154540768c4" [dependencies.tracing] version = "0.1.6" -- cgit From ddbf93a82ff2ee0ca3265baf2f5b4442717b9101 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 Feb 2023 01:23:40 +0100 Subject: Set scissoring properly in `text::Pipeline` --- wgpu/src/backend.rs | 3 ++- wgpu/src/text.rs | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 9d1e3d1c..d07898f7 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -291,7 +291,8 @@ impl Backend { } if !layer.text.is_empty() { - self.text_pipeline.render(text_layer, &mut render_pass); + self.text_pipeline + .render(text_layer, bounds, &mut render_pass); text_layer += 1; } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 73708fd8..655ad987 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -187,10 +187,18 @@ impl Pipeline { pub fn render<'a>( &'a self, layer: usize, + bounds: Rectangle, render_pass: &mut wgpu::RenderPass<'a>, ) { let renderer = &self.renderers[layer]; + render_pass.set_scissor_rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ); + renderer .render(&self.atlas, render_pass) .expect("Render text"); -- cgit From 05c787c2efbd8c8bc11925e1605b8b09ba744268 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 Feb 2023 23:21:04 +0100 Subject: Grow atlas in `text::Pipeline` when necessary --- wgpu/Cargo.toml | 2 +- wgpu/src/backend.rs | 55 +++++++++++++++++++++++++++++++++++++++-------------- wgpu/src/text.rs | 54 ++++++++++++++++++++++++++++++++++------------------ 3 files changed, 78 insertions(+), 33 deletions(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 46115a3c..6132768d 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -60,7 +60,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "ccf19c67e8a4564263626bc6b86b6154540768c4" +rev = "541efc5df644b1a25e96113f602a3f6803ce8a07" [dependencies.tracing] version = "0.1.6" diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index d07898f7..7f44fafa 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -93,10 +93,17 @@ impl Backend { encoder, scale_factor, transformation, - target_size, &layers, ); + while !self.prepare_text( + device, + queue, + scale_factor, + target_size, + &layers, + ) {} + self.render( device, encoder, @@ -115,6 +122,38 @@ impl Backend { self.image_pipeline.end_frame(device, queue, encoder); } + fn prepare_text( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + scale_factor: f32, + target_size: Size, + layers: &[Layer<'_>], + ) -> bool { + for layer in layers { + let bounds = (layer.bounds * scale_factor).snap(); + + if bounds.width < 1 || bounds.height < 1 { + continue; + } + + if !layer.text.is_empty() { + if !self.text_pipeline.prepare( + device, + queue, + &layer.text, + layer.bounds, + scale_factor, + target_size, + ) { + return false; + } + } + } + + true + } + fn prepare( &mut self, device: &wgpu::Device, @@ -122,14 +161,13 @@ impl Backend { _encoder: &mut wgpu::CommandEncoder, scale_factor: f32, transformation: Transformation, - target_size: Size, layers: &[Layer<'_>], ) { for layer in layers { let bounds = (layer.bounds * scale_factor).snap(); if bounds.width < 1 || bounds.height < 1 { - return; + continue; } if !layer.quads.is_empty() { @@ -170,17 +208,6 @@ impl Backend { ); } } - - if !layer.text.is_empty() { - self.text_pipeline.prepare( - device, - queue, - &layer.text, - layer.bounds, - scale_factor, - target_size, - ); - } } } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 655ad987..4406177a 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -86,7 +86,7 @@ impl Pipeline { bounds: Rectangle, scale_factor: f32, target_size: Size, - ) { + ) -> bool { self.system.as_mut().unwrap().with_mut(|fields| { if self.renderers.len() <= self.prepare_layer { self.renderers @@ -165,23 +165,39 @@ impl Pipeline { } }); - renderer - .prepare( - device, - queue, - &mut self.atlas, - glyphon::Resolution { - width: target_size.width, - height: target_size.height, - }, - text_areas, - glyphon::Color::rgb(0, 0, 0), - &mut glyphon::SwashCache::new(fields.fonts), - ) - .expect("Prepare text sections"); - - self.prepare_layer += 1; - }); + let result = renderer.prepare( + device, + queue, + &mut self.atlas, + glyphon::Resolution { + width: target_size.width, + height: target_size.height, + }, + text_areas, + glyphon::Color::rgb(0, 0, 0), + &mut glyphon::SwashCache::new(fields.fonts), + ); + + match result { + Ok(()) => { + self.prepare_layer += 1; + + true + } + Err(glyphon::PrepareError::AtlasFull(content_type)) => { + self.prepare_layer = 0; + + if self.atlas.grow(device, content_type) { + false + } else { + // If the atlas cannot grow, then all bets are off. + // Instead of panicking, we will just pray that the result + // will be somewhat readable... + true + } + } + } + }) } pub fn render<'a>( @@ -205,6 +221,8 @@ impl Pipeline { } pub fn end_frame(&mut self) { + self.atlas.trim(); + self.system .as_mut() .unwrap() -- cgit From 0715d7d0c781a4c7ead4dfdbf0f4656022df67e1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 9 Feb 2023 00:20:07 +0100 Subject: Update `glyphon` in `iced_wgpu` --- wgpu/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 6132768d..632873a3 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -60,7 +60,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "541efc5df644b1a25e96113f602a3f6803ce8a07" +rev = "65b481d758f50fd13fc21af2cc5ef62ddee64955" [dependencies.tracing] version = "0.1.6" -- cgit From 2097a56b58d8fe1784a625f69c1cdd38fcfb9a52 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 9 Feb 2023 06:54:51 +0100 Subject: Provide some margin to static buffers when growing --- wgpu/src/buffer/static.rs | 13 ++++++++----- wgpu/src/triangle.rs | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/wgpu/src/buffer/static.rs b/wgpu/src/buffer/static.rs index 7ae7edd2..d8ae116e 100644 --- a/wgpu/src/buffer/static.rs +++ b/wgpu/src/buffer/static.rs @@ -2,8 +2,7 @@ use bytemuck::{Pod, Zeroable}; use std::marker::PhantomData; use std::mem; -//128 triangles/indices -const DEFAULT_STATIC_BUFFER_COUNT: wgpu::BufferAddress = 1_000; +const DEFAULT_COUNT: wgpu::BufferAddress = 128; /// A generic buffer struct useful for items which have no alignment requirements /// (e.g. Vertex, Index buffers) & no dynamic offsets. @@ -25,7 +24,7 @@ impl Buffer { label: &'static str, usages: wgpu::BufferUsages, ) -> Self { - let size = (mem::size_of::() as u64) * DEFAULT_STATIC_BUFFER_COUNT; + let size = (mem::size_of::() as u64) * DEFAULT_COUNT; Self { offsets: Vec::new(), @@ -57,9 +56,13 @@ impl Buffer { let size = (mem::size_of::() * new_count) as u64; if self.size < size { + self.size = + (mem::size_of::() * (new_count + new_count / 2)) as u64; + + self.gpu = + Self::gpu_buffer(device, self.label, self.size, self.usages); + self.offsets.clear(); - self.size = size; - self.gpu = Self::gpu_buffer(device, self.label, size, self.usages); true } else { false diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 1b537bf4..4b4fa16d 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -43,7 +43,7 @@ impl Layer { Self { index_buffer: Buffer::new( device, - "iced_wgpu::triangle vertex buffer", + "iced_wgpu::triangle index buffer", wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, ), index_strides: Vec::new(), -- cgit From 17a4d817c45da732e9fdc6559b6bbc7d5be94052 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 9 Feb 2023 07:00:21 +0100 Subject: Collapse conditional and please `clippy` --- wgpu/src/backend.rs | 10 +++++----- wgpu/src/text.rs | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 7f44fafa..e650d9a5 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -137,17 +137,17 @@ impl Backend { continue; } - if !layer.text.is_empty() { - if !self.text_pipeline.prepare( + if !layer.text.is_empty() + && !self.text_pipeline.prepare( device, queue, &layer.text, layer.bounds, scale_factor, target_size, - ) { - return false; - } + ) + { + return false; } } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 4406177a..c0f49417 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -187,6 +187,7 @@ impl Pipeline { Err(glyphon::PrepareError::AtlasFull(content_type)) => { self.prepare_layer = 0; + #[allow(clippy::needless_bool)] if self.atlas.grow(device, content_type) { false } else { -- cgit From 51844c5d0c7f7c9b65c56330862b69f0baa0e3c1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 10 Feb 2023 18:52:35 +0100 Subject: Trim `Cache` every 300 frames in `text::Pipeline` --- wgpu/src/text.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index c0f49417..dea6ab18 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -316,6 +316,7 @@ struct Cache<'a> { entries: FxHashMap>, recently_used: FxHashSet, hasher: HashBuilder, + trim_count: usize, } #[cfg(not(target_arch = "wasm32"))] @@ -325,11 +326,14 @@ type HashBuilder = twox_hash::RandomXxHashBuilder64; type HashBuilder = std::hash::BuildHasherDefault; impl<'a> Cache<'a> { + const TRIM_INTERVAL: usize = 300; + fn new() -> Self { Self { entries: FxHashMap::default(), recently_used: FxHashSet::default(), hasher: HashBuilder::default(), + trim_count: 0, } } @@ -389,10 +393,16 @@ impl<'a> Cache<'a> { } fn trim(&mut self) { - self.entries - .retain(|key, _| self.recently_used.contains(key)); + if self.trim_count >= Self::TRIM_INTERVAL { + self.entries + .retain(|key, _| self.recently_used.contains(key)); + + self.recently_used.clear(); - self.recently_used.clear(); + self.trim_count = 0; + } else { + self.trim_count += 1; + } } } -- cgit From 26e902f7d84290f8163a25c37488d29db4fc0708 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 10 Feb 2023 20:03:33 +0100 Subject: Compute grapheme index in `find_cursor_position` for `TextInput` --- native/src/text.rs | 13 +------------ native/src/widget/text_input.rs | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/native/src/text.rs b/native/src/text.rs index 1bbd36cc..4c72abc3 100644 --- a/native/src/text.rs +++ b/native/src/text.rs @@ -1,6 +1,6 @@ //! Draw and interact with text. use crate::alignment; -use crate::{Color, Point, Rectangle, Size, Vector}; +use crate::{Color, Point, Rectangle, Size}; use std::borrow::Cow; @@ -34,10 +34,6 @@ pub struct Text<'a, Font> { pub enum Hit { /// The point was within the bounds of the returned character index. CharOffset(usize), - /// The provided point was not within the bounds of a glyph. The index - /// of the character with the closest centeroid position is returned, - /// as well as its delta. - NearestCharOffset(usize, Vector), } impl Hit { @@ -45,13 +41,6 @@ impl Hit { pub fn cursor(self) -> usize { match self { Self::CharOffset(i) => i, - Self::NearestCharOffset(i, delta) => { - if delta.x > f32::EPSILON { - i + 1 - } else { - i - } - } } } } diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index f51416e1..65a9bd3b 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -1193,17 +1193,26 @@ where let size = size.unwrap_or_else(|| renderer.default_size()); let offset = offset(renderer, text_bounds, font, size, value, state); + let value = value.to_string(); - renderer + let char_offset = renderer .hit_test( - &value.to_string(), + &value, size, font, Size::INFINITY, Point::new(x + offset, text_bounds.height / 2.0), true, ) - .map(text::Hit::cursor) + .map(text::Hit::cursor)?; + + Some( + unicode_segmentation::UnicodeSegmentation::graphemes( + &value[..char_offset], + true, + ) + .count(), + ) } const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500; -- cgit From a0597471b81b122b43e1bb90e43e1bcde1e8a892 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 10 Feb 2023 20:24:18 +0100 Subject: Remove `iced_glutin` and `iced_glow` leftovers --- Cargo.toml | 3 - glutin/Cargo.toml | 42 ---- glutin/README.md | 29 --- glutin/src/application.rs | 508 ---------------------------------------------- glutin/src/lib.rs | 33 --- src/lib.rs | 11 +- src/settings.rs | 12 -- winit/src/settings.rs | 8 - 8 files changed, 1 insertion(+), 645 deletions(-) delete mode 100644 glutin/Cargo.toml delete mode 100644 glutin/README.md delete mode 100644 glutin/src/application.rs delete mode 100644 glutin/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 42c9488c..5fc0cc10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,6 @@ system = ["iced_winit/system"] # Enables chrome traces chrome-trace = [ "iced_winit/chrome-trace", - "iced_glutin?/trace", "iced_wgpu?/tracing", ] @@ -50,7 +49,6 @@ members = [ "core", "futures", "graphics", - "glutin", "lazy", "native", "style", @@ -65,7 +63,6 @@ iced_futures = { version = "0.6", path = "futures" } iced_native = { version = "0.9", path = "native" } iced_graphics = { version = "0.7", path = "graphics" } iced_winit = { version = "0.8", path = "winit", features = ["application"] } -iced_glutin = { version = "0.7", path = "glutin", optional = true } thiserror = "1.0" [dependencies.image_rs] diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml deleted file mode 100644 index 10d3778b..00000000 --- a/glutin/Cargo.toml +++ /dev/null @@ -1,42 +0,0 @@ -[package] -name = "iced_glutin" -version = "0.7.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2021" -description = "A glutin runtime for Iced" -license = "MIT" -repository = "https://github.com/iced-rs/iced" -documentation = "https://docs.rs/iced_glutin" -keywords = ["gui", "ui", "graphics", "interface", "widgets"] -categories = ["gui"] - -[features] -trace = ["iced_winit/trace"] -debug = ["iced_winit/debug"] -system = ["iced_winit/system"] - -[dependencies] -log = "0.4" - -[dependencies.glutin] -version = "0.29" -git = "https://github.com/iced-rs/glutin" -rev = "da8d291486b4c9bec12487a46c119c4b1d386abf" - -[dependencies.iced_native] -version = "0.9" -path = "../native" - -[dependencies.iced_winit] -version = "0.8" -path = "../winit" -features = ["application"] - -[dependencies.iced_graphics] -version = "0.7" -path = "../graphics" -features = ["opengl"] - -[dependencies.tracing] -version = "0.1.6" -optional = true diff --git a/glutin/README.md b/glutin/README.md deleted file mode 100644 index 45e8ee6b..00000000 --- a/glutin/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# `iced_glutin` -[![Documentation](https://docs.rs/iced_glutin/badge.svg)][documentation] -[![Crates.io](https://img.shields.io/crates/v/iced_glutin.svg)](https://crates.io/crates/iced_glutin) -[![License](https://img.shields.io/crates/l/iced_glutin.svg)](https://github.com/iced-rs/iced/blob/master/LICENSE) -[![Discord Server](https://img.shields.io/discord/628993209984614400?label=&labelColor=6A7EC2&logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/3xZJ65GAhd) - -`iced_glutin` offers some convenient abstractions on top of [`iced_native`] to quickstart development when using [`glutin`]. - -It exposes a renderer-agnostic `Application` trait that can be implemented and then run with a simple call. The use of this trait is optional. A `conversion` module is provided for users that decide to implement a custom event loop. - -

- The native target -

- -[documentation]: https://docs.rs/iced_glutin -[`iced_native`]: ../native -[`glutin`]: https://github.com/rust-windowing/glutin - -## Installation -Add `iced_glutin` as a dependency in your `Cargo.toml`: - -```toml -iced_glutin = "0.7" -``` - -__Iced moves fast and the `master` branch can contain breaking changes!__ If -you want to learn about a specific release, check out [the release list]. - -[the release list]: https://github.com/iced-rs/iced/releases diff --git a/glutin/src/application.rs b/glutin/src/application.rs deleted file mode 100644 index 5921bdd0..00000000 --- a/glutin/src/application.rs +++ /dev/null @@ -1,508 +0,0 @@ -//! Create interactive, native cross-platform applications. -use crate::mouse; -use crate::{Error, Executor, Runtime}; - -pub use iced_winit::application::StyleSheet; -pub use iced_winit::Application; - -use iced_graphics::window; -use iced_winit::application; -use iced_winit::conversion; -use iced_winit::futures; -use iced_winit::futures::channel::mpsc; -use iced_winit::renderer; -use iced_winit::time::Instant; -use iced_winit::user_interface; -use iced_winit::{Clipboard, Command, Debug, Event, Proxy, Settings}; - -use glutin::window::Window; -use std::mem::ManuallyDrop; - -#[cfg(feature = "tracing")] -use tracing::{info_span, instrument::Instrument}; - -/// Runs an [`Application`] with an executor, compositor, and the provided -/// settings. -pub fn run( - settings: Settings, - compositor_settings: C::Settings, -) -> Result<(), Error> -where - A: Application + 'static, - E: Executor + 'static, - C: window::GLCompositor + 'static, - ::Theme: StyleSheet, -{ - use futures::task; - use futures::Future; - use glutin::event_loop::EventLoopBuilder; - use glutin::platform::run_return::EventLoopExtRunReturn; - use glutin::ContextBuilder; - - #[cfg(feature = "trace")] - let _guard = iced_winit::Profiler::init(); - - let mut debug = Debug::new(); - debug.startup_started(); - - #[cfg(feature = "tracing")] - let _ = info_span!("Application::Glutin", "RUN").entered(); - - let mut event_loop = EventLoopBuilder::with_user_event().build(); - let proxy = event_loop.create_proxy(); - - let runtime = { - let executor = E::new().map_err(Error::ExecutorCreationFailed)?; - let proxy = Proxy::new(event_loop.create_proxy()); - - Runtime::new(executor, proxy) - }; - - let (application, init_command) = { - let flags = settings.flags; - - runtime.enter(|| A::new(flags)) - }; - - let context = { - let builder = settings.window.into_builder( - &application.title(), - event_loop.primary_monitor(), - settings.id, - ); - - log::debug!("Window builder: {:#?}", builder); - - let opengl_builder = ContextBuilder::new() - .with_vsync(true) - .with_multisampling(C::sample_count(&compositor_settings) as u16); - - let opengles_builder = opengl_builder.clone().with_gl( - glutin::GlRequest::Specific(glutin::Api::OpenGlEs, (2, 0)), - ); - - let (first_builder, second_builder) = if settings.try_opengles_first { - (opengles_builder, opengl_builder) - } else { - (opengl_builder, opengles_builder) - }; - - log::info!("Trying first builder: {:#?}", first_builder); - - let context = first_builder - .build_windowed(builder.clone(), &event_loop) - .or_else(|_| { - log::info!("Trying second builder: {:#?}", second_builder); - second_builder.build_windowed(builder, &event_loop) - }) - .map_err(|error| { - use glutin::CreationError; - use iced_graphics::Error as ContextError; - - match error { - CreationError::Window(error) => { - Error::WindowCreationFailed(error) - } - CreationError::OpenGlVersionNotSupported => { - Error::GraphicsCreationFailed( - ContextError::VersionNotSupported, - ) - } - CreationError::NoAvailablePixelFormat => { - Error::GraphicsCreationFailed( - ContextError::NoAvailablePixelFormat, - ) - } - error => Error::GraphicsCreationFailed( - ContextError::BackendError(error.to_string()), - ), - } - })?; - - #[allow(unsafe_code)] - unsafe { - context.make_current().expect("Make OpenGL context current") - } - }; - - #[allow(unsafe_code)] - let (compositor, renderer) = unsafe { - C::new(compositor_settings, |address| { - context.get_proc_address(address) - })? - }; - - let (mut event_sender, event_receiver) = mpsc::unbounded(); - let (control_sender, mut control_receiver) = mpsc::unbounded(); - - let mut instance = Box::pin({ - let run_instance = run_instance::( - application, - compositor, - renderer, - runtime, - proxy, - debug, - event_receiver, - control_sender, - context, - init_command, - settings.exit_on_close_request, - ); - - #[cfg(feature = "tracing")] - let run_instance = - run_instance.instrument(info_span!("Application", "LOOP")); - - run_instance - }); - - let mut context = task::Context::from_waker(task::noop_waker_ref()); - - let _ = event_loop.run_return(move |event, _, control_flow| { - use glutin::event_loop::ControlFlow; - - if let ControlFlow::ExitWithCode(_) = control_flow { - return; - } - - let event = match event { - glutin::event::Event::WindowEvent { - event: - glutin::event::WindowEvent::ScaleFactorChanged { - new_inner_size, - .. - }, - window_id, - } => Some(glutin::event::Event::WindowEvent { - event: glutin::event::WindowEvent::Resized(*new_inner_size), - window_id, - }), - _ => event.to_static(), - }; - - if let Some(event) = event { - event_sender.start_send(event).expect("Send event"); - - let poll = instance.as_mut().poll(&mut context); - - match poll { - task::Poll::Pending => { - if let Ok(Some(flow)) = control_receiver.try_next() { - *control_flow = flow; - } - } - task::Poll::Ready(_) => { - *control_flow = ControlFlow::Exit; - } - } - } - }); - - Ok(()) -} - -async fn run_instance( - mut application: A, - mut compositor: C, - mut renderer: A::Renderer, - mut runtime: Runtime, A::Message>, - mut proxy: glutin::event_loop::EventLoopProxy, - mut debug: Debug, - mut event_receiver: mpsc::UnboundedReceiver< - glutin::event::Event<'_, A::Message>, - >, - mut control_sender: mpsc::UnboundedSender, - mut context: glutin::ContextWrapper, - init_command: Command, - exit_on_close_request: bool, -) where - A: Application + 'static, - E: Executor + 'static, - C: window::GLCompositor + 'static, - ::Theme: StyleSheet, -{ - use glutin::event; - use glutin::event_loop::ControlFlow; - use iced_winit::futures::stream::StreamExt; - - let mut clipboard = Clipboard::connect(context.window()); - let mut cache = user_interface::Cache::default(); - let mut state = application::State::new(&application, context.window()); - let mut viewport_version = state.viewport_version(); - let mut should_exit = false; - - application::run_command( - &application, - &mut cache, - &state, - &mut renderer, - init_command, - &mut runtime, - &mut clipboard, - &mut should_exit, - &mut proxy, - &mut debug, - context.window(), - || compositor.fetch_information(), - ); - runtime.track(application.subscription()); - - let mut user_interface = - ManuallyDrop::new(application::build_user_interface( - &application, - user_interface::Cache::default(), - &mut renderer, - state.logical_size(), - &mut debug, - )); - - let mut mouse_interaction = mouse::Interaction::default(); - let mut events = Vec::new(); - let mut messages = Vec::new(); - let mut redraw_pending = false; - - debug.startup_finished(); - - while let Some(event) = event_receiver.next().await { - match event { - event::Event::NewEvents(start_cause) => { - redraw_pending = matches!( - start_cause, - event::StartCause::Init - | event::StartCause::Poll - | event::StartCause::ResumeTimeReached { .. } - ); - } - event::Event::MainEventsCleared => { - if !redraw_pending && events.is_empty() && messages.is_empty() { - continue; - } - - debug.event_processing_started(); - - let (interface_state, statuses) = user_interface.update( - &events, - state.cursor_position(), - &mut renderer, - &mut clipboard, - &mut messages, - ); - - debug.event_processing_finished(); - - for event in events.drain(..).zip(statuses.into_iter()) { - runtime.broadcast(event); - } - - if !messages.is_empty() - || matches!( - interface_state, - user_interface::State::Outdated - ) - { - let mut cache = - ManuallyDrop::into_inner(user_interface).into_cache(); - - // Update application - application::update( - &mut application, - &mut cache, - &state, - &mut renderer, - &mut runtime, - &mut clipboard, - &mut should_exit, - &mut proxy, - &mut debug, - &mut messages, - context.window(), - || compositor.fetch_information(), - ); - - // Update window - state.synchronize(&application, context.window()); - - user_interface = - ManuallyDrop::new(application::build_user_interface( - &application, - cache, - &mut renderer, - state.logical_size(), - &mut debug, - )); - - if should_exit { - break; - } - } - - // TODO: Avoid redrawing all the time by forcing widgets to - // request redraws on state changes - // - // Then, we can use the `interface_state` here to decide if a redraw - // is needed right away, or simply wait until a specific time. - let redraw_event = Event::Window( - crate::window::Event::RedrawRequested(Instant::now()), - ); - - let (interface_state, _) = user_interface.update( - &[redraw_event.clone()], - state.cursor_position(), - &mut renderer, - &mut clipboard, - &mut messages, - ); - - debug.draw_started(); - let new_mouse_interaction = user_interface.draw( - &mut renderer, - state.theme(), - &renderer::Style { - text_color: state.text_color(), - }, - state.cursor_position(), - ); - debug.draw_finished(); - - if new_mouse_interaction != mouse_interaction { - context.window().set_cursor_icon( - conversion::mouse_interaction(new_mouse_interaction), - ); - - mouse_interaction = new_mouse_interaction; - } - - context.window().request_redraw(); - runtime - .broadcast((redraw_event, crate::event::Status::Ignored)); - - let _ = control_sender.start_send(match interface_state { - user_interface::State::Updated { - redraw_request: Some(redraw_request), - } => match redraw_request { - crate::window::RedrawRequest::NextFrame => { - ControlFlow::Poll - } - crate::window::RedrawRequest::At(at) => { - ControlFlow::WaitUntil(at) - } - }, - _ => ControlFlow::Wait, - }); - - redraw_pending = false; - } - event::Event::PlatformSpecific(event::PlatformSpecific::MacOS( - event::MacOS::ReceivedUrl(url), - )) => { - use iced_native::event; - events.push(iced_native::Event::PlatformSpecific( - event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl( - url, - )), - )); - } - event::Event::UserEvent(message) => { - messages.push(message); - } - event::Event::RedrawRequested(_) => { - #[cfg(feature = "tracing")] - let _ = info_span!("Application", "FRAME").entered(); - - debug.render_started(); - - #[allow(unsafe_code)] - unsafe { - if !context.is_current() { - context = context - .make_current() - .expect("Make OpenGL context current"); - } - } - - let current_viewport_version = state.viewport_version(); - - if viewport_version != current_viewport_version { - let physical_size = state.physical_size(); - let logical_size = state.logical_size(); - - debug.layout_started(); - user_interface = ManuallyDrop::new( - ManuallyDrop::into_inner(user_interface) - .relayout(logical_size, &mut renderer), - ); - debug.layout_finished(); - - debug.draw_started(); - let new_mouse_interaction = user_interface.draw( - &mut renderer, - state.theme(), - &renderer::Style { - text_color: state.text_color(), - }, - state.cursor_position(), - ); - debug.draw_finished(); - - if new_mouse_interaction != mouse_interaction { - context.window().set_cursor_icon( - conversion::mouse_interaction( - new_mouse_interaction, - ), - ); - - mouse_interaction = new_mouse_interaction; - } - - context.resize(glutin::dpi::PhysicalSize::new( - physical_size.width, - physical_size.height, - )); - - compositor.resize_viewport(physical_size); - - viewport_version = current_viewport_version; - } - - compositor.present( - &mut renderer, - state.viewport(), - state.background_color(), - &debug.overlay(), - ); - - context.swap_buffers().expect("Swap buffers"); - - debug.render_finished(); - - // TODO: Handle animations! - // Maybe we can use `ControlFlow::WaitUntil` for this. - } - event::Event::WindowEvent { - event: window_event, - .. - } => { - if application::requests_exit(&window_event, state.modifiers()) - && exit_on_close_request - { - break; - } - - state.update(context.window(), &window_event, &mut debug); - - if let Some(event) = conversion::window_event( - &window_event, - state.scale_factor(), - state.modifiers(), - ) { - events.push(event); - } - } - _ => {} - } - } - - // Manually drop the user interface - drop(ManuallyDrop::into_inner(user_interface)); -} diff --git a/glutin/src/lib.rs b/glutin/src/lib.rs deleted file mode 100644 index 33afd664..00000000 --- a/glutin/src/lib.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! A windowing shell for [`iced`], on top of [`glutin`]. -//! -//! ![The native path of the Iced ecosystem](https://github.com/iced-rs/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/native.png?raw=true) -//! -//! [`iced`]: https://github.com/iced-rs/iced -//! [`glutin`]: https://github.com/rust-windowing/glutin -#![doc( - html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" -)] -#![deny( - missing_docs, - missing_debug_implementations, - unsafe_code, - unused_results, - clippy::extra_unused_lifetimes, - clippy::from_over_into, - clippy::needless_borrow, - clippy::new_without_default, - clippy::useless_conversion -)] -#![forbid(rust_2018_idioms)] -#![allow(clippy::inherent_to_string, clippy::type_complexity)] -#![cfg_attr(docsrs, feature(doc_cfg))] - -pub use glutin; - -#[doc(no_inline)] -pub use iced_winit::*; - -pub mod application; - -#[doc(no_inline)] -pub use application::Application; diff --git a/src/lib.rs b/src/lib.rs index 17c5ab97..31ec4f48 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -182,17 +182,8 @@ pub mod touch; pub mod widget; pub mod window; -#[cfg(all(not(feature = "glow"), feature = "wgpu"))] -use iced_winit as runtime; - -#[cfg(feature = "glow")] -use iced_glutin as runtime; - -#[cfg(all(not(feature = "glow"), feature = "wgpu"))] use iced_wgpu as renderer; - -#[cfg(feature = "glow")] -use iced_glow as renderer; +use iced_winit as runtime; pub use iced_native::theme; pub use runtime::event; diff --git a/src/settings.rs b/src/settings.rs index ce07b0dd..13b3af7c 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -49,15 +49,6 @@ pub struct Settings { /// /// [`Application`]: crate::Application pub exit_on_close_request: bool, - - /// Whether the [`Application`] should try to build the context - /// using OpenGL ES first then OpenGL. - /// - /// By default, it is disabled. - /// **Note:** Only works for the `glow` backend. - /// - /// [`Application`]: crate::Application - pub try_opengles_first: bool, } impl Settings { @@ -75,7 +66,6 @@ impl Settings { default_text_size: default_settings.default_text_size, antialiasing: default_settings.antialiasing, exit_on_close_request: default_settings.exit_on_close_request, - try_opengles_first: default_settings.try_opengles_first, } } } @@ -93,7 +83,6 @@ where default_text_size: 16.0, antialiasing: false, exit_on_close_request: true, - try_opengles_first: false, } } } @@ -105,7 +94,6 @@ impl From> for iced_winit::Settings { window: settings.window.into(), flags: settings.flags, exit_on_close_request: settings.exit_on_close_request, - try_opengles_first: settings.try_opengles_first, } } } diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 78d58000..5c64727b 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -51,14 +51,6 @@ pub struct Settings { /// /// [`Application`]: crate::Application pub exit_on_close_request: bool, - - /// Whether the [`Application`] should try to build the context - /// using OpenGL ES first then OpenGL. - /// - /// NOTE: Only works for the `glow` backend. - /// - /// [`Application`]: crate::Application - pub try_opengles_first: bool, } /// The window settings of an application. -- cgit From b2c87cdd238baa1562dc0422c00d1bf62132700f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 10 Feb 2023 22:36:02 +0100 Subject: Make `iced_wgpu` a mandatory dependency --- Cargo.toml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5fc0cc10..551e12ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,17 +12,14 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] [features] -default = ["wgpu"] # Enables the `Image` widget -image = ["iced_wgpu?/image", "image_rs"] +image = ["iced_wgpu/image", "image_rs"] # Enables the `Svg` widget -svg = ["iced_wgpu?/svg"] +svg = ["iced_wgpu/svg"] # Enables the `Canvas` widget canvas = ["iced_graphics/canvas"] # Enables the `QRCode` widget qr_code = ["iced_graphics/qr_code"] -# Enables the `iced_wgpu` renderer -wgpu = ["iced_wgpu"] # Enables a debug view in native platforms (press F12) debug = ["iced_winit/debug"] # Enables `tokio` as the `executor::Default` on native platforms @@ -38,7 +35,7 @@ system = ["iced_winit/system"] # Enables chrome traces chrome-trace = [ "iced_winit/chrome-trace", - "iced_wgpu?/tracing", + "iced_wgpu/tracing", ] [badges] @@ -71,10 +68,10 @@ package = "image" optional = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -iced_wgpu = { version = "0.9", path = "wgpu", optional = true } +iced_wgpu = { version = "0.9", path = "wgpu" } [target.'cfg(target_arch = "wasm32")'.dependencies] -iced_wgpu = { version = "0.9", path = "wgpu", features = ["webgl"], optional = true } +iced_wgpu = { version = "0.9", path = "wgpu", features = ["webgl"] } [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] -- cgit From 700262e05c76e003158acfeb8edd9f6b026d78cf Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 24 Feb 2023 13:56:37 +0100 Subject: Fix `checkbox` example --- examples/checkbox/fonts/icons.ttf | Bin 1272 -> 1784 bytes examples/checkbox/src/main.rs | 28 +++++++++++++++++++--------- native/src/widget/checkbox.rs | 2 +- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/examples/checkbox/fonts/icons.ttf b/examples/checkbox/fonts/icons.ttf index a2046844..82f28481 100644 Binary files a/examples/checkbox/fonts/icons.ttf and b/examples/checkbox/fonts/icons.ttf differ diff --git a/examples/checkbox/src/main.rs b/examples/checkbox/src/main.rs index 09950bb8..77111490 100644 --- a/examples/checkbox/src/main.rs +++ b/examples/checkbox/src/main.rs @@ -1,10 +1,9 @@ +use iced::executor; +use iced::font::{self, Font}; use iced::widget::{checkbox, column, container}; -use iced::{Element, Font, Length, Sandbox, Settings}; +use iced::{Application, Command, Element, Length, Settings, Theme}; -const ICON_FONT: Font = Font::External { - name: "Icons", - bytes: include_bytes!("../fonts/icons.ttf"), -}; +const ICON_FONT: Font = Font::Name("icons"); pub fn main() -> iced::Result { Example::run(Settings::default()) @@ -20,24 +19,35 @@ struct Example { enum Message { DefaultChecked(bool), CustomChecked(bool), + FontLoaded(Result<(), font::Error>), } -impl Sandbox for Example { +impl Application for Example { type Message = Message; + type Flags = (); + type Executor = executor::Default; + type Theme = Theme; - fn new() -> Self { - Default::default() + fn new(_flags: Self::Flags) -> (Self, Command) { + ( + Self::default(), + font::load(include_bytes!("../fonts/icons.ttf").as_ref()) + .map(Message::FontLoaded), + ) } fn title(&self) -> String { String::from("Checkbox - Iced") } - fn update(&mut self, message: Message) { + fn update(&mut self, message: Message) -> Command { match message { Message::DefaultChecked(value) => self.default_checkbox = value, Message::CustomChecked(value) => self.custom_checkbox = value, + Message::FontLoaded(_) => (), } + + Command::none() } fn view(&self) -> Element { diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 0e21e995..6ba06d3b 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -267,7 +267,7 @@ where code_point, size, } = &self.icon; - let size = size.map(f32::from).unwrap_or(bounds.height * 0.7); + let size = size.unwrap_or(bounds.height * 0.7); if self.is_checked { renderer.fill_text(text::Text { -- cgit From 8059c40142d601588e01c152ce1bb72a1295dde8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 24 Feb 2023 13:58:17 +0100 Subject: Fix `clippy` lints --- native/src/overlay/menu.rs | 2 +- native/src/widget/checkbox.rs | 2 +- native/src/widget/pick_list.rs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index 0509f7bc..bd58a122 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -451,7 +451,7 @@ where let text_size = self.text_size.unwrap_or_else(|| renderer.default_size()); let option_height = - (text_size * 1.2 + f32::from(self.padding.vertical())) as usize; + (text_size * 1.2 + self.padding.vertical()) as usize; let offset = viewport.y - bounds.y; let start = (offset / option_height as f32) as usize; diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 6ba06d3b..cd8b9c6b 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -272,7 +272,7 @@ where if self.is_checked { renderer.fill_text(text::Text { content: &code_point.to_string(), - font: font.clone(), + font: *font, size, bounds: Rectangle { x: bounds.center_x(), diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index b4cda748..8ff82f3b 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -600,12 +600,12 @@ pub fn draw<'a, T, Renderer>( font, code_point, size, - }) => Some((font.clone(), *code_point, *size)), + }) => Some((*font, *code_point, *size)), Handle::Dynamic { open, closed } => { if state().is_open { - Some((open.font.clone(), open.code_point, open.size)) + Some((open.font, open.code_point, open.size)) } else { - Some((closed.font.clone(), closed.code_point, closed.size)) + Some((closed.font, closed.code_point, closed.size)) } } Handle::None => None, -- cgit