From fe49cc8eb534e4607fd4340292e63c1c8d0c57c0 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Sat, 25 Jul 2026 19:24:32 +0300 Subject: [PATCH] revert(web): drop vendored iced renderer Keep iced_wgpu external and leave the WebGPU storage-buffer optimization out of scope. --- Cargo.lock | 2 + Cargo.toml | 4 - crates/iced_wgpu/Cargo.toml | 146 --- crates/iced_wgpu/LICENSE | 19 - crates/iced_wgpu/OCS_PATCH.md | 8 - crates/iced_wgpu/README.md | 20 - crates/iced_wgpu/src/buffer.rs | 132 --- crates/iced_wgpu/src/color.rs | 205 ---- crates/iced_wgpu/src/engine.rs | 78 -- crates/iced_wgpu/src/geometry.rs | 785 -------------- crates/iced_wgpu/src/image/atlas.rs | 542 ---------- .../iced_wgpu/src/image/atlas/allocation.rs | 52 - crates/iced_wgpu/src/image/atlas/allocator.rs | 108 -- crates/iced_wgpu/src/image/atlas/entry.rs | 27 - crates/iced_wgpu/src/image/atlas/layer.rs | 22 - crates/iced_wgpu/src/image/cache.rs | 636 ----------- crates/iced_wgpu/src/image/mod.rs | 758 -------------- crates/iced_wgpu/src/image/null.rs | 16 - crates/iced_wgpu/src/image/raster.rs | 123 --- crates/iced_wgpu/src/image/vector.rs | 231 ---- crates/iced_wgpu/src/layer.rs | 405 ------- crates/iced_wgpu/src/lib.rs | 984 ------------------ crates/iced_wgpu/src/primitive.rs | 242 ----- crates/iced_wgpu/src/quad.rs | 362 ------- crates/iced_wgpu/src/quad/gradient.rs | 187 ---- crates/iced_wgpu/src/quad/solid.rs | 162 --- crates/iced_wgpu/src/settings.rs | 84 -- crates/iced_wgpu/src/shader/blit.wgsl | 37 - crates/iced_wgpu/src/shader/color.wgsl | 14 - .../src/shader/color/linear_rgb.wgsl | 3 - crates/iced_wgpu/src/shader/image.wgsl | 129 --- crates/iced_wgpu/src/shader/quad.wgsl | 13 - .../iced_wgpu/src/shader/quad/gradient.wgsl | 184 ---- crates/iced_wgpu/src/shader/quad/solid.wgsl | 102 -- crates/iced_wgpu/src/shader/triangle.wgsl | 5 - .../src/shader/triangle/gradient.wgsl | 127 --- .../iced_wgpu/src/shader/triangle/solid.wgsl | 24 - crates/iced_wgpu/src/shader/vertex.wgsl | 7 - crates/iced_wgpu/src/text.rs | 648 ------------ crates/iced_wgpu/src/triangle.rs | 963 ----------------- crates/iced_wgpu/src/triangle/msaa.rs | 371 ------- crates/iced_wgpu/src/window.rs | 5 - crates/iced_wgpu/src/window/compositor.rs | 358 ------- src/scene/pipeline/mod.rs | 10 +- src/scene/pipeline/wire_gpu.rs | 51 +- src/scene/view/render.rs | 3 - 46 files changed, 47 insertions(+), 9347 deletions(-) delete mode 100644 crates/iced_wgpu/Cargo.toml delete mode 100644 crates/iced_wgpu/LICENSE delete mode 100644 crates/iced_wgpu/OCS_PATCH.md delete mode 100644 crates/iced_wgpu/README.md delete mode 100644 crates/iced_wgpu/src/buffer.rs delete mode 100644 crates/iced_wgpu/src/color.rs delete mode 100644 crates/iced_wgpu/src/engine.rs delete mode 100644 crates/iced_wgpu/src/geometry.rs delete mode 100644 crates/iced_wgpu/src/image/atlas.rs delete mode 100644 crates/iced_wgpu/src/image/atlas/allocation.rs delete mode 100644 crates/iced_wgpu/src/image/atlas/allocator.rs delete mode 100644 crates/iced_wgpu/src/image/atlas/entry.rs delete mode 100644 crates/iced_wgpu/src/image/atlas/layer.rs delete mode 100644 crates/iced_wgpu/src/image/cache.rs delete mode 100644 crates/iced_wgpu/src/image/mod.rs delete mode 100644 crates/iced_wgpu/src/image/null.rs delete mode 100644 crates/iced_wgpu/src/image/raster.rs delete mode 100644 crates/iced_wgpu/src/image/vector.rs delete mode 100644 crates/iced_wgpu/src/layer.rs delete mode 100644 crates/iced_wgpu/src/lib.rs delete mode 100644 crates/iced_wgpu/src/primitive.rs delete mode 100644 crates/iced_wgpu/src/quad.rs delete mode 100644 crates/iced_wgpu/src/quad/gradient.rs delete mode 100644 crates/iced_wgpu/src/quad/solid.rs delete mode 100644 crates/iced_wgpu/src/settings.rs delete mode 100644 crates/iced_wgpu/src/shader/blit.wgsl delete mode 100644 crates/iced_wgpu/src/shader/color.wgsl delete mode 100644 crates/iced_wgpu/src/shader/color/linear_rgb.wgsl delete mode 100644 crates/iced_wgpu/src/shader/image.wgsl delete mode 100644 crates/iced_wgpu/src/shader/quad.wgsl delete mode 100644 crates/iced_wgpu/src/shader/quad/gradient.wgsl delete mode 100644 crates/iced_wgpu/src/shader/quad/solid.wgsl delete mode 100644 crates/iced_wgpu/src/shader/triangle.wgsl delete mode 100644 crates/iced_wgpu/src/shader/triangle/gradient.wgsl delete mode 100644 crates/iced_wgpu/src/shader/triangle/solid.wgsl delete mode 100644 crates/iced_wgpu/src/shader/vertex.wgsl delete mode 100644 crates/iced_wgpu/src/text.rs delete mode 100644 crates/iced_wgpu/src/triangle.rs delete mode 100644 crates/iced_wgpu/src/triangle/msaa.rs delete mode 100644 crates/iced_wgpu/src/window.rs delete mode 100644 crates/iced_wgpu/src/window/compositor.rs diff --git a/Cargo.lock b/Cargo.lock index 1f515372..48ec007e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2306,6 +2306,8 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff144a999b0ca0f8a10257934500060240825c42e950ec0ebee9c8ae30561c13" dependencies = [ "bitflags 2.13.1", "bytemuck", diff --git a/Cargo.toml b/Cargo.toml index 7ddbfb82..f4aedb79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,10 +92,6 @@ windows-sys = { version = "0.61", features = ["Win32_UI_Shell", "Win32_UI_Window [patch.crates-io] # Track the verified DWG round-trip, I/O, and unified PERF fixes. acadrust = { git = "https://github.com/OpenAEC-Foundation/acadifc.git", rev = "bee1a58" } -# iced_wgpu 0.14 requests WebGL2 limits on every wasm adapter, including -# BrowserWebGpu. Keep the release source local with the one adapter-aware limit -# fix so WebGPU can expose storage buffers while WebGL2 remains the fallback. -iced_wgpu = { path = "crates/iced_wgpu" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] # Native enables the plugin host runtime (out-of-process plugins). diff --git a/crates/iced_wgpu/Cargo.toml b/crates/iced_wgpu/Cargo.toml deleted file mode 100644 index b80d3cf0..00000000 --- a/crates/iced_wgpu/Cargo.toml +++ /dev/null @@ -1,146 +0,0 @@ -# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO -# -# When uploading crates to the registry Cargo will automatically -# "normalize" Cargo.toml files for maximal compatibility -# with all versions of Cargo and also rewrite `path` dependencies -# to registry (e.g., crates.io) dependencies. -# -# If you are reading this file be aware that the original Cargo.toml -# will likely look very different (and much more reasonable). -# See Cargo.toml.orig for the original contents. - -[package] -edition = "2024" -name = "iced_wgpu" -version = "0.14.0" -authors = ["Héctor Ramón Jiménez "] -build = false -autolib = false -autobins = false -autoexamples = false -autotests = false -autobenches = false -description = "A renderer for iced on top of wgpu" -homepage = "https://iced.rs" -readme = "README.md" -keywords = [ - "gui", - "ui", - "graphics", - "interface", - "widgets", -] -categories = ["gui"] -license = "MIT" -repository = "https://github.com/iced-rs/iced" - -[package.metadata.docs.rs] -rustdoc-args = [ - "--cfg", - "docsrs", -] -all-features = true - -[features] -default = ["wgpu/default"] -geometry = [ - "iced_graphics/geometry", - "lyon", -] -image = ["iced_graphics/image"] -strict-assertions = [] -svg = [ - "iced_graphics/svg", - "resvg/text", -] -web-colors = ["iced_graphics/web-colors"] -webgl = ["wgpu/webgl"] - -[lib] -name = "iced_wgpu" -path = "src/lib.rs" - -[dependencies.bitflags] -version = "2.0" - -[dependencies.bytemuck] -version = "1.0" -features = ["derive"] - -[dependencies.cryoglyph] -version = "0.1" - -[dependencies.futures] -version = "0.3" -features = [ - "std", - "async-await", -] -default-features = false - -[dependencies.glam] -version = "0.25" - -[dependencies.guillotiere] -version = "0.6" - -[dependencies.iced_debug] -version = "0.14.0" - -[dependencies.iced_graphics] -version = "0.14.0" - -[dependencies.log] -version = "0.4" - -[dependencies.lyon] -version = "1.0" -optional = true - -[dependencies.resvg] -version = "0.45" -optional = true - -[dependencies.rustc-hash] -version = "2.0" - -[dependencies.thiserror] -version = "2" - -[dependencies.wgpu] -version = "27.0" -features = [ - "std", - "wgsl", -] -default-features = false - -[lints.clippy] -default_trait_access = "deny" -filter_map_next = "deny" -from_over_into = "deny" -large-enum-variant = "allow" -manual_let_else = "deny" -map-entry = "allow" -match-wildcard-for-single-variants = "deny" -needless_borrow = "deny" -new_without_default = "deny" -redundant-closure-for-method-calls = "deny" -result_large_err = "allow" -semicolon_if_nothing_returned = "deny" -trivially-copy-pass-by-ref = "deny" -type-complexity = "allow" -unused_async = "deny" -useless_conversion = "deny" - -[lints.rust] -missing_docs = "deny" -unsafe_code = "deny" -unused_results = "deny" - -[lints.rust.rust_2018_idioms] -level = "deny" -priority = -1 - -[lints.rustdoc] -broken_intra_doc_links = "forbid" diff --git a/crates/iced_wgpu/LICENSE b/crates/iced_wgpu/LICENSE deleted file mode 100644 index 2c16cff0..00000000 --- a/crates/iced_wgpu/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright 2019 Héctor Ramón, Iced contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/crates/iced_wgpu/OCS_PATCH.md b/crates/iced_wgpu/OCS_PATCH.md deleted file mode 100644 index 40d42737..00000000 --- a/crates/iced_wgpu/OCS_PATCH.md +++ /dev/null @@ -1,8 +0,0 @@ -# OpenCADStudio patch - -This directory vendors `iced_wgpu` 0.14.0 under its MIT license. - -OpenCADStudio changes only the WASM device-limit selection in -`src/window/compositor.rs`: Browser WebGPU first requests normal WebGPU limits -so storage-buffer pipelines are available, then falls back to WebGL2 limits. -An actual WebGL adapter continues to request WebGL2 limits directly. diff --git a/crates/iced_wgpu/README.md b/crates/iced_wgpu/README.md deleted file mode 100644 index 8e9602ea..00000000 --- a/crates/iced_wgpu/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# `iced_wgpu` -[![Documentation](https://docs.rs/iced_wgpu/badge.svg)][documentation] -[![Crates.io](https://img.shields.io/crates/v/iced_wgpu.svg)](https://crates.io/crates/iced_wgpu) -[![License](https://img.shields.io/crates/l/iced_wgpu.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_wgpu` is a [`wgpu`] renderer for [`iced_runtime`]. For now, it is the default renderer of Iced on [native platforms]. - -[`wgpu`] supports most modern graphics backends: Vulkan, Metal, DX12, OpenGL, and WebGPU. - -

- The native target -

- -[documentation]: https://docs.rs/iced_wgpu -[`iced_runtime`]: ../runtime -[`wgpu`]: https://github.com/gfx-rs/wgpu -[native platforms]: https://github.com/gfx-rs/wgpu#supported-platforms -[WebGPU API]: https://gpuweb.github.io/gpuweb/ -[`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph diff --git a/crates/iced_wgpu/src/buffer.rs b/crates/iced_wgpu/src/buffer.rs deleted file mode 100644 index 172922da..00000000 --- a/crates/iced_wgpu/src/buffer.rs +++ /dev/null @@ -1,132 +0,0 @@ -use std::marker::PhantomData; -use std::num::NonZeroU64; -use std::ops::RangeBounds; - -pub const MAX_WRITE_SIZE: usize = 100 * 1024; - -const MAX_WRITE_SIZE_U64: NonZeroU64 = NonZeroU64::new(MAX_WRITE_SIZE as u64) - .expect("MAX_WRITE_SIZE must be non-zero"); - -#[derive(Debug)] -pub struct Buffer { - label: &'static str, - size: u64, - usage: wgpu::BufferUsages, - pub(crate) 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 = next_copy_size::(new_count); - - 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 - } - } - - /// Returns the size of the written bytes. - pub fn write( - &mut self, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - belt: &mut wgpu::util::StagingBelt, - offset: usize, - contents: &[T], - ) -> usize { - let bytes: &[u8] = bytemuck::cast_slice(contents); - let mut bytes_written = 0; - - // Split write into multiple chunks if necessary - while bytes_written + MAX_WRITE_SIZE < bytes.len() { - belt.write_buffer( - encoder, - &self.raw, - (offset + bytes_written) as u64, - MAX_WRITE_SIZE_U64, - device, - ) - .copy_from_slice( - &bytes[bytes_written..bytes_written + MAX_WRITE_SIZE], - ); - - bytes_written += MAX_WRITE_SIZE; - } - - // There will always be some bytes left, since the previous - // loop guarantees `bytes_written < bytes.len()` - let bytes_left = ((bytes.len() - bytes_written) as u64) - .try_into() - .expect("non-empty write"); - - // Write them - belt.write_buffer( - encoder, - &self.raw, - (offset + bytes_written) as u64, - bytes_left, - device, - ) - .copy_from_slice(&bytes[bytes_written..]); - - bytes.len() - } - - pub fn slice( - &self, - bounds: impl RangeBounds, - ) -> wgpu::BufferSlice<'_> { - self.raw.slice(bounds) - } - - pub fn range(&self, start: usize, end: usize) -> wgpu::BufferSlice<'_> { - self.slice( - start as u64 * std::mem::size_of::() as u64 - ..end as u64 * std::mem::size_of::() as u64, - ) - } -} - -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/crates/iced_wgpu/src/color.rs b/crates/iced_wgpu/src/color.rs deleted file mode 100644 index 52fe8463..00000000 --- a/crates/iced_wgpu/src/color.rs +++ /dev/null @@ -1,205 +0,0 @@ -use std::borrow::Cow; - -use wgpu::util::DeviceExt; - -pub fn convert( - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - source: wgpu::Texture, - format: wgpu::TextureFormat, -) -> wgpu::Texture { - if source.format() == format { - return source; - } - - let sampler = device.create_sampler(&wgpu::SamplerDescriptor { - label: Some("iced_wgpu.offscreen.sampler"), - ..wgpu::SamplerDescriptor::default() - }); - - #[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)] - #[repr(C)] - struct Ratio { - u: f32, - v: f32, - // Padding field for 16-byte alignment. - // See https://docs.rs/wgpu/latest/wgpu/struct.DownlevelFlags.html#associatedconstant.BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED - _padding: [f32; 2], - } - - let ratio = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("iced-wgpu::triangle::msaa ratio"), - contents: bytemuck::bytes_of(&Ratio { - u: 1.0, - v: 1.0, - _padding: [0.0; 2], - }), - usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM, - }); - - let constant_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("iced_wgpu.offscreen.blit.sampler_layout"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler( - wgpu::SamplerBindingType::NonFiltering, - ), - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - ], - }); - - let constant_bind_group = - device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu.offscreen.sampler.bind_group"), - layout: &constant_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Sampler(&sampler), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: ratio.as_entire_binding(), - }, - ], - }); - - let texture_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("iced_wgpu.offscreen.blit.texture_layout"), - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { - filterable: false, - }, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, - }, - count: None, - }], - }); - - let pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("iced_wgpu.offscreen.blit.pipeline_layout"), - bind_group_layouts: &[&constant_layout, &texture_layout], - push_constant_ranges: &[], - }); - - let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("iced_wgpu.offscreen.blit.shader"), - source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!( - "shader/blit.wgsl" - ))), - }); - - let pipeline = - device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("iced_wgpu.offscreen.blit.pipeline"), - layout: Some(&pipeline_layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: Some("vs_main"), - buffers: &[], - compilation_options: wgpu::PipelineCompilationOptions::default( - ), - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: Some("fs_main"), - targets: &[Some(wgpu::ColorTargetState { - format, - blend: Some(wgpu::BlendState { - color: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::SrcAlpha, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - alpha: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::One, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - }), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: wgpu::PipelineCompilationOptions::default( - ), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - front_face: wgpu::FrontFace::Cw, - ..wgpu::PrimitiveState::default() - }, - depth_stencil: None, - multisample: wgpu::MultisampleState::default(), - multiview: None, - cache: None, - }); - - let texture = device.create_texture(&wgpu::TextureDescriptor { - label: Some("iced_wgpu.offscreen.conversion.source_texture"), - size: source.size(), - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT - | wgpu::TextureUsages::COPY_SRC, - view_formats: &[], - }); - - let view = &texture.create_view(&wgpu::TextureViewDescriptor::default()); - - let texture_bind_group = - device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu.offscreen.blit.texture_bind_group"), - layout: &texture_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView( - &source - .create_view(&wgpu::TextureViewDescriptor::default()), - ), - }], - }); - - let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("iced_wgpu.offscreen.blit.render_pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view, - depth_slice: None, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - - pass.set_pipeline(&pipeline); - pass.set_bind_group(0, &constant_bind_group, &[]); - pass.set_bind_group(1, &texture_bind_group, &[]); - pass.draw(0..6, 0..1); - - texture -} diff --git a/crates/iced_wgpu/src/engine.rs b/crates/iced_wgpu/src/engine.rs deleted file mode 100644 index d86499eb..00000000 --- a/crates/iced_wgpu/src/engine.rs +++ /dev/null @@ -1,78 +0,0 @@ -use crate::graphics::{Antialiasing, Shell}; -use crate::primitive; -use crate::quad; -use crate::text; -use crate::triangle; - -use std::sync::{Arc, RwLock}; - -#[derive(Clone)] -pub struct Engine { - pub(crate) device: wgpu::Device, - pub(crate) queue: wgpu::Queue, - pub(crate) format: wgpu::TextureFormat, - - pub(crate) quad_pipeline: quad::Pipeline, - pub(crate) text_pipeline: text::Pipeline, - pub(crate) triangle_pipeline: triangle::Pipeline, - #[cfg(any(feature = "image", feature = "svg"))] - pub(crate) image_pipeline: crate::image::Pipeline, - pub(crate) primitive_storage: Arc>, - _shell: Shell, -} - -impl Engine { - pub fn new( - _adapter: &wgpu::Adapter, - device: wgpu::Device, - queue: wgpu::Queue, - format: wgpu::TextureFormat, - antialiasing: Option, // TODO: Initialize AA pipelines lazily - shell: Shell, - ) -> Self { - Self { - format, - - quad_pipeline: quad::Pipeline::new(&device, format), - text_pipeline: text::Pipeline::new(&device, &queue, format), - triangle_pipeline: triangle::Pipeline::new( - &device, - format, - antialiasing, - ), - - #[cfg(any(feature = "image", feature = "svg"))] - image_pipeline: { - let backend = _adapter.get_info().backend; - - crate::image::Pipeline::new(&device, format, backend) - }, - - primitive_storage: Arc::new(RwLock::new( - primitive::Storage::default(), - )), - - device, - queue, - _shell: shell, - } - } - - #[cfg(any(feature = "image", feature = "svg"))] - pub fn create_image_cache(&self) -> crate::image::Cache { - self.image_pipeline.create_cache( - &self.device, - &self.queue, - &self._shell, - ) - } - - pub fn trim(&mut self) { - self.text_pipeline.trim(); - - self.primitive_storage - .write() - .expect("primitive storage should be writable") - .trim(); - } -} diff --git a/crates/iced_wgpu/src/geometry.rs b/crates/iced_wgpu/src/geometry.rs deleted file mode 100644 index c1433c45..00000000 --- a/crates/iced_wgpu/src/geometry.rs +++ /dev/null @@ -1,785 +0,0 @@ -//! Build and draw geometry. -use crate::core::text::LineHeight; -use crate::core::{ - self, Pixels, Point, Radians, Rectangle, Size, Svg, Transformation, Vector, -}; -use crate::graphics::cache::{self, Cached}; -use crate::graphics::color; -use crate::graphics::geometry::fill::{self, Fill}; -use crate::graphics::geometry::{ - self, LineCap, LineDash, LineJoin, Path, Stroke, Style, -}; -use crate::graphics::gradient::{self, Gradient}; -use crate::graphics::mesh::{self, Mesh}; -use crate::graphics::{Image, Text}; -use crate::text; - -use lyon::geom::euclid; -use lyon::tessellation; - -use std::borrow::Cow; -use std::sync::Arc; - -#[derive(Debug)] -pub enum Geometry { - Live { - meshes: Vec, - images: Vec, - text: Vec, - }, - Cached(Cache), -} - -#[derive(Debug, Clone, Default)] -pub struct Cache { - pub meshes: Option, - pub images: Option>, - pub text: Option, -} - -impl Cached for Geometry { - type Cache = Cache; - - fn load(cache: &Self::Cache) -> Self { - Geometry::Cached(cache.clone()) - } - - fn cache( - self, - group: cache::Group, - previous: Option, - ) -> Self::Cache { - match self { - Self::Live { - meshes, - images, - text, - } => { - let images = if images.is_empty() { - None - } else { - Some(Arc::from(images)) - }; - - let meshes = Arc::from(meshes); - - if let Some(mut previous) = previous { - if let Some(cache) = &mut previous.meshes { - cache.update(meshes); - } else { - previous.meshes = if meshes.is_empty() { - None - } else { - Some(mesh::Cache::new(meshes)) - }; - } - - if let Some(cache) = &mut previous.text { - cache.update(text); - } else { - previous.text = text::Cache::new(group, text); - } - - previous.images = images; - - previous - } else { - Cache { - meshes: if meshes.is_empty() { - None - } else { - Some(mesh::Cache::new(meshes)) - }, - images, - text: text::Cache::new(group, text), - } - } - } - Self::Cached(cache) => cache, - } - } -} - -/// A frame for drawing some geometry. -pub struct Frame { - clip_bounds: Rectangle, - buffers: BufferStack, - meshes: Vec, - images: Vec, - text: Vec, - transforms: Transforms, - fill_tessellator: tessellation::FillTessellator, - stroke_tessellator: tessellation::StrokeTessellator, -} - -impl Frame { - /// Creates a new [`Frame`] with the given clip bounds. - pub fn new(bounds: Rectangle) -> Frame { - Frame { - clip_bounds: bounds, - buffers: BufferStack::new(), - meshes: Vec::new(), - images: Vec::new(), - text: Vec::new(), - transforms: Transforms { - previous: Vec::new(), - current: Transform(lyon::math::Transform::identity()), - }, - fill_tessellator: tessellation::FillTessellator::new(), - stroke_tessellator: tessellation::StrokeTessellator::new(), - } - } -} - -impl geometry::frame::Backend for Frame { - type Geometry = Geometry; - - #[inline] - fn width(&self) -> f32 { - self.clip_bounds.width - } - - #[inline] - fn height(&self) -> f32 { - self.clip_bounds.height - } - - #[inline] - fn size(&self) -> Size { - self.clip_bounds.size() - } - - #[inline] - fn center(&self) -> Point { - Point::new(self.clip_bounds.width / 2.0, self.clip_bounds.height / 2.0) - } - - fn fill(&mut self, path: &Path, fill: impl Into) { - let Fill { style, rule } = fill.into(); - - let mut buffer = self - .buffers - .get_fill(&self.transforms.current.transform_style(style)); - - let options = tessellation::FillOptions::default() - .with_fill_rule(into_fill_rule(rule)); - - if self.transforms.current.is_identity() { - self.fill_tessellator.tessellate_path( - path.raw(), - &options, - buffer.as_mut(), - ) - } else { - let path = path.transform(&self.transforms.current.0); - - self.fill_tessellator.tessellate_path( - path.raw(), - &options, - buffer.as_mut(), - ) - } - .expect("Tessellate path."); - } - - fn fill_rectangle( - &mut self, - top_left: Point, - size: Size, - fill: impl Into, - ) { - let Fill { style, rule } = fill.into(); - - let mut buffer = self - .buffers - .get_fill(&self.transforms.current.transform_style(style)); - - let top_left = self - .transforms - .current - .0 - .transform_point(lyon::math::Point::new(top_left.x, top_left.y)); - - let size = - self.transforms.current.0.transform_vector( - lyon::math::Vector::new(size.width, size.height), - ); - - let options = tessellation::FillOptions::default() - .with_fill_rule(into_fill_rule(rule)); - - self.fill_tessellator - .tessellate_rectangle( - &lyon::math::Box2D::new(top_left, top_left + size), - &options, - buffer.as_mut(), - ) - .expect("Fill rectangle"); - } - - fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { - let stroke = stroke.into(); - - let mut buffer = self - .buffers - .get_stroke(&self.transforms.current.transform_style(stroke.style)); - - let mut options = tessellation::StrokeOptions::default(); - options.line_width = stroke.width; - options.start_cap = into_line_cap(stroke.line_cap); - options.end_cap = into_line_cap(stroke.line_cap); - options.line_join = into_line_join(stroke.line_join); - - let path = if stroke.line_dash.segments.is_empty() { - Cow::Borrowed(path) - } else { - Cow::Owned(dashed(path, stroke.line_dash)) - }; - - if self.transforms.current.is_identity() { - self.stroke_tessellator.tessellate_path( - path.raw(), - &options, - buffer.as_mut(), - ) - } else { - let path = path.transform(&self.transforms.current.0); - - self.stroke_tessellator.tessellate_path( - path.raw(), - &options, - buffer.as_mut(), - ) - } - .expect("Stroke path"); - } - - fn stroke_rectangle<'a>( - &mut self, - top_left: Point, - size: Size, - stroke: impl Into>, - ) { - let stroke = stroke.into(); - - let mut buffer = self - .buffers - .get_stroke(&self.transforms.current.transform_style(stroke.style)); - - let top_left = self - .transforms - .current - .0 - .transform_point(lyon::math::Point::new(top_left.x, top_left.y)); - - let size = - self.transforms.current.0.transform_vector( - lyon::math::Vector::new(size.width, size.height), - ); - - let mut options = tessellation::StrokeOptions::default(); - options.line_width = stroke.width; - options.start_cap = into_line_cap(stroke.line_cap); - options.end_cap = into_line_cap(stroke.line_cap); - options.line_join = into_line_join(stroke.line_join); - - self.stroke_tessellator - .tessellate_rectangle( - &lyon::math::Box2D::new(top_left, top_left + size), - &options, - buffer.as_mut(), - ) - .expect("Stroke rectangle"); - } - - fn stroke_text<'a>( - &mut self, - text: impl Into, - stroke: impl Into>, - ) { - let text = text.into(); - let stroke = stroke.into(); - - text.draw_with(|glyph, _color| self.stroke(&glyph, stroke)); - } - - fn fill_text(&mut self, text: impl Into) { - let text = text.into(); - - let (scale_x, scale_y) = self.transforms.current.scale(); - - if self.transforms.current.is_scale_translation() - && scale_x == scale_y - && scale_x > 0.0 - && scale_y > 0.0 - { - let (bounds, size, line_height) = - if self.transforms.current.is_identity() { - ( - Rectangle::new( - text.position, - Size::new(text.max_width, f32::INFINITY), - ), - text.size, - text.line_height, - ) - } else { - let position = - self.transforms.current.transform_point(text.position); - - let size = Pixels(text.size.0 * scale_y); - - let line_height = match text.line_height { - LineHeight::Absolute(size) => { - LineHeight::Absolute(Pixels(size.0 * scale_y)) - } - LineHeight::Relative(factor) => { - LineHeight::Relative(factor) - } - }; - - ( - Rectangle::new( - position, - Size::new(text.max_width, f32::INFINITY), - ), - size, - line_height, - ) - }; - - self.text.push(Text::Cached { - content: text.content, - bounds, - color: text.color, - size, - line_height: line_height.to_absolute(size), - font: text.font, - align_x: text.align_x, - align_y: text.align_y, - shaping: text.shaping, - clip_bounds: self.clip_bounds, - }); - } else { - text.draw_with(|path, color| self.fill(&path, color)); - } - } - - #[inline] - fn translate(&mut self, translation: Vector) { - self.transforms.current.0 = - self.transforms - .current - .0 - .pre_translate(lyon::math::Vector::new( - translation.x, - translation.y, - )); - } - - #[inline] - fn rotate(&mut self, angle: impl Into) { - self.transforms.current.0 = self - .transforms - .current - .0 - .pre_rotate(lyon::math::Angle::radians(angle.into().0)); - } - - #[inline] - fn scale(&mut self, scale: impl Into) { - let scale = scale.into(); - - self.scale_nonuniform(Vector { x: scale, y: scale }); - } - - #[inline] - fn scale_nonuniform(&mut self, scale: impl Into) { - let scale = scale.into(); - - self.transforms.current.0 = - self.transforms.current.0.pre_scale(scale.x, scale.y); - } - - fn push_transform(&mut self) { - self.transforms.previous.push(self.transforms.current); - } - - fn pop_transform(&mut self) { - self.transforms.current = self.transforms.previous.pop().unwrap(); - } - - fn draft(&mut self, clip_bounds: Rectangle) -> Frame { - Frame::new(clip_bounds) - } - - fn paste(&mut self, frame: Frame) { - self.meshes.extend(frame.meshes); - self.meshes - .extend(frame.buffers.into_meshes(frame.clip_bounds)); - - self.images.extend(frame.images); - self.text.extend(frame.text); - } - - fn into_geometry(mut self) -> Self::Geometry { - self.meshes - .extend(self.buffers.into_meshes(self.clip_bounds)); - - Geometry::Live { - meshes: self.meshes, - images: self.images, - text: self.text, - } - } - - fn draw_image(&mut self, bounds: Rectangle, image: impl Into) { - let mut image = image.into(); - - let (bounds, external_rotation) = - self.transforms.current.transform_rectangle(bounds); - - image.rotation += external_rotation; - image.border_radius = - image.border_radius * self.transforms.current.scale().0; - - self.images.push(Image::Raster { - image, - bounds, - clip_bounds: self.clip_bounds, - }); - } - - fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into) { - let mut svg = svg.into(); - - let (bounds, external_rotation) = - self.transforms.current.transform_rectangle(bounds); - - svg.rotation += external_rotation; - - self.images.push(Image::Vector { - svg, - bounds, - clip_bounds: self.clip_bounds, - }); - } -} - -enum Buffer { - Solid(tessellation::VertexBuffers), - Gradient(tessellation::VertexBuffers), -} - -struct BufferStack { - stack: Vec, -} - -impl BufferStack { - fn new() -> Self { - Self { stack: Vec::new() } - } - - fn get_mut(&mut self, style: &Style) -> &mut Buffer { - match style { - Style::Solid(_) => match self.stack.last() { - Some(Buffer::Solid(_)) => {} - _ => { - self.stack.push(Buffer::Solid( - tessellation::VertexBuffers::new(), - )); - } - }, - Style::Gradient(_) => match self.stack.last() { - Some(Buffer::Gradient(_)) => {} - _ => { - self.stack.push(Buffer::Gradient( - tessellation::VertexBuffers::new(), - )); - } - }, - } - - self.stack.last_mut().unwrap() - } - - fn get_fill<'a>( - &'a mut self, - style: &Style, - ) -> Box { - match (style, self.get_mut(style)) { - (Style::Solid(color), Buffer::Solid(buffer)) => { - Box::new(tessellation::BuffersBuilder::new( - buffer, - TriangleVertex2DBuilder(color::pack(*color)), - )) - } - (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { - Box::new(tessellation::BuffersBuilder::new( - buffer, - GradientVertex2DBuilder { - gradient: gradient.pack(), - }, - )) - } - _ => unreachable!(), - } - } - - fn get_stroke<'a>( - &'a mut self, - style: &Style, - ) -> Box { - match (style, self.get_mut(style)) { - (Style::Solid(color), Buffer::Solid(buffer)) => { - Box::new(tessellation::BuffersBuilder::new( - buffer, - TriangleVertex2DBuilder(color::pack(*color)), - )) - } - (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { - Box::new(tessellation::BuffersBuilder::new( - buffer, - GradientVertex2DBuilder { - gradient: gradient.pack(), - }, - )) - } - _ => unreachable!(), - } - } - - fn into_meshes(self, clip_bounds: Rectangle) -> impl Iterator { - self.stack - .into_iter() - .filter_map(move |buffer| match buffer { - Buffer::Solid(buffer) if !buffer.indices.is_empty() => { - Some(Mesh::Solid { - buffers: mesh::Indexed { - vertices: buffer.vertices, - indices: buffer.indices, - }, - clip_bounds, - transformation: Transformation::IDENTITY, - }) - } - Buffer::Gradient(buffer) if !buffer.indices.is_empty() => { - Some(Mesh::Gradient { - buffers: mesh::Indexed { - vertices: buffer.vertices, - indices: buffer.indices, - }, - clip_bounds, - transformation: Transformation::IDENTITY, - }) - } - _ => None, - }) - } -} - -#[derive(Debug)] -struct Transforms { - previous: Vec, - current: Transform, -} - -#[derive(Debug, Clone, Copy)] -struct Transform(lyon::math::Transform); - -impl Transform { - fn is_identity(&self) -> bool { - self.0 == lyon::math::Transform::identity() - } - - fn is_scale_translation(&self) -> bool { - self.0.m12.abs() < 2.0 * f32::EPSILON - && self.0.m21.abs() < 2.0 * f32::EPSILON - } - - fn scale(&self) -> (f32, f32) { - (self.0.m11, self.0.m22) - } - - fn transform_point(&self, point: Point) -> Point { - let transformed = self - .0 - .transform_point(euclid::Point2D::new(point.x, point.y)); - - Point { - x: transformed.x, - y: transformed.y, - } - } - - fn transform_style(&self, style: Style) -> Style { - match style { - Style::Solid(color) => Style::Solid(color), - Style::Gradient(gradient) => { - Style::Gradient(self.transform_gradient(gradient)) - } - } - } - - fn transform_gradient(&self, mut gradient: Gradient) -> Gradient { - match &mut gradient { - Gradient::Linear(linear) => { - linear.start = self.transform_point(linear.start); - linear.end = self.transform_point(linear.end); - } - } - - gradient - } - - fn transform_rectangle( - &self, - rectangle: Rectangle, - ) -> (Rectangle, Radians) { - let top_left = self.transform_point(rectangle.position()); - let top_right = self.transform_point( - rectangle.position() + Vector::new(rectangle.width, 0.0), - ); - let bottom_left = self.transform_point( - rectangle.position() + Vector::new(0.0, rectangle.height), - ); - - Rectangle::with_vertices(top_left, top_right, bottom_left) - } -} -struct GradientVertex2DBuilder { - gradient: gradient::Packed, -} - -impl tessellation::FillVertexConstructor - for GradientVertex2DBuilder -{ - fn new_vertex( - &mut self, - vertex: tessellation::FillVertex<'_>, - ) -> mesh::GradientVertex2D { - let position = vertex.position(); - - mesh::GradientVertex2D { - position: [position.x, position.y], - gradient: self.gradient, - } - } -} - -impl tessellation::StrokeVertexConstructor - for GradientVertex2DBuilder -{ - fn new_vertex( - &mut self, - vertex: tessellation::StrokeVertex<'_, '_>, - ) -> mesh::GradientVertex2D { - let position = vertex.position(); - - mesh::GradientVertex2D { - position: [position.x, position.y], - gradient: self.gradient, - } - } -} - -struct TriangleVertex2DBuilder(color::Packed); - -impl tessellation::FillVertexConstructor - for TriangleVertex2DBuilder -{ - fn new_vertex( - &mut self, - vertex: tessellation::FillVertex<'_>, - ) -> mesh::SolidVertex2D { - let position = vertex.position(); - - mesh::SolidVertex2D { - position: [position.x, position.y], - color: self.0, - } - } -} - -impl tessellation::StrokeVertexConstructor - for TriangleVertex2DBuilder -{ - fn new_vertex( - &mut self, - vertex: tessellation::StrokeVertex<'_, '_>, - ) -> mesh::SolidVertex2D { - let position = vertex.position(); - - mesh::SolidVertex2D { - position: [position.x, position.y], - color: self.0, - } - } -} - -fn into_line_join(line_join: LineJoin) -> lyon::tessellation::LineJoin { - match line_join { - LineJoin::Miter => lyon::tessellation::LineJoin::Miter, - LineJoin::Round => lyon::tessellation::LineJoin::Round, - LineJoin::Bevel => lyon::tessellation::LineJoin::Bevel, - } -} - -fn into_line_cap(line_cap: LineCap) -> lyon::tessellation::LineCap { - match line_cap { - LineCap::Butt => lyon::tessellation::LineCap::Butt, - LineCap::Square => lyon::tessellation::LineCap::Square, - LineCap::Round => lyon::tessellation::LineCap::Round, - } -} - -fn into_fill_rule(rule: fill::Rule) -> lyon::tessellation::FillRule { - match rule { - fill::Rule::NonZero => lyon::tessellation::FillRule::NonZero, - fill::Rule::EvenOdd => lyon::tessellation::FillRule::EvenOdd, - } -} - -pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path { - use lyon::algorithms::walk::{ - RepeatedPattern, WalkerEvent, walk_along_path, - }; - use lyon::path::iterator::PathIterator; - - Path::new(|builder| { - let segments_odd = (line_dash.segments.len() % 2 == 1) - .then(|| [line_dash.segments, line_dash.segments].concat()); - - let mut draw_line = false; - - walk_along_path( - path.raw().iter().flattened( - lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE, - ), - 0.0, - lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE, - &mut RepeatedPattern { - callback: |event: WalkerEvent<'_>| { - let point = Point { - x: event.position.x, - y: event.position.y, - }; - - if draw_line { - builder.line_to(point); - } else { - builder.move_to(point); - } - - draw_line = !draw_line; - - true - }, - index: line_dash.offset, - intervals: segments_odd - .as_deref() - .unwrap_or(line_dash.segments), - }, - ); - }) -} diff --git a/crates/iced_wgpu/src/image/atlas.rs b/crates/iced_wgpu/src/image/atlas.rs deleted file mode 100644 index bbd71d4e..00000000 --- a/crates/iced_wgpu/src/image/atlas.rs +++ /dev/null @@ -1,542 +0,0 @@ -pub mod entry; - -mod allocation; -mod allocator; -mod layer; - -pub use allocation::Allocation; -pub use entry::Entry; -pub use layer::Layer; - -use allocator::Allocator; - -pub const DEFAULT_SIZE: u32 = 2048; -pub const MAX_SIZE: u32 = 2048; - -use crate::core::Size; -use crate::graphics::color; - -use std::sync::Arc; - -#[derive(Debug)] -pub struct Atlas { - size: u32, - backend: wgpu::Backend, - texture: wgpu::Texture, - texture_view: wgpu::TextureView, - texture_bind_group: Arc, - texture_layout: wgpu::BindGroupLayout, - layers: Vec, -} - -impl Atlas { - pub fn new( - device: &wgpu::Device, - backend: wgpu::Backend, - texture_layout: wgpu::BindGroupLayout, - ) -> Self { - Self::with_size(device, backend, texture_layout, DEFAULT_SIZE) - } - - pub fn with_size( - device: &wgpu::Device, - backend: wgpu::Backend, - texture_layout: wgpu::BindGroupLayout, - size: u32, - ) -> Self { - let size = size.min(MAX_SIZE); - - let layers = match backend { - // On the GL backend we start with 2 layers, to help wgpu figure - // out that this texture is `GL_TEXTURE_2D_ARRAY` rather than `GL_TEXTURE_2D` - // https://github.com/gfx-rs/wgpu/blob/004e3efe84a320d9331371ed31fa50baa2414911/wgpu-hal/src/gles/mod.rs#L371 - wgpu::Backend::Gl => vec![Layer::Empty, Layer::Empty], - _ => vec![Layer::Empty], - }; - - let extent = wgpu::Extent3d { - width: size, - height: size, - depth_or_array_layers: layers.len() as u32, - }; - - let texture = device.create_texture(&wgpu::TextureDescriptor { - label: Some("iced_wgpu::image texture atlas"), - size: extent, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: if color::GAMMA_CORRECTION { - wgpu::TextureFormat::Rgba8UnormSrgb - } else { - wgpu::TextureFormat::Rgba8Unorm - }, - usage: wgpu::TextureUsages::COPY_DST - | wgpu::TextureUsages::COPY_SRC - | wgpu::TextureUsages::TEXTURE_BINDING, - view_formats: &[], - }); - - let texture_view = texture.create_view(&wgpu::TextureViewDescriptor { - dimension: Some(wgpu::TextureViewDimension::D2Array), - ..Default::default() - }); - - let texture_bind_group = - device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu::image texture atlas bind group"), - layout: &texture_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&texture_view), - }], - }); - - Atlas { - size, - backend, - texture, - texture_view, - texture_bind_group: Arc::new(texture_bind_group), - texture_layout, - layers, - } - } - - pub fn bind_group(&self) -> &Arc { - &self.texture_bind_group - } - - pub fn upload( - &mut self, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - belt: &mut wgpu::util::StagingBelt, - width: u32, - height: u32, - pixels: &[u8], - ) -> Option { - let entry = { - let current_size = self.layers.len(); - let entry = self.allocate(width, height)?; - - // We grow the internal texture after allocating if necessary - let new_layers = self.layers.len() - current_size; - self.grow(new_layers, device, encoder, self.backend); - - entry - }; - - log::debug!("Allocated atlas entry: {entry:?}"); - - match &entry { - Entry::Contiguous(allocation) => { - self.upload_allocation( - pixels, width, 0, allocation, device, encoder, belt, - ); - } - Entry::Fragmented { fragments, .. } => { - for fragment in fragments { - let (x, y) = fragment.position; - let offset = 4 * (y * width + x) as usize; - - self.upload_allocation( - pixels, - width, - offset, - &fragment.allocation, - device, - encoder, - belt, - ); - } - } - } - - if log::log_enabled!(log::Level::Debug) { - log::debug!( - "Atlas layers: {} (busy: {}, allocations: {})", - self.layers.len(), - self.layers.iter().filter(|layer| !layer.is_empty()).count(), - self.layers.iter().map(Layer::allocations).sum::(), - ); - } - - Some(entry) - } - - pub fn remove(&mut self, entry: &Entry) { - log::debug!("Removing atlas entry: {entry:?}"); - - match entry { - Entry::Contiguous(allocation) => { - self.deallocate(allocation); - } - Entry::Fragmented { fragments, .. } => { - for fragment in fragments { - self.deallocate(&fragment.allocation); - } - } - } - } - - fn allocate(&mut self, width: u32, height: u32) -> Option { - // Allocate one layer if texture fits perfectly - if width == self.size && height == self.size { - let mut empty_layers = self - .layers - .iter_mut() - .enumerate() - .filter(|(_, layer)| layer.is_empty()); - - if let Some((i, layer)) = empty_layers.next() { - *layer = Layer::Full; - - return Some(Entry::Contiguous(Allocation::Full { - layer: i, - size: self.size, - })); - } - - self.layers.push(Layer::Full); - - return Some(Entry::Contiguous(Allocation::Full { - layer: self.layers.len() - 1, - size: self.size, - })); - } - - // Split big textures across multiple layers - if width > self.size || height > self.size { - let mut fragments = Vec::new(); - let mut y = 0; - - while y < height { - let height = std::cmp::min(height - y, self.size); - let mut x = 0; - - while x < width { - let width = std::cmp::min(width - x, self.size); - - let allocation = self.allocate(width, height)?; - - if let Entry::Contiguous(allocation) = allocation { - fragments.push(entry::Fragment { - position: (x, y), - allocation, - }); - } - - x += width; - } - - y += height; - } - - return Some(Entry::Fragmented { - size: Size::new(width, height), - fragments, - }); - } - - // Try allocating on an existing layer - for (i, layer) in self.layers.iter_mut().enumerate() { - match layer { - Layer::Empty => { - let mut allocator = Allocator::new(self.size); - - if let Some(region) = allocator.allocate(width, height) { - *layer = Layer::Busy(allocator); - - return Some(Entry::Contiguous(Allocation::Partial { - region, - layer: i, - atlas_size: self.size, - })); - } - } - Layer::Busy(allocator) => { - if let Some(region) = allocator.allocate(width, height) { - return Some(Entry::Contiguous(Allocation::Partial { - region, - layer: i, - atlas_size: self.size, - })); - } - } - Layer::Full => {} - } - } - - // Create new layer with atlas allocator - let mut allocator = Allocator::new(self.size); - - if let Some(region) = allocator.allocate(width, height) { - self.layers.push(Layer::Busy(allocator)); - - return Some(Entry::Contiguous(Allocation::Partial { - region, - layer: self.layers.len() - 1, - atlas_size: self.size, - })); - } - - // We ran out of memory (?) - None - } - - fn deallocate(&mut self, allocation: &Allocation) { - log::debug!("Deallocating atlas: {allocation:?}"); - - match allocation { - Allocation::Full { layer, .. } => { - self.layers[*layer] = Layer::Empty; - } - Allocation::Partial { layer, region, .. } => { - let layer = &mut self.layers[*layer]; - - if let Layer::Busy(allocator) = layer { - allocator.deallocate(region); - - if allocator.is_empty() { - *layer = Layer::Empty; - } - } - } - } - } - - fn upload_allocation( - &self, - pixels: &[u8], - image_width: u32, - offset: usize, - allocation: &Allocation, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - belt: &mut wgpu::util::StagingBelt, - ) { - let (x, y) = allocation.position(); - let Size { width, height } = allocation.size(); - let layer = allocation.layer(); - let padding = allocation.padding(); - - // It is a webgpu requirement that: - // BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0 - // So we calculate bytes_per_row by rounding width up to the next - // multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT. - let bytes_per_row = (4 * (width + padding.width * 2)) - .next_multiple_of(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT) - as usize; - let total_bytes = - bytes_per_row * (height + padding.height * 2) as usize; - - let buffer_slice = belt.allocate( - wgpu::BufferSize::new(total_bytes as u64).unwrap(), - wgpu::BufferSize::new(8 * 4).unwrap(), - device, - ); - - const PIXEL: usize = 4; - - let mut fragment = buffer_slice.get_mapped_range_mut(); - let w = width as usize; - let h = height as usize; - let pad_w = padding.width as usize; - let pad_h = padding.height as usize; - let stride = PIXEL * w; - - // Copy image rows - for row in 0..h { - let src = offset + row * PIXEL * image_width as usize; - let dst = (row + pad_h) * bytes_per_row; - - fragment[dst + PIXEL * pad_w..dst + PIXEL * pad_w + stride] - .copy_from_slice(&pixels[src..src + stride]); - - // Add padding to the sides, if needed - for i in 0..pad_w { - fragment[dst + PIXEL * i..dst + PIXEL * (i + 1)] - .copy_from_slice(&pixels[src..src + PIXEL]); - - fragment[dst + stride + PIXEL * (pad_w + i) - ..dst + stride + PIXEL * (pad_w + i + 1)] - .copy_from_slice( - &pixels[src + stride - PIXEL..src + stride], - ); - } - } - - // Add padding on top and bottom - for row in 0..pad_h { - let dst_top = row * bytes_per_row; - let dst_bottom = (pad_h + h + row) * bytes_per_row; - let src_top = offset; - let src_bottom = offset + (h - 1) * PIXEL * image_width as usize; - - // Top - fragment[dst_top + PIXEL * pad_w..dst_top + PIXEL * (pad_w + w)] - .copy_from_slice(&pixels[src_top..src_top + PIXEL * w]); - - // Bottom - fragment - [dst_bottom + PIXEL * pad_w..dst_bottom + PIXEL * (pad_w + w)] - .copy_from_slice(&pixels[src_bottom..src_bottom + PIXEL * w]); - - // Corners - for i in 0..pad_w { - // Top left - fragment[dst_top + PIXEL * i..dst_top + PIXEL * (i + 1)] - .copy_from_slice(&pixels[offset..offset + PIXEL]); - - // Top right - fragment[dst_top + PIXEL * (w + pad_w + i) - ..dst_top + PIXEL * (w + pad_w + i + 1)] - .copy_from_slice( - &pixels[offset + PIXEL * (w - 1)..offset + PIXEL * w], - ); - - // Bottom left - fragment[dst_bottom + PIXEL * i..dst_bottom + PIXEL * (i + 1)] - .copy_from_slice(&pixels[src_bottom..src_bottom + PIXEL]); - - // Bottom right - fragment[dst_bottom + PIXEL * (w + pad_w + i) - ..dst_bottom + PIXEL * (w + pad_w + i + 1)] - .copy_from_slice( - &pixels[src_bottom + PIXEL * (w - 1) - ..src_bottom + PIXEL * w], - ); - } - } - - // Copy actual image - encoder.copy_buffer_to_texture( - wgpu::TexelCopyBufferInfo { - buffer: buffer_slice.buffer(), - layout: wgpu::TexelCopyBufferLayout { - offset: buffer_slice.offset(), - bytes_per_row: Some(bytes_per_row as u32), - rows_per_image: Some(height + padding.height * 2), - }, - }, - wgpu::TexelCopyTextureInfo { - texture: &self.texture, - mip_level: 0, - origin: wgpu::Origin3d { - x: x - padding.width, - y: y - padding.height, - z: layer as u32, - }, - aspect: wgpu::TextureAspect::default(), - }, - wgpu::Extent3d { - width: width + padding.width * 2, - height: height + padding.height * 2, - depth_or_array_layers: 1, - }, - ); - } - - fn grow( - &mut self, - amount: usize, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - backend: wgpu::Backend, - ) { - if amount == 0 { - return; - } - - // On the GL backend if layers.len() is a multiple of 6 we need to help wgpu figure out that this texture - // is still a `GL_TEXTURE_2D_ARRAY` rather than `GL_TEXTURE_CUBE_MAP` or `GL_TEXTURE_CUBE_ARRAY`. - // This will over-allocate some unused memory on GL, but it's better than not being able to - // grow the atlas past multiples of 6! - // https://github.com/gfx-rs/wgpu/blob/004e3efe84a320d9331371ed31fa50baa2414911/wgpu-hal/src/gles/mod.rs#L371 - let depth_or_array_layers = match backend { - wgpu::Backend::Gl if self.layers.len().is_multiple_of(6) => { - self.layers.len() as u32 + 1 - } - _ => self.layers.len() as u32, - }; - - let new_texture = device.create_texture(&wgpu::TextureDescriptor { - label: Some("iced_wgpu::image texture atlas"), - size: wgpu::Extent3d { - width: self.size, - height: self.size, - depth_or_array_layers, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: if color::GAMMA_CORRECTION { - wgpu::TextureFormat::Rgba8UnormSrgb - } else { - wgpu::TextureFormat::Rgba8Unorm - }, - usage: wgpu::TextureUsages::COPY_DST - | wgpu::TextureUsages::COPY_SRC - | wgpu::TextureUsages::TEXTURE_BINDING, - view_formats: &[], - }); - - let amount_to_copy = self.layers.len() - amount; - - for (i, layer) in - self.layers.iter_mut().take(amount_to_copy).enumerate() - { - if layer.is_empty() { - continue; - } - - encoder.copy_texture_to_texture( - wgpu::TexelCopyTextureInfo { - texture: &self.texture, - mip_level: 0, - origin: wgpu::Origin3d { - x: 0, - y: 0, - z: i as u32, - }, - aspect: wgpu::TextureAspect::default(), - }, - wgpu::TexelCopyTextureInfo { - texture: &new_texture, - mip_level: 0, - origin: wgpu::Origin3d { - x: 0, - y: 0, - z: i as u32, - }, - aspect: wgpu::TextureAspect::default(), - }, - wgpu::Extent3d { - width: self.size, - height: self.size, - depth_or_array_layers: 1, - }, - ); - } - - self.texture = new_texture; - self.texture_view = - self.texture.create_view(&wgpu::TextureViewDescriptor { - dimension: Some(wgpu::TextureViewDimension::D2Array), - ..Default::default() - }); - - self.texture_bind_group = - Arc::new(device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu::image texture atlas bind group"), - layout: &self.texture_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView( - &self.texture_view, - ), - }], - })); - } -} diff --git a/crates/iced_wgpu/src/image/atlas/allocation.rs b/crates/iced_wgpu/src/image/atlas/allocation.rs deleted file mode 100644 index 295a7a13..00000000 --- a/crates/iced_wgpu/src/image/atlas/allocation.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::core::Size; -use crate::image::atlas::allocator; - -#[derive(Debug)] -pub enum Allocation { - Partial { - layer: usize, - region: allocator::Region, - atlas_size: u32, - }, - Full { - layer: usize, - size: u32, - }, -} - -impl Allocation { - pub fn position(&self) -> (u32, u32) { - match self { - Allocation::Partial { region, .. } => region.position(), - Allocation::Full { .. } => (0, 0), - } - } - - pub fn size(&self) -> Size { - match self { - Allocation::Partial { region, .. } => region.size(), - Allocation::Full { size, .. } => Size::new(*size, *size), - } - } - - pub fn padding(&self) -> Size { - match self { - Allocation::Partial { region, .. } => region.padding(), - Allocation::Full { .. } => Size::new(0, 0), - } - } - - pub fn layer(&self) -> usize { - match self { - Allocation::Partial { layer, .. } => *layer, - Allocation::Full { layer, .. } => *layer, - } - } - - pub fn atlas_size(&self) -> u32 { - match self { - Allocation::Partial { atlas_size, .. } => *atlas_size, - Allocation::Full { size, .. } => *size, - } - } -} diff --git a/crates/iced_wgpu/src/image/atlas/allocator.rs b/crates/iced_wgpu/src/image/atlas/allocator.rs deleted file mode 100644 index 263619f6..00000000 --- a/crates/iced_wgpu/src/image/atlas/allocator.rs +++ /dev/null @@ -1,108 +0,0 @@ -use crate::core; - -use guillotiere::{AtlasAllocator, Size}; - -pub struct Allocator { - raw: AtlasAllocator, - allocations: usize, -} - -impl Allocator { - const PADDING: u32 = 1; - - pub fn new(size: u32) -> Allocator { - let raw = AtlasAllocator::new(Size::new(size as i32, size as i32)); - - Allocator { - raw, - allocations: 0, - } - } - - pub fn allocate(&mut self, width: u32, height: u32) -> Option { - let size = self.raw.size(); - - let padded_width = width + Self::PADDING * 2; - let padded_height = height + Self::PADDING * 2; - - let pad_width = padded_width as i32 <= size.width; - let pad_height = padded_height as i32 <= size.height; - - let mut allocation = self.raw.allocate(Size::new( - if pad_width { padded_width } else { width } as i32, - if pad_height { padded_height } else { height } as i32, - ))?; - - if pad_width { - allocation.rectangle.min.x += Self::PADDING as i32; - allocation.rectangle.max.x -= Self::PADDING as i32; - } - - if pad_height { - allocation.rectangle.min.y += Self::PADDING as i32; - allocation.rectangle.max.y -= Self::PADDING as i32; - } - - self.allocations += 1; - - Some(Region { - allocation, - padding: core::Size::new( - if pad_width { Self::PADDING } else { 0 }, - if pad_height { Self::PADDING } else { 0 }, - ), - }) - } - - pub fn deallocate(&mut self, region: &Region) { - self.raw.deallocate(region.allocation.id); - - self.allocations = self.allocations.saturating_sub(1); - } - - pub fn is_empty(&self) -> bool { - self.allocations == 0 - } - - pub fn allocations(&self) -> usize { - self.allocations - } -} - -pub struct Region { - allocation: guillotiere::Allocation, - padding: core::Size, -} - -impl Region { - pub fn position(&self) -> (u32, u32) { - let rectangle = &self.allocation.rectangle; - - (rectangle.min.x as u32, rectangle.min.y as u32) - } - - pub fn size(&self) -> core::Size { - let size = self.allocation.rectangle.size(); - - core::Size::new(size.width as u32, size.height as u32) - } - - pub fn padding(&self) -> crate::core::Size { - self.padding - } -} - -impl std::fmt::Debug for Allocator { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Allocator") - } -} - -impl std::fmt::Debug for Region { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Region") - .field("id", &self.allocation.id) - .field("rectangle", &self.allocation.rectangle) - .finish() - } -} diff --git a/crates/iced_wgpu/src/image/atlas/entry.rs b/crates/iced_wgpu/src/image/atlas/entry.rs deleted file mode 100644 index 7e4c92a2..00000000 --- a/crates/iced_wgpu/src/image/atlas/entry.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::core::Size; -use crate::image::atlas; - -#[derive(Debug)] -pub enum Entry { - Contiguous(atlas::Allocation), - Fragmented { - size: Size, - fragments: Vec, - }, -} - -impl Entry { - #[cfg(feature = "image")] - pub fn size(&self) -> Size { - match self { - Entry::Contiguous(allocation) => allocation.size(), - Entry::Fragmented { size, .. } => *size, - } - } -} - -#[derive(Debug)] -pub struct Fragment { - pub position: (u32, u32), - pub allocation: atlas::Allocation, -} diff --git a/crates/iced_wgpu/src/image/atlas/layer.rs b/crates/iced_wgpu/src/image/atlas/layer.rs deleted file mode 100644 index fd6788d9..00000000 --- a/crates/iced_wgpu/src/image/atlas/layer.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::image::atlas::Allocator; - -#[derive(Debug)] -pub enum Layer { - Empty, - Busy(Allocator), - Full, -} - -impl Layer { - pub fn is_empty(&self) -> bool { - matches!(self, Layer::Empty) - } - - pub fn allocations(&self) -> usize { - match self { - Layer::Empty => 0, - Layer::Busy(allocator) => allocator.allocations(), - Layer::Full => 1, - } - } -} diff --git a/crates/iced_wgpu/src/image/cache.rs b/crates/iced_wgpu/src/image/cache.rs deleted file mode 100644 index 5e8059e8..00000000 --- a/crates/iced_wgpu/src/image/cache.rs +++ /dev/null @@ -1,636 +0,0 @@ -use crate::core::{self, Size}; -use crate::graphics::Shell; -use crate::image::atlas::{self, Atlas}; - -#[cfg(all(feature = "image", not(target_arch = "wasm32")))] -use worker::Worker; - -#[cfg(feature = "image")] -use std::collections::HashMap; - -use std::sync::Arc; - -pub struct Cache { - atlas: Atlas, - #[cfg(feature = "image")] - raster: Raster, - #[cfg(feature = "svg")] - vector: crate::image::vector::Cache, - #[cfg(all(feature = "image", not(target_arch = "wasm32")))] - worker: Worker, -} - -impl Cache { - pub fn new( - device: &wgpu::Device, - _queue: &wgpu::Queue, - backend: wgpu::Backend, - layout: wgpu::BindGroupLayout, - _shell: &Shell, - ) -> Self { - #[cfg(all(feature = "image", not(target_arch = "wasm32")))] - let worker = - Worker::new(device, _queue, backend, layout.clone(), _shell); - - Self { - atlas: Atlas::new(device, backend, layout), - #[cfg(feature = "image")] - raster: Raster { - cache: crate::image::raster::Cache::default(), - pending: HashMap::new(), - belt: wgpu::util::StagingBelt::new(2 * 1024 * 1024), - }, - #[cfg(feature = "svg")] - vector: crate::image::vector::Cache::default(), - #[cfg(all(feature = "image", not(target_arch = "wasm32")))] - worker, - } - } - - #[cfg(feature = "image")] - pub fn allocate_image( - &mut self, - handle: &core::image::Handle, - callback: impl FnOnce(Result) - + Send - + 'static, - ) { - use crate::image::raster::Memory; - - let callback = Box::new(callback); - - if let Some(callbacks) = self.raster.pending.get_mut(&handle.id()) { - callbacks.push(callback); - return; - } - - if let Some(Memory::Device { - allocation, entry, .. - }) = self.raster.cache.get_mut(handle) - { - if let Some(allocation) = allocation - .as_ref() - .and_then(core::image::Allocation::upgrade) - { - callback(Ok(allocation)); - return; - } - - #[allow(unsafe_code)] - let new = unsafe { core::image::allocate(handle, entry.size()) }; - *allocation = Some(new.downgrade()); - callback(Ok(new)); - - return; - } - - let _ = self.raster.pending.insert(handle.id(), vec![callback]); - - #[cfg(not(target_arch = "wasm32"))] - self.worker.load(handle); - } - - #[cfg(feature = "image")] - pub fn load_image( - &mut self, - device: &wgpu::Device, - queue: &wgpu::Queue, - handle: &core::image::Handle, - ) -> Result { - use crate::image::raster::Memory; - - if !self.raster.cache.contains(handle) { - self.raster.cache.insert(handle, Memory::load(handle)); - } - - match self.raster.cache.get_mut(handle).unwrap() { - Memory::Host(image) => { - let mut encoder = device.create_command_encoder( - &wgpu::CommandEncoderDescriptor { - label: Some("raster image upload"), - }, - ); - - let entry = self.atlas.upload( - device, - &mut encoder, - &mut self.raster.belt, - image.width(), - image.height(), - image, - ); - - self.raster.belt.finish(); - let submission = queue.submit([encoder.finish()]); - self.raster.belt.recall(); - - let Some(entry) = entry else { - return Err(core::image::Error::OutOfMemory); - }; - - let _ = device.poll(wgpu::PollType::Wait { - submission_index: Some(submission), - timeout: None, - }); - - #[allow(unsafe_code)] - let allocation = unsafe { - core::image::allocate( - handle, - Size::new(image.width(), image.height()), - ) - }; - - self.raster.cache.insert( - handle, - Memory::Device { - entry, - bind_group: None, - allocation: Some(allocation.downgrade()), - }, - ); - - Ok(allocation) - } - Memory::Device { - entry, allocation, .. - } => { - if let Some(allocation) = allocation - .as_ref() - .and_then(core::image::Allocation::upgrade) - { - return Ok(allocation); - } - - #[allow(unsafe_code)] - let new = - unsafe { core::image::allocate(handle, entry.size()) }; - - *allocation = Some(new.downgrade()); - - Ok(new) - } - Memory::Error(error) => Err(error.clone()), - } - } - - #[cfg(feature = "image")] - pub fn measure_image( - &mut self, - handle: &core::image::Handle, - ) -> Option> { - self.receive(); - - let image = load_image( - &mut self.raster.cache, - &mut self.raster.pending, - #[cfg(not(target_arch = "wasm32"))] - &self.worker, - handle, - None, - )?; - - Some(image.dimensions()) - } - - #[cfg(feature = "svg")] - pub fn measure_svg(&mut self, handle: &core::svg::Handle) -> Size { - // TODO: Concurrency - self.vector.load(handle).viewport_dimensions() - } - - #[cfg(feature = "image")] - pub fn upload_raster( - &mut self, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - belt: &mut wgpu::util::StagingBelt, - handle: &core::image::Handle, - ) -> Option<(&atlas::Entry, &Arc)> { - use crate::image::raster::Memory; - - self.receive(); - - let memory = load_image( - &mut self.raster.cache, - &mut self.raster.pending, - #[cfg(not(target_arch = "wasm32"))] - &self.worker, - handle, - None, - )?; - - if let Memory::Device { - entry, bind_group, .. - } = memory - { - return Some(( - entry, - bind_group.as_ref().unwrap_or(self.atlas.bind_group()), - )); - } - - let image = memory.host()?; - - const MAX_SYNC_SIZE: usize = 2 * 1024 * 1024; - - // TODO: Concurrent Wasm support - if image.len() < MAX_SYNC_SIZE || cfg!(target_arch = "wasm32") { - let entry = self.atlas.upload( - device, - encoder, - belt, - image.width(), - image.height(), - &image, - )?; - - *memory = Memory::Device { - entry, - bind_group: None, - allocation: None, - }; - - if let Memory::Device { entry, .. } = memory { - return Some((entry, self.atlas.bind_group())); - } - } - - if !self.raster.pending.contains_key(&handle.id()) { - let _ = self.raster.pending.insert(handle.id(), Vec::new()); - - #[cfg(not(target_arch = "wasm32"))] - self.worker.upload(handle, image); - } - - None - } - - #[cfg(feature = "svg")] - pub fn upload_vector( - &mut self, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - belt: &mut wgpu::util::StagingBelt, - handle: &core::svg::Handle, - color: Option, - size: Size, - scale: f32, - ) -> Option<(&atlas::Entry, &Arc)> { - // TODO: Concurrency - self.vector - .upload( - device, - encoder, - belt, - handle, - color, - size, - scale, - &mut self.atlas, - ) - .map(|entry| (entry, self.atlas.bind_group())) - } - - pub fn trim(&mut self) { - #[cfg(feature = "image")] - { - self.receive(); - self.raster.cache.trim(&mut self.atlas, |_bind_group| { - #[cfg(not(target_arch = "wasm32"))] - self.worker.drop(_bind_group); - }); - } - - #[cfg(feature = "svg")] - self.vector.trim(&mut self.atlas); // TODO: Concurrency - } - - #[cfg(feature = "image")] - fn receive(&mut self) { - #[cfg(not(target_arch = "wasm32"))] - while let Ok(work) = self.worker.try_recv() { - use crate::image::raster::Memory; - - match work { - worker::Work::Upload { - handle, - entry, - bind_group, - } => { - let callbacks = self.raster.pending.remove(&handle.id()); - - let allocation = if let Some(callbacks) = callbacks { - #[allow(unsafe_code)] - let allocation = unsafe { - core::image::allocate(&handle, entry.size()) - }; - - let reference = allocation.downgrade(); - - for callback in callbacks { - callback(Ok(allocation.clone())); - } - - Some(reference) - } else { - None - }; - - self.raster.cache.insert( - &handle, - Memory::Device { - entry, - bind_group: Some(bind_group), - allocation, - }, - ); - } - worker::Work::Error { handle, error } => { - let callbacks = self.raster.pending.remove(&handle.id()); - - if let Some(callbacks) = callbacks { - for callback in callbacks { - callback(Err(error.clone())); - } - } - - self.raster.cache.insert(&handle, Memory::Error(error)); - } - } - } - } -} - -#[cfg(all(feature = "image", not(target_arch = "wasm32")))] -impl Drop for Cache { - fn drop(&mut self) { - self.worker.quit(); - } -} - -#[cfg(feature = "image")] -struct Raster { - cache: crate::image::raster::Cache, - pending: HashMap>, - belt: wgpu::util::StagingBelt, -} - -#[cfg(feature = "image")] -type Callback = - Box) + Send>; - -#[cfg(feature = "image")] -fn load_image<'a>( - cache: &'a mut crate::image::raster::Cache, - pending: &mut HashMap>, - #[cfg(not(target_arch = "wasm32"))] worker: &Worker, - handle: &core::image::Handle, - callback: Option, -) -> Option<&'a mut crate::image::raster::Memory> { - use crate::image::raster::Memory; - - if !cache.contains(handle) { - if cfg!(target_arch = "wasm32") { - // TODO: Concurrent support for Wasm - cache.insert(handle, Memory::load(handle)); - } else if let core::image::Handle::Rgba { .. } = handle { - // Load RGBA handles synchronously, since it's very cheap - cache.insert(handle, Memory::load(handle)); - } else if !pending.contains_key(&handle.id()) { - let _ = pending.insert(handle.id(), Vec::from_iter(callback)); - - #[cfg(not(target_arch = "wasm32"))] - worker.load(handle); - } - } - - cache.get_mut(handle) -} - -#[cfg(all(feature = "image", not(target_arch = "wasm32")))] -mod worker { - use crate::core::Bytes; - use crate::core::image; - use crate::graphics::Shell; - use crate::image::atlas::{self, Atlas}; - use crate::image::raster; - - use std::sync::Arc; - use std::sync::mpsc; - use std::thread; - - pub struct Worker { - jobs: mpsc::SyncSender, - quit: mpsc::SyncSender<()>, - work: mpsc::Receiver, - handle: Option>, - } - - impl Worker { - pub fn new( - device: &wgpu::Device, - queue: &wgpu::Queue, - backend: wgpu::Backend, - texture_layout: wgpu::BindGroupLayout, - shell: &Shell, - ) -> Self { - let (jobs_sender, jobs_receiver) = mpsc::sync_channel(1_000); - let (quit_sender, quit_receiver) = mpsc::sync_channel(1); - let (work_sender, work_receiver) = mpsc::sync_channel(1_000); - - let instance = Instance { - device: device.clone(), - queue: queue.clone(), - backend, - texture_layout, - shell: shell.clone(), - belt: wgpu::util::StagingBelt::new(4 * 1024 * 1024), - jobs: jobs_receiver, - output: work_sender, - quit: quit_receiver, - }; - - let handle = thread::spawn(move || instance.run()); - - Self { - jobs: jobs_sender, - quit: quit_sender, - work: work_receiver, - handle: Some(handle), - } - } - - pub fn load(&self, handle: &image::Handle) { - let _ = self.jobs.send(Job::Load(handle.clone())); - } - - pub fn upload(&self, handle: &image::Handle, image: raster::Image) { - let _ = self.jobs.send(Job::Upload { - handle: handle.clone(), - width: image.width(), - height: image.height(), - rgba: image.into_raw(), - }); - } - - pub fn drop(&self, bind_group: Arc) { - let _ = self.jobs.send(Job::Drop(bind_group)); - } - - pub fn try_recv(&self) -> Result { - self.work.try_recv() - } - - pub fn quit(&mut self) { - let _ = self.quit.try_send(()); - let _ = self.jobs.send(Job::Quit); - let _ = self.handle.take().map(thread::JoinHandle::join); - } - } - - pub struct Instance { - device: wgpu::Device, - queue: wgpu::Queue, - backend: wgpu::Backend, - texture_layout: wgpu::BindGroupLayout, - shell: Shell, - belt: wgpu::util::StagingBelt, - jobs: mpsc::Receiver, - output: mpsc::SyncSender, - quit: mpsc::Receiver<()>, - } - - #[derive(Debug)] - enum Job { - Load(image::Handle), - Upload { - handle: image::Handle, - rgba: Bytes, - width: u32, - height: u32, - }, - Drop(Arc), - Quit, - } - - pub enum Work { - Upload { - handle: image::Handle, - entry: atlas::Entry, - bind_group: Arc, - }, - Error { - handle: image::Handle, - error: image::Error, - }, - } - - impl Instance { - fn run(mut self) { - loop { - if self.quit.try_recv().is_ok() { - return; - } - - let Ok(job) = self.jobs.recv() else { - return; - }; - - match job { - Job::Load(handle) => { - match crate::graphics::image::load(&handle) { - Ok(image) => self.upload( - handle, - image.width(), - image.height(), - image.into_raw(), - Shell::invalidate_layout, - ), - Err(error) => { - let _ = self - .output - .send(Work::Error { handle, error }); - } - } - } - Job::Upload { - handle, - rgba, - width, - height, - } => { - self.upload( - handle, - width, - height, - rgba, - Shell::request_redraw, - ); - } - Job::Drop(bind_group) => { - drop(bind_group); - } - Job::Quit => return, - } - } - } - - fn upload( - &mut self, - handle: image::Handle, - width: u32, - height: u32, - rgba: Bytes, - callback: fn(&Shell), - ) { - let mut encoder = self.device.create_command_encoder( - &wgpu::CommandEncoderDescriptor { - label: Some("raster image upload"), - }, - ); - - let mut atlas = Atlas::with_size( - &self.device, - self.backend, - self.texture_layout.clone(), - width.max(height), - ); - - let Some(entry) = atlas.upload( - &self.device, - &mut encoder, - &mut self.belt, - width, - height, - &rgba, - ) else { - return; - }; - - let output = self.output.clone(); - let shell = self.shell.clone(); - - self.belt.finish(); - let submission = self.queue.submit([encoder.finish()]); - self.belt.recall(); - - let bind_group = atlas.bind_group().clone(); - - self.queue.on_submitted_work_done(move || { - let _ = output.send(Work::Upload { - handle, - entry, - bind_group, - }); - - callback(&shell); - }); - - let _ = self.device.poll(wgpu::PollType::Wait { - submission_index: Some(submission), - timeout: None, - }); - } - } -} diff --git a/crates/iced_wgpu/src/image/mod.rs b/crates/iced_wgpu/src/image/mod.rs deleted file mode 100644 index ba98f443..00000000 --- a/crates/iced_wgpu/src/image/mod.rs +++ /dev/null @@ -1,758 +0,0 @@ -pub(crate) mod cache; -pub(crate) use cache::Cache; - -mod atlas; - -#[cfg(feature = "image")] -mod raster; - -#[cfg(feature = "svg")] -mod vector; - -use crate::Buffer; -use crate::core::border; -use crate::core::{Rectangle, Size, Transformation}; -use crate::graphics::Shell; - -use bytemuck::{Pod, Zeroable}; - -use std::mem; -use std::sync::Arc; - -pub use crate::graphics::Image; - -pub type Batch = Vec; - -#[derive(Debug, Clone)] -pub struct Pipeline { - raw: wgpu::RenderPipeline, - backend: wgpu::Backend, - nearest_sampler: wgpu::Sampler, - linear_sampler: wgpu::Sampler, - texture_layout: wgpu::BindGroupLayout, - constant_layout: wgpu::BindGroupLayout, -} - -impl Pipeline { - pub fn new( - device: &wgpu::Device, - format: wgpu::TextureFormat, - backend: wgpu::Backend, - ) -> Self { - let nearest_sampler = device.create_sampler(&wgpu::SamplerDescriptor { - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - min_filter: wgpu::FilterMode::Nearest, - mag_filter: wgpu::FilterMode::Nearest, - mipmap_filter: wgpu::FilterMode::Nearest, - ..Default::default() - }); - - let linear_sampler = device.create_sampler(&wgpu::SamplerDescriptor { - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - min_filter: wgpu::FilterMode::Linear, - mag_filter: wgpu::FilterMode::Linear, - mipmap_filter: wgpu::FilterMode::Linear, - ..Default::default() - }); - - let constant_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("iced_wgpu::image constants layout"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: wgpu::BufferSize::new( - mem::size_of::() as u64, - ), - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler( - wgpu::SamplerBindingType::Filtering, - ), - count: None, - }, - ], - }); - - let texture_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("iced_wgpu::image texture atlas layout"), - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { - filterable: true, - }, - view_dimension: wgpu::TextureViewDimension::D2Array, - multisampled: false, - }, - count: None, - }], - }); - - let layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("iced_wgpu::image pipeline layout"), - push_constant_ranges: &[], - bind_group_layouts: &[&constant_layout, &texture_layout], - }); - - let shader = - device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("iced_wgpu image shader"), - source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( - concat!( - include_str!("../shader/vertex.wgsl"), - "\n", - include_str!("../shader/image.wgsl"), - ), - )), - }); - - let pipeline = - device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("iced_wgpu::image pipeline"), - layout: Some(&layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: Some("vs_main"), - buffers: &[wgpu::VertexBufferLayout { - array_stride: mem::size_of::() as u64, - step_mode: wgpu::VertexStepMode::Instance, - attributes: &wgpu::vertex_attr_array!( - // Center - 0 => Float32x2, - // Clip bounds - 1 => Float32x4, - // Border radius - 2 => Float32x4, - // Tile - 3 => Float32x4, - // Rotation - 4 => Float32, - // Opacity - 5 => Float32, - // Atlas position - 6 => Float32x2, - // Atlas scale - 7 => Float32x2, - // Layer - 8 => Sint32, - // Snap - 9 => Uint32, - ), - }], - compilation_options: - wgpu::PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: Some("fs_main"), - targets: &[Some(wgpu::ColorTargetState { - format, - blend: Some(wgpu::BlendState { - color: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::SrcAlpha, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - alpha: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::One, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - }), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: - wgpu::PipelineCompilationOptions::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - front_face: wgpu::FrontFace::Cw, - ..Default::default() - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - cache: None, - }); - - Pipeline { - raw: pipeline, - backend, - nearest_sampler, - linear_sampler, - texture_layout, - constant_layout, - } - } - - pub fn create_cache( - &self, - device: &wgpu::Device, - queue: &wgpu::Queue, - shell: &Shell, - ) -> Cache { - Cache::new( - device, - queue, - self.backend, - self.texture_layout.clone(), - shell, - ) - } -} - -#[derive(Default)] -pub struct State { - layers: Vec, - prepare_layer: usize, - nearest_instances: Vec, - linear_instances: Vec, -} - -impl State { - pub fn new() -> Self { - Self::default() - } - - pub fn prepare( - &mut self, - pipeline: &Pipeline, - device: &wgpu::Device, - belt: &mut wgpu::util::StagingBelt, - encoder: &mut wgpu::CommandEncoder, - cache: &mut Cache, - images: &Batch, - transformation: Transformation, - scale: f32, - ) { - if self.layers.len() <= self.prepare_layer { - self.layers.push(Layer::new( - device, - &pipeline.constant_layout, - &pipeline.nearest_sampler, - &pipeline.linear_sampler, - )); - } - - let layer = &mut self.layers[self.prepare_layer]; - - let mut atlas: Option> = None; - - for image in images { - match &image { - #[cfg(feature = "image")] - Image::Raster { - image, - bounds, - clip_bounds, - } => { - if let Some((atlas_entry, bind_group)) = cache - .upload_raster(device, encoder, belt, &image.handle) - { - match atlas.as_mut() { - None => { - atlas = Some(bind_group.clone()); - } - Some(atlas) if atlas != bind_group => { - layer.push( - atlas, - &self.nearest_instances, - &self.linear_instances, - ); - - *atlas = Arc::clone(bind_group); - } - _ => {} - } - - add_instances( - *bounds, - *clip_bounds, - image.border_radius, - f32::from(image.rotation), - image.opacity, - image.snap, - atlas_entry, - match image.filter_method { - crate::core::image::FilterMethod::Nearest => { - &mut self.nearest_instances - } - crate::core::image::FilterMethod::Linear => { - &mut self.linear_instances - } - }, - ); - } - } - #[cfg(not(feature = "image"))] - Image::Raster { .. } => continue, - - #[cfg(feature = "svg")] - Image::Vector { - svg, - bounds, - clip_bounds, - } => { - if let Some((atlas_entry, bind_group)) = cache - .upload_vector( - device, - encoder, - belt, - &svg.handle, - svg.color, - bounds.size(), - scale, - ) - { - match atlas.as_mut() { - None => { - atlas = Some(bind_group.clone()); - } - Some(atlas) if atlas != bind_group => { - layer.push( - atlas, - &self.nearest_instances, - &self.linear_instances, - ); - - *atlas = bind_group.clone(); - } - _ => {} - } - - add_instances( - *bounds, - *clip_bounds, - border::radius(0), - f32::from(svg.rotation), - svg.opacity, - true, - atlas_entry, - &mut self.nearest_instances, - ); - } - } - #[cfg(not(feature = "svg"))] - Image::Vector { .. } => continue, - } - } - - if let Some(atlas) = &atlas { - layer.push(atlas, &self.nearest_instances, &self.linear_instances); - } - - layer.prepare( - device, - encoder, - belt, - transformation, - scale, - &self.nearest_instances, - &self.linear_instances, - ); - - self.prepare_layer += 1; - self.nearest_instances.clear(); - self.linear_instances.clear(); - } - - pub fn render<'a>( - &'a self, - pipeline: &'a Pipeline, - layer: usize, - bounds: Rectangle, - render_pass: &mut wgpu::RenderPass<'a>, - ) { - if let Some(layer) = self.layers.get(layer) { - render_pass.set_pipeline(&pipeline.raw); - - render_pass.set_scissor_rect( - bounds.x, - bounds.y, - bounds.width, - bounds.height, - ); - - layer.render(render_pass); - } - } - - pub fn trim(&mut self) { - for layer in &mut self.layers[..self.prepare_layer] { - layer.clear(); - } - - self.prepare_layer = 0; - } -} - -#[derive(Debug)] -struct Layer { - uniforms: wgpu::Buffer, - instances: Buffer, - nearest: Vec, - nearest_layout: wgpu::BindGroup, - nearest_total: usize, - linear: Vec, - linear_layout: wgpu::BindGroup, - linear_total: usize, -} - -#[derive(Debug)] -struct Group { - atlas: Arc, - instance_count: usize, -} - -impl Layer { - fn new( - device: &wgpu::Device, - constant_layout: &wgpu::BindGroupLayout, - nearest_sampler: &wgpu::Sampler, - linear_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 instances = Buffer::new( - device, - "iced_wgpu::image instance buffer", - Instance::INITIAL, - wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - ); - - let nearest_layout = - 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( - nearest_sampler, - ), - }, - ], - }); - - let linear_layout = - 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( - linear_sampler, - ), - }, - ], - }); - - Self { - uniforms, - instances, - nearest: Vec::new(), - nearest_layout, - nearest_total: 0, - linear: Vec::new(), - linear_layout, - linear_total: 0, - } - } - - fn prepare( - &mut self, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - belt: &mut wgpu::util::StagingBelt, - transformation: Transformation, - scale_factor: f32, - nearest: &[Instance], - linear: &[Instance], - ) { - let uniforms = Uniforms { - transform: transformation.into(), - scale_factor, - _padding: [0.0; 3], - }; - - let bytes = bytemuck::bytes_of(&uniforms); - - belt.write_buffer( - encoder, - &self.uniforms, - 0, - (bytes.len() as u64).try_into().expect("Sized uniforms"), - device, - ) - .copy_from_slice(bytes); - - let _ = self - .instances - .resize(device, self.nearest_total + self.linear_total); - - let mut offset = 0; - - if !nearest.is_empty() { - offset += self.instances.write(device, encoder, belt, 0, nearest); - } - - if !linear.is_empty() { - let _ = self.instances.write(device, encoder, belt, offset, linear); - } - } - - fn push( - &mut self, - atlas: &Arc, - nearest: &[Instance], - linear: &[Instance], - ) { - let new_nearest = nearest.len() - self.nearest_total; - - if new_nearest > 0 { - self.nearest.push(Group { - atlas: atlas.clone(), - instance_count: new_nearest, - }); - - self.nearest_total = nearest.len(); - } - - let new_linear = linear.len() - self.linear_total; - - if new_linear > 0 { - self.linear.push(Group { - atlas: atlas.clone(), - instance_count: new_linear, - }); - - self.linear_total = linear.len(); - } - } - - fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { - render_pass.set_vertex_buffer(0, self.instances.slice(..)); - - let mut offset = 0; - - if !self.nearest.is_empty() { - render_pass.set_bind_group(0, &self.nearest_layout, &[]); - - for group in &self.nearest { - render_pass.set_bind_group(1, group.atlas.as_ref(), &[]); - render_pass - .draw(0..6, offset..offset + group.instance_count as u32); - - offset += group.instance_count as u32; - } - } - - if !self.linear.is_empty() { - render_pass.set_bind_group(0, &self.linear_layout, &[]); - - for group in &self.linear { - render_pass.set_bind_group(1, group.atlas.as_ref(), &[]); - render_pass - .draw(0..6, offset..offset + group.instance_count as u32); - - offset += group.instance_count as u32; - } - } - } - - fn clear(&mut self) { - self.nearest.clear(); - self.nearest_total = 0; - - self.linear.clear(); - self.linear_total = 0; - } -} - -#[repr(C)] -#[derive(Debug, Clone, Copy, Zeroable, Pod)] -struct Instance { - _center: [f32; 2], - _clip_bounds: [f32; 4], - _border_radius: [f32; 4], - _tile: [f32; 4], - _rotation: f32, - _opacity: f32, - _position_in_atlas: [f32; 2], - _size_in_atlas: [f32; 2], - _layer: u32, - _snap: u32, -} - -impl Instance { - pub const INITIAL: usize = 20; -} - -#[repr(C)] -#[derive(Debug, Clone, Copy, Zeroable, Pod)] -struct Uniforms { - transform: [f32; 16], - scale_factor: f32, - // Uniforms must be aligned to their largest member, - // this uses a mat4x4 which aligns to 16, so align to that - _padding: [f32; 3], -} - -fn add_instances( - bounds: Rectangle, - clip_bounds: Rectangle, - border_radius: border::Radius, - rotation: f32, - opacity: f32, - snap: bool, - entry: &atlas::Entry, - instances: &mut Vec, -) { - let center = [ - bounds.x + bounds.width / 2.0, - bounds.y + bounds.height / 2.0, - ]; - - let clip_bounds = [ - clip_bounds.x, - clip_bounds.y, - clip_bounds.width, - clip_bounds.height, - ]; - - let border_radius = border_radius.into(); - - match entry { - atlas::Entry::Contiguous(allocation) => { - add_instance( - center, - clip_bounds, - border_radius, - [bounds.x, bounds.y, bounds.width, bounds.height], - rotation, - opacity, - snap, - allocation, - instances, - ); - } - atlas::Entry::Fragmented { fragments, size } => { - let scaling_x = bounds.width / size.width as f32; - let scaling_y = bounds.height / size.height as f32; - - for fragment in fragments { - let allocation = &fragment.allocation; - let (fragment_x, fragment_y) = fragment.position; - - let Size { - width: fragment_width, - height: fragment_height, - } = allocation.size(); - - let tile = [ - bounds.x + fragment_x as f32 * scaling_x, - bounds.y + fragment_y as f32 * scaling_y, - fragment_width as f32 * scaling_x, - fragment_height as f32 * scaling_y, - ]; - - add_instance( - center, - clip_bounds, - border_radius, - tile, - rotation, - opacity, - snap, - allocation, - instances, - ); - } - } - } -} - -#[inline] -fn add_instance( - center: [f32; 2], - clip_bounds: [f32; 4], - border_radius: [f32; 4], - tile: [f32; 4], - rotation: f32, - opacity: f32, - snap: bool, - allocation: &atlas::Allocation, - instances: &mut Vec, -) { - let (x, y) = allocation.position(); - let Size { width, height } = allocation.size(); - let layer = allocation.layer(); - let atlas_size = allocation.atlas_size(); - - let instance = Instance { - _center: center, - _clip_bounds: clip_bounds, - _border_radius: border_radius, - _tile: tile, - _rotation: rotation, - _opacity: opacity, - _position_in_atlas: [ - x as f32 / atlas_size as f32, - y as f32 / atlas_size as f32, - ], - _size_in_atlas: [ - width as f32 / atlas_size as f32, - height as f32 / atlas_size as f32, - ], - _layer: layer as u32, - _snap: snap as u32, - }; - - instances.push(instance); -} diff --git a/crates/iced_wgpu/src/image/null.rs b/crates/iced_wgpu/src/image/null.rs deleted file mode 100644 index d0d74cfc..00000000 --- a/crates/iced_wgpu/src/image/null.rs +++ /dev/null @@ -1,16 +0,0 @@ -pub use crate::graphics::Image; - -#[derive(Debug, Default)] -pub struct Batch; - -impl Batch { - pub fn push(&mut self, _image: Image) {} - - pub fn clear(&mut self) {} - - pub fn is_empty(&self) -> bool { - true - } - - pub fn append(&mut self, _batch: &mut Self) {} -} diff --git a/crates/iced_wgpu/src/image/raster.rs b/crates/iced_wgpu/src/image/raster.rs deleted file mode 100644 index 881e75e3..00000000 --- a/crates/iced_wgpu/src/image/raster.rs +++ /dev/null @@ -1,123 +0,0 @@ -use crate::core::Size; -use crate::core::image; -use crate::graphics; -use crate::image::atlas::{self, Atlas}; - -use rustc_hash::{FxHashMap, FxHashSet}; -use std::sync::{Arc, Weak}; - -pub type Image = graphics::image::Buffer; - -/// Entry in cache corresponding to an image handle -#[derive(Debug)] -pub enum Memory { - /// Image data on host - Host(Image), - /// Storage entry - Device { - entry: atlas::Entry, - bind_group: Option>, - allocation: Option>, - }, - Error(image::Error), -} - -impl Memory { - pub fn load(handle: &image::Handle) -> Self { - match graphics::image::load(handle) { - Ok(image) => Self::Host(image), - Err(error) => Self::Error(error), - } - } - - pub fn dimensions(&self) -> Size { - match self { - Memory::Host(image) => { - let (width, height) = image.dimensions(); - - Size::new(width, height) - } - Memory::Device { entry, .. } => entry.size(), - Memory::Error(_) => Size::new(1, 1), - } - } - - pub fn host(&self) -> Option { - match self { - Memory::Host(image) => Some(image.clone()), - Memory::Device { .. } | Memory::Error(_) => None, - } - } -} - -#[derive(Debug, Default)] -pub struct Cache { - map: FxHashMap, - hits: FxHashSet, - should_trim: bool, -} - -impl Cache { - pub fn get_mut(&mut self, handle: &image::Handle) -> Option<&mut Memory> { - let _ = self.hits.insert(handle.id()); - - self.map.get_mut(&handle.id()) - } - - pub fn insert(&mut self, handle: &image::Handle, memory: Memory) { - let _ = self.map.insert(handle.id(), memory); - let _ = self.hits.insert(handle.id()); - - self.should_trim = true; - } - - pub fn contains(&self, handle: &image::Handle) -> bool { - self.map.contains_key(&handle.id()) - } - - pub fn trim( - &mut self, - atlas: &mut Atlas, - on_drop: impl Fn(Arc), - ) { - // Only trim if new entries have landed in the `Cache` - if !self.should_trim { - return; - } - - let hits = &self.hits; - - self.map.retain(|id, memory| { - // Retain active allocations - if let Memory::Device { allocation, .. } = memory - && allocation - .as_ref() - .is_some_and(|allocation| allocation.strong_count() > 0) - { - return true; - } - - let retain = hits.contains(id); - - if !retain { - log::debug!("Dropping image allocation: {id:?}"); - - if let Memory::Device { - entry, bind_group, .. - } = memory - { - if let Some(bind_group) = bind_group.take() { - on_drop(bind_group); - } else { - atlas.remove(entry); - } - } - } - - retain - }); - - self.hits.clear(); - self.should_trim = false; - } -} diff --git a/crates/iced_wgpu/src/image/vector.rs b/crates/iced_wgpu/src/image/vector.rs deleted file mode 100644 index 0704a966..00000000 --- a/crates/iced_wgpu/src/image/vector.rs +++ /dev/null @@ -1,231 +0,0 @@ -use crate::core::svg; -use crate::core::{Color, Size}; -use crate::image::atlas::{self, Atlas}; - -use resvg::tiny_skia; -use resvg::usvg; -use rustc_hash::{FxHashMap, FxHashSet}; -use std::fs; -use std::panic; -use std::sync::Arc; - -/// Entry in cache corresponding to an svg handle -pub enum Svg { - /// Parsed svg - Loaded(usvg::Tree), - /// Svg not found or failed to parse - NotFound, -} - -impl Svg { - /// Viewport width and height - pub fn viewport_dimensions(&self) -> Size { - match self { - Svg::Loaded(tree) => { - let size = tree.size(); - - Size::new(size.width() as u32, size.height() as u32) - } - Svg::NotFound => Size::new(1, 1), - } - } -} - -/// Caches svg vector and raster data -#[derive(Debug, Default)] -pub struct Cache { - svgs: FxHashMap, - rasterized: FxHashMap<(u64, u32, u32, ColorFilter), atlas::Entry>, - svg_hits: FxHashSet, - rasterized_hits: FxHashSet<(u64, u32, u32, ColorFilter)>, - should_trim: bool, - fontdb: Option>, -} - -type ColorFilter = Option<[u8; 4]>; - -impl Cache { - /// Load svg - pub fn load(&mut self, handle: &svg::Handle) -> &Svg { - if self.svgs.contains_key(&handle.id()) { - return self.svgs.get(&handle.id()).unwrap(); - } - - // TODO: Reuse `cosmic-text` font database - if self.fontdb.is_none() { - let mut fontdb = usvg::fontdb::Database::new(); - fontdb.load_system_fonts(); - - self.fontdb = Some(Arc::new(fontdb)); - } - - let options = usvg::Options { - fontdb: self - .fontdb - .as_ref() - .expect("fontdb must be initialized") - .clone(), - ..usvg::Options::default() - }; - - let svg = match handle.data() { - svg::Data::Path(path) => fs::read_to_string(path) - .ok() - .and_then(|contents| { - usvg::Tree::from_str(&contents, &options).ok() - }) - .map(Svg::Loaded) - .unwrap_or(Svg::NotFound), - svg::Data::Bytes(bytes) => { - match usvg::Tree::from_data(bytes, &options) { - Ok(tree) => Svg::Loaded(tree), - Err(_) => Svg::NotFound, - } - } - }; - - self.should_trim = true; - - let _ = self.svgs.insert(handle.id(), svg); - self.svgs.get(&handle.id()).unwrap() - } - - /// Load svg and upload raster data - pub fn upload( - &mut self, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - belt: &mut wgpu::util::StagingBelt, - handle: &svg::Handle, - color: Option, - size: Size, - scale: f32, - atlas: &mut Atlas, - ) -> Option<&atlas::Entry> { - let id = handle.id(); - - let (width, height) = ( - (scale * size.width).ceil() as u32, - (scale * size.height).ceil() as u32, - ); - - let color = color.map(Color::into_rgba8); - let key = (id, width, height, color); - - // TODO: Optimize! - // We currently rerasterize the SVG when its size changes. This is slow - // as heck. A GPU rasterizer like `pathfinder` may perform better. - // It would be cool to be able to smooth resize the `svg` example. - if self.rasterized.contains_key(&key) { - let _ = self.svg_hits.insert(id); - let _ = self.rasterized_hits.insert(key); - - return self.rasterized.get(&key); - } - - match self.load(handle) { - Svg::Loaded(tree) => { - if width == 0 || height == 0 { - return None; - } - - // TODO: Optimize! - // We currently rerasterize the SVG when its size changes. This is slow - // as heck. A GPU rasterizer like `pathfinder` may perform better. - // It would be cool to be able to smooth resize the `svg` example. - let mut img = tiny_skia::Pixmap::new(width, height)?; - - let tree_size = tree.size().to_int_size(); - - let target_size = if width > height { - tree_size.scale_to_width(width) - } else { - tree_size.scale_to_height(height) - }; - - let transform = if let Some(target_size) = target_size { - let tree_size = tree_size.to_size(); - let target_size = target_size.to_size(); - - tiny_skia::Transform::from_scale( - target_size.width() / tree_size.width(), - target_size.height() / tree_size.height(), - ) - } else { - tiny_skia::Transform::default() - }; - - // SVG rendering can panic on malformed or complex vectors. - // We catch panics to prevent crashes and continue gracefully. - let render = - panic::catch_unwind(panic::AssertUnwindSafe(|| { - resvg::render(tree, transform, &mut img.as_mut()); - })); - - if let Err(error) = render { - log::warn!( - "SVG rendering for {handle:?} panicked: {error:?}" - ); - } - - let mut rgba = img.take(); - - if let Some(color) = color { - rgba.chunks_exact_mut(4).for_each(|rgba| { - if rgba[3] > 0 { - rgba[0] = color[0]; - rgba[1] = color[1]; - rgba[2] = color[2]; - } - }); - } - - let allocation = atlas - .upload(device, encoder, belt, width, height, &rgba)?; - - log::debug!("allocating {id} {width}x{height}"); - - let _ = self.svg_hits.insert(id); - let _ = self.rasterized_hits.insert(key); - let _ = self.rasterized.insert(key, allocation); - self.should_trim = true; - - self.rasterized.get(&key) - } - Svg::NotFound => None, - } - } - - /// Load svg and upload raster data - pub fn trim(&mut self, atlas: &mut Atlas) { - if !self.should_trim { - return; - } - - let svg_hits = &self.svg_hits; - let rasterized_hits = &self.rasterized_hits; - - self.svgs.retain(|k, _| svg_hits.contains(k)); - self.rasterized.retain(|k, entry| { - let retain = rasterized_hits.contains(k); - - if !retain { - atlas.remove(entry); - } - - retain - }); - self.svg_hits.clear(); - self.rasterized_hits.clear(); - self.should_trim = false; - } -} - -impl std::fmt::Debug for Svg { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Svg::Loaded(_) => write!(f, "Svg::Loaded"), - Svg::NotFound => write!(f, "Svg::NotFound"), - } - } -} diff --git a/crates/iced_wgpu/src/layer.rs b/crates/iced_wgpu/src/layer.rs deleted file mode 100644 index c61730b6..00000000 --- a/crates/iced_wgpu/src/layer.rs +++ /dev/null @@ -1,405 +0,0 @@ -use crate::core::{ - self, Background, Color, Point, Rectangle, Svg, Transformation, renderer, -}; -use crate::graphics; -use crate::graphics::Mesh; -use crate::graphics::color; -use crate::graphics::layer; -use crate::graphics::mesh; -use crate::graphics::text::{Editor, Paragraph}; -use crate::image::{self, Image}; -use crate::primitive::{self, Primitive}; -use crate::quad::{self, Quad}; -use crate::text::{self, Text}; -use crate::triangle; - -pub type Stack = layer::Stack; - -#[derive(Debug)] -pub struct Layer { - pub bounds: Rectangle, - pub quads: quad::Batch, - pub triangles: triangle::Batch, - pub primitives: primitive::Batch, - pub images: image::Batch, - pub text: text::Batch, - pending_meshes: Vec, - pending_text: Vec, -} - -impl Layer { - pub fn is_empty(&self) -> bool { - self.quads.is_empty() - && self.triangles.is_empty() - && self.primitives.is_empty() - && self.images.is_empty() - && self.text.is_empty() - && self.pending_meshes.is_empty() - && self.pending_text.is_empty() - } - - pub fn draw_quad( - &mut self, - quad: renderer::Quad, - background: Background, - transformation: Transformation, - ) { - let bounds = quad.bounds * transformation; - - let quad = Quad { - position: [bounds.x, bounds.y], - size: [bounds.width, bounds.height], - border_color: color::pack(quad.border.color), - border_radius: (quad.border.radius * transformation.scale_factor()) - .into(), - border_width: quad.border.width * transformation.scale_factor(), - shadow_color: color::pack(quad.shadow.color), - shadow_offset: (quad.shadow.offset * transformation.scale_factor()) - .into(), - shadow_blur_radius: quad.shadow.blur_radius - * transformation.scale_factor(), - snap: quad.snap as u32, - }; - - self.quads.add(quad, &background); - } - - pub fn draw_paragraph( - &mut self, - paragraph: &Paragraph, - position: Point, - color: Color, - clip_bounds: Rectangle, - transformation: Transformation, - ) { - let paragraph = Text::Paragraph { - paragraph: paragraph.downgrade(), - position, - color, - clip_bounds, - transformation, - }; - - self.pending_text.push(paragraph); - } - - pub fn draw_editor( - &mut self, - editor: &Editor, - position: Point, - color: Color, - clip_bounds: Rectangle, - transformation: Transformation, - ) { - let editor = Text::Editor { - editor: editor.downgrade(), - position, - color, - clip_bounds, - transformation, - }; - - self.pending_text.push(editor); - } - - pub fn draw_text( - &mut self, - text: crate::core::Text, - position: Point, - color: Color, - clip_bounds: Rectangle, - transformation: Transformation, - ) { - let text = Text::Cached { - content: text.content, - bounds: Rectangle::new(position, text.bounds) * transformation, - color, - size: text.size * transformation.scale_factor(), - line_height: text.line_height.to_absolute(text.size) - * transformation.scale_factor(), - font: text.font, - align_x: text.align_x, - align_y: text.align_y, - shaping: text.shaping, - clip_bounds: clip_bounds * transformation, - }; - - self.pending_text.push(text); - } - - pub fn draw_text_raw( - &mut self, - raw: graphics::text::Raw, - transformation: Transformation, - ) { - let raw = Text::Raw { - raw, - transformation, - }; - - self.pending_text.push(raw); - } - - pub fn draw_image(&mut self, image: Image, transformation: Transformation) { - match image { - Image::Raster { - image, - bounds, - clip_bounds, - } => { - self.draw_raster(image, bounds, clip_bounds, transformation); - } - Image::Vector { - svg, - bounds, - clip_bounds, - } => { - self.draw_svg(svg, bounds, clip_bounds, transformation); - } - } - } - - pub fn draw_raster( - &mut self, - image: core::Image, - bounds: Rectangle, - clip_bounds: Rectangle, - transformation: Transformation, - ) { - let image = Image::Raster { - image: core::Image { - border_radius: image.border_radius - * transformation.scale_factor(), - ..image - }, - bounds: bounds * transformation, - clip_bounds: clip_bounds * transformation, - }; - - self.images.push(image); - } - - pub fn draw_svg( - &mut self, - svg: Svg, - bounds: Rectangle, - clip_bounds: Rectangle, - transformation: Transformation, - ) { - let svg = Image::Vector { - svg, - bounds: bounds * transformation, - clip_bounds: clip_bounds * transformation, - }; - - self.images.push(svg); - } - - pub fn draw_mesh( - &mut self, - mut mesh: Mesh, - transformation: Transformation, - ) { - match &mut mesh { - Mesh::Solid { - transformation: local_transformation, - .. - } - | Mesh::Gradient { - transformation: local_transformation, - .. - } => { - *local_transformation = *local_transformation * transformation; - } - } - - self.pending_meshes.push(mesh); - } - - pub fn draw_mesh_group( - &mut self, - meshes: Vec, - transformation: Transformation, - ) { - self.flush_meshes(); - - self.triangles.push(triangle::Item::Group { - meshes, - transformation, - }); - } - - pub fn draw_mesh_cache( - &mut self, - cache: mesh::Cache, - transformation: Transformation, - ) { - self.flush_meshes(); - - self.triangles.push(triangle::Item::Cached { - cache, - transformation, - }); - } - - pub fn draw_text_group( - &mut self, - text: Vec, - transformation: Transformation, - ) { - self.flush_text(); - - self.text.push(text::Item::Group { - text, - transformation, - }); - } - - pub fn draw_text_cache( - &mut self, - cache: text::Cache, - transformation: Transformation, - ) { - self.flush_text(); - - self.text.push(text::Item::Cached { - cache, - transformation, - }); - } - - pub fn draw_primitive( - &mut self, - bounds: Rectangle, - primitive: impl Primitive, - transformation: Transformation, - ) { - let bounds = bounds * transformation; - - self.primitives - .push(primitive::Instance::new(bounds, primitive)); - } - - fn flush_meshes(&mut self) { - if !self.pending_meshes.is_empty() { - self.triangles.push(triangle::Item::Group { - transformation: Transformation::IDENTITY, - meshes: self.pending_meshes.drain(..).collect(), - }); - } - } - - fn flush_text(&mut self) { - if !self.pending_text.is_empty() { - self.text.push(text::Item::Group { - transformation: Transformation::IDENTITY, - text: self.pending_text.drain(..).collect(), - }); - } - } -} - -impl graphics::Layer for Layer { - fn with_bounds(bounds: Rectangle) -> Self { - Self { - bounds, - ..Self::default() - } - } - - fn bounds(&self) -> Rectangle { - self.bounds - } - - fn flush(&mut self) { - self.flush_meshes(); - self.flush_text(); - } - - fn resize(&mut self, bounds: Rectangle) { - self.bounds = bounds; - } - - fn reset(&mut self) { - self.bounds = Rectangle::INFINITE; - - self.quads.clear(); - self.triangles.clear(); - self.primitives.clear(); - self.text.clear(); - self.images.clear(); - self.pending_meshes.clear(); - self.pending_text.clear(); - } - - fn start(&self) -> usize { - if !self.quads.is_empty() { - return 1; - } - - if !self.triangles.is_empty() { - return 2; - } - - if !self.primitives.is_empty() { - return 3; - } - - if !self.images.is_empty() { - return 4; - } - - if !self.text.is_empty() { - return 5; - } - - usize::MAX - } - - fn end(&self) -> usize { - if !self.text.is_empty() { - return 5; - } - - if !self.images.is_empty() { - return 4; - } - - if !self.primitives.is_empty() { - return 3; - } - - if !self.triangles.is_empty() { - return 2; - } - - if !self.quads.is_empty() { - return 1; - } - - 0 - } - - fn merge(&mut self, layer: &mut Self) { - self.quads.append(&mut layer.quads); - self.triangles.append(&mut layer.triangles); - self.primitives.append(&mut layer.primitives); - self.images.append(&mut layer.images); - self.text.append(&mut layer.text); - } -} - -impl Default for Layer { - fn default() -> Self { - Self { - bounds: Rectangle::INFINITE, - quads: quad::Batch::default(), - triangles: triangle::Batch::default(), - primitives: primitive::Batch::default(), - text: text::Batch::default(), - images: image::Batch::default(), - pending_meshes: Vec::new(), - pending_text: Vec::new(), - } - } -} diff --git a/crates/iced_wgpu/src/lib.rs b/crates/iced_wgpu/src/lib.rs deleted file mode 100644 index ec103cf3..00000000 --- a/crates/iced_wgpu/src/lib.rs +++ /dev/null @@ -1,984 +0,0 @@ -//! A [`wgpu`] renderer for [Iced]. -//! -//! ![The native path of the Iced ecosystem](https://github.com/iced-rs/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/native.png?raw=true) -//! -//! [`wgpu`] supports most modern graphics backends: Vulkan, Metal, DX11, and -//! DX12 (OpenGL and WebGL are still WIP). Additionally, it will support the -//! incoming [WebGPU API]. -//! -//! Currently, `iced_wgpu` supports the following primitives: -//! - Text, which is rendered using [`glyphon`]. -//! - Quads or rectangles, with rounded borders and a solid background color. -//! - Clip areas, useful to implement scrollables or hide overflowing content. -//! - Images and SVG, loaded from memory or the file system. -//! - Meshes of triangles, useful to draw geometry freely. -//! -//! [Iced]: https://github.com/iced-rs/iced -//! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs -//! [WebGPU API]: https://gpuweb.github.io/gpuweb/ -//! [`glyphon`]: https://github.com/grovesNL/glyphon -#![doc( - html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" -)] -#![cfg_attr(docsrs, feature(doc_cfg))] -#![allow(missing_docs)] -pub mod layer; -pub mod primitive; -pub mod settings; -pub mod window; - -#[cfg(feature = "geometry")] -pub mod geometry; - -mod buffer; -mod color; -mod engine; -mod quad; -mod text; -mod triangle; - -#[cfg(any(feature = "image", feature = "svg"))] -#[path = "image/mod.rs"] -mod image; - -#[cfg(not(any(feature = "image", feature = "svg")))] -#[path = "image/null.rs"] -mod image; - -use buffer::Buffer; - -use iced_debug as debug; -pub use iced_graphics as graphics; -pub use iced_graphics::core; - -pub use wgpu; - -pub use engine::Engine; -pub use layer::Layer; -pub use primitive::Primitive; -pub use settings::Settings; - -#[cfg(feature = "geometry")] -pub use geometry::Geometry; - -use crate::core::renderer; -use crate::core::{ - Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, -}; -use crate::graphics::mesh; -use crate::graphics::text::{Editor, Paragraph}; -use crate::graphics::{Shell, Viewport}; - -/// A [`wgpu`] graphics renderer for [`iced`]. -/// -/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs -/// [`iced`]: https://github.com/iced-rs/iced -pub struct Renderer { - engine: Engine, - - default_font: Font, - default_text_size: Pixels, - layers: layer::Stack, - - quad: quad::State, - triangle: triangle::State, - text: text::State, - text_viewport: text::Viewport, - - #[cfg(any(feature = "svg", feature = "image"))] - image: image::State, - - // TODO: Centralize all the image feature handling - #[cfg(any(feature = "svg", feature = "image"))] - image_cache: std::cell::RefCell, - - staging_belt: wgpu::util::StagingBelt, -} - -impl Renderer { - pub fn new( - engine: Engine, - default_font: Font, - default_text_size: Pixels, - ) -> Self { - Self { - default_font, - default_text_size, - layers: layer::Stack::new(), - - quad: quad::State::new(), - triangle: triangle::State::new( - &engine.device, - &engine.triangle_pipeline, - ), - text: text::State::new(), - text_viewport: engine.text_pipeline.create_viewport(&engine.device), - - #[cfg(any(feature = "svg", feature = "image"))] - image: image::State::new(), - - #[cfg(any(feature = "svg", feature = "image"))] - image_cache: std::cell::RefCell::new(engine.create_image_cache()), - - // TODO: Resize belt smartly (?) - // It would be great if the `StagingBelt` API exposed methods - // for introspection to detect when a resize may be worth it. - staging_belt: wgpu::util::StagingBelt::new( - buffer::MAX_WRITE_SIZE as u64, - ), - - engine, - } - } - - fn draw( - &mut self, - clear_color: Option, - target: &wgpu::TextureView, - viewport: &Viewport, - ) -> wgpu::CommandEncoder { - let mut encoder = self.engine.device.create_command_encoder( - &wgpu::CommandEncoderDescriptor { - label: Some("iced_wgpu encoder"), - }, - ); - - self.prepare(&mut encoder, viewport); - self.render(&mut encoder, target, clear_color, viewport); - - self.quad.trim(); - self.triangle.trim(); - self.text.trim(); - - // TODO: Provide window id (?) - self.engine.trim(); - - #[cfg(any(feature = "svg", feature = "image"))] - { - self.image.trim(); - self.image_cache.borrow_mut().trim(); - } - - encoder - } - - pub fn present( - &mut self, - clear_color: Option, - _format: wgpu::TextureFormat, - frame: &wgpu::TextureView, - viewport: &Viewport, - ) -> wgpu::SubmissionIndex { - let encoder = self.draw(clear_color, frame, viewport); - - self.staging_belt.finish(); - let submission = self.engine.queue.submit([encoder.finish()]); - self.staging_belt.recall(); - submission - } - - /// Renders the current surface to an offscreen buffer. - /// - /// Returns RGBA bytes of the texture data. - pub fn screenshot( - &mut self, - viewport: &Viewport, - background_color: Color, - ) -> Vec { - #[derive(Clone, Copy, Debug)] - struct BufferDimensions { - width: u32, - height: u32, - unpadded_bytes_per_row: usize, - padded_bytes_per_row: usize, - } - - impl BufferDimensions { - fn new(size: Size) -> Self { - let unpadded_bytes_per_row = size.width as usize * 4; //slice of buffer per row; always RGBA - let alignment = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize; //256 - let padded_bytes_per_row_padding = (alignment - - unpadded_bytes_per_row % alignment) - % alignment; - let padded_bytes_per_row = - unpadded_bytes_per_row + padded_bytes_per_row_padding; - - Self { - width: size.width, - height: size.height, - unpadded_bytes_per_row, - padded_bytes_per_row, - } - } - } - - let dimensions = BufferDimensions::new(viewport.physical_size()); - - let texture_extent = wgpu::Extent3d { - width: dimensions.width, - height: dimensions.height, - depth_or_array_layers: 1, - }; - - let texture = - self.engine.device.create_texture(&wgpu::TextureDescriptor { - label: Some("iced_wgpu.offscreen.source_texture"), - size: texture_extent, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: self.engine.format, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT - | wgpu::TextureUsages::COPY_SRC - | wgpu::TextureUsages::TEXTURE_BINDING, - view_formats: &[], - }); - - let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); - - let mut encoder = self.draw(Some(background_color), &view, viewport); - - let texture = crate::color::convert( - &self.engine.device, - &mut encoder, - texture, - if graphics::color::GAMMA_CORRECTION { - wgpu::TextureFormat::Rgba8UnormSrgb - } else { - wgpu::TextureFormat::Rgba8Unorm - }, - ); - - let output_buffer = - self.engine.device.create_buffer(&wgpu::BufferDescriptor { - label: Some("iced_wgpu.offscreen.output_texture_buffer"), - size: (dimensions.padded_bytes_per_row - * dimensions.height as usize) as u64, - usage: wgpu::BufferUsages::MAP_READ - | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - - encoder.copy_texture_to_buffer( - texture.as_image_copy(), - wgpu::TexelCopyBufferInfo { - buffer: &output_buffer, - layout: wgpu::TexelCopyBufferLayout { - offset: 0, - bytes_per_row: Some(dimensions.padded_bytes_per_row as u32), - rows_per_image: None, - }, - }, - texture_extent, - ); - - self.staging_belt.finish(); - let index = self.engine.queue.submit([encoder.finish()]); - self.staging_belt.recall(); - - let slice = output_buffer.slice(..); - slice.map_async(wgpu::MapMode::Read, |_| {}); - - let _ = self.engine.device.poll(wgpu::PollType::Wait { - submission_index: Some(index), - timeout: None, - }); - - let mapped_buffer = slice.get_mapped_range(); - - mapped_buffer.chunks(dimensions.padded_bytes_per_row).fold( - vec![], - |mut acc, row| { - acc.extend(&row[..dimensions.unpadded_bytes_per_row]); - acc - }, - ) - } - - fn prepare( - &mut self, - encoder: &mut wgpu::CommandEncoder, - viewport: &Viewport, - ) { - let scale_factor = viewport.scale_factor(); - - self.text_viewport - .update(&self.engine.queue, viewport.physical_size()); - - let physical_bounds = Rectangle::::from(Rectangle::with_size( - viewport.physical_size(), - )); - - self.layers.merge(); - - for layer in self.layers.iter() { - let clip_bounds = layer.bounds * scale_factor; - - if physical_bounds - .intersection(&clip_bounds) - .and_then(Rectangle::snap) - .is_none() - { - continue; - } - - if !layer.quads.is_empty() { - let prepare_span = debug::prepare(debug::Primitive::Quad); - - self.quad.prepare( - &self.engine.quad_pipeline, - &self.engine.device, - &mut self.staging_belt, - encoder, - &layer.quads, - viewport.projection(), - scale_factor, - ); - - prepare_span.finish(); - } - - if !layer.triangles.is_empty() { - let prepare_span = debug::prepare(debug::Primitive::Triangle); - - self.triangle.prepare( - &self.engine.triangle_pipeline, - &self.engine.device, - &mut self.staging_belt, - encoder, - &layer.triangles, - Transformation::scale(scale_factor), - viewport.physical_size(), - ); - - prepare_span.finish(); - } - - if !layer.primitives.is_empty() { - let prepare_span = debug::prepare(debug::Primitive::Shader); - - let mut primitive_storage = self - .engine - .primitive_storage - .write() - .expect("Write primitive storage"); - - for instance in &layer.primitives { - instance.primitive.prepare( - &mut primitive_storage, - &self.engine.device, - &self.engine.queue, - self.engine.format, - &instance.bounds, - viewport, - ); - } - - prepare_span.finish(); - } - - #[cfg(any(feature = "svg", feature = "image"))] - if !layer.images.is_empty() { - let prepare_span = debug::prepare(debug::Primitive::Image); - - self.image.prepare( - &self.engine.image_pipeline, - &self.engine.device, - &mut self.staging_belt, - encoder, - &mut self.image_cache.borrow_mut(), - &layer.images, - viewport.projection(), - scale_factor, - ); - - prepare_span.finish(); - } - - if !layer.text.is_empty() { - let prepare_span = debug::prepare(debug::Primitive::Text); - - self.text.prepare( - &self.engine.text_pipeline, - &self.engine.device, - &self.engine.queue, - &self.text_viewport, - encoder, - &layer.text, - layer.bounds, - Transformation::scale(scale_factor), - ); - - prepare_span.finish(); - } - } - } - - fn render( - &mut self, - encoder: &mut wgpu::CommandEncoder, - frame: &wgpu::TextureView, - clear_color: Option, - viewport: &Viewport, - ) { - use std::mem::ManuallyDrop; - - let mut render_pass = ManuallyDrop::new(encoder.begin_render_pass( - &wgpu::RenderPassDescriptor { - label: Some("iced_wgpu render pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: frame, - depth_slice: None, - resolve_target: None, - ops: wgpu::Operations { - load: match clear_color { - Some(background_color) => wgpu::LoadOp::Clear({ - let [r, g, b, a] = - graphics::color::pack(background_color) - .components(); - - wgpu::Color { - r: f64::from(r), - g: f64::from(g), - b: f64::from(b), - a: f64::from(a), - } - }), - None => wgpu::LoadOp::Load, - }, - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }, - )); - - let mut quad_layer = 0; - let mut mesh_layer = 0; - let mut text_layer = 0; - - #[cfg(any(feature = "svg", feature = "image"))] - let mut image_layer = 0; - - let scale_factor = viewport.scale_factor(); - let physical_bounds = Rectangle::::from(Rectangle::with_size( - viewport.physical_size(), - )); - - let scale = Transformation::scale(scale_factor); - - for layer in self.layers.iter() { - let Some(physical_bounds) = - physical_bounds.intersection(&(layer.bounds * scale_factor)) - else { - continue; - }; - - let Some(scissor_rect) = physical_bounds.snap() else { - continue; - }; - - if !layer.quads.is_empty() { - let render_span = debug::render(debug::Primitive::Quad); - self.quad.render( - &self.engine.quad_pipeline, - quad_layer, - scissor_rect, - &layer.quads, - &mut render_pass, - ); - render_span.finish(); - - quad_layer += 1; - } - - if !layer.triangles.is_empty() { - let _ = ManuallyDrop::into_inner(render_pass); - - let render_span = debug::render(debug::Primitive::Triangle); - mesh_layer += self.triangle.render( - &self.engine.triangle_pipeline, - encoder, - frame, - mesh_layer, - &layer.triangles, - physical_bounds, - scale, - ); - render_span.finish(); - - render_pass = ManuallyDrop::new(encoder.begin_render_pass( - &wgpu::RenderPassDescriptor { - label: Some("iced_wgpu render pass"), - color_attachments: &[Some( - wgpu::RenderPassColorAttachment { - view: frame, - depth_slice: None, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, - }, - }, - )], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }, - )); - } - - if !layer.primitives.is_empty() { - let render_span = debug::render(debug::Primitive::Shader); - - let primitive_storage = self - .engine - .primitive_storage - .read() - .expect("Read primitive storage"); - - let mut need_render = Vec::new(); - - for instance in &layer.primitives { - let bounds = instance.bounds * scale; - - if let Some(clip_bounds) = (instance.bounds * scale) - .intersection(&physical_bounds) - .and_then(Rectangle::snap) - { - render_pass.set_viewport( - bounds.x, - bounds.y, - bounds.width, - bounds.height, - 0.0, - 1.0, - ); - - render_pass.set_scissor_rect( - clip_bounds.x, - clip_bounds.y, - clip_bounds.width, - clip_bounds.height, - ); - - let drawn = instance - .primitive - .draw(&primitive_storage, &mut render_pass); - - if !drawn { - need_render.push((instance, clip_bounds)); - } - } - } - - render_pass.set_viewport( - 0.0, - 0.0, - viewport.physical_width() as f32, - viewport.physical_height() as f32, - 0.0, - 1.0, - ); - - render_pass.set_scissor_rect( - 0, - 0, - viewport.physical_width(), - viewport.physical_height(), - ); - - if !need_render.is_empty() { - let _ = ManuallyDrop::into_inner(render_pass); - - for (instance, clip_bounds) in need_render { - instance.primitive.render( - &primitive_storage, - encoder, - frame, - &clip_bounds, - ); - } - - render_pass = ManuallyDrop::new(encoder.begin_render_pass( - &wgpu::RenderPassDescriptor { - label: Some("iced_wgpu render pass"), - color_attachments: &[Some( - wgpu::RenderPassColorAttachment { - view: frame, - depth_slice: None, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, - }, - }, - )], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }, - )); - } - - render_span.finish(); - } - - #[cfg(any(feature = "svg", feature = "image"))] - if !layer.images.is_empty() { - let render_span = debug::render(debug::Primitive::Image); - self.image.render( - &self.engine.image_pipeline, - image_layer, - scissor_rect, - &mut render_pass, - ); - render_span.finish(); - - image_layer += 1; - } - - if !layer.text.is_empty() { - let render_span = debug::render(debug::Primitive::Text); - text_layer += self.text.render( - &self.engine.text_pipeline, - &self.text_viewport, - text_layer, - &layer.text, - scissor_rect, - &mut render_pass, - ); - render_span.finish(); - } - } - - let _ = ManuallyDrop::into_inner(render_pass); - - debug::layers_rendered(|| { - self.layers - .iter() - .filter(|layer| { - !layer.is_empty() - && physical_bounds - .intersection(&(layer.bounds * scale_factor)) - .is_some_and(|viewport| viewport.snap().is_some()) - }) - .count() - }); - } -} - -impl core::Renderer for Renderer { - fn start_layer(&mut self, bounds: Rectangle) { - self.layers.push_clip(bounds); - } - - fn end_layer(&mut self) { - self.layers.pop_clip(); - } - - fn start_transformation(&mut self, transformation: Transformation) { - self.layers.push_transformation(transformation); - } - - fn end_transformation(&mut self) { - self.layers.pop_transformation(); - } - - fn fill_quad( - &mut self, - quad: core::renderer::Quad, - background: impl Into, - ) { - let (layer, transformation) = self.layers.current_mut(); - layer.draw_quad(quad, background.into(), transformation); - } - - fn reset(&mut self, new_bounds: Rectangle) { - self.layers.reset(new_bounds); - } - - fn allocate_image( - &mut self, - _handle: &core::image::Handle, - _callback: impl FnOnce(Result) - + Send - + 'static, - ) { - #[cfg(feature = "image")] - self.image_cache - .get_mut() - .allocate_image(_handle, _callback); - } -} - -impl core::text::Renderer for Renderer { - type Font = Font; - type Paragraph = Paragraph; - type Editor = Editor; - - const ICON_FONT: Font = Font::with_name("Iced-Icons"); - const CHECKMARK_ICON: char = '\u{f00c}'; - const ARROW_DOWN_ICON: char = '\u{e800}'; - const ICED_LOGO: char = '\u{e801}'; - const SCROLL_UP_ICON: char = '\u{e802}'; - const SCROLL_DOWN_ICON: char = '\u{e803}'; - const SCROLL_LEFT_ICON: char = '\u{e804}'; - const SCROLL_RIGHT_ICON: char = '\u{e805}'; - - fn default_font(&self) -> Self::Font { - self.default_font - } - - fn default_size(&self) -> Pixels { - self.default_text_size - } - - fn fill_paragraph( - &mut self, - text: &Self::Paragraph, - position: Point, - color: Color, - clip_bounds: Rectangle, - ) { - let (layer, transformation) = self.layers.current_mut(); - - layer.draw_paragraph( - text, - position, - color, - clip_bounds, - transformation, - ); - } - - fn fill_editor( - &mut self, - editor: &Self::Editor, - position: Point, - color: Color, - clip_bounds: Rectangle, - ) { - let (layer, transformation) = self.layers.current_mut(); - layer.draw_editor(editor, position, color, clip_bounds, transformation); - } - - fn fill_text( - &mut self, - text: core::Text, - position: Point, - color: Color, - clip_bounds: Rectangle, - ) { - let (layer, transformation) = self.layers.current_mut(); - layer.draw_text(text, position, color, clip_bounds, transformation); - } -} - -impl graphics::text::Renderer for Renderer { - fn fill_raw(&mut self, raw: graphics::text::Raw) { - let (layer, transformation) = self.layers.current_mut(); - layer.draw_text_raw(raw, transformation); - } -} - -#[cfg(feature = "image")] -impl core::image::Renderer for Renderer { - type Handle = core::image::Handle; - - fn load_image( - &self, - handle: &Self::Handle, - ) -> Result { - self.image_cache.borrow_mut().load_image( - &self.engine.device, - &self.engine.queue, - handle, - ) - } - - fn measure_image(&self, handle: &Self::Handle) -> Option> { - self.image_cache.borrow_mut().measure_image(handle) - } - - fn draw_image( - &mut self, - image: core::Image, - bounds: Rectangle, - clip_bounds: Rectangle, - ) { - let (layer, transformation) = self.layers.current_mut(); - layer.draw_raster(image, bounds, clip_bounds, transformation); - } -} - -#[cfg(feature = "svg")] -impl core::svg::Renderer for Renderer { - fn measure_svg(&self, handle: &core::svg::Handle) -> core::Size { - self.image_cache.borrow_mut().measure_svg(handle) - } - - fn draw_svg( - &mut self, - svg: core::Svg, - bounds: Rectangle, - clip_bounds: Rectangle, - ) { - let (layer, transformation) = self.layers.current_mut(); - layer.draw_svg(svg, bounds, clip_bounds, transformation); - } -} - -impl graphics::mesh::Renderer for Renderer { - fn draw_mesh(&mut self, mesh: graphics::Mesh) { - debug_assert!( - !mesh.indices().is_empty(), - "Mesh must not have empty indices" - ); - - debug_assert!( - mesh.indices().len().is_multiple_of(3), - "Mesh indices length must be a multiple of 3" - ); - - let (layer, transformation) = self.layers.current_mut(); - layer.draw_mesh(mesh, transformation); - } - - fn draw_mesh_cache(&mut self, cache: mesh::Cache) { - let (layer, transformation) = self.layers.current_mut(); - layer.draw_mesh_cache(cache, transformation); - } -} - -#[cfg(feature = "geometry")] -impl graphics::geometry::Renderer for Renderer { - type Geometry = Geometry; - type Frame = geometry::Frame; - - fn new_frame(&self, bounds: Rectangle) -> Self::Frame { - geometry::Frame::new(bounds) - } - - fn draw_geometry(&mut self, geometry: Self::Geometry) { - let (layer, transformation) = self.layers.current_mut(); - - match geometry { - Geometry::Live { - meshes, - images, - text, - } => { - layer.draw_mesh_group(meshes, transformation); - - for image in images { - layer.draw_image(image, transformation); - } - - layer.draw_text_group(text, transformation); - } - Geometry::Cached(cache) => { - if let Some(meshes) = cache.meshes { - layer.draw_mesh_cache(meshes, transformation); - } - - if let Some(images) = cache.images { - for image in images.iter().cloned() { - layer.draw_image(image, transformation); - } - } - - if let Some(text) = cache.text { - layer.draw_text_cache(text, transformation); - } - } - } - } -} - -impl primitive::Renderer for Renderer { - fn draw_primitive(&mut self, bounds: Rectangle, primitive: impl Primitive) { - let (layer, transformation) = self.layers.current_mut(); - layer.draw_primitive(bounds, primitive, transformation); - } -} - -impl graphics::compositor::Default for crate::Renderer { - type Compositor = window::Compositor; -} - -impl renderer::Headless for Renderer { - async fn new( - default_font: Font, - default_text_size: Pixels, - backend: Option<&str>, - ) -> Option { - if backend.is_some_and(|backend| backend != "wgpu") { - return None; - } - - let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { - backends: wgpu::Backends::from_env() - .unwrap_or(wgpu::Backends::PRIMARY), - flags: wgpu::InstanceFlags::empty(), - ..wgpu::InstanceDescriptor::default() - }); - - let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::HighPerformance, - force_fallback_adapter: false, - compatible_surface: None, - }) - .await - .ok()?; - - let (device, queue) = adapter - .request_device(&wgpu::DeviceDescriptor { - label: Some("iced_wgpu [headless]"), - required_features: wgpu::Features::empty(), - required_limits: wgpu::Limits { - max_bind_groups: 2, - ..wgpu::Limits::default() - }, - memory_hints: wgpu::MemoryHints::MemoryUsage, - trace: wgpu::Trace::Off, - experimental_features: wgpu::ExperimentalFeatures::disabled(), - }) - .await - .ok()?; - - let engine = Engine::new( - &adapter, - device, - queue, - if graphics::color::GAMMA_CORRECTION { - wgpu::TextureFormat::Rgba8UnormSrgb - } else { - wgpu::TextureFormat::Rgba8Unorm - }, - Some(graphics::Antialiasing::MSAAx4), - Shell::headless(), - ); - - Some(Self::new(engine, default_font, default_text_size)) - } - - fn name(&self) -> String { - "wgpu".to_owned() - } - - fn screenshot( - &mut self, - size: Size, - scale_factor: f32, - background_color: Color, - ) -> Vec { - self.screenshot( - &Viewport::with_physical_size(size, scale_factor), - background_color, - ) - } -} diff --git a/crates/iced_wgpu/src/primitive.rs b/crates/iced_wgpu/src/primitive.rs deleted file mode 100644 index 4c18bb6a..00000000 --- a/crates/iced_wgpu/src/primitive.rs +++ /dev/null @@ -1,242 +0,0 @@ -//! Draw custom primitives. -use crate::core::{self, Rectangle}; -use crate::graphics::Viewport; -use crate::graphics::futures::{MaybeSend, MaybeSync}; - -use rustc_hash::FxHashMap; -use std::any::{Any, TypeId}; -use std::fmt::Debug; - -/// A batch of primitives. -pub type Batch = Vec; - -/// A set of methods which allows a [`Primitive`] to be rendered. -pub trait Primitive: Debug + MaybeSend + MaybeSync + 'static { - /// The shared renderer of this [`Primitive`]. - /// - /// Normally, this will contain a bunch of [`wgpu`] state; like - /// a rendering pipeline, buffers, and textures. - /// - /// All instances of this [`Primitive`] type will share the same - /// [`Renderer`]. - type Pipeline: Pipeline + MaybeSend + MaybeSync; - - /// Processes the [`Primitive`], allowing for GPU buffer allocation. - fn prepare( - &self, - pipeline: &mut Self::Pipeline, - device: &wgpu::Device, - queue: &wgpu::Queue, - bounds: &Rectangle, - viewport: &Viewport, - ); - - /// Draws the [`Primitive`] in the given [`wgpu::RenderPass`]. - /// - /// When possible, this should be implemented over [`render`](Self::render) - /// since reusing the existing render pass should be considerably more - /// efficient than issuing a new one. - /// - /// The viewport and scissor rect of the render pass provided is set - /// to the bounds and clip bounds of the [`Primitive`], respectively. - /// - /// If you have complex composition needs, then you can leverage - /// [`render`](Self::render) by returning `false` here. - /// - /// By default, it does nothing and returns `false`. - fn draw( - &self, - _pipeline: &Self::Pipeline, - _render_pass: &mut wgpu::RenderPass<'_>, - ) -> bool { - false - } - - /// Renders the [`Primitive`], using the given [`wgpu::CommandEncoder`]. - /// - /// This will only be called if [`draw`](Self::draw) returns `false`. - /// - /// By default, it does nothing. - fn render( - &self, - _pipeline: &Self::Pipeline, - _encoder: &mut wgpu::CommandEncoder, - _target: &wgpu::TextureView, - _clip_bounds: &Rectangle, - ) { - } -} - -/// The pipeline of a graphics [`Primitive`]. -pub trait Pipeline: Any + MaybeSend + MaybeSync { - /// Creates the [`Pipeline`] of a [`Primitive`]. - /// - /// This will only be called once, when the first [`Primitive`] with this kind - /// of [`Pipeline`] is encountered. - fn new( - device: &wgpu::Device, - queue: &wgpu::Queue, - format: wgpu::TextureFormat, - ) -> Self - where - Self: Sized; - - /// Trims any cached data in the [`Pipeline`]. - /// - /// This will normally be called at the end of a frame. - fn trim(&mut self) {} -} - -pub(crate) trait Stored: - Debug + MaybeSend + MaybeSync + 'static -{ - fn prepare( - &self, - storage: &mut Storage, - device: &wgpu::Device, - queue: &wgpu::Queue, - format: wgpu::TextureFormat, - bounds: &Rectangle, - viewport: &Viewport, - ); - - fn draw( - &self, - storage: &Storage, - render_pass: &mut wgpu::RenderPass<'_>, - ) -> bool; - - fn render( - &self, - storage: &Storage, - encoder: &mut wgpu::CommandEncoder, - target: &wgpu::TextureView, - clip_bounds: &Rectangle, - ); -} - -#[derive(Debug)] -struct BlackBox { - primitive: P, -} - -impl Stored for BlackBox

{ - fn prepare( - &self, - storage: &mut Storage, - device: &wgpu::Device, - queue: &wgpu::Queue, - format: wgpu::TextureFormat, - bounds: &Rectangle, - viewport: &Viewport, - ) { - if !storage.has::

() { - storage.store::(P::Pipeline::new(device, queue, format)); - } - - let renderer = storage - .get_mut::

() - .expect("renderer should be initialized") - .downcast_mut::() - .expect("renderer should have the proper type"); - - self.primitive - .prepare(renderer, device, queue, bounds, viewport); - } - - fn draw( - &self, - storage: &Storage, - render_pass: &mut wgpu::RenderPass<'_>, - ) -> bool { - let renderer = storage - .get::

() - .expect("renderer should be initialized") - .downcast_ref::() - .expect("renderer should have the proper type"); - - self.primitive.draw(renderer, render_pass) - } - - fn render( - &self, - storage: &Storage, - encoder: &mut wgpu::CommandEncoder, - target: &wgpu::TextureView, - clip_bounds: &Rectangle, - ) { - let renderer = storage - .get::

() - .expect("renderer should be initialized") - .downcast_ref::() - .expect("renderer should have the proper type"); - - self.primitive - .render(renderer, encoder, target, clip_bounds); - } -} - -#[derive(Debug)] -/// An instance of a specific [`Primitive`]. -pub struct Instance { - /// The bounds of the [`Instance`]. - pub(crate) bounds: Rectangle, - - /// The [`Primitive`] to render. - pub(crate) primitive: Box, -} - -impl Instance { - /// Creates a new [`Instance`] with the given [`Primitive`]. - pub fn new(bounds: Rectangle, primitive: impl Primitive) -> Self { - Instance { - bounds, - primitive: Box::new(BlackBox { primitive }), - } - } -} - -/// A renderer than can draw custom primitives. -pub trait Renderer: core::Renderer { - /// Draws a custom primitive. - fn draw_primitive(&mut self, bounds: Rectangle, primitive: impl Primitive); -} - -/// Stores custom, user-provided types. -#[derive(Default)] -pub struct Storage { - pipelines: FxHashMap>, -} - -impl Storage { - /// Returns `true` if `Storage` contains a type `T`. - pub fn has(&self) -> bool { - self.pipelines.contains_key(&TypeId::of::()) - } - - /// Inserts the data `T` in to [`Storage`]. - pub fn store(&mut self, pipeline: P) { - let _ = self.pipelines.insert(TypeId::of::(), Box::new(pipeline)); - } - - /// Returns a reference to the data with type `T` if it exists in [`Storage`]. - pub fn get(&self) -> Option<&dyn Any> { - self.pipelines - .get(&TypeId::of::()) - .map(|pipeline| pipeline.as_ref() as &dyn Any) - } - - /// Returns a mutable reference to the data with type `T` if it exists in [`Storage`]. - pub fn get_mut(&mut self) -> Option<&mut dyn Any> { - self.pipelines - .get_mut(&TypeId::of::()) - .map(|pipeline| pipeline.as_mut() as &mut dyn Any) - } - - /// Trims the cache of all the pipelines in the [`Storage`]. - pub fn trim(&mut self) { - for pipeline in self.pipelines.values_mut() { - pipeline.trim(); - } - } -} diff --git a/crates/iced_wgpu/src/quad.rs b/crates/iced_wgpu/src/quad.rs deleted file mode 100644 index 31bf69cf..00000000 --- a/crates/iced_wgpu/src/quad.rs +++ /dev/null @@ -1,362 +0,0 @@ -mod gradient; -mod solid; - -use gradient::Gradient; -use solid::Solid; - -use crate::core::{Background, Rectangle, Transformation}; -use crate::graphics; -use crate::graphics::color; - -use bytemuck::{Pod, Zeroable}; - -use std::mem; - -const INITIAL_INSTANCES: usize = 2_000; - -/// The properties of a quad. -#[derive(Clone, Copy, Debug, Pod, Zeroable)] -#[repr(C)] -pub struct Quad { - /// The position of the [`Quad`]. - pub position: [f32; 2], - - /// The size of the [`Quad`]. - pub size: [f32; 2], - - /// The border color of the [`Quad`], in __linear RGB__. - pub border_color: color::Packed, - - /// The border radii of the [`Quad`]. - pub border_radius: [f32; 4], - - /// The border width of the [`Quad`]. - pub border_width: f32, - - /// The shadow color of the [`Quad`]. - pub shadow_color: color::Packed, - - /// The shadow offset of the [`Quad`]. - pub shadow_offset: [f32; 2], - - /// The shadow blur radius of the [`Quad`]. - pub shadow_blur_radius: f32, - - /// Whether the [`Quad`] should be snapped to the pixel grid. - pub snap: u32, -} - -#[derive(Debug, Clone)] -pub struct Pipeline { - solid: solid::Pipeline, - gradient: gradient::Pipeline, - constant_layout: wgpu::BindGroupLayout, -} - -#[derive(Default)] -pub struct State { - layers: Vec, - prepare_layer: usize, -} - -impl State { - pub fn new() -> Self { - Self::default() - } - - pub fn prepare( - &mut self, - pipeline: &Pipeline, - device: &wgpu::Device, - belt: &mut wgpu::util::StagingBelt, - encoder: &mut wgpu::CommandEncoder, - quads: &Batch, - transformation: Transformation, - scale: f32, - ) { - if self.layers.len() <= self.prepare_layer { - self.layers - .push(Layer::new(device, &pipeline.constant_layout)); - } - - let layer = &mut self.layers[self.prepare_layer]; - layer.prepare(device, encoder, belt, quads, transformation, scale); - - self.prepare_layer += 1; - } - - pub fn render<'a>( - &'a self, - pipeline: &'a Pipeline, - layer: usize, - bounds: Rectangle, - quads: &Batch, - render_pass: &mut wgpu::RenderPass<'a>, - ) { - if let Some(layer) = self.layers.get(layer) { - render_pass.set_scissor_rect( - bounds.x, - bounds.y, - bounds.width, - bounds.height, - ); - - let mut solid_offset = 0; - let mut gradient_offset = 0; - - for (kind, count) in &quads.order { - match kind { - Kind::Solid => { - pipeline.solid.render( - render_pass, - &layer.constants, - &layer.solid, - solid_offset..(solid_offset + count), - ); - - solid_offset += count; - } - Kind::Gradient => { - pipeline.gradient.render( - render_pass, - &layer.constants, - &layer.gradient, - gradient_offset..(gradient_offset + count), - ); - - gradient_offset += count; - } - } - } - } - } - - pub fn trim(&mut self) { - self.prepare_layer = 0; - } -} - -impl Pipeline { - pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Pipeline { - let constant_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("iced_wgpu::quad uniforms layout"), - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: wgpu::BufferSize::new( - mem::size_of::() as wgpu::BufferAddress, - ), - }, - count: None, - }], - }); - - Self { - solid: solid::Pipeline::new(device, format, &constant_layout), - gradient: gradient::Pipeline::new(device, format, &constant_layout), - constant_layout, - } - } -} - -#[derive(Debug)] -pub struct Layer { - constants: wgpu::BindGroup, - constants_buffer: wgpu::Buffer, - solid: solid::Layer, - gradient: gradient::Layer, -} - -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 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(), - }], - }); - - Self { - constants, - constants_buffer, - solid: solid::Layer::new(device), - gradient: gradient::Layer::new(device), - } - } - - pub fn prepare( - &mut self, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - belt: &mut wgpu::util::StagingBelt, - quads: &Batch, - transformation: Transformation, - scale: f32, - ) { - self.update(device, encoder, belt, transformation, scale); - - if !quads.solids.is_empty() { - self.solid.prepare(device, encoder, belt, &quads.solids); - } - - if !quads.gradients.is_empty() { - self.gradient - .prepare(device, encoder, belt, &quads.gradients); - } - } - - pub fn update( - &mut self, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - belt: &mut wgpu::util::StagingBelt, - transformation: Transformation, - scale: f32, - ) { - let uniforms = Uniforms::new(transformation, scale); - let bytes = bytemuck::bytes_of(&uniforms); - - belt.write_buffer( - encoder, - &self.constants_buffer, - 0, - (bytes.len() as u64).try_into().expect("Sized uniforms"), - device, - ) - .copy_from_slice(bytes); - } -} - -/// A group of [`Quad`]s rendered together. -#[derive(Default, Debug)] -pub struct Batch { - /// The solid quads of the [`Layer`]. - solids: Vec, - - /// The gradient quads of the [`Layer`]. - gradients: Vec, - - /// The quad order of the [`Layer`]. - order: Order, -} - -/// The quad order of a [`Layer`]; stored as a tuple of the quad type & its count. -type Order = Vec<(Kind, usize)>; - -impl Batch { - /// Returns true if there are no quads of any type in [`Quads`]. - pub fn is_empty(&self) -> bool { - self.solids.is_empty() && self.gradients.is_empty() - } - - /// Adds a [`Quad`] with the provided `Background` type to the quad [`Layer`]. - pub fn add(&mut self, quad: Quad, background: &Background) { - let kind = match background { - Background::Color(color) => { - self.solids.push(Solid { - color: color::pack(*color), - quad, - }); - - Kind::Solid - } - Background::Gradient(gradient) => { - self.gradients.push(Gradient { - gradient: graphics::gradient::pack( - gradient, - Rectangle::new(quad.position.into(), quad.size.into()), - ), - quad, - }); - - Kind::Gradient - } - }; - - match self.order.last_mut() { - Some((last_kind, count)) if kind == *last_kind => { - *count += 1; - } - _ => { - self.order.push((kind, 1)); - } - } - } - - pub fn clear(&mut self) { - self.solids.clear(); - self.gradients.clear(); - self.order.clear(); - } - - pub fn append(&mut self, batch: &mut Batch) { - self.solids.append(&mut batch.solids); - self.gradients.append(&mut batch.gradients); - self.order.append(&mut batch.order); - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -/// The kind of a quad. -enum Kind { - /// A solid quad - Solid, - /// A gradient quad - Gradient, -} - -fn color_target_state( - format: wgpu::TextureFormat, -) -> [Option; 1] { - [Some(wgpu::ColorTargetState { - format, - blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING), - write_mask: wgpu::ColorWrites::ALL, - })] -} - -#[repr(C)] -#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)] -struct Uniforms { - transform: [f32; 16], - scale: f32, - // Uniforms must be aligned to their largest member, - // this uses a mat4x4 which aligns to 16, so align to that - _padding: [f32; 3], -} - -impl Uniforms { - fn new(transformation: Transformation, scale: f32) -> Uniforms { - Self { - transform: *transformation.as_ref(), - scale, - _padding: [0.0; 3], - } - } -} - -impl Default for Uniforms { - fn default() -> Self { - Self { - transform: *Transformation::IDENTITY.as_ref(), - scale: 1.0, - _padding: [0.0; 3], - } - } -} diff --git a/crates/iced_wgpu/src/quad/gradient.rs b/crates/iced_wgpu/src/quad/gradient.rs deleted file mode 100644 index 6a5eed8d..00000000 --- a/crates/iced_wgpu/src/quad/gradient.rs +++ /dev/null @@ -1,187 +0,0 @@ -use crate::Buffer; -use crate::graphics::gradient; -use crate::quad::{self, Quad}; - -use bytemuck::{Pod, Zeroable}; -use std::ops::Range; - -/// A quad filled with interpolated colors. -#[derive(Clone, Copy, Debug)] -#[repr(C)] -pub struct Gradient { - /// The background gradient data of the quad. - pub gradient: gradient::Packed, - - /// The [`Quad`] data of the [`Gradient`]. - pub quad: Quad, -} - -#[allow(unsafe_code)] -unsafe impl Pod for Gradient {} - -#[allow(unsafe_code)] -unsafe impl Zeroable for Gradient {} - -#[derive(Debug)] -pub struct Layer { - instances: Buffer, - instance_count: usize, -} - -impl Layer { - pub fn new(device: &wgpu::Device) -> Self { - let instances = Buffer::new( - device, - "iced_wgpu.quad.gradient.buffer", - quad::INITIAL_INSTANCES, - wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - ); - - Self { - instances, - instance_count: 0, - } - } - - pub fn prepare( - &mut self, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - belt: &mut wgpu::util::StagingBelt, - instances: &[Gradient], - ) { - let _ = self.instances.resize(device, instances.len()); - let _ = self.instances.write(device, encoder, belt, 0, instances); - - self.instance_count = instances.len(); - } -} - -#[derive(Debug, Clone)] -pub struct Pipeline { - #[cfg(not(target_arch = "wasm32"))] - pipeline: wgpu::RenderPipeline, -} - -impl Pipeline { - #[allow(unused_variables)] - pub fn new( - device: &wgpu::Device, - format: wgpu::TextureFormat, - constants_layout: &wgpu::BindGroupLayout, - ) -> Self { - #[cfg(not(target_arch = "wasm32"))] - { - let layout = device.create_pipeline_layout( - &wgpu::PipelineLayoutDescriptor { - label: Some("iced_wgpu.quad.gradient.pipeline"), - push_constant_ranges: &[], - bind_group_layouts: &[constants_layout], - }, - ); - - let shader = - device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("iced_wgpu.quad.gradient.shader"), - source: wgpu::ShaderSource::Wgsl( - std::borrow::Cow::Borrowed(concat!( - include_str!("../shader/quad.wgsl"), - "\n", - include_str!("../shader/vertex.wgsl"), - "\n", - include_str!("../shader/quad/gradient.wgsl"), - "\n", - include_str!("../shader/color.wgsl"), - "\n", - include_str!("../shader/color/linear_rgb.wgsl") - )), - ), - }); - - let pipeline = device.create_render_pipeline( - &wgpu::RenderPipelineDescriptor { - label: Some("iced_wgpu.quad.gradient.pipeline"), - layout: Some(&layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: Some("gradient_vs_main"), - buffers: &[wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::() - as u64, - step_mode: wgpu::VertexStepMode::Instance, - attributes: &wgpu::vertex_attr_array!( - // Colors 1-2 - 0 => Uint32x4, - // Colors 3-4 - 1 => Uint32x4, - // Colors 5-6 - 2 => Uint32x4, - // Colors 7-8 - 3 => Uint32x4, - // Offsets 1-8 - 4 => Uint32x4, - // Direction - 5 => Float32x4, - // Position & Scale - 6 => Float32x4, - // Border color - 7 => Float32x4, - // Border radius - 8 => Float32x4, - // Border width - 9 => Float32, - // Snap - 10 => Uint32, - ), - }], - compilation_options: - wgpu::PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: Some("gradient_fs_main"), - targets: &quad::color_target_state(format), - compilation_options: - wgpu::PipelineCompilationOptions::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - front_face: wgpu::FrontFace::Cw, - ..Default::default() - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - cache: None, - }, - ); - - Self { pipeline } - } - - #[cfg(target_arch = "wasm32")] - Self {} - } - - #[allow(unused_variables)] - pub fn render<'a>( - &'a self, - render_pass: &mut wgpu::RenderPass<'a>, - constants: &'a wgpu::BindGroup, - layer: &'a Layer, - range: Range, - ) { - #[cfg(not(target_arch = "wasm32"))] - { - render_pass.set_pipeline(&self.pipeline); - render_pass.set_bind_group(0, constants, &[]); - render_pass.set_vertex_buffer(0, layer.instances.slice(..)); - - render_pass.draw(0..6, range.start as u32..range.end as u32); - } - } -} diff --git a/crates/iced_wgpu/src/quad/solid.rs b/crates/iced_wgpu/src/quad/solid.rs deleted file mode 100644 index 7e48358e..00000000 --- a/crates/iced_wgpu/src/quad/solid.rs +++ /dev/null @@ -1,162 +0,0 @@ -use crate::Buffer; -use crate::graphics::color; -use crate::quad::{self, Quad}; - -use bytemuck::{Pod, Zeroable}; -use std::ops::Range; - -/// A quad filled with a solid color. -#[derive(Clone, Copy, Debug, Pod, Zeroable)] -#[repr(C)] -pub struct Solid { - /// The background color data of the quad. - pub color: color::Packed, - - /// The [`Quad`] data of the [`Solid`]. - pub quad: Quad, -} - -#[derive(Debug)] -pub struct Layer { - instances: Buffer, - instance_count: usize, -} - -impl Layer { - pub fn new(device: &wgpu::Device) -> Self { - let instances = Buffer::new( - device, - "iced_wgpu.quad.solid.buffer", - quad::INITIAL_INSTANCES, - wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - ); - - Self { - instances, - instance_count: 0, - } - } - - pub fn prepare( - &mut self, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - belt: &mut wgpu::util::StagingBelt, - instances: &[Solid], - ) { - let _ = self.instances.resize(device, instances.len()); - let _ = self.instances.write(device, encoder, belt, 0, instances); - - self.instance_count = instances.len(); - } -} - -#[derive(Debug, Clone)] -pub struct Pipeline { - pipeline: wgpu::RenderPipeline, -} - -impl Pipeline { - pub fn new( - device: &wgpu::Device, - format: wgpu::TextureFormat, - constants_layout: &wgpu::BindGroupLayout, - ) -> Self { - let layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("iced_wgpu.quad.solid.pipeline"), - push_constant_ranges: &[], - bind_group_layouts: &[constants_layout], - }); - - let shader = - device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("iced_wgpu.quad.solid.shader"), - source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( - concat!( - include_str!("../shader/color.wgsl"), - "\n", - include_str!("../shader/quad.wgsl"), - "\n", - include_str!("../shader/vertex.wgsl"), - "\n", - include_str!("../shader/quad/solid.wgsl"), - ), - )), - }); - - let pipeline = - device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("iced_wgpu.quad.solid.pipeline"), - layout: Some(&layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: Some("solid_vs_main"), - buffers: &[wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::() as u64, - step_mode: wgpu::VertexStepMode::Instance, - attributes: &wgpu::vertex_attr_array!( - // Color - 0 => Float32x4, - // Position - 1 => Float32x2, - // Size - 2 => Float32x2, - // Border color - 3 => Float32x4, - // Border radius - 4 => Float32x4, - // Border width - 5 => Float32, - // Shadow color - 6 => Float32x4, - // Shadow offset - 7 => Float32x2, - // Shadow blur radius - 8 => Float32, - // Snap - 9 => Uint32, - ), - }], - compilation_options: - wgpu::PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: Some("solid_fs_main"), - targets: &quad::color_target_state(format), - compilation_options: - wgpu::PipelineCompilationOptions::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - front_face: wgpu::FrontFace::Cw, - ..Default::default() - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - cache: None, - }); - - Self { pipeline } - } - - pub fn render<'a>( - &'a self, - render_pass: &mut wgpu::RenderPass<'a>, - constants: &'a wgpu::BindGroup, - layer: &'a Layer, - range: Range, - ) { - render_pass.set_pipeline(&self.pipeline); - render_pass.set_bind_group(0, constants, &[]); - render_pass.set_vertex_buffer(0, layer.instances.slice(..)); - - render_pass.draw(0..6, range.start as u32..range.end as u32); - } -} diff --git a/crates/iced_wgpu/src/settings.rs b/crates/iced_wgpu/src/settings.rs deleted file mode 100644 index 646263b7..00000000 --- a/crates/iced_wgpu/src/settings.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! Configure a renderer. -use crate::core::{Font, Pixels}; -use crate::graphics::{self, Antialiasing}; - -/// The settings of a [`Renderer`]. -/// -/// [`Renderer`]: crate::Renderer -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Settings { - /// The present mode of the [`Renderer`]. - /// - /// [`Renderer`]: crate::Renderer - pub present_mode: wgpu::PresentMode, - - /// The graphics backends to use. - pub backends: wgpu::Backends, - - /// 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: Pixels, - - /// 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 { - present_mode: wgpu::PresentMode::AutoVsync, - backends: wgpu::Backends::all(), - default_font: Font::default(), - default_text_size: Pixels(16.0), - antialiasing: None, - } - } -} - -impl From for Settings { - fn from(settings: graphics::Settings) -> Self { - Self { - present_mode: if settings.vsync { - wgpu::PresentMode::AutoVsync - } else { - wgpu::PresentMode::AutoNoVsync - }, - default_font: settings.default_font, - default_text_size: settings.default_text_size, - antialiasing: settings.antialiasing, - ..Settings::default() - } - } -} - -/// Obtains a [`wgpu::PresentMode`] from the current environment -/// configuration, if set. -/// -/// The value returned by this function can be changed by setting -/// the `ICED_PRESENT_MODE` env variable. The possible values are: -/// -/// - `vsync` → [`wgpu::PresentMode::AutoVsync`] -/// - `no_vsync` → [`wgpu::PresentMode::AutoNoVsync`] -/// - `immediate` → [`wgpu::PresentMode::Immediate`] -/// - `fifo` → [`wgpu::PresentMode::Fifo`] -/// - `fifo_relaxed` → [`wgpu::PresentMode::FifoRelaxed`] -/// - `mailbox` → [`wgpu::PresentMode::Mailbox`] -pub fn present_mode_from_env() -> Option { - let present_mode = std::env::var("ICED_PRESENT_MODE").ok()?; - - match present_mode.to_lowercase().as_str() { - "vsync" => Some(wgpu::PresentMode::AutoVsync), - "no_vsync" => Some(wgpu::PresentMode::AutoNoVsync), - "immediate" => Some(wgpu::PresentMode::Immediate), - "fifo" => Some(wgpu::PresentMode::Fifo), - "fifo_relaxed" => Some(wgpu::PresentMode::FifoRelaxed), - "mailbox" => Some(wgpu::PresentMode::Mailbox), - _ => None, - } -} diff --git a/crates/iced_wgpu/src/shader/blit.wgsl b/crates/iced_wgpu/src/shader/blit.wgsl deleted file mode 100644 index 5e600de4..00000000 --- a/crates/iced_wgpu/src/shader/blit.wgsl +++ /dev/null @@ -1,37 +0,0 @@ -var uvs: array, 6> = array, 6>( - vec2(0.0, 0.0), - vec2(1.0, 0.0), - vec2(1.0, 1.0), - vec2(0.0, 0.0), - vec2(0.0, 1.0), - vec2(1.0, 1.0) -); - -@group(0) @binding(0) var u_sampler: sampler; -@group(0) @binding(1) var u_ratio: vec4; -@group(1) @binding(0) var u_texture: texture_2d; - -struct VertexInput { - @builtin(vertex_index) vertex_index: u32, -} - -struct VertexOutput { - @builtin(position) position: vec4, - @location(0) uv: vec2, -} - -@vertex -fn vs_main(input: VertexInput) -> VertexOutput { - let uv = uvs[input.vertex_index]; - - var out: VertexOutput; - out.uv = uv * u_ratio.xy; - out.position = vec4(uv * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 0.0, 1.0); - - return out; -} - -@fragment -fn fs_main(input: VertexOutput) -> @location(0) vec4 { - return textureSample(u_texture, u_sampler, input.uv); -} diff --git a/crates/iced_wgpu/src/shader/color.wgsl b/crates/iced_wgpu/src/shader/color.wgsl deleted file mode 100644 index a8066368..00000000 --- a/crates/iced_wgpu/src/shader/color.wgsl +++ /dev/null @@ -1,14 +0,0 @@ -fn premultiply(color: vec4) -> vec4 { - return vec4(color.xyz * color.a, color.a); -} - -fn unpack_color(data: vec2) -> vec4 { - return premultiply(unpack_u32(data)); -} - -fn unpack_u32(data: vec2) -> vec4 { - let rg: vec2 = unpack2x16float(data.x); - let ba: vec2 = unpack2x16float(data.y); - - return vec4(rg.y, rg.x, ba.y, ba.x); -} diff --git a/crates/iced_wgpu/src/shader/color/linear_rgb.wgsl b/crates/iced_wgpu/src/shader/color/linear_rgb.wgsl deleted file mode 100644 index a5cf45d4..00000000 --- a/crates/iced_wgpu/src/shader/color/linear_rgb.wgsl +++ /dev/null @@ -1,3 +0,0 @@ -fn interpolate_color(from_: vec4, to_: vec4, factor: f32) -> vec4 { - return mix(from_, to_, factor); -} diff --git a/crates/iced_wgpu/src/shader/image.wgsl b/crates/iced_wgpu/src/shader/image.wgsl deleted file mode 100644 index 3a237c36..00000000 --- a/crates/iced_wgpu/src/shader/image.wgsl +++ /dev/null @@ -1,129 +0,0 @@ -struct Globals { - transform: mat4x4, - scale_factor: f32, -} - -@group(0) @binding(0) var globals: Globals; -@group(0) @binding(1) var u_sampler: sampler; -@group(1) @binding(0) var u_texture: texture_2d_array; - -struct VertexInput { - @builtin(vertex_index) vertex_index: u32, - @location(0) center: vec2, - @location(1) clip_bounds: vec4, - @location(2) border_radius: vec4, - @location(3) tile: vec4, - @location(4) rotation: f32, - @location(5) opacity: f32, - @location(6) atlas_pos: vec2, - @location(7) atlas_scale: vec2, - @location(8) layer: i32, - @location(9) snap: u32, -} - -struct VertexOutput { - @builtin(position) position: vec4, - @location(0) @interpolate(flat) clip_bounds: vec4, - @location(1) @interpolate(flat) border_radius: vec4, - @location(2) @interpolate(flat) atlas: vec4, - @location(3) @interpolate(flat) layer: i32, - @location(4) @interpolate(flat) opacity: f32, - @location(5) uv: vec2, -} - -@vertex -fn vs_main(input: VertexInput) -> VertexOutput { - var out: VertexOutput; - - // Generate a vertex position in the range [0, 1] from the vertex index - let corner = vertex_position(input.vertex_index); - - let tile = input.tile; - let center = input.center; - - // List the unrotated tile corners - let corners = array, 4>( - tile.xy, // Top left - tile.xy + vec2(tile.z, 0.0), // Top right - tile.xy + vec2(0.0, tile.w), // Bottom left - tile.xy + tile.zw // Bottom right - ); - - // Rotate tile corners around center - let cos_r = cos(-input.rotation); // Clockwise - let sin_r = sin(-input.rotation); - var rotated = array, 4>(); - - for (var i = 0u; i < 4u; i++) { - let c = corners[i] - input.center; - rotated[i] = vec2(c.x * cos_r - c.y * sin_r, c.x * sin_r + c.y * cos_r) + input.center; - } - - // Find bounding box of rotated tile - var min_xy = rotated[0]; - var max_xy = rotated[0]; - for (var i = 1u; i < 4u; i++) { - min_xy = min(min_xy, rotated[i]); - max_xy = max(max_xy, rotated[i]); - } - let rotated_bounds = vec4(min_xy, max_xy - min_xy); - - // Intersect with clip bounds - let clip_min = max(rotated_bounds.xy, input.clip_bounds.xy); - let clip_max = min(rotated_bounds.xy + rotated_bounds.zw, input.clip_bounds.xy + input.clip_bounds.zw); - let clipped_tile = vec4(clip_min, max(vec2(0.0), clip_max - clip_min)); - - // Calculate the vertex position - let v_pos = clipped_tile.xy + corner * clipped_tile.zw; - out.position = vec4(vec2(globals.scale_factor), 1.0, 1.0) * vec4(v_pos, 0.0, 1.0); - out.clip_bounds = globals.scale_factor * input.clip_bounds; - - // Calculate rotated UV - let uv = input.atlas_pos + (v_pos - tile.xy) / tile.zw * input.atlas_scale; - let uv_center = input.atlas_pos + input.atlas_scale / 2.0; - - let d = uv - uv_center; - out.uv = vec2(d.x * cos_r - d.y * sin_r, d.x * sin_r + d.y * cos_r) + uv_center; - - // Snap position to the pixel grid - if bool(input.snap) { - out.position = round(out.position); - out.clip_bounds = vec4( - round(out.clip_bounds.xy), - round(out.clip_bounds.xy + out.clip_bounds.zw) - out.clip_bounds.xy, - ); - } - - out.position = globals.transform * out.position; - out.border_radius = globals.scale_factor * min(input.border_radius, vec4(min(input.clip_bounds.z, input.clip_bounds.w) / 2.0)); - out.atlas = vec4(input.atlas_pos, input.atlas_pos + input.atlas_scale); - out.layer = input.layer; - out.opacity = input.opacity; - - return out; -} - -@fragment -fn fs_main(input: VertexOutput) -> @location(0) vec4 { - let fragment = input.position.xy; - let position = input.clip_bounds.xy; - let scale = input.clip_bounds.zw; - - let d = rounded_box_sdf( - 2.0 * (fragment - position - scale / 2.0), - scale, - input.border_radius * 2.0, - ) / 2.0; - - let antialias: f32 = clamp(1.0 - d, 0.0, 1.0); - let inside = all(input.uv >= input.atlas.xy) && all(input.uv <= input.atlas.zw); - - return textureSample(u_texture, u_sampler, input.uv, input.layer) * vec4(1.0, 1.0, 1.0, antialias * input.opacity * f32(inside)); -} - -fn rounded_box_sdf(p: vec2, size: vec2, corners: vec4) -> f32 { - var box_half = select(corners.yz, corners.xw, p.x > 0.0); - var corner = select(box_half.y, box_half.x, p.y > 0.0); - var q = abs(p) - size + corner; - return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - corner; -} diff --git a/crates/iced_wgpu/src/shader/quad.wgsl b/crates/iced_wgpu/src/shader/quad.wgsl deleted file mode 100644 index f8258a4e..00000000 --- a/crates/iced_wgpu/src/shader/quad.wgsl +++ /dev/null @@ -1,13 +0,0 @@ -struct Globals { - transform: mat4x4, - scale: f32, -} - -@group(0) @binding(0) var globals: Globals; - -fn rounded_box_sdf(p: vec2, size: vec2, corners: vec4) -> f32 { - var box_half = select(corners.yz, corners.xw, p.x > 0.0); - var corner = select(box_half.y, box_half.x, p.y > 0.0); - var q = abs(p) - size + corner; - return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - corner; -} diff --git a/crates/iced_wgpu/src/shader/quad/gradient.wgsl b/crates/iced_wgpu/src/shader/quad/gradient.wgsl deleted file mode 100644 index 7c7a6e45..00000000 --- a/crates/iced_wgpu/src/shader/quad/gradient.wgsl +++ /dev/null @@ -1,184 +0,0 @@ -struct GradientVertexInput { - @builtin(vertex_index) vertex_index: u32, - @location(0) @interpolate(flat) colors_1: vec4, - @location(1) @interpolate(flat) colors_2: vec4, - @location(2) @interpolate(flat) colors_3: vec4, - @location(3) @interpolate(flat) colors_4: vec4, - @location(4) @interpolate(flat) offsets: vec4, - @location(5) direction: vec4, - @location(6) position_and_scale: vec4, - @location(7) border_color: vec4, - @location(8) border_radius: vec4, - @location(9) border_width: f32, - @location(10) snap: u32, -} - -struct GradientVertexOutput { - @builtin(position) position: vec4, - @location(1) @interpolate(flat) colors_1: vec4, - @location(2) @interpolate(flat) colors_2: vec4, - @location(3) @interpolate(flat) colors_3: vec4, - @location(4) @interpolate(flat) colors_4: vec4, - @location(5) @interpolate(flat) offsets: vec4, - @location(6) direction: vec4, - @location(7) position_and_scale: vec4, - @location(8) border_color: vec4, - @location(9) border_radius: vec4, - @location(10) border_width: f32, -} - -@vertex -fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput { - var out: GradientVertexOutput; - - var pos: vec2 = input.position_and_scale.xy * globals.scale; - var scale: vec2 = input.position_and_scale.zw * globals.scale; - - var pos_snap = vec2(0.0, 0.0); - var scale_snap = vec2(0.0, 0.0); - - if bool(input.snap) { - pos_snap = round(pos + vec2(0.001, 0.001)) - pos; - scale_snap = round(pos + scale + vec2(0.001, 0.001)) - pos - pos_snap - scale; - } - - var min_border_radius = min(input.position_and_scale.z, input.position_and_scale.w) * 0.5; - var border_radius: vec4 = vec4( - min(input.border_radius.x, min_border_radius), - min(input.border_radius.y, min_border_radius), - min(input.border_radius.z, min_border_radius), - min(input.border_radius.w, min_border_radius) - ); - - var transform: mat4x4 = mat4x4( - vec4(scale.x + scale_snap.x + 1.0, 0.0, 0.0, 0.0), - vec4(0.0, scale.y + scale_snap.y + 1.0, 0.0, 0.0), - vec4(0.0, 0.0, 1.0, 0.0), - vec4(pos + pos_snap - vec2(0.5, 0.5), 0.0, 1.0) - ); - - out.position = globals.transform * transform * vec4(vertex_position(input.vertex_index), 0.0, 1.0); - out.colors_1 = input.colors_1; - out.colors_2 = input.colors_2; - out.colors_3 = input.colors_3; - out.colors_4 = input.colors_4; - out.offsets = input.offsets; - out.direction = input.direction * globals.scale; - out.position_and_scale = vec4(pos + pos_snap, scale + scale_snap); - out.border_color = premultiply(input.border_color); - out.border_radius = border_radius * globals.scale; - out.border_width = input.border_width * globals.scale; - - return out; -} - -fn random(coords: vec2) -> f32 { - return fract(sin(dot(coords, vec2(12.9898,78.233))) * 43758.5453); -} - -/// Returns the current interpolated color with a max 8-stop gradient -fn gradient( - raw_position: vec2, - direction: vec4, - colors: array, 8>, - offsets: array, - last_index: i32 -) -> vec4 { - let start = direction.xy; - let end = direction.zw; - - let v1 = end - start; - let v2 = raw_position - start; - let unit = normalize(v1); - let coord_offset = dot(unit, v2) / length(v1); - - //need to store these as a var to use dynamic indexing in a loop - //this is already added to wgsl spec but not in wgpu yet - var colors_arr = colors; - var offsets_arr = offsets; - - var color: vec4; - - let noise_granularity: f32 = 0.3/255.0; - - for (var i: i32 = 0; i < last_index; i++) { - let curr_offset = offsets_arr[i]; - let next_offset = offsets_arr[i+1]; - - if (coord_offset <= offsets_arr[0]) { - color = colors_arr[0]; - } - - if (curr_offset <= coord_offset && coord_offset <= next_offset) { - let from_ = colors_arr[i]; - let to_ = colors_arr[i+1]; - let factor = smoothstep(curr_offset, next_offset, coord_offset); - - color = interpolate_color(from_, to_, factor); - } - - if (coord_offset >= offsets_arr[last_index]) { - color = colors_arr[last_index]; - } - } - - return color + mix(-noise_granularity, noise_granularity, random(raw_position)); -} - -@fragment -fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { - let colors = array, 8>( - unpack_color(input.colors_1.xy), - unpack_color(input.colors_1.zw), - unpack_color(input.colors_2.xy), - unpack_color(input.colors_2.zw), - unpack_color(input.colors_3.xy), - unpack_color(input.colors_3.zw), - unpack_color(input.colors_4.xy), - unpack_color(input.colors_4.zw), - ); - - let offsets_1: vec4 = unpack_u32(input.offsets.xy); - let offsets_2: vec4 = unpack_u32(input.offsets.zw); - - var offsets = array( - offsets_1.x, - offsets_1.y, - offsets_1.z, - offsets_1.w, - offsets_2.x, - offsets_2.y, - offsets_2.z, - offsets_2.w, - ); - - //TODO could just pass this in to the shader but is probably more performant to just check it here - var last_index = 7; - for (var i: i32 = 0; i <= 7; i++) { - if (offsets[i] > 1.0) { - last_index = i - 1; - break; - } - } - - var mixed_color: vec4 = gradient(input.position.xy, input.direction, colors, offsets, last_index); - - let pos = input.position_and_scale.xy; - let scale = input.position_and_scale.zw; - - var dist: f32 = rounded_box_sdf( - -(input.position.xy - pos - scale / 2.0) * 2.0, - scale, - input.border_radius * 2.0 - ) / 2.0; - - if (input.border_width > 0.0) { - mixed_color = mix( - mixed_color, - input.border_color, - clamp(0.5 + dist + input.border_width, 0.0, 1.0) - ); - } - - return mixed_color * clamp(0.5-dist, 0.0, 1.0); -} diff --git a/crates/iced_wgpu/src/shader/quad/solid.wgsl b/crates/iced_wgpu/src/shader/quad/solid.wgsl deleted file mode 100644 index 2a07e3fa..00000000 --- a/crates/iced_wgpu/src/shader/quad/solid.wgsl +++ /dev/null @@ -1,102 +0,0 @@ -struct SolidVertexInput { - @builtin(vertex_index) vertex_index: u32, - @location(0) color: vec4, - @location(1) pos: vec2, - @location(2) scale: vec2, - @location(3) border_color: vec4, - @location(4) border_radius: vec4, - @location(5) border_width: f32, - @location(6) shadow_color: vec4, - @location(7) shadow_offset: vec2, - @location(8) shadow_blur_radius: f32, - @location(9) snap: u32, -} - -struct SolidVertexOutput { - @builtin(position) position: vec4, - @location(0) color: vec4, - @location(1) border_color: vec4, - @location(2) pos: vec2, - @location(3) scale: vec2, - @location(4) border_radius: vec4, - @location(5) border_width: f32, - @location(6) shadow_color: vec4, - @location(7) shadow_offset: vec2, - @location(8) shadow_blur_radius: f32, -} - -@vertex -fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput { - var out: SolidVertexOutput; - - var pos: vec2 = (input.pos + min(input.shadow_offset, vec2(0.0, 0.0)) - input.shadow_blur_radius) * globals.scale; - var scale: vec2 = (input.scale + vec2(abs(input.shadow_offset.x), abs(input.shadow_offset.y)) + input.shadow_blur_radius * 2.0) * globals.scale; - - var pos_snap = vec2(0.0, 0.0); - var scale_snap = vec2(0.0, 0.0); - - if bool(input.snap) { - pos_snap = round(pos + vec2(0.001, 0.001)) - pos; - scale_snap = round(pos + scale + vec2(0.001, 0.001)) - pos - pos_snap - scale; - } - - let border_radius = min(input.border_radius, vec4(min(input.scale.x, input.scale.y) / 2.0)); - - var transform: mat4x4 = mat4x4( - vec4(scale.x + scale_snap.x + 1.0, 0.0, 0.0, 0.0), - vec4(0.0, scale.y + scale_snap.y + 1.0, 0.0, 0.0), - vec4(0.0, 0.0, 1.0, 0.0), - vec4(pos + pos_snap - vec2(0.5, 0.5), 0.0, 1.0) - ); - - out.position = globals.transform * transform * vec4(vertex_position(input.vertex_index), 0.0, 1.0); - out.color = premultiply(input.color); - out.border_color = premultiply(input.border_color); - out.pos = input.pos * globals.scale + pos_snap; - out.scale = input.scale * globals.scale + scale_snap; - out.border_radius = border_radius * globals.scale; - out.border_width = input.border_width * globals.scale; - out.shadow_color = premultiply(input.shadow_color); - out.shadow_offset = input.shadow_offset * globals.scale; - out.shadow_blur_radius = input.shadow_blur_radius * globals.scale; - - return out; -} - -@fragment -fn solid_fs_main( - input: SolidVertexOutput -) -> @location(0) vec4 { - var mixed_color: vec4 = input.color; - - var dist = rounded_box_sdf( - -(input.position.xy - input.pos - input.scale * 0.5) * 2.0, - input.scale, - input.border_radius * 2.0 - ) / 2.0; - - if (input.border_width > 0.0) { - mixed_color = mix( - input.color, - input.border_color, - clamp(0.5 + dist + input.border_width, 0.0, 1.0) - ); - } - - var quad_alpha: f32 = clamp(0.5-dist, 0.0, 1.0); - - let quad_color = mixed_color * quad_alpha; - - if input.shadow_color.a > 0.0 { - var shadow_dist: f32 = rounded_box_sdf( - -(input.position.xy - input.pos - input.shadow_offset - input.scale/2.0) * 2.0, - input.scale, - input.border_radius * 2.0 - ) / 2.0; - let shadow_alpha = 1.0 - smoothstep(-input.shadow_blur_radius, input.shadow_blur_radius, max(shadow_dist, 0.0)); - - return mix(quad_color, input.shadow_color, (1.0 - quad_alpha) * shadow_alpha); - } else { - return quad_color; - } -} diff --git a/crates/iced_wgpu/src/shader/triangle.wgsl b/crates/iced_wgpu/src/shader/triangle.wgsl deleted file mode 100644 index e4c19344..00000000 --- a/crates/iced_wgpu/src/shader/triangle.wgsl +++ /dev/null @@ -1,5 +0,0 @@ -struct Globals { - transform: mat4x4, -} - -@group(0) @binding(0) var globals: Globals; diff --git a/crates/iced_wgpu/src/shader/triangle/gradient.wgsl b/crates/iced_wgpu/src/shader/triangle/gradient.wgsl deleted file mode 100644 index 31a48026..00000000 --- a/crates/iced_wgpu/src/shader/triangle/gradient.wgsl +++ /dev/null @@ -1,127 +0,0 @@ -struct GradientVertexInput { - @location(0) v_pos: vec2, - @location(1) @interpolate(flat) colors_1: vec4, - @location(2) @interpolate(flat) colors_2: vec4, - @location(3) @interpolate(flat) colors_3: vec4, - @location(4) @interpolate(flat) colors_4: vec4, - @location(5) @interpolate(flat) offsets: vec4, - @location(6) direction: vec4, -} - -struct GradientVertexOutput { - @builtin(position) position: vec4, - @location(0) raw_position: vec2, - @location(1) @interpolate(flat) colors_1: vec4, - @location(2) @interpolate(flat) colors_2: vec4, - @location(3) @interpolate(flat) colors_3: vec4, - @location(4) @interpolate(flat) colors_4: vec4, - @location(5) @interpolate(flat) offsets: vec4, - @location(6) direction: vec4, -} - -@vertex -fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput { - var output: GradientVertexOutput; - - output.position = globals.transform * vec4(input.v_pos, 0.0, 1.0); - output.raw_position = input.v_pos; - output.colors_1 = input.colors_1; - output.colors_2 = input.colors_2; - output.colors_3 = input.colors_3; - output.colors_4 = input.colors_4; - output.offsets = input.offsets; - output.direction = input.direction; - - return output; -} - -/// Returns the current interpolated color with a max 8-stop gradient -fn gradient( - raw_position: vec2, - direction: vec4, - colors: array, 8>, - offsets: array, - last_index: i32 -) -> vec4 { - let start = direction.xy; - let end = direction.zw; - - let v1 = end - start; - let v2 = raw_position - start; - let unit = normalize(v1); - let coord_offset = dot(unit, v2) / length(v1); - - //need to store these as a var to use dynamic indexing in a loop - //this is already added to wgsl spec but not in wgpu yet - var colors_arr = colors; - var offsets_arr = offsets; - - var color: vec4; - - let noise_granularity: f32 = 0.3/255.0; - - for (var i: i32 = 0; i < last_index; i++) { - let curr_offset = offsets_arr[i]; - let next_offset = offsets_arr[i+1]; - - if (coord_offset <= offsets_arr[0]) { - color = colors_arr[0]; - } - - if (curr_offset <= coord_offset && coord_offset <= next_offset) { - let from_ = colors_arr[i]; - let to_ = colors_arr[i+1]; - let factor = smoothstep(curr_offset, next_offset, coord_offset); - - color = interpolate_color(from_, to_, factor); - } - - if (coord_offset >= offsets_arr[last_index]) { - color = colors_arr[last_index]; - } - } - - return color + mix(-noise_granularity, noise_granularity, random(raw_position)); -} - -@fragment -fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { - let colors = array, 8>( - unpack_color(input.colors_1.xy), - unpack_color(input.colors_1.zw), - unpack_color(input.colors_2.xy), - unpack_color(input.colors_2.zw), - unpack_color(input.colors_3.xy), - unpack_color(input.colors_3.zw), - unpack_color(input.colors_4.xy), - unpack_color(input.colors_4.zw), - ); - - let offsets_1: vec4 = unpack_u32(input.offsets.xy); - let offsets_2: vec4 = unpack_u32(input.offsets.zw); - - var offsets = array( - offsets_1.x, - offsets_1.y, - offsets_1.z, - offsets_1.w, - offsets_2.x, - offsets_2.y, - offsets_2.z, - offsets_2.w, - ); - - var last_index = 7; - for (var i: i32 = 0; i <= 7; i++) { - if (offsets[i] >= 1.0) { - last_index = i; - break; - } - } - - return gradient(input.raw_position, input.direction, colors, offsets, last_index); -} - -fn random(coords: vec2) -> f32 { - return fract(sin(dot(coords, vec2(12.9898,78.233))) * 43758.5453); -} diff --git a/crates/iced_wgpu/src/shader/triangle/solid.wgsl b/crates/iced_wgpu/src/shader/triangle/solid.wgsl deleted file mode 100644 index b0d8057b..00000000 --- a/crates/iced_wgpu/src/shader/triangle/solid.wgsl +++ /dev/null @@ -1,24 +0,0 @@ -struct SolidVertexInput { - @location(0) position: vec2, - @location(1) color: vec4, -} - -struct SolidVertexOutput { - @builtin(position) position: vec4, - @location(0) color: vec4, -} - -@vertex -fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput { - var out: SolidVertexOutput; - - out.color = premultiply(input.color); - out.position = globals.transform * vec4(input.position, 0.0, 1.0); - - return out; -} - -@fragment -fn solid_fs_main(input: SolidVertexOutput) -> @location(0) vec4 { - return input.color; -} diff --git a/crates/iced_wgpu/src/shader/vertex.wgsl b/crates/iced_wgpu/src/shader/vertex.wgsl deleted file mode 100644 index e6af6fc0..00000000 --- a/crates/iced_wgpu/src/shader/vertex.wgsl +++ /dev/null @@ -1,7 +0,0 @@ -// Compute the normalized quad coordinates based on the vertex index. -fn vertex_position(vertex_index: u32) -> vec2 { - // #: 0 1 2 3 4 5 - // x: 1 1 0 0 0 1 - // y: 1 0 0 0 1 1 - return vec2((vec2(1u, 2u) + vertex_index) % vec2(6u) < vec2(3u)); -} diff --git a/crates/iced_wgpu/src/text.rs b/crates/iced_wgpu/src/text.rs deleted file mode 100644 index 16efa092..00000000 --- a/crates/iced_wgpu/src/text.rs +++ /dev/null @@ -1,648 +0,0 @@ -use crate::core::alignment; -use crate::core::text::Alignment; -use crate::core::{Rectangle, Size, Transformation}; -use crate::graphics::cache; -use crate::graphics::color; -use crate::graphics::text::cache::{self as text_cache, Cache as BufferCache}; -use crate::graphics::text::{Editor, Paragraph, font_system, to_color}; - -use rustc_hash::FxHashMap; -use std::collections::hash_map; -use std::sync::atomic::{self, AtomicU64}; -use std::sync::{self, Arc, RwLock}; - -pub use crate::graphics::Text; - -const COLOR_MODE: cryoglyph::ColorMode = if color::GAMMA_CORRECTION { - cryoglyph::ColorMode::Accurate -} else { - cryoglyph::ColorMode::Web -}; - -pub type Batch = Vec; - -#[derive(Debug)] -pub enum Item { - Group { - transformation: Transformation, - text: Vec, - }, - Cached { - transformation: Transformation, - cache: Cache, - }, -} - -#[derive(Debug, Clone)] -pub struct Cache { - id: Id, - group: cache::Group, - text: Arc<[Text]>, - version: usize, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Id(u64); - -impl Cache { - pub fn new(group: cache::Group, text: Vec) -> Option { - static NEXT_ID: AtomicU64 = AtomicU64::new(0); - - if text.is_empty() { - return None; - } - - Some(Self { - id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)), - group, - text: Arc::from(text), - version: 0, - }) - } - - pub fn update(&mut self, text: Vec) { - if self.text.is_empty() && text.is_empty() { - return; - } - - self.text = Arc::from(text); - self.version += 1; - } -} - -struct Upload { - renderer: cryoglyph::TextRenderer, - buffer_cache: BufferCache, - transformation: Transformation, - version: usize, - group_version: usize, - text: sync::Weak<[Text]>, - _atlas: sync::Weak<()>, -} - -#[derive(Default)] -pub struct Storage { - groups: FxHashMap, - uploads: FxHashMap, -} - -struct Group { - atlas: cryoglyph::TextAtlas, - version: usize, - should_trim: bool, - handle: Arc<()>, // Keeps track of active uploads -} - -impl Storage { - fn get(&self, cache: &Cache) -> Option<(&cryoglyph::TextAtlas, &Upload)> { - if cache.text.is_empty() { - return None; - } - - self.groups - .get(&cache.group) - .map(|group| &group.atlas) - .zip(self.uploads.get(&cache.id)) - } - - fn prepare( - &mut self, - device: &wgpu::Device, - queue: &wgpu::Queue, - viewport: &cryoglyph::Viewport, - encoder: &mut wgpu::CommandEncoder, - format: wgpu::TextureFormat, - state: &cryoglyph::Cache, - cache: &Cache, - new_transformation: Transformation, - bounds: Rectangle, - ) { - let group_count = self.groups.len(); - - let group = self.groups.entry(cache.group).or_insert_with(|| { - log::debug!( - "New text atlas: {:?} (total: {})", - cache.group, - group_count + 1 - ); - - Group { - atlas: cryoglyph::TextAtlas::with_color_mode( - device, queue, state, format, COLOR_MODE, - ), - version: 0, - should_trim: false, - handle: Arc::new(()), - } - }); - - match self.uploads.entry(cache.id) { - hash_map::Entry::Occupied(entry) => { - let upload = entry.into_mut(); - - if upload.version != cache.version - || upload.group_version != group.version - || upload.transformation != new_transformation - { - if !cache.text.is_empty() { - let _ = prepare( - device, - queue, - viewport, - encoder, - &mut upload.renderer, - &mut group.atlas, - &mut upload.buffer_cache, - &cache.text, - bounds, - new_transformation, - ); - } - - // Only trim if glyphs have changed - group.should_trim = - group.should_trim || upload.version != cache.version; - - upload.text = Arc::downgrade(&cache.text); - upload.version = cache.version; - upload.group_version = group.version; - upload.transformation = new_transformation; - - upload.buffer_cache.trim(); - } - } - hash_map::Entry::Vacant(entry) => { - let mut renderer = cryoglyph::TextRenderer::new( - &mut group.atlas, - device, - wgpu::MultisampleState::default(), - None, - ); - - let mut buffer_cache = BufferCache::new(); - - if !cache.text.is_empty() { - let _ = prepare( - device, - queue, - viewport, - encoder, - &mut renderer, - &mut group.atlas, - &mut buffer_cache, - &cache.text, - bounds, - new_transformation, - ); - } - - let _ = entry.insert(Upload { - renderer, - buffer_cache, - transformation: new_transformation, - version: 0, - group_version: group.version, - text: Arc::downgrade(&cache.text), - _atlas: Arc::downgrade(&group.handle), - }); - - group.should_trim = cache.group.is_singleton(); - - log::debug!( - "New text upload: {} (total: {})", - cache.id.0, - self.uploads.len() - ); - } - } - } - - pub fn trim(&mut self) { - self.uploads - .retain(|_id, upload| upload.text.strong_count() > 0); - - self.groups.retain(|id, group| { - let active_uploads = Arc::weak_count(&group.handle); - - if active_uploads == 0 { - log::debug!("Dropping text atlas: {id:?}"); - - return false; - } - - if group.should_trim { - log::trace!("Trimming text atlas: {id:?}"); - - group.atlas.trim(); - group.should_trim = false; - - // We only need to worry about glyph fighting - // when the atlas may be shared by multiple - // uploads. - if !id.is_singleton() { - log::debug!( - "Invalidating text atlas: {id:?} \ - (uploads: {active_uploads})" - ); - - group.version += 1; - } - } - - true - }); - } -} - -pub struct Viewport(cryoglyph::Viewport); - -impl Viewport { - pub fn update(&mut self, queue: &wgpu::Queue, resolution: Size) { - self.0.update( - queue, - cryoglyph::Resolution { - width: resolution.width, - height: resolution.height, - }, - ); - } -} - -#[derive(Clone)] -pub struct Pipeline { - format: wgpu::TextureFormat, - cache: cryoglyph::Cache, - atlas: Arc>, -} - -impl Pipeline { - pub fn new( - device: &wgpu::Device, - queue: &wgpu::Queue, - format: wgpu::TextureFormat, - ) -> Self { - let cache = cryoglyph::Cache::new(device); - let atlas = cryoglyph::TextAtlas::with_color_mode( - device, queue, &cache, format, COLOR_MODE, - ); - - Pipeline { - format, - cache, - atlas: Arc::new(RwLock::new(atlas)), - } - } - - pub fn create_viewport(&self, device: &wgpu::Device) -> Viewport { - Viewport(cryoglyph::Viewport::new(device, &self.cache)) - } - - pub fn trim(&self) { - self.atlas.write().expect("Write text atlas").trim(); - } -} - -#[derive(Default)] -pub struct State { - renderers: Vec, - prepare_layer: usize, - cache: BufferCache, - storage: Storage, -} - -impl State { - pub fn new() -> Self { - Self::default() - } - - pub fn prepare( - &mut self, - pipeline: &Pipeline, - device: &wgpu::Device, - queue: &wgpu::Queue, - viewport: &Viewport, - encoder: &mut wgpu::CommandEncoder, - batch: &Batch, - layer_bounds: Rectangle, - layer_transformation: Transformation, - ) { - let mut atlas = pipeline.atlas.write().expect("Write to text atlas"); - - for item in batch { - match item { - Item::Group { - transformation, - text, - } => { - if self.renderers.len() <= self.prepare_layer { - self.renderers.push(cryoglyph::TextRenderer::new( - &mut atlas, - device, - wgpu::MultisampleState::default(), - None, - )); - } - - let renderer = &mut self.renderers[self.prepare_layer]; - let result = prepare( - device, - queue, - &viewport.0, - encoder, - renderer, - &mut atlas, - &mut self.cache, - text, - layer_bounds * layer_transformation, - layer_transformation * *transformation, - ); - - match result { - Ok(()) => { - self.prepare_layer += 1; - } - Err(cryoglyph::PrepareError::AtlasFull) => { - // If the atlas cannot grow, then all bets are off. - // Instead of panicking, we will just pray that the result - // will be somewhat readable... - } - } - } - Item::Cached { - transformation, - cache, - } => { - self.storage.prepare( - device, - queue, - &viewport.0, - encoder, - pipeline.format, - &pipeline.cache, - cache, - layer_transformation * *transformation, - layer_bounds * layer_transformation, - ); - } - } - } - } - - pub fn render<'a>( - &'a self, - pipeline: &'a Pipeline, - viewport: &'a Viewport, - start: usize, - batch: &'a Batch, - bounds: Rectangle, - render_pass: &mut wgpu::RenderPass<'a>, - ) -> usize { - let atlas = pipeline.atlas.read().expect("Read text atlas"); - let mut layer_count = 0; - - render_pass.set_scissor_rect( - bounds.x, - bounds.y, - bounds.width, - bounds.height, - ); - - for item in batch { - match item { - Item::Group { .. } => { - let renderer = &self.renderers[start + layer_count]; - - renderer - .render(&atlas, &viewport.0, render_pass) - .expect("Render text"); - - layer_count += 1; - } - Item::Cached { cache, .. } => { - if let Some((atlas, upload)) = self.storage.get(cache) { - upload - .renderer - .render(atlas, &viewport.0, render_pass) - .expect("Render cached text"); - } - } - } - } - - layer_count - } - - pub fn trim(&mut self) { - self.cache.trim(); - self.storage.trim(); - - self.prepare_layer = 0; - } -} - -fn prepare( - device: &wgpu::Device, - queue: &wgpu::Queue, - viewport: &cryoglyph::Viewport, - encoder: &mut wgpu::CommandEncoder, - renderer: &mut cryoglyph::TextRenderer, - atlas: &mut cryoglyph::TextAtlas, - buffer_cache: &mut BufferCache, - sections: &[Text], - layer_bounds: Rectangle, - layer_transformation: Transformation, -) -> Result<(), cryoglyph::PrepareError> { - let mut font_system = font_system().write().expect("Write font system"); - let font_system = font_system.raw(); - - enum Allocation { - Paragraph(Paragraph), - Editor(Editor), - Cache(text_cache::KeyHash), - Raw(Arc), - } - - let allocations: Vec<_> = sections - .iter() - .map(|section| match section { - Text::Paragraph { paragraph, .. } => { - paragraph.upgrade().map(Allocation::Paragraph) - } - Text::Editor { editor, .. } => { - editor.upgrade().map(Allocation::Editor) - } - Text::Cached { - content, - bounds, - size, - line_height, - font, - shaping, - align_x, - .. - } => { - let (key, _) = buffer_cache.allocate( - font_system, - text_cache::Key { - content, - size: f32::from(*size), - line_height: f32::from(*line_height), - font: *font, - align_x: *align_x, - bounds: Size { - width: bounds.width, - height: bounds.height, - }, - shaping: *shaping, - }, - ); - - Some(Allocation::Cache(key)) - } - Text::Raw { raw, .. } => raw.buffer.upgrade().map(Allocation::Raw), - }) - .collect(); - - let text_areas = sections.iter().zip(allocations.iter()).filter_map( - |(section, allocation)| { - let (buffer, position, color, clip_bounds, transformation) = - match section { - Text::Paragraph { - position, - color, - clip_bounds, - transformation, - .. - } => { - let Some(Allocation::Paragraph(paragraph)) = allocation - else { - return None; - }; - - ( - paragraph.buffer(), - *position, - *color, - *clip_bounds, - *transformation, - ) - } - Text::Editor { - position, - color, - clip_bounds, - transformation, - .. - } => { - let Some(Allocation::Editor(editor)) = allocation - else { - return None; - }; - - ( - editor.buffer(), - *position, - *color, - *clip_bounds, - *transformation, - ) - } - Text::Cached { - bounds, - align_x, - align_y, - color, - clip_bounds, - .. - } => { - let Some(Allocation::Cache(key)) = allocation else { - return None; - }; - - let entry = - buffer_cache.get(key).expect("Get cached buffer"); - - let mut position = bounds.position(); - - position.x = match align_x { - Alignment::Default - | Alignment::Left - | Alignment::Justified => position.x, - Alignment::Center => { - position.x - entry.min_bounds.width / 2.0 - } - Alignment::Right => { - position.x - entry.min_bounds.width - } - }; - - position.y = match align_y { - alignment::Vertical::Top => position.y, - alignment::Vertical::Center => { - position.y - entry.min_bounds.height / 2.0 - } - alignment::Vertical::Bottom => { - position.y - entry.min_bounds.height - } - }; - - ( - &entry.buffer, - position, - *color, - *clip_bounds, - Transformation::IDENTITY, - ) - } - Text::Raw { - raw, - transformation, - } => { - let Some(Allocation::Raw(buffer)) = allocation else { - return None; - }; - - ( - buffer.as_ref(), - raw.position, - raw.color, - raw.clip_bounds, - *transformation, - ) - } - }; - - let position = position * transformation * layer_transformation; - - let clip_bounds = layer_bounds.intersection( - &(clip_bounds * transformation * layer_transformation), - )?; - - Some(cryoglyph::TextArea { - buffer, - left: position.x, - top: position.y, - scale: transformation.scale_factor() - * layer_transformation.scale_factor(), - bounds: cryoglyph::TextBounds { - left: clip_bounds.x.round() as i32, - top: clip_bounds.y.round() as i32, - right: (clip_bounds.x + clip_bounds.width).round() as i32, - bottom: (clip_bounds.y + clip_bounds.height).round() as i32, - }, - default_color: to_color(color), - }) - }, - ); - - renderer.prepare( - device, - queue, - encoder, - font_system, - atlas, - viewport, - text_areas, - &mut cryoglyph::SwashCache::new(), - ) -} diff --git a/crates/iced_wgpu/src/triangle.rs b/crates/iced_wgpu/src/triangle.rs deleted file mode 100644 index fa5ecb9c..00000000 --- a/crates/iced_wgpu/src/triangle.rs +++ /dev/null @@ -1,963 +0,0 @@ -//! Draw meshes of triangles. -mod msaa; - -use crate::Buffer; -use crate::core::{Point, Rectangle, Size, Transformation, Vector}; -use crate::graphics::Antialiasing; -use crate::graphics::mesh::{self, Mesh}; - -use rustc_hash::FxHashMap; -use std::collections::hash_map; -use std::sync::Weak; - -const INITIAL_INDEX_COUNT: usize = 1_000; -const INITIAL_VERTEX_COUNT: usize = 1_000; - -pub type Batch = Vec; - -#[derive(Debug)] -pub enum Item { - Group { - transformation: Transformation, - meshes: Vec, - }, - Cached { - transformation: Transformation, - cache: mesh::Cache, - }, -} - -#[derive(Debug)] -struct Upload { - layer: Layer, - transformation: Transformation, - version: usize, - batch: Weak<[Mesh]>, -} - -#[derive(Debug, Default)] -pub struct Storage { - uploads: FxHashMap, -} - -impl Storage { - pub fn new() -> Self { - Self::default() - } - - fn get(&self, cache: &mesh::Cache) -> Option<&Upload> { - if cache.is_empty() { - return None; - } - - self.uploads.get(&cache.id()) - } - - fn prepare( - &mut self, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - belt: &mut wgpu::util::StagingBelt, - solid: &solid::Pipeline, - gradient: &gradient::Pipeline, - cache: &mesh::Cache, - new_transformation: Transformation, - ) { - match self.uploads.entry(cache.id()) { - hash_map::Entry::Occupied(entry) => { - let upload = entry.into_mut(); - - if !cache.is_empty() - && (upload.version != cache.version() - || upload.transformation != new_transformation) - { - upload.layer.prepare( - device, - encoder, - belt, - solid, - gradient, - cache.batch(), - new_transformation, - ); - - upload.batch = cache.downgrade(); - upload.version = cache.version(); - upload.transformation = new_transformation; - } - } - hash_map::Entry::Vacant(entry) => { - let mut layer = Layer::new(device, solid, gradient); - - layer.prepare( - device, - encoder, - belt, - solid, - gradient, - cache.batch(), - new_transformation, - ); - - let _ = entry.insert(Upload { - layer, - transformation: new_transformation, - version: 0, - batch: cache.downgrade(), - }); - - log::debug!( - "New mesh upload: {:?} (total: {})", - cache.id(), - self.uploads.len() - ); - } - } - } - - pub fn trim(&mut self) { - self.uploads - .retain(|_id, upload| upload.batch.strong_count() > 0); - } -} - -#[derive(Debug, Clone)] -pub struct Pipeline { - msaa: Option, - solid: solid::Pipeline, - gradient: gradient::Pipeline, -} - -pub struct State { - msaa: Option, - layers: Vec, - prepare_layer: usize, - storage: Storage, -} - -impl State { - pub fn new(device: &wgpu::Device, pipeline: &Pipeline) -> Self { - Self { - msaa: pipeline - .msaa - .as_ref() - .map(|pipeline| msaa::State::new(device, pipeline)), - layers: Vec::new(), - prepare_layer: 0, - storage: Storage::new(), - } - } - - pub fn prepare( - &mut self, - pipeline: &Pipeline, - device: &wgpu::Device, - belt: &mut wgpu::util::StagingBelt, - encoder: &mut wgpu::CommandEncoder, - items: &[Item], - scale: Transformation, - target_size: Size, - ) { - let projection = if let Some((state, pipeline)) = - self.msaa.as_mut().zip(pipeline.msaa.as_ref()) - { - state.prepare(device, encoder, belt, pipeline, target_size) * scale - } else { - Transformation::orthographic(target_size.width, target_size.height) - * scale - }; - - for item in items { - match item { - Item::Group { - transformation, - meshes, - } => { - if self.layers.len() <= self.prepare_layer { - self.layers.push(Layer::new( - device, - &pipeline.solid, - &pipeline.gradient, - )); - } - - let layer = &mut self.layers[self.prepare_layer]; - layer.prepare( - device, - encoder, - belt, - &pipeline.solid, - &pipeline.gradient, - meshes, - projection * *transformation, - ); - - self.prepare_layer += 1; - } - Item::Cached { - transformation, - cache, - } => { - self.storage.prepare( - device, - encoder, - belt, - &pipeline.solid, - &pipeline.gradient, - cache, - projection * *transformation, - ); - } - } - } - } - - pub fn render( - &mut self, - pipeline: &Pipeline, - encoder: &mut wgpu::CommandEncoder, - target: &wgpu::TextureView, - start: usize, - batch: &Batch, - bounds: Rectangle, - screen_transformation: Transformation, - ) -> usize { - let mut layer_count = 0; - - let items = batch.iter().filter_map(|item| match item { - Item::Group { - transformation, - meshes, - } => { - let layer = &self.layers[start + layer_count]; - layer_count += 1; - - Some(( - layer, - meshes.as_slice(), - screen_transformation * *transformation, - )) - } - Item::Cached { - transformation, - cache, - } => { - let upload = self.storage.get(cache)?; - - Some(( - &upload.layer, - cache.batch(), - screen_transformation * *transformation, - )) - } - }); - - render( - encoder, - target, - self.msaa.as_ref().zip(pipeline.msaa.as_ref()), - &pipeline.solid, - &pipeline.gradient, - bounds, - items, - ); - - layer_count - } - - pub fn trim(&mut self) { - self.storage.trim(); - - self.prepare_layer = 0; - } -} - -impl Pipeline { - pub fn new( - device: &wgpu::Device, - format: wgpu::TextureFormat, - antialiasing: Option, - ) -> Pipeline { - Pipeline { - msaa: antialiasing.map(|a| msaa::Pipeline::new(device, format, a)), - solid: solid::Pipeline::new(device, format, antialiasing), - gradient: gradient::Pipeline::new(device, format, antialiasing), - } - } -} - -fn render<'a>( - encoder: &mut wgpu::CommandEncoder, - target: &wgpu::TextureView, - mut msaa: Option<(&msaa::State, &msaa::Pipeline)>, - solid: &solid::Pipeline, - gradient: &gradient::Pipeline, - bounds: Rectangle, - group: impl Iterator, -) { - { - let mut render_pass = if let Some((_state, pipeline)) = &mut msaa { - pipeline.render_pass(encoder) - } else { - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("iced_wgpu.triangle.render_pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: target, - depth_slice: None, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }) - }; - - for (layer, meshes, transformation) in group { - layer.render( - solid, - gradient, - meshes, - bounds, - transformation, - &mut render_pass, - ); - } - } - - if let Some((state, pipeline)) = msaa { - state.render(pipeline, encoder, target); - } -} - -#[derive(Debug)] -pub struct Layer { - index_buffer: Buffer, - solid: solid::Layer, - gradient: gradient::Layer, -} - -impl Layer { - fn new( - device: &wgpu::Device, - solid: &solid::Pipeline, - gradient: &gradient::Pipeline, - ) -> Self { - Self { - index_buffer: Buffer::new( - device, - "iced_wgpu.triangle.index_buffer", - INITIAL_INDEX_COUNT, - wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, - ), - solid: solid::Layer::new(device, &solid.constants_layout), - gradient: gradient::Layer::new(device, &gradient.constants_layout), - } - } - - fn prepare( - &mut self, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - belt: &mut wgpu::util::StagingBelt, - solid: &solid::Pipeline, - gradient: &gradient::Pipeline, - meshes: &[Mesh], - transformation: Transformation, - ) { - // 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. - // We are not currently using the return value of these functions as we have no system in - // place to calculate mesh diff, or to know whether or not that would be more performant for - // the majority of use cases. Therefore we will write GPU data every frame (for now). - let _ = self.index_buffer.resize(device, count.indices); - let _ = self.solid.vertices.resize(device, count.solid_vertices); - let _ = self - .gradient - .vertices - .resize(device, count.gradient_vertices); - - if self.solid.uniforms.resize(device, count.solids) { - self.solid.constants = solid::Layer::bind_group( - device, - &self.solid.uniforms.raw, - &solid.constants_layout, - ); - } - - if self.gradient.uniforms.resize(device, count.gradients) { - self.gradient.constants = gradient::Layer::bind_group( - device, - &self.gradient.uniforms.raw, - &gradient.constants_layout, - ); - } - - let mut solid_vertex_offset = 0; - let mut solid_uniform_offset = 0; - let mut gradient_vertex_offset = 0; - let mut gradient_uniform_offset = 0; - let mut index_offset = 0; - - for mesh in meshes { - let clip_bounds = mesh.clip_bounds() * transformation; - let snap_distance = clip_bounds - .snap() - .map(|snapped_bounds| { - Point::new(snapped_bounds.x as f32, snapped_bounds.y as f32) - - clip_bounds.position() - }) - .unwrap_or(Vector::ZERO); - - let uniforms = Uniforms::new( - transformation - * mesh.transformation() - * Transformation::translate( - snap_distance.x, - snap_distance.y, - ), - ); - - let indices = mesh.indices(); - - index_offset += self.index_buffer.write( - device, - encoder, - belt, - index_offset, - indices, - ); - - match mesh { - Mesh::Solid { buffers, .. } => { - solid_vertex_offset += self.solid.vertices.write( - device, - encoder, - belt, - solid_vertex_offset, - &buffers.vertices, - ); - - solid_uniform_offset += self.solid.uniforms.write( - device, - encoder, - belt, - solid_uniform_offset, - &[uniforms], - ); - } - Mesh::Gradient { buffers, .. } => { - gradient_vertex_offset += self.gradient.vertices.write( - device, - encoder, - belt, - gradient_vertex_offset, - &buffers.vertices, - ); - - gradient_uniform_offset += self.gradient.uniforms.write( - device, - encoder, - belt, - gradient_uniform_offset, - &[uniforms], - ); - } - } - } - } - - fn render<'a>( - &'a self, - solid: &'a solid::Pipeline, - gradient: &'a gradient::Pipeline, - meshes: &[Mesh], - bounds: Rectangle, - transformation: Transformation, - render_pass: &mut wgpu::RenderPass<'a>, - ) { - let mut num_solids = 0; - let mut num_gradients = 0; - let mut solid_offset = 0; - let mut gradient_offset = 0; - let mut index_offset = 0; - let mut last_is_solid = None; - - for mesh in meshes { - let Some(clip_bounds) = bounds - .intersection(&(mesh.clip_bounds() * transformation)) - .and_then(Rectangle::snap) - else { - match mesh { - Mesh::Solid { buffers, .. } => { - solid_offset += buffers.vertices.len(); - num_solids += 1; - } - Mesh::Gradient { buffers, .. } => { - gradient_offset += buffers.vertices.len(); - num_gradients += 1; - } - } - continue; - }; - - render_pass.set_scissor_rect( - clip_bounds.x, - clip_bounds.y, - clip_bounds.width, - clip_bounds.height, - ); - - match mesh { - Mesh::Solid { buffers, .. } => { - 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, - &[(num_solids * std::mem::size_of::()) - as u32], - ); - - render_pass.set_vertex_buffer( - 0, - self.solid.vertices.range( - solid_offset, - solid_offset + buffers.vertices.len(), - ), - ); - - num_solids += 1; - solid_offset += buffers.vertices.len(); - } - Mesh::Gradient { buffers, .. } => { - 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, - &[(num_gradients * std::mem::size_of::()) - as u32], - ); - - render_pass.set_vertex_buffer( - 0, - self.gradient.vertices.range( - gradient_offset, - gradient_offset + buffers.vertices.len(), - ), - ); - - num_gradients += 1; - gradient_offset += buffers.vertices.len(); - } - }; - - render_pass.set_index_buffer( - self.index_buffer - .range(index_offset, index_offset + mesh.indices().len()), - wgpu::IndexFormat::Uint32, - ); - - render_pass.draw_indexed(0..mesh.indices().len() as u32, 0, 0..1); - - index_offset += mesh.indices().len(); - } - } -} - -fn fragment_target( - texture_format: wgpu::TextureFormat, -) -> wgpu::ColorTargetState { - wgpu::ColorTargetState { - format: texture_format, - blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING), - write_mask: wgpu::ColorWrites::ALL, - } -} - -fn primitive_state() -> wgpu::PrimitiveState { - wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - front_face: wgpu::FrontFace::Cw, - ..Default::default() - } -} - -fn multisample_state( - antialiasing: Option, -) -> wgpu::MultisampleState { - wgpu::MultisampleState { - count: antialiasing.map(Antialiasing::sample_count).unwrap_or(1), - mask: !0, - alpha_to_coverage_enabled: false, - } -} - -#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] -#[repr(C)] -pub struct Uniforms { - transform: [f32; 16], - /// Uniform values must be 256-aligned; - /// see: [`wgpu::Limits`] `min_uniform_buffer_offset_alignment`. - _padding: [f32; 48], -} - -impl Uniforms { - pub fn new(transform: Transformation) -> Self { - Self { - transform: transform.into(), - _padding: [0.0; 48], - } - } - - pub fn entry() -> wgpu::BindGroupLayoutEntry { - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: true, - min_binding_size: wgpu::BufferSize::new( - std::mem::size_of::() as u64, - ), - }, - count: None, - } - } - - pub fn min_size() -> Option { - wgpu::BufferSize::new(std::mem::size_of::() as u64) - } -} - -mod solid { - use crate::Buffer; - use crate::graphics::Antialiasing; - use crate::graphics::mesh; - use crate::triangle; - - #[derive(Debug, Clone)] - pub struct Pipeline { - pub pipeline: wgpu::RenderPipeline, - pub constants_layout: wgpu::BindGroupLayout, - } - - #[derive(Debug)] - pub struct Layer { - pub vertices: Buffer, - pub uniforms: Buffer, - 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", - triangle::INITIAL_VERTEX_COUNT, - wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - ); - - let uniforms = Buffer::new( - device, - "iced_wgpu.triangle.solid.uniforms", - 1, - wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - ); - - 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: triangle::Uniforms::min_size(), - }, - ), - }], - }) - } - } - - impl Pipeline { - pub fn new( - device: &wgpu::Device, - format: wgpu::TextureFormat, - antialiasing: Option, - ) -> Self { - let constants_layout = device.create_bind_group_layout( - &wgpu::BindGroupLayoutDescriptor { - label: Some("iced_wgpu.triangle.solid.bind_group_layout"), - entries: &[triangle::Uniforms::entry()], - }, - ); - - let layout = device.create_pipeline_layout( - &wgpu::PipelineLayoutDescriptor { - label: Some("iced_wgpu.triangle.solid.pipeline_layout"), - bind_group_layouts: &[&constants_layout], - push_constant_ranges: &[], - }, - ); - - let shader = - device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("iced_wgpu.triangle.solid.shader"), - source: wgpu::ShaderSource::Wgsl( - std::borrow::Cow::Borrowed(concat!( - include_str!("shader/triangle.wgsl"), - "\n", - include_str!("shader/triangle/solid.wgsl"), - "\n", - include_str!("shader/color.wgsl"), - )), - ), - }); - - let pipeline = - device.create_render_pipeline( - &wgpu::RenderPipelineDescriptor { - label: Some("iced_wgpu::triangle::solid pipeline"), - layout: Some(&layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: Some("solid_vs_main"), - buffers: &[wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::< - mesh::SolidVertex2D, - >( - ) - as u64, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &wgpu::vertex_attr_array!( - // Position - 0 => Float32x2, - // Color - 1 => Float32x4, - ), - }], - compilation_options: - wgpu::PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: Some("solid_fs_main"), - targets: &[Some(triangle::fragment_target(format))], - compilation_options: - wgpu::PipelineCompilationOptions::default(), - }), - primitive: triangle::primitive_state(), - depth_stencil: None, - multisample: triangle::multisample_state(antialiasing), - multiview: None, - cache: None, - }, - ); - - Self { - pipeline, - constants_layout, - } - } - } -} - -mod gradient { - use crate::Buffer; - use crate::graphics::Antialiasing; - use crate::graphics::mesh; - use crate::triangle; - - #[derive(Debug, Clone)] - pub struct Pipeline { - pub pipeline: wgpu::RenderPipeline, - pub constants_layout: wgpu::BindGroupLayout, - } - - #[derive(Debug)] - pub struct Layer { - pub vertices: Buffer, - pub uniforms: Buffer, - 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.gradient.vertex_buffer", - triangle::INITIAL_VERTEX_COUNT, - wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - ); - - let uniforms = Buffer::new( - device, - "iced_wgpu.triangle.gradient.uniforms", - 1, - wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - ); - - let constants = - Self::bind_group(device, &uniforms.raw, constants_layout); - - Self { - vertices, - uniforms, - constants, - } - } - - pub fn bind_group( - device: &wgpu::Device, - uniform_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: triangle::Uniforms::min_size(), - }, - ), - }], - }) - } - } - - impl Pipeline { - pub fn new( - device: &wgpu::Device, - format: wgpu::TextureFormat, - antialiasing: Option, - ) -> Self { - let constants_layout = device.create_bind_group_layout( - &wgpu::BindGroupLayoutDescriptor { - label: Some( - "iced_wgpu.triangle.gradient.bind_group_layout", - ), - entries: &[triangle::Uniforms::entry()], - }, - ); - - let layout = device.create_pipeline_layout( - &wgpu::PipelineLayoutDescriptor { - label: Some("iced_wgpu.triangle.gradient.pipeline_layout"), - bind_group_layouts: &[&constants_layout], - push_constant_ranges: &[], - }, - ); - - let shader = - device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("iced_wgpu.triangle.gradient.shader"), - source: wgpu::ShaderSource::Wgsl( - std::borrow::Cow::Borrowed(concat!( - include_str!("shader/triangle.wgsl"), - "\n", - include_str!("shader/triangle/gradient.wgsl"), - "\n", - include_str!("shader/color.wgsl"), - "\n", - include_str!("shader/color/linear_rgb.wgsl") - )), - ), - }); - - let pipeline = device.create_render_pipeline( - &wgpu::RenderPipelineDescriptor { - label: Some("iced_wgpu.triangle.gradient.pipeline"), - layout: Some(&layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: Some("gradient_vs_main"), - buffers: &[wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::< - mesh::GradientVertex2D, - >() - as u64, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &wgpu::vertex_attr_array!( - // Position - 0 => Float32x2, - // Colors 1-2 - 1 => Uint32x4, - // Colors 3-4 - 2 => Uint32x4, - // Colors 5-6 - 3 => Uint32x4, - // Colors 7-8 - 4 => Uint32x4, - // Offsets - 5 => Uint32x4, - // Direction - 6 => Float32x4 - ), - }], - compilation_options: - wgpu::PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: Some("gradient_fs_main"), - targets: &[Some(triangle::fragment_target(format))], - compilation_options: - wgpu::PipelineCompilationOptions::default(), - }), - primitive: triangle::primitive_state(), - depth_stencil: None, - multisample: triangle::multisample_state(antialiasing), - multiview: None, - cache: None, - }, - ); - - Self { - pipeline, - constants_layout, - } - } - } -} diff --git a/crates/iced_wgpu/src/triangle/msaa.rs b/crates/iced_wgpu/src/triangle/msaa.rs deleted file mode 100644 index 8ca49461..00000000 --- a/crates/iced_wgpu/src/triangle/msaa.rs +++ /dev/null @@ -1,371 +0,0 @@ -use crate::core::{Size, Transformation}; -use crate::graphics; - -use std::num::NonZeroU64; -use std::sync::{Arc, RwLock}; - -#[derive(Debug, Clone)] -pub struct Pipeline { - format: wgpu::TextureFormat, - sampler: wgpu::Sampler, - raw: wgpu::RenderPipeline, - constant_layout: wgpu::BindGroupLayout, - texture_layout: wgpu::BindGroupLayout, - sample_count: u32, - targets: Arc>>, -} - -impl Pipeline { - pub fn new( - device: &wgpu::Device, - format: wgpu::TextureFormat, - antialiasing: graphics::Antialiasing, - ) -> Pipeline { - let sampler = - device.create_sampler(&wgpu::SamplerDescriptor::default()); - - let constant_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("iced_wgpu::triangle:msaa uniforms layout"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler( - wgpu::SamplerBindingType::NonFiltering, - ), - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - ], - }); - - let texture_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("iced_wgpu::triangle::msaa texture layout"), - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { - filterable: false, - }, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, - }, - count: None, - }], - }); - - let layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("iced_wgpu::triangle::msaa pipeline layout"), - push_constant_ranges: &[], - bind_group_layouts: &[&constant_layout, &texture_layout], - }); - - let shader = - device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("iced_wgpu triangle blit_shader"), - source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( - include_str!("../shader/blit.wgsl"), - )), - }); - - let pipeline = - device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("iced_wgpu::triangle::msaa pipeline"), - layout: Some(&layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: Some("vs_main"), - buffers: &[], - compilation_options: - wgpu::PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: Some("fs_main"), - targets: &[Some(wgpu::ColorTargetState { - format, - blend: Some( - wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING, - ), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: - wgpu::PipelineCompilationOptions::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - front_face: wgpu::FrontFace::Cw, - ..Default::default() - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - cache: None, - }); - - Self { - format, - sampler, - raw: pipeline, - constant_layout, - texture_layout, - sample_count: antialiasing.sample_count(), - targets: Arc::new(RwLock::new(None)), - } - } - - fn targets( - &self, - device: &wgpu::Device, - region_size: Size, - ) -> Targets { - let mut targets = self.targets.write().expect("Write MSAA targets"); - - match targets.as_mut() { - Some(targets) - if region_size.width <= targets.size.width - && region_size.height <= targets.size.height => {} - _ => { - *targets = Some(Targets::new( - device, - self.format, - &self.texture_layout, - self.sample_count, - region_size, - )); - } - } - - targets.as_ref().unwrap().clone() - } - - pub fn render_pass<'a>( - &self, - encoder: &'a mut wgpu::CommandEncoder, - ) -> wgpu::RenderPass<'a> { - let targets = self.targets.read().expect("Read MSAA targets"); - let targets = targets.as_ref().unwrap(); - - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("iced_wgpu.triangle.render_pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &targets.attachment, - depth_slice: None, - resolve_target: Some(&targets.resolve), - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }) - } -} - -#[derive(Debug, Clone)] -struct Targets { - attachment: wgpu::TextureView, - resolve: wgpu::TextureView, - bind_group: wgpu::BindGroup, - size: Size, -} - -impl Targets { - pub fn new( - device: &wgpu::Device, - format: wgpu::TextureFormat, - texture_layout: &wgpu::BindGroupLayout, - sample_count: u32, - size: Size, - ) -> Targets { - let extent = wgpu::Extent3d { - width: size.width, - height: size.height, - depth_or_array_layers: 1, - }; - - let attachment = device.create_texture(&wgpu::TextureDescriptor { - label: Some("iced_wgpu::triangle::msaa attachment"), - size: extent, - mip_level_count: 1, - sample_count, - dimension: wgpu::TextureDimension::D2, - format, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - view_formats: &[], - }); - - let resolve = device.create_texture(&wgpu::TextureDescriptor { - label: Some("iced_wgpu::triangle::msaa resolve target"), - size: extent, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT - | wgpu::TextureUsages::TEXTURE_BINDING, - view_formats: &[], - }); - - let attachment = - attachment.create_view(&wgpu::TextureViewDescriptor::default()); - - let resolve = - resolve.create_view(&wgpu::TextureViewDescriptor::default()); - - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu::triangle::msaa texture bind group"), - layout: texture_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&resolve), - }], - }); - - Targets { - attachment, - resolve, - bind_group, - size, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] -#[repr(C)] -struct Ratio { - u: f32, - v: f32, - // Padding field for 16-byte alignment. - // See https://docs.rs/wgpu/latest/wgpu/struct.DownlevelFlags.html#associatedconstant.BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED - _padding: [f32; 2], -} - -pub struct State { - ratio: wgpu::Buffer, - constants: wgpu::BindGroup, - last_ratio: Option, -} - -impl State { - pub fn new(device: &wgpu::Device, pipeline: &Pipeline) -> Self { - let ratio = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("iced_wgpu::triangle::msaa ratio"), - size: std::mem::size_of::() as u64, - usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM, - mapped_at_creation: false, - }); - - let constants = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu::triangle::msaa uniforms bind group"), - layout: &pipeline.constant_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Sampler(&pipeline.sampler), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: ratio.as_entire_binding(), - }, - ], - }); - - Self { - ratio, - constants, - last_ratio: None, - } - } - - pub fn prepare( - &mut self, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - belt: &mut wgpu::util::StagingBelt, - pipeline: &Pipeline, - region_size: Size, - ) -> Transformation { - let targets = pipeline.targets(device, region_size); - - let ratio = Ratio { - u: region_size.width as f32 / targets.size.width as f32, - v: region_size.height as f32 / targets.size.height as f32, - _padding: [0.0; 2], - }; - - if Some(ratio) != self.last_ratio { - belt.write_buffer( - encoder, - &self.ratio, - 0, - NonZeroU64::new(std::mem::size_of::() as u64) - .expect("non-empty ratio"), - device, - ) - .copy_from_slice(bytemuck::bytes_of(&ratio)); - - self.last_ratio = Some(ratio); - } - - Transformation::orthographic(targets.size.width, targets.size.height) - } - - pub fn render( - &self, - pipeline: &Pipeline, - encoder: &mut wgpu::CommandEncoder, - target: &wgpu::TextureView, - ) { - let mut render_pass = - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("iced_wgpu::triangle::msaa render pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: target, - depth_slice: None, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - - render_pass.set_pipeline(&pipeline.raw); - render_pass.set_bind_group(0, &self.constants, &[]); - render_pass.set_bind_group( - 1, - &pipeline - .targets - .read() - .expect("Read MSAA targets") - .as_ref() - .unwrap() - .bind_group, - &[], - ); - render_pass.draw(0..6, 0..1); - } -} diff --git a/crates/iced_wgpu/src/window.rs b/crates/iced_wgpu/src/window.rs deleted file mode 100644 index 9545a14e..00000000 --- a/crates/iced_wgpu/src/window.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! Display rendering results on windows. -pub mod compositor; - -pub use compositor::Compositor; -pub use wgpu::Surface; diff --git a/crates/iced_wgpu/src/window/compositor.rs b/crates/iced_wgpu/src/window/compositor.rs deleted file mode 100644 index f89d6310..00000000 --- a/crates/iced_wgpu/src/window/compositor.rs +++ /dev/null @@ -1,358 +0,0 @@ -//! Connect a window with a renderer. -use crate::core::Color; -use crate::graphics::color; -use crate::graphics::compositor; -use crate::graphics::error; -use crate::graphics::{self, Shell, Viewport}; -use crate::settings::{self, Settings}; -use crate::{Engine, Renderer}; - -/// A window graphics backend for iced powered by `wgpu`. -pub struct Compositor { - instance: wgpu::Instance, - adapter: wgpu::Adapter, - format: wgpu::TextureFormat, - alpha_mode: wgpu::CompositeAlphaMode, - engine: Engine, - settings: Settings, -} - -/// A compositor error. -#[derive(Debug, Clone, thiserror::Error)] -pub enum Error { - /// The surface creation failed. - #[error("the surface creation failed: {0}")] - SurfaceCreationFailed(#[from] wgpu::CreateSurfaceError), - /// The surface is not compatible. - #[error("the surface is not compatible")] - IncompatibleSurface, - /// No adapter was found for the options requested. - #[error("no adapter was found for the options requested: {0:?}")] - NoAdapterFound(String), - /// No device request succeeded. - #[error("no device request succeeded: {0:?}")] - RequestDeviceFailed(Vec<(wgpu::Limits, wgpu::RequestDeviceError)>), -} - -impl From for graphics::Error { - fn from(error: Error) -> Self { - Self::GraphicsAdapterNotFound { - backend: "wgpu", - reason: error::Reason::RequestFailed(error.to_string()), - } - } -} - -impl Compositor { - /// Requests a new [`Compositor`] with the given [`Settings`]. - /// - /// Returns `None` if no compatible graphics adapter could be found. - pub async fn request( - settings: Settings, - compatible_window: Option, - shell: Shell, - ) -> Result { - let instance = wgpu::util::new_instance_with_webgpu_detection(&wgpu::InstanceDescriptor { - backends: settings.backends, - flags: if cfg!(feature = "strict-assertions") { - wgpu::InstanceFlags::debugging() - } else { - wgpu::InstanceFlags::empty() - }, - ..Default::default() - }) - .await; - - log::info!("{settings:#?}"); - - #[cfg(not(target_arch = "wasm32"))] - if log::max_level() >= log::LevelFilter::Info { - let available_adapters: Vec<_> = instance - .enumerate_adapters(settings.backends) - .iter() - .map(wgpu::Adapter::get_info) - .collect(); - log::info!("Available adapters: {available_adapters:#?}"); - } - - #[allow(unsafe_code)] - let compatible_surface = - compatible_window.and_then(|window| instance.create_surface(window).ok()); - - let adapter_options = wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::from_env() - .unwrap_or(wgpu::PowerPreference::HighPerformance), - compatible_surface: compatible_surface.as_ref(), - force_fallback_adapter: false, - }; - - let adapter = instance - .request_adapter(&adapter_options) - .await - .map_err(|_error| Error::NoAdapterFound(format!("{adapter_options:?}")))?; - - log::info!("Selected: {:#?}", adapter.get_info()); - - let (format, alpha_mode) = compatible_surface - .as_ref() - .and_then(|surface| { - let capabilities = surface.get_capabilities(&adapter); - - let formats = capabilities.formats.iter().copied(); - - log::info!("Available formats: {formats:#?}"); - - let mut formats = - formats.filter(|format| format.required_features() == wgpu::Features::empty()); - - let format = if color::GAMMA_CORRECTION { - formats.find(wgpu::TextureFormat::is_srgb) - } else { - formats.find(|format| !wgpu::TextureFormat::is_srgb(format)) - }; - - let format = format.or_else(|| { - log::warn!("No format found!"); - - capabilities.formats.first().copied() - }); - - let alpha_modes = capabilities.alpha_modes; - - log::info!("Available alpha modes: {alpha_modes:#?}"); - - let preferred_alpha = - if alpha_modes.contains(&wgpu::CompositeAlphaMode::PostMultiplied) { - wgpu::CompositeAlphaMode::PostMultiplied - } else if alpha_modes.contains(&wgpu::CompositeAlphaMode::PreMultiplied) { - wgpu::CompositeAlphaMode::PreMultiplied - } else { - wgpu::CompositeAlphaMode::Auto - }; - - format.zip(Some(preferred_alpha)) - }) - .ok_or(Error::IncompatibleSurface)?; - - log::info!("Selected format: {format:?} with alpha mode: {alpha_mode:?}"); - - #[cfg(target_arch = "wasm32")] - let limits = if adapter.get_info().backend == wgpu::Backend::BrowserWebGpu { - vec![ - wgpu::Limits::default().using_resolution(adapter.limits()), - wgpu::Limits::downlevel_webgl2_defaults().using_resolution(adapter.limits()), - ] - } else { - vec![wgpu::Limits::downlevel_webgl2_defaults().using_resolution(adapter.limits())] - }; - - #[cfg(not(target_arch = "wasm32"))] - let limits = vec![wgpu::Limits::default(), wgpu::Limits::downlevel_defaults()]; - - let limits = limits.into_iter().map(|limits| wgpu::Limits { - max_bind_groups: 2, - max_non_sampler_bindings: 2048, - ..limits - }); - - let mut errors = Vec::new(); - - for required_limits in limits { - let result = adapter - .request_device(&wgpu::DeviceDescriptor { - label: Some("iced_wgpu::window::compositor device descriptor"), - required_features: wgpu::Features::empty(), - required_limits: required_limits.clone(), - memory_hints: wgpu::MemoryHints::MemoryUsage, - trace: wgpu::Trace::Off, - experimental_features: wgpu::ExperimentalFeatures::disabled(), - }) - .await; - - match result { - Ok((device, queue)) => { - let engine = Engine::new( - &adapter, - device, - queue, - format, - settings.antialiasing, - shell, - ); - - return Ok(Compositor { - instance, - adapter, - format, - alpha_mode, - engine, - settings, - }); - } - Err(error) => { - errors.push((required_limits, error)); - } - } - } - - Err(Error::RequestDeviceFailed(errors)) - } -} - -/// Creates a [`Compositor`] with the given [`Settings`] and window. -pub async fn new( - settings: Settings, - compatible_window: W, - shell: Shell, -) -> Result { - Compositor::request(settings, Some(compatible_window), shell).await -} - -/// Presents the given primitives with the given [`Compositor`]. -pub fn present( - renderer: &mut Renderer, - surface: &mut wgpu::Surface<'static>, - viewport: &Viewport, - background_color: Color, - on_pre_present: impl FnOnce(), -) -> Result<(), compositor::SurfaceError> { - match surface.get_current_texture() { - Ok(frame) => { - let view = &frame - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - - let _submission = renderer.present( - Some(background_color), - frame.texture.format(), - view, - viewport, - ); - - // Present the frame - on_pre_present(); - frame.present(); - - Ok(()) - } - Err(error) => match error { - wgpu::SurfaceError::Timeout => Err(compositor::SurfaceError::Timeout), - wgpu::SurfaceError::Outdated => Err(compositor::SurfaceError::Outdated), - wgpu::SurfaceError::Lost => Err(compositor::SurfaceError::Lost), - wgpu::SurfaceError::OutOfMemory => Err(compositor::SurfaceError::OutOfMemory), - wgpu::SurfaceError::Other => Err(compositor::SurfaceError::Other), - }, - } -} - -impl graphics::Compositor for Compositor { - type Renderer = Renderer; - type Surface = wgpu::Surface<'static>; - - async fn with_backend( - settings: graphics::Settings, - _display: impl compositor::Display, - compatible_window: impl compositor::Window, - shell: Shell, - backend: Option<&str>, - ) -> Result { - match backend { - None | Some("wgpu") => { - let mut settings = Settings::from(settings); - - if let Some(backends) = wgpu::Backends::from_env() { - settings.backends = backends; - } - - if let Some(present_mode) = settings::present_mode_from_env() { - settings.present_mode = present_mode; - } - - Ok(new(settings, compatible_window, shell).await?) - } - Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound { - backend: "wgpu", - reason: error::Reason::DidNotMatch { - preferred_backend: backend.to_owned(), - }, - }), - } - } - - fn create_renderer(&self) -> Self::Renderer { - Renderer::new( - self.engine.clone(), - self.settings.default_font, - self.settings.default_text_size, - ) - } - - fn create_surface( - &mut self, - window: W, - width: u32, - height: u32, - ) -> Self::Surface { - let mut surface = self - .instance - .create_surface(window) - .expect("Create surface"); - - if width > 0 && height > 0 { - self.configure_surface(&mut surface, width, height); - } - - surface - } - - fn configure_surface(&mut self, surface: &mut Self::Surface, width: u32, height: u32) { - surface.configure( - &self.engine.device, - &wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: self.format, - present_mode: self.settings.present_mode, - width, - height, - alpha_mode: self.alpha_mode, - view_formats: vec![], - desired_maximum_frame_latency: 1, - }, - ); - } - - fn information(&self) -> compositor::Information { - let information = self.adapter.get_info(); - - compositor::Information { - adapter: information.name, - backend: format!("{:?}", information.backend), - } - } - - fn present( - &mut self, - renderer: &mut Self::Renderer, - surface: &mut Self::Surface, - viewport: &Viewport, - background_color: Color, - on_pre_present: impl FnOnce(), - ) -> Result<(), compositor::SurfaceError> { - present( - renderer, - surface, - viewport, - background_color, - on_pre_present, - ) - } - - fn screenshot( - &mut self, - renderer: &mut Self::Renderer, - viewport: &Viewport, - background_color: Color, - ) -> Vec { - renderer.screenshot(viewport, background_color) - } -} diff --git a/src/scene/pipeline/mod.rs b/src/scene/pipeline/mod.rs index 126ce08e..a2b9e6ef 100644 --- a/src/scene/pipeline/mod.rs +++ b/src/scene/pipeline/mod.rs @@ -47,7 +47,7 @@ pub struct Pipeline { /// Used to draw ghost copies of selected wires through occluding geometry. wire_xray_pipeline: wgpu::RenderPipeline, /// Layout for the per-wire `WireConst` storage buffer (group 1 of the wire / - /// xray pipelines). `Some` on native/WebGPU fast paths; `None` in packed + /// xray pipelines). `Some` only on the fast native path; `None` in packed /// compatibility mode. Passed to `WireGpu::from_run` / `from_batch`. pub(crate) wire_const_bgl: Option, wipeout_pipeline: wgpu::RenderPipeline, @@ -338,8 +338,8 @@ impl Pipeline { }); // ── Wire pipeline ────────────────────────────────────────────────── - // Select once per device. Native and WebGPU hoist shared constants into - // storage when limits allow; WebGL2/compat keeps packed attributes. + // Select once per device. The fast native path hoists shared constants + // into storage; compatibility mode keeps them in 10 packed attributes. let wire_mode = wire_gpu::WirePipelineMode::select(device); let renderer_mode_name = if wire_mode.uses_storage() { "fast-storage" } else { "packed-compat" }; @@ -357,9 +357,12 @@ impl Pipeline { renderer_mode_name, device.limits().max_storage_buffers_per_shader_stage ); + #[cfg(not(target_arch = "wasm32"))] let wire_const_bgl = wire_mode .uses_storage() .then(|| wire_gpu::WireConst::bind_group_layout(device)); + #[cfg(target_arch = "wasm32")] + let wire_const_bgl: Option = None; let mut wire_bgls: Vec<&wgpu::BindGroupLayout> = vec![&frame_bgl]; if let Some(bgl) = &wire_const_bgl { wire_bgls.push(bgl); @@ -376,6 +379,7 @@ impl Pipeline { let wire_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("wire.shader"), source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(match wire_mode { + #[cfg(not(target_arch = "wasm32"))] wire_gpu::WirePipelineMode::IndexedStorage => { include_str!("../../shaders/wire_indexed.wgsl") } diff --git a/src/scene/pipeline/wire_gpu.rs b/src/scene/pipeline/wire_gpu.rs index 732efdeb..d571e880 100644 --- a/src/scene/pipeline/wire_gpu.rs +++ b/src/scene/pipeline/wire_gpu.rs @@ -58,17 +58,18 @@ fn instance_buffer_mapped( // ── Instance layout ─────────────────────────────────────────────────────── -// ── WebGPU/native: slim per-segment instance + shared constants ───────────── +// ── Native: slim per-segment instance + shared per-wire constants ─────────── // // Every segment of a wire used to carry the wire's color / line-weight / dash // pattern / draw-depth (~44 B) on each instance — re-fetched once per segment -// even though it's constant along the wire. On storage-capable adapters we hoist those into a +// even though it's constant along the wire. On native we hoist those into a // per-wire `WireConst` storage buffer indexed by `wire_id`, so the instance // keeps only the per-segment data (endpoints + arc-length distances). Cuts the // instance from 104 B to one 64-byte cache line and removes the redundant // per-segment re-fetch of the shared constants. WebGL2 has no vertex-stage // storage buffers, so the wasm build below keeps the original self-contained // fat instance. +#[cfg(not(target_arch = "wasm32"))] #[repr(C)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] pub struct WireInstance { @@ -86,6 +87,8 @@ pub struct WireInstance { /// exactly one 64-byte cache line. pub taper_ratio: [u16; 2], } + +#[cfg(not(target_arch = "wasm32"))] impl WireInstance { pub fn layout<'a>() -> wgpu::VertexBufferLayout<'a> { // Must match `InstanceIn` in wire_indexed.wgsl. @@ -107,10 +110,11 @@ impl WireInstance { } } -/// Per-wire constants shared by every segment of a wire. std430 +/// Per-wire constants shared by every segment of a wire (native only). std430 /// layout: three vec4 then eight scalars = 80 B, matching `WireConst` in /// wire_indexed.wgsl. `align_end` / `align_total` carry the "A"-type endpoint /// alignment (see `wire_distances`); 0.0 total = no alignment. +#[cfg(not(target_arch = "wasm32"))] #[repr(C)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] pub struct WireConst { @@ -130,6 +134,8 @@ pub struct WireConst { pub _pad1: f32, pub _pad2: f32, } + +#[cfg(not(target_arch = "wasm32"))] impl WireConst { /// Bind-group layout for the per-wire storage buffer (group 1 of the wire / /// xray pipelines). Read-only storage, visible to the vertex stage. @@ -152,8 +158,8 @@ impl WireConst { // ── Packed compatibility instance (no vertex-stage storage) ──────────────── // -// WebGL2 and limited native adapters use this layout. WebGPU selects the -// indexed storage path when its reported limits satisfy the same requirement. +// Web always uses this layout. Native selects it at runtime for adapters whose +// storage-buffer limits are insufficient, or when --compat-renderer is set. #[repr(C)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] pub struct PackedWireInstance { @@ -218,15 +224,21 @@ impl PackedWireInstance { } } +#[cfg(target_arch = "wasm32")] +pub type WireInstance = PackedWireInstance; + /// Wire and hatch pipelines switch together: the fast path uses storage /// buffers; the compatibility path carries wire constants in packed vertex /// attributes and hatch data in a texture. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum WirePipelineMode { + #[cfg(not(target_arch = "wasm32"))] IndexedStorage, Packed, } -fn select_pipeline(max_storage_buffers_per_stage: u32, forced: bool) -> WirePipelineMode { + +#[cfg(not(target_arch = "wasm32"))] +fn select_native_pipeline(max_storage_buffers_per_stage: u32, forced: bool) -> WirePipelineMode { const REQUIRED_STORAGE_BUFFERS_PER_STAGE: u32 = 5; if forced || max_storage_buffers_per_stage < REQUIRED_STORAGE_BUFFERS_PER_STAGE { WirePipelineMode::Packed @@ -237,15 +249,23 @@ fn select_pipeline(max_storage_buffers_per_stage: u32, forced: bool) -> WirePipe impl WirePipelineMode { pub fn select(device: &wgpu::Device) -> Self { - #[cfg(not(target_arch = "wasm32"))] - let forced = crate::cli::gui_config().compat_renderer; #[cfg(target_arch = "wasm32")] - let forced = false; - select_pipeline(device.limits().max_storage_buffers_per_shader_stage, forced) + { + let _ = device; + Self::Packed + } + #[cfg(not(target_arch = "wasm32"))] + { + select_native_pipeline( + device.limits().max_storage_buffers_per_shader_stage, + crate::cli::gui_config().compat_renderer, + ) + } } pub fn uses_storage(self) -> bool { match self { + #[cfg(not(target_arch = "wasm32"))] Self::IndexedStorage => true, Self::Packed => false, } @@ -253,6 +273,7 @@ impl WirePipelineMode { pub fn layout<'a>(self) -> wgpu::VertexBufferLayout<'a> { match self { + #[cfg(not(target_arch = "wasm32"))] Self::IndexedStorage => WireInstance::layout(), Self::Packed => PackedWireInstance::layout(), } @@ -473,8 +494,9 @@ fn emit_wire_packed( instances } -/// Storage path: emit slim per-segment instances (positions + distances + `wire_id`) +/// Native: emit slim per-segment instances (positions + distances + `wire_id`) /// plus the one `WireConst` record every segment of this wire shares. +#[cfg(not(target_arch = "wasm32"))] pub(crate) fn emit_wire_native( wire: &WireModel, wire_id: u32, @@ -557,10 +579,11 @@ pub(crate) fn wire_draw_depth( } } -/// Build the shared per-wire `WireConst` storage buffer and its bind group. -/// All instance-buffer chunks from one build reference the same +/// Build the shared per-wire `WireConst` storage buffer and its bind group +/// (native only). All instance-buffer chunks from one build reference the same /// buffer via their global `wire_id`, so a single bind group is cloned into /// each chunk. +#[cfg(not(target_arch = "wasm32"))] fn build_const_bind_group( device: &wgpu::Device, bgl: &wgpu::BindGroupLayout, @@ -654,6 +677,7 @@ impl WireGpu { mesh_edge: bool, const_bgl: Option<&wgpu::BindGroupLayout>, ) -> Vec { + #[cfg(not(target_arch = "wasm32"))] if let Some(const_bgl) = const_bgl { const MAX_INSTANCES: usize = 268_435_456 / std::mem::size_of::(); @@ -745,6 +769,7 @@ impl WireGpu { if total_segs == 0 { return vec![]; } + #[cfg(not(target_arch = "wasm32"))] if let Some(const_bgl) = const_bgl { // GPU max buffer size is 256 MB; chunk to stay within the limit. const MAX_INSTANCES: usize = diff --git a/src/scene/view/render.rs b/src/scene/view/render.rs index 418ae676..35f19113 100644 --- a/src/scene/view/render.rs +++ b/src/scene/view/render.rs @@ -436,10 +436,7 @@ impl shader::Primitive for Primitive { // the whole wire buffer. Only for the scissor-free, mesh-free // (single-batch) Model set; scissored paper viewports and mixed // 2D/3D sets fall through to the shared batched path below. - #[cfg(not(target_arch = "wasm32"))] let mut arena_served = false; - #[cfg(target_arch = "wasm32")] - let arena_served = false; #[cfg(not(target_arch = "wasm32"))] let _perf = crate::perf::enabled(); #[cfg(not(target_arch = "wasm32"))]