summaryrefslogtreecommitdiffstats
path: root/wgpu
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--wgpu/Cargo.toml20
-rw-r--r--wgpu/fonts/Iced-Icons.ttf (renamed from graphics/fonts/Icons.ttf)bin5032 -> 5108 bytes
-rw-r--r--wgpu/src/backend.rs381
-rw-r--r--wgpu/src/buffer.rs86
-rw-r--r--wgpu/src/buffer/dynamic.rs23
-rw-r--r--wgpu/src/buffer/static.rs32
-rw-r--r--wgpu/src/image.rs255
-rw-r--r--wgpu/src/image/atlas.rs49
-rw-r--r--wgpu/src/lib.rs7
-rw-r--r--wgpu/src/quad.rs228
-rw-r--r--wgpu/src/settings.rs37
-rw-r--r--wgpu/src/text.rs571
-rw-r--r--wgpu/src/triangle.rs561
-rw-r--r--wgpu/src/window/compositor.rs43
14 files changed, 1344 insertions, 949 deletions
diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml
index f1e22cf6..632873a3 100644
--- a/wgpu/Cargo.toml
+++ b/wgpu/Cargo.toml
@@ -23,19 +23,27 @@ dds = ["iced_graphics/dds"]
farbfeld = ["iced_graphics/farbfeld"]
canvas = ["iced_graphics/canvas"]
qr_code = ["iced_graphics/qr_code"]
-default_system_font = ["iced_graphics/font-source"]
spirv = ["wgpu/spirv"]
webgl = ["wgpu/webgl"]
[dependencies]
wgpu = "0.14"
-wgpu_glyph = "0.18"
-glyph_brush = "0.7"
raw-window-handle = "0.5"
log = "0.4"
guillotiere = "0.6"
futures = "0.3"
bitflags = "1.2"
+once_cell = "1.0"
+rustc-hash = "1.1"
+ouroboros = "0.15"
+
+[dependencies.twox-hash]
+version = "1.6"
+default-features = false
+
+[target.'cfg(not(target_arch = "wasm32"))'.dependencies.twox-hash]
+version = "1.6.1"
+features = ["std"]
[dependencies.bytemuck]
version = "1.9"
@@ -48,7 +56,11 @@ path = "../native"
[dependencies.iced_graphics]
version = "0.7"
path = "../graphics"
-features = ["font-fallback", "font-icons"]
+
+[dependencies.glyphon]
+version = "0.2"
+git = "https://github.com/hecrj/glyphon.git"
+rev = "65b481d758f50fd13fc21af2cc5ef62ddee64955"
[dependencies.tracing]
version = "0.1.6"
diff --git a/graphics/fonts/Icons.ttf b/wgpu/fonts/Iced-Icons.ttf
index 5e455b69..7112f086 100644
--- a/graphics/fonts/Icons.ttf
+++ b/wgpu/fonts/Iced-Icons.ttf
Binary files differ
diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs
index 6a299425..e650d9a5 100644
--- a/wgpu/src/backend.rs
+++ b/wgpu/src/backend.rs
@@ -4,11 +4,8 @@ use crate::triangle;
use crate::{Settings, Transformation};
use iced_graphics::backend;
-use iced_graphics::font;
use iced_graphics::layer::Layer;
-use iced_graphics::{Primitive, Viewport};
-use iced_native::alignment;
-use iced_native::{Font, Size};
+use iced_graphics::{Color, Font, Primitive, Size, Viewport};
#[cfg(feature = "tracing")]
use tracing::info_span;
@@ -16,11 +13,13 @@ use tracing::info_span;
#[cfg(any(feature = "image", feature = "svg"))]
use crate::image;
+use std::borrow::Cow;
+
/// A [`wgpu`] graphics backend for [`iced`].
///
/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
/// [`iced`]: https://github.com/iced-rs/iced
-#[derive(Debug)]
+#[allow(missing_debug_implementations)]
pub struct Backend {
quad_pipeline: quad::Pipeline,
text_pipeline: text::Pipeline,
@@ -29,6 +28,7 @@ pub struct Backend {
#[cfg(any(feature = "image", feature = "svg"))]
image_pipeline: image::Pipeline,
+ default_font: Font,
default_text_size: f32,
}
@@ -36,16 +36,11 @@ impl Backend {
/// Creates a new [`Backend`].
pub fn new(
device: &wgpu::Device,
+ queue: &wgpu::Queue,
settings: Settings,
format: wgpu::TextureFormat,
) -> Self {
- let text_pipeline = text::Pipeline::new(
- device,
- format,
- settings.default_font,
- settings.text_multithreading,
- );
-
+ let text_pipeline = text::Pipeline::new(device, queue, format);
let quad_pipeline = quad::Pipeline::new(device, format);
let triangle_pipeline =
triangle::Pipeline::new(device, format, settings.antialiasing);
@@ -61,6 +56,7 @@ impl Backend {
#[cfg(any(feature = "image", feature = "svg"))]
image_pipeline,
+ default_font: settings.default_font,
default_text_size: settings.default_text_size,
}
}
@@ -72,8 +68,9 @@ impl Backend {
pub fn present<T: AsRef<str>>(
&mut self,
device: &wgpu::Device,
- staging_belt: &mut wgpu::util::StagingBelt,
+ queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
+ clear_color: Option<Color>,
frame: &wgpu::TextureView,
primitives: &[Primitive],
viewport: &Viewport,
@@ -90,167 +87,245 @@ impl Backend {
let mut layers = Layer::generate(primitives, viewport);
layers.push(Layer::overlay(overlay_text, viewport));
+ self.prepare(
+ device,
+ queue,
+ encoder,
+ scale_factor,
+ transformation,
+ &layers,
+ );
+
+ while !self.prepare_text(
+ device,
+ queue,
+ scale_factor,
+ target_size,
+ &layers,
+ ) {}
+
+ self.render(
+ device,
+ encoder,
+ frame,
+ clear_color,
+ scale_factor,
+ target_size,
+ &layers,
+ );
+
+ self.quad_pipeline.end_frame();
+ self.text_pipeline.end_frame();
+ self.triangle_pipeline.end_frame();
+
+ #[cfg(any(feature = "image", feature = "svg"))]
+ self.image_pipeline.end_frame(device, queue, encoder);
+ }
+
+ fn prepare_text(
+ &mut self,
+ device: &wgpu::Device,
+ queue: &wgpu::Queue,
+ scale_factor: f32,
+ target_size: Size<u32>,
+ layers: &[Layer<'_>],
+ ) -> bool {
for layer in layers {
- self.flush(
- device,
- scale_factor,
- transformation,
- &layer,
- staging_belt,
- encoder,
- frame,
- target_size,
- );
+ let bounds = (layer.bounds * scale_factor).snap();
+
+ if bounds.width < 1 || bounds.height < 1 {
+ continue;
+ }
+
+ if !layer.text.is_empty()
+ && !self.text_pipeline.prepare(
+ device,
+ queue,
+ &layer.text,
+ layer.bounds,
+ scale_factor,
+ target_size,
+ )
+ {
+ return false;
+ }
}
- #[cfg(any(feature = "image", feature = "svg"))]
- self.image_pipeline.trim_cache(device, encoder);
+ true
}
- fn flush(
+ fn prepare(
&mut self,
device: &wgpu::Device,
+ queue: &wgpu::Queue,
+ _encoder: &mut wgpu::CommandEncoder,
scale_factor: f32,
transformation: Transformation,
- layer: &Layer<'_>,
- staging_belt: &mut wgpu::util::StagingBelt,
+ layers: &[Layer<'_>],
+ ) {
+ for layer in layers {
+ let bounds = (layer.bounds * scale_factor).snap();
+
+ if bounds.width < 1 || bounds.height < 1 {
+ continue;
+ }
+
+ if !layer.quads.is_empty() {
+ self.quad_pipeline.prepare(
+ device,
+ queue,
+ &layer.quads,
+ transformation,
+ scale_factor,
+ );
+ }
+
+ if !layer.meshes.is_empty() {
+ let scaled = transformation
+ * Transformation::scale(scale_factor, scale_factor);
+
+ self.triangle_pipeline.prepare(
+ device,
+ queue,
+ &layer.meshes,
+ scaled,
+ );
+ }
+
+ #[cfg(any(feature = "image", feature = "svg"))]
+ {
+ if !layer.images.is_empty() {
+ let scaled = transformation
+ * Transformation::scale(scale_factor, scale_factor);
+
+ self.image_pipeline.prepare(
+ device,
+ queue,
+ _encoder,
+ &layer.images,
+ scaled,
+ scale_factor,
+ );
+ }
+ }
+ }
+ }
+
+ fn render(
+ &mut self,
+ device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
target: &wgpu::TextureView,
+ clear_color: Option<Color>,
+ scale_factor: f32,
target_size: Size<u32>,
+ layers: &[Layer<'_>],
) {
- let bounds = (layer.bounds * scale_factor).snap();
+ use std::mem::ManuallyDrop;
- if bounds.width < 1 || bounds.height < 1 {
- return;
- }
+ let mut quad_layer = 0;
+ let mut triangle_layer = 0;
+ #[cfg(any(feature = "image", feature = "svg"))]
+ let mut image_layer = 0;
+ let mut text_layer = 0;
+
+ let mut render_pass = ManuallyDrop::new(encoder.begin_render_pass(
+ &wgpu::RenderPassDescriptor {
+ label: Some("iced_wgpu::quad render pass"),
+ color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+ view: target,
+ resolve_target: None,
+ ops: wgpu::Operations {
+ load: match clear_color {
+ Some(background_color) => wgpu::LoadOp::Clear({
+ let [r, g, b, a] =
+ background_color.into_linear();
+
+ wgpu::Color {
+ r: f64::from(r),
+ g: f64::from(g),
+ b: f64::from(b),
+ a: f64::from(a),
+ }
+ }),
+ None => wgpu::LoadOp::Load,
+ },
+ store: true,
+ },
+ })],
+ depth_stencil_attachment: None,
+ },
+ ));
- if !layer.quads.is_empty() {
- self.quad_pipeline.draw(
- device,
- staging_belt,
- encoder,
- &layer.quads,
- transformation,
- scale_factor,
- bounds,
- target,
- );
- }
+ for layer in layers {
+ let bounds = (layer.bounds * scale_factor).snap();
- if !layer.meshes.is_empty() {
- let scaled = transformation
- * Transformation::scale(scale_factor, scale_factor);
-
- self.triangle_pipeline.draw(
- device,
- staging_belt,
- encoder,
- target,
- target_size,
- scaled,
- scale_factor,
- &layer.meshes,
- );
- }
+ if bounds.width < 1 || bounds.height < 1 {
+ return;
+ }
- #[cfg(any(feature = "image", feature = "svg"))]
- {
- if !layer.images.is_empty() {
- let scaled = transformation
- * Transformation::scale(scale_factor, scale_factor);
+ if !layer.quads.is_empty() {
+ self.quad_pipeline
+ .render(quad_layer, bounds, &mut render_pass);
+
+ quad_layer += 1;
+ }
- self.image_pipeline.draw(
+ if !layer.meshes.is_empty() {
+ let _ = ManuallyDrop::into_inner(render_pass);
+
+ self.triangle_pipeline.render(
device,
- staging_belt,
encoder,
- &layer.images,
- scaled,
- bounds,
target,
+ triangle_layer,
+ target_size,
+ &layer.meshes,
scale_factor,
);
+
+ triangle_layer += 1;
+
+ render_pass = ManuallyDrop::new(encoder.begin_render_pass(
+ &wgpu::RenderPassDescriptor {
+ label: Some("iced_wgpu::quad render pass"),
+ color_attachments: &[Some(
+ wgpu::RenderPassColorAttachment {
+ view: target,
+ resolve_target: None,
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Load,
+ store: true,
+ },
+ },
+ )],
+ depth_stencil_attachment: None,
+ },
+ ));
}
- }
- if !layer.text.is_empty() {
- for text in layer.text.iter() {
- // Target physical coordinates directly to avoid blurry text
- let text = wgpu_glyph::Section {
- // TODO: We `round` here to avoid rerasterizing text when
- // its position changes slightly. This can make text feel a
- // bit "jumpy". We may be able to do better once we improve
- // our text rendering/caching pipeline.
- screen_position: (
- (text.bounds.x * scale_factor).round(),
- (text.bounds.y * scale_factor).round(),
- ),
- // TODO: Fix precision issues with some scale factors.
- //
- // The `ceil` here can cause some words to render on the
- // same line when they should not.
- //
- // Ideally, `wgpu_glyph` should be able to compute layout
- // using logical positions, and then apply the proper
- // scaling when rendering. This would ensure that both
- // measuring and rendering follow the same layout rules.
- bounds: (
- (text.bounds.width * scale_factor).ceil(),
- (text.bounds.height * scale_factor).ceil(),
- ),
- text: vec![wgpu_glyph::Text {
- text: text.content,
- scale: wgpu_glyph::ab_glyph::PxScale {
- x: text.size * scale_factor,
- y: text.size * scale_factor,
- },
- font_id: self.text_pipeline.find_font(text.font),
- extra: wgpu_glyph::Extra {
- color: text.color,
- z: 0.0,
- },
- }],
- layout: wgpu_glyph::Layout::default()
- .h_align(match text.horizontal_alignment {
- alignment::Horizontal::Left => {
- wgpu_glyph::HorizontalAlign::Left
- }
- alignment::Horizontal::Center => {
- wgpu_glyph::HorizontalAlign::Center
- }
- alignment::Horizontal::Right => {
- wgpu_glyph::HorizontalAlign::Right
- }
- })
- .v_align(match text.vertical_alignment {
- alignment::Vertical::Top => {
- wgpu_glyph::VerticalAlign::Top
- }
- alignment::Vertical::Center => {
- wgpu_glyph::VerticalAlign::Center
- }
- alignment::Vertical::Bottom => {
- wgpu_glyph::VerticalAlign::Bottom
- }
- }),
- };
-
- self.text_pipeline.queue(text);
+ #[cfg(any(feature = "image", feature = "svg"))]
+ {
+ if !layer.images.is_empty() {
+ self.image_pipeline.render(
+ image_layer,
+ bounds,
+ &mut render_pass,
+ );
+
+ image_layer += 1;
+ }
}
- self.text_pipeline.draw_queued(
- device,
- staging_belt,
- encoder,
- target,
- transformation,
- wgpu_glyph::Region {
- x: bounds.x,
- y: bounds.y,
- width: bounds.width,
- height: bounds.height,
- },
- );
+ if !layer.text.is_empty() {
+ self.text_pipeline
+ .render(text_layer, bounds, &mut render_pass);
+
+ text_layer += 1;
+ }
}
+
+ let _ = ManuallyDrop::into_inner(render_pass);
}
}
@@ -261,9 +336,13 @@ impl iced_graphics::Backend for Backend {
}
impl backend::Text for Backend {
- const ICON_FONT: Font = font::ICONS;
- const CHECKMARK_ICON: char = font::CHECKMARK_ICON;
- const ARROW_DOWN_ICON: char = font::ARROW_DOWN_ICON;
+ const ICON_FONT: Font = Font::Name("Iced-Icons");
+ const CHECKMARK_ICON: char = '\u{f00c}';
+ const ARROW_DOWN_ICON: char = '\u{e800}';
+
+ fn default_font(&self) -> Font {
+ self.default_font
+ }
fn default_size(&self) -> f32 {
self.default_text_size
@@ -297,6 +376,10 @@ impl backend::Text for Backend {
nearest_only,
)
}
+
+ fn load_font(&mut self, font: Cow<'static, [u8]>) {
+ self.text_pipeline.load_font(font);
+ }
}
#[cfg(feature = "image")]
diff --git a/wgpu/src/buffer.rs b/wgpu/src/buffer.rs
index 7c092d0b..c210dd4e 100644
--- a/wgpu/src/buffer.rs
+++ b/wgpu/src/buffer.rs
@@ -1,3 +1,89 @@
//! Utilities for buffer operations.
pub mod dynamic;
pub mod r#static;
+
+use std::marker::PhantomData;
+use std::ops::RangeBounds;
+
+#[derive(Debug)]
+pub struct Buffer<T> {
+ label: &'static str,
+ size: u64,
+ usage: wgpu::BufferUsages,
+ raw: wgpu::Buffer,
+ type_: PhantomData<T>,
+}
+
+impl<T: bytemuck::Pod> Buffer<T> {
+ pub fn new(
+ device: &wgpu::Device,
+ label: &'static str,
+ amount: usize,
+ usage: wgpu::BufferUsages,
+ ) -> Self {
+ let size = next_copy_size::<T>(amount);
+
+ let raw = device.create_buffer(&wgpu::BufferDescriptor {
+ label: Some(label),
+ size,
+ usage,
+ mapped_at_creation: false,
+ });
+
+ Self {
+ label,
+ size,
+ usage,
+ raw,
+ type_: PhantomData,
+ }
+ }
+
+ pub fn resize(&mut self, device: &wgpu::Device, new_count: usize) -> bool {
+ let new_size = (std::mem::size_of::<T>() * new_count) as u64;
+
+ if self.size < new_size {
+ self.raw = device.create_buffer(&wgpu::BufferDescriptor {
+ label: Some(self.label),
+ size: new_size,
+ usage: self.usage,
+ mapped_at_creation: false,
+ });
+
+ self.size = new_size;
+
+ true
+ } else {
+ false
+ }
+ }
+
+ pub fn write(
+ &self,
+ queue: &wgpu::Queue,
+ offset_count: usize,
+ contents: &[T],
+ ) {
+ queue.write_buffer(
+ &self.raw,
+ (std::mem::size_of::<T>() * offset_count) as u64,
+ bytemuck::cast_slice(contents),
+ );
+ }
+
+ pub fn slice(
+ &self,
+ bounds: impl RangeBounds<wgpu::BufferAddress>,
+ ) -> wgpu::BufferSlice<'_> {
+ self.raw.slice(bounds)
+ }
+}
+
+fn next_copy_size<T>(amount: usize) -> u64 {
+ let align_mask = wgpu::COPY_BUFFER_ALIGNMENT - 1;
+
+ (((std::mem::size_of::<T>() * amount).next_power_of_two() as u64
+ + align_mask)
+ & !align_mask)
+ .max(wgpu::COPY_BUFFER_ALIGNMENT)
+}
diff --git a/wgpu/src/buffer/dynamic.rs b/wgpu/src/buffer/dynamic.rs
index 88289b98..43fc47ac 100644
--- a/wgpu/src/buffer/dynamic.rs
+++ b/wgpu/src/buffer/dynamic.rs
@@ -112,25 +112,8 @@ impl<T: ShaderType + WriteInto> Buffer<T> {
}
/// Write the contents of this dynamic buffer to the GPU via staging belt command.
- pub fn write(
- &mut self,
- device: &wgpu::Device,
- staging_belt: &mut wgpu::util::StagingBelt,
- encoder: &mut wgpu::CommandEncoder,
- ) {
- let size = self.cpu.get_ref().len();
-
- if let Some(buffer_size) = wgpu::BufferSize::new(size as u64) {
- let mut buffer = staging_belt.write_buffer(
- encoder,
- &self.gpu,
- 0,
- buffer_size,
- device,
- );
-
- buffer.copy_from_slice(self.cpu.get_ref());
- }
+ pub fn write(&mut self, queue: &wgpu::Queue) {
+ queue.write_buffer(&self.gpu, 0, self.cpu.get_ref());
}
// Gets the aligned offset at the given index from the CPU buffer.
@@ -184,7 +167,7 @@ impl Internal {
}
/// Returns bytearray of aligned CPU buffer.
- pub(super) fn get_ref(&self) -> &Vec<u8> {
+ pub(super) fn get_ref(&self) -> &[u8] {
match self {
Internal::Uniform(buf) => buf.as_ref(),
#[cfg(not(target_arch = "wasm32"))]
diff --git a/wgpu/src/buffer/static.rs b/wgpu/src/buffer/static.rs
index ef87422f..d8ae116e 100644
--- a/wgpu/src/buffer/static.rs
+++ b/wgpu/src/buffer/static.rs
@@ -2,8 +2,7 @@ use bytemuck::{Pod, Zeroable};
use std::marker::PhantomData;
use std::mem;
-//128 triangles/indices
-const DEFAULT_STATIC_BUFFER_COUNT: wgpu::BufferAddress = 128;
+const DEFAULT_COUNT: wgpu::BufferAddress = 128;
/// A generic buffer struct useful for items which have no alignment requirements
/// (e.g. Vertex, Index buffers) & no dynamic offsets.
@@ -25,7 +24,7 @@ impl<T: Pod + Zeroable> Buffer<T> {
label: &'static str,
usages: wgpu::BufferUsages,
) -> Self {
- let size = (mem::size_of::<T>() as u64) * DEFAULT_STATIC_BUFFER_COUNT;
+ let size = (mem::size_of::<T>() as u64) * DEFAULT_COUNT;
Self {
offsets: Vec::new(),
@@ -57,9 +56,13 @@ impl<T: Pod + Zeroable> Buffer<T> {
let size = (mem::size_of::<T>() * new_count) as u64;
if self.size < size {
+ self.size =
+ (mem::size_of::<T>() * (new_count + new_count / 2)) as u64;
+
+ self.gpu =
+ Self::gpu_buffer(device, self.label, self.size, self.usages);
+
self.offsets.clear();
- self.size = size;
- self.gpu = Self::gpu_buffer(device, self.label, size, self.usages);
true
} else {
false
@@ -71,28 +74,15 @@ impl<T: Pod + Zeroable> Buffer<T> {
/// Returns the size of the written bytes.
pub fn write(
&mut self,
- device: &wgpu::Device,
- staging_belt: &mut wgpu::util::StagingBelt,
- encoder: &mut wgpu::CommandEncoder,
+ queue: &wgpu::Queue,
offset: u64,
content: &[T],
) -> u64 {
let bytes = bytemuck::cast_slice(content);
let bytes_size = bytes.len() as u64;
- if let Some(buffer_size) = wgpu::BufferSize::new(bytes_size) {
- let mut buffer = staging_belt.write_buffer(
- encoder,
- &self.gpu,
- offset,
- buffer_size,
- device,
- );
-
- buffer.copy_from_slice(bytes);
-
- self.offsets.push(offset);
- }
+ queue.write_buffer(&self.gpu, offset, bytes);
+ self.offsets.push(offset);
bytes_size
}
diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs
index a5e63b17..db05d2ff 100644
--- a/wgpu/src/image.rs
+++ b/wgpu/src/image.rs
@@ -6,7 +6,7 @@ use iced_graphics::image::raster;
#[cfg(feature = "svg")]
use iced_graphics::image::vector;
-use crate::Transformation;
+use crate::{Buffer, Transformation};
use atlas::Atlas;
use iced_graphics::layer;
@@ -34,15 +34,107 @@ pub struct Pipeline {
vector_cache: RefCell<vector::Cache<Atlas>>,
pipeline: wgpu::RenderPipeline,
- uniforms: wgpu::Buffer,
vertices: wgpu::Buffer,
indices: wgpu::Buffer,
- instances: wgpu::Buffer,
- constants: wgpu::BindGroup,
+ sampler: wgpu::Sampler,
texture: wgpu::BindGroup,
texture_version: usize,
- texture_layout: wgpu::BindGroupLayout,
texture_atlas: Atlas,
+ texture_layout: wgpu::BindGroupLayout,
+ constant_layout: wgpu::BindGroupLayout,
+
+ layers: Vec<Layer>,
+ prepare_layer: usize,
+}
+
+#[derive(Debug)]
+struct Layer {
+ uniforms: wgpu::Buffer,
+ constants: wgpu::BindGroup,
+ instances: Buffer<Instance>,
+ instance_count: usize,
+}
+
+impl Layer {
+ fn new(
+ device: &wgpu::Device,
+ constant_layout: &wgpu::BindGroupLayout,
+ sampler: &wgpu::Sampler,
+ ) -> Self {
+ let uniforms = device.create_buffer(&wgpu::BufferDescriptor {
+ label: Some("iced_wgpu::image uniforms buffer"),
+ size: mem::size_of::<Uniforms>() as u64,
+ usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
+ mapped_at_creation: false,
+ });
+
+ let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: Some("iced_wgpu::image constants bind group"),
+ layout: constant_layout,
+ entries: &[
+ wgpu::BindGroupEntry {
+ binding: 0,
+ resource: wgpu::BindingResource::Buffer(
+ wgpu::BufferBinding {
+ buffer: &uniforms,
+ offset: 0,
+ size: None,
+ },
+ ),
+ },
+ wgpu::BindGroupEntry {
+ binding: 1,
+ resource: wgpu::BindingResource::Sampler(sampler),
+ },
+ ],
+ });
+
+ let instances = Buffer::new(
+ device,
+ "iced_wgpu::image instance buffer",
+ Instance::INITIAL,
+ wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
+ );
+
+ Self {
+ uniforms,
+ constants,
+ instances,
+ instance_count: 0,
+ }
+ }
+
+ fn prepare(
+ &mut self,
+ device: &wgpu::Device,
+ queue: &wgpu::Queue,
+ instances: &[Instance],
+ transformation: Transformation,
+ ) {
+ queue.write_buffer(
+ &self.uniforms,
+ 0,
+ bytemuck::bytes_of(&Uniforms {
+ transform: transformation.into(),
+ }),
+ );
+
+ let _ = self.instances.resize(device, instances.len());
+ self.instances.write(queue, 0, instances);
+
+ self.instance_count = instances.len();
+ }
+
+ fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
+ render_pass.set_bind_group(0, &self.constants, &[]);
+ render_pass.set_vertex_buffer(1, self.instances.slice(..));
+
+ render_pass.draw_indexed(
+ 0..QUAD_INDICES.len() as u32,
+ 0,
+ 0..self.instance_count as u32,
+ );
+ }
}
impl Pipeline {
@@ -86,35 +178,6 @@ impl Pipeline {
],
});
- let uniforms_buffer = device.create_buffer(&wgpu::BufferDescriptor {
- label: Some("iced_wgpu::image uniforms buffer"),
- size: mem::size_of::<Uniforms>() as u64,
- usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
- mapped_at_creation: false,
- });
-
- let constant_bind_group =
- device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: Some("iced_wgpu::image constants bind group"),
- layout: &constant_layout,
- entries: &[
- wgpu::BindGroupEntry {
- binding: 0,
- resource: wgpu::BindingResource::Buffer(
- wgpu::BufferBinding {
- buffer: &uniforms_buffer,
- offset: 0,
- size: None,
- },
- ),
- },
- wgpu::BindGroupEntry {
- binding: 1,
- resource: wgpu::BindingResource::Sampler(&sampler),
- },
- ],
- });
-
let texture_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("iced_wgpu::image texture atlas layout"),
@@ -225,13 +288,6 @@ impl Pipeline {
usage: wgpu::BufferUsages::INDEX,
});
- let instances = device.create_buffer(&wgpu::BufferDescriptor {
- label: Some("iced_wgpu::image instance buffer"),
- size: mem::size_of::<Instance>() as u64 * Instance::MAX as u64,
- usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
- mapped_at_creation: false,
- });
-
let texture_atlas = Atlas::new(device);
let texture = device.create_bind_group(&wgpu::BindGroupDescriptor {
@@ -253,15 +309,17 @@ impl Pipeline {
vector_cache: RefCell::new(vector::Cache::default()),
pipeline,
- uniforms: uniforms_buffer,
vertices,
indices,
- instances,
- constants: constant_bind_group,
+ sampler,
texture,
texture_version: texture_atlas.layer_count(),
- texture_layout,
texture_atlas,
+ texture_layout,
+ constant_layout,
+
+ layers: Vec::new(),
+ prepare_layer: 0,
}
}
@@ -281,18 +339,19 @@ impl Pipeline {
svg.viewport_dimensions()
}
- pub fn draw(
+ pub fn prepare(
&mut self,
device: &wgpu::Device,
- staging_belt: &mut wgpu::util::StagingBelt,
+ queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
images: &[layer::Image],
transformation: Transformation,
- bounds: Rectangle<u32>,
- target: &wgpu::TextureView,
_scale: f32,
) {
#[cfg(feature = "tracing")]
+ let _ = info_span!("Wgpu::Image", "PREPARE").entered();
+
+ #[cfg(feature = "tracing")]
let _ = info_span!("Wgpu::Image", "DRAW").entered();
let instances: &mut Vec<Instance> = &mut Vec::new();
@@ -309,7 +368,7 @@ impl Pipeline {
layer::Image::Raster { handle, bounds } => {
if let Some(atlas_entry) = raster_cache.upload(
handle,
- &mut (device, encoder),
+ &mut (device, queue, encoder),
&mut self.texture_atlas,
) {
add_instances(
@@ -336,7 +395,7 @@ impl Pipeline {
*color,
size,
_scale,
- &mut (device, encoder),
+ &mut (device, queue, encoder),
&mut self.texture_atlas,
) {
add_instances(
@@ -376,68 +435,28 @@ impl Pipeline {
self.texture_version = texture_version;
}
- {
- let mut uniforms_buffer = staging_belt.write_buffer(
- encoder,
- &self.uniforms,
- 0,
- wgpu::BufferSize::new(mem::size_of::<Uniforms>() as u64)
- .unwrap(),
+ if self.layers.len() <= self.prepare_layer {
+ self.layers.push(Layer::new(
device,
- );
-
- uniforms_buffer.copy_from_slice(bytemuck::bytes_of(&Uniforms {
- transform: transformation.into(),
- }));
+ &self.constant_layout,
+ &self.sampler,
+ ));
}
- let mut i = 0;
- let total = instances.len();
-
- while i < total {
- let end = (i + Instance::MAX).min(total);
- let amount = end - i;
-
- let mut instances_buffer = staging_belt.write_buffer(
- encoder,
- &self.instances,
- 0,
- wgpu::BufferSize::new(
- (amount * std::mem::size_of::<Instance>()) as u64,
- )
- .unwrap(),
- device,
- );
-
- instances_buffer.copy_from_slice(bytemuck::cast_slice(
- &instances[i..i + amount],
- ));
+ let layer = &mut self.layers[self.prepare_layer];
+ layer.prepare(device, queue, instances, transformation);
- let mut render_pass =
- encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
- label: Some("iced_wgpu::image render pass"),
- color_attachments: &[Some(
- wgpu::RenderPassColorAttachment {
- view: target,
- resolve_target: None,
- ops: wgpu::Operations {
- load: wgpu::LoadOp::Load,
- store: true,
- },
- },
- )],
- depth_stencil_attachment: None,
- });
+ self.prepare_layer += 1;
+ }
+ pub fn render<'a>(
+ &'a self,
+ layer: usize,
+ bounds: Rectangle<u32>,
+ render_pass: &mut wgpu::RenderPass<'a>,
+ ) {
+ if let Some(layer) = self.layers.get(layer) {
render_pass.set_pipeline(&self.pipeline);
- render_pass.set_bind_group(0, &self.constants, &[]);
- render_pass.set_bind_group(1, &self.texture, &[]);
- render_pass.set_index_buffer(
- self.indices.slice(..),
- wgpu::IndexFormat::Uint16,
- );
- render_pass.set_vertex_buffer(0, self.vertices.slice(..));
- render_pass.set_vertex_buffer(1, self.instances.slice(..));
render_pass.set_scissor_rect(
bounds.x,
@@ -446,30 +465,34 @@ impl Pipeline {
bounds.height,
);
- render_pass.draw_indexed(
- 0..QUAD_INDICES.len() as u32,
- 0,
- 0..amount as u32,
+ render_pass.set_bind_group(1, &self.texture, &[]);
+ render_pass.set_index_buffer(
+ self.indices.slice(..),
+ wgpu::IndexFormat::Uint16,
);
+ render_pass.set_vertex_buffer(0, self.vertices.slice(..));
- i += Instance::MAX;
+ layer.render(render_pass);
}
}
- pub fn trim_cache(
+ pub fn end_frame(
&mut self,
device: &wgpu::Device,
+ queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
) {
#[cfg(feature = "image")]
self.raster_cache
.borrow_mut()
- .trim(&mut self.texture_atlas, &mut (device, encoder));
+ .trim(&mut self.texture_atlas, &mut (device, queue, encoder));
#[cfg(feature = "svg")]
self.vector_cache
.borrow_mut()
- .trim(&mut self.texture_atlas, &mut (device, encoder));
+ .trim(&mut self.texture_atlas, &mut (device, queue, encoder));
+
+ self.prepare_layer = 0;
}
}
@@ -507,7 +530,7 @@ struct Instance {
}
impl Instance {
- pub const MAX: usize = 1_000;
+ pub const INITIAL: usize = 1_000;
}
#[repr(C)]
diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs
index eafe2f96..7df67abd 100644
--- a/wgpu/src/image/atlas.rs
+++ b/wgpu/src/image/atlas.rs
@@ -185,13 +185,13 @@ impl Atlas {
fn upload_allocation(
&mut self,
- buffer: &wgpu::Buffer,
+ data: &[u8],
image_width: u32,
image_height: u32,
padding: u32,
offset: usize,
allocation: &Allocation,
- encoder: &mut wgpu::CommandEncoder,
+ queue: &wgpu::Queue,
) {
let (x, y) = allocation.position();
let Size { width, height } = allocation.size();
@@ -203,15 +203,7 @@ impl Atlas {
depth_or_array_layers: 1,
};
- encoder.copy_buffer_to_texture(
- wgpu::ImageCopyBuffer {
- buffer,
- layout: wgpu::ImageDataLayout {
- offset: offset as u64,
- bytes_per_row: NonZeroU32::new(4 * image_width + padding),
- rows_per_image: NonZeroU32::new(image_height),
- },
- },
+ queue.write_texture(
wgpu::ImageCopyTexture {
texture: &self.texture,
mip_level: 0,
@@ -222,6 +214,12 @@ impl Atlas {
},
aspect: wgpu::TextureAspect::default(),
},
+ data,
+ wgpu::ImageDataLayout {
+ offset: offset as u64,
+ bytes_per_row: NonZeroU32::new(4 * image_width + padding),
+ rows_per_image: NonZeroU32::new(image_height),
+ },
extent,
);
}
@@ -301,17 +299,19 @@ impl Atlas {
impl image::Storage for Atlas {
type Entry = Entry;
- type State<'a> = (&'a wgpu::Device, &'a mut wgpu::CommandEncoder);
+ type State<'a> = (
+ &'a wgpu::Device,
+ &'a wgpu::Queue,
+ &'a mut wgpu::CommandEncoder,
+ );
fn upload(
&mut self,
width: u32,
height: u32,
data: &[u8],
- (device, encoder): &mut Self::State<'_>,
+ (device, queue, encoder): &mut Self::State<'_>,
) -> Option<Self::Entry> {
- use wgpu::util::DeviceExt;
-
let entry = {
let current_size = self.layers.len();
let entry = self.allocate(width, height)?;
@@ -344,17 +344,16 @@ impl image::Storage for Atlas {
)
}
- let buffer =
- device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
- label: Some("iced_wgpu::image staging buffer"),
- contents: &padded_data,
- usage: wgpu::BufferUsages::COPY_SRC,
- });
-
match &entry {
Entry::Contiguous(allocation) => {
self.upload_allocation(
- &buffer, width, height, padding, 0, allocation, encoder,
+ &padded_data,
+ width,
+ height,
+ padding,
+ 0,
+ allocation,
+ queue,
);
}
Entry::Fragmented { fragments, .. } => {
@@ -363,13 +362,13 @@ impl image::Storage for Atlas {
let offset = (y * padded_width as u32 + 4 * x) as usize;
self.upload_allocation(
- &buffer,
+ &padded_data,
width,
height,
padding,
offset,
&fragment.allocation,
- encoder,
+ queue,
);
}
}
diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs
index 1a293681..9da40572 100644
--- a/wgpu/src/lib.rs
+++ b/wgpu/src/lib.rs
@@ -47,14 +47,17 @@ mod quad;
mod text;
mod triangle;
-pub use iced_graphics::{Antialiasing, Color, Error, Primitive, Viewport};
+pub use iced_graphics::{
+ Antialiasing, Color, Error, Font, Primitive, Viewport,
+};
pub use iced_native::Theme;
pub use wgpu;
pub use backend::Backend;
pub use settings::Settings;
-pub(crate) use iced_graphics::Transformation;
+use crate::buffer::Buffer;
+use iced_graphics::Transformation;
#[cfg(any(feature = "image", feature = "svg"))]
mod image;
diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs
index 2f5fcc6b..246cc5e1 100644
--- a/wgpu/src/quad.rs
+++ b/wgpu/src/quad.rs
@@ -1,4 +1,4 @@
-use crate::Transformation;
+use crate::{Buffer, Transformation};
use iced_graphics::layer;
use iced_native::Rectangle;
@@ -12,11 +12,11 @@ use tracing::info_span;
#[derive(Debug)]
pub struct Pipeline {
pipeline: wgpu::RenderPipeline,
- constants: wgpu::BindGroup,
- constants_buffer: wgpu::Buffer,
+ constant_layout: wgpu::BindGroupLayout,
vertices: wgpu::Buffer,
indices: wgpu::Buffer,
- instances: wgpu::Buffer,
+ layers: Vec<Layer>,
+ prepare_layer: usize,
}
impl Pipeline {
@@ -38,22 +38,6 @@ impl Pipeline {
}],
});
- let constants_buffer = device.create_buffer(&wgpu::BufferDescriptor {
- label: Some("iced_wgpu::quad uniforms buffer"),
- size: mem::size_of::<Uniforms>() as wgpu::BufferAddress,
- usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
- mapped_at_creation: false,
- });
-
- let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: Some("iced_wgpu::quad uniforms bind group"),
- layout: &constant_layout,
- entries: &[wgpu::BindGroupEntry {
- binding: 0,
- resource: constants_buffer.as_entire_binding(),
- }],
- });
-
let layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("iced_wgpu::quad pipeline layout"),
@@ -148,118 +132,146 @@ impl Pipeline {
usage: wgpu::BufferUsages::INDEX,
});
- let instances = device.create_buffer(&wgpu::BufferDescriptor {
- label: Some("iced_wgpu::quad instance buffer"),
- size: mem::size_of::<layer::Quad>() as u64 * MAX_INSTANCES as u64,
- usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
- mapped_at_creation: false,
- });
-
Pipeline {
pipeline,
- constants,
- constants_buffer,
+ constant_layout,
vertices,
indices,
- instances,
+ layers: Vec::new(),
+ prepare_layer: 0,
}
}
- pub fn draw(
+ pub fn prepare(
&mut self,
device: &wgpu::Device,
- staging_belt: &mut wgpu::util::StagingBelt,
- encoder: &mut wgpu::CommandEncoder,
+ queue: &wgpu::Queue,
instances: &[layer::Quad],
transformation: Transformation,
scale: f32,
- bounds: Rectangle<u32>,
- target: &wgpu::TextureView,
) {
- #[cfg(feature = "tracing")]
- let _ = info_span!("Wgpu::Quad", "DRAW").entered();
+ if self.layers.len() <= self.prepare_layer {
+ self.layers.push(Layer::new(device, &self.constant_layout));
+ }
- let uniforms = Uniforms::new(transformation, scale);
+ let layer = &mut self.layers[self.prepare_layer];
+ layer.prepare(device, queue, instances, transformation, scale);
+
+ self.prepare_layer += 1;
+ }
+
+ pub fn render<'a>(
+ &'a self,
+ layer: usize,
+ bounds: Rectangle<u32>,
+ render_pass: &mut wgpu::RenderPass<'a>,
+ ) {
+ if let Some(layer) = self.layers.get(layer) {
+ render_pass.set_pipeline(&self.pipeline);
+
+ render_pass.set_scissor_rect(
+ bounds.x,
+ bounds.y,
+ bounds.width,
+ bounds.height,
+ );
- {
- let mut constants_buffer = staging_belt.write_buffer(
- encoder,
- &self.constants_buffer,
- 0,
- wgpu::BufferSize::new(mem::size_of::<Uniforms>() as u64)
- .unwrap(),
- device,
+ render_pass.set_index_buffer(
+ self.indices.slice(..),
+ wgpu::IndexFormat::Uint16,
);
+ render_pass.set_vertex_buffer(0, self.vertices.slice(..));
- constants_buffer.copy_from_slice(bytemuck::bytes_of(&uniforms));
+ layer.draw(render_pass);
}
+ }
- let mut i = 0;
- let total = instances.len();
+ pub fn end_frame(&mut self) {
+ self.prepare_layer = 0;
+ }
+}
- while i < total {
- let end = (i + MAX_INSTANCES).min(total);
- let amount = end - i;
+#[derive(Debug)]
+struct Layer {
+ constants: wgpu::BindGroup,
+ constants_buffer: wgpu::Buffer,
+ instances: Buffer<layer::Quad>,
+ instance_count: usize,
+}
- let instance_bytes = bytemuck::cast_slice(&instances[i..end]);
+impl Layer {
+ pub fn new(
+ device: &wgpu::Device,
+ constant_layout: &wgpu::BindGroupLayout,
+ ) -> Self {
+ let constants_buffer = device.create_buffer(&wgpu::BufferDescriptor {
+ label: Some("iced_wgpu::quad uniforms buffer"),
+ size: mem::size_of::<Uniforms>() as wgpu::BufferAddress,
+ usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
+ mapped_at_creation: false,
+ });
- let mut instance_buffer = staging_belt.write_buffer(
- encoder,
- &self.instances,
- 0,
- wgpu::BufferSize::new(instance_bytes.len() as u64).unwrap(),
- device,
- );
+ let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: Some("iced_wgpu::quad uniforms bind group"),
+ layout: constant_layout,
+ entries: &[wgpu::BindGroupEntry {
+ binding: 0,
+ resource: constants_buffer.as_entire_binding(),
+ }],
+ });
- instance_buffer.copy_from_slice(instance_bytes);
-
- #[cfg(feature = "tracing")]
- let _ = info_span!("Wgpu::Quad", "BEGIN_RENDER_PASS").enter();
-
- {
- let mut render_pass =
- encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
- label: Some("iced_wgpu::quad render pass"),
- color_attachments: &[Some(
- wgpu::RenderPassColorAttachment {
- view: target,
- resolve_target: None,
- ops: wgpu::Operations {
- load: wgpu::LoadOp::Load,
- store: true,
- },
- },
- )],
- depth_stencil_attachment: None,
- });
-
- render_pass.set_pipeline(&self.pipeline);
- render_pass.set_bind_group(0, &self.constants, &[]);
- render_pass.set_index_buffer(
- self.indices.slice(..),
- wgpu::IndexFormat::Uint16,
- );
- render_pass.set_vertex_buffer(0, self.vertices.slice(..));
- render_pass.set_vertex_buffer(1, self.instances.slice(..));
-
- render_pass.set_scissor_rect(
- bounds.x,
- bounds.y,
- bounds.width,
- // TODO: Address anti-aliasing adjustments properly
- bounds.height,
- );
-
- render_pass.draw_indexed(
- 0..QUAD_INDICES.len() as u32,
- 0,
- 0..amount as u32,
- );
- }
-
- i += MAX_INSTANCES;
+ let instances = Buffer::new(
+ device,
+ "iced_wgpu::quad instance buffer",
+ INITIAL_INSTANCES,
+ wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
+ );
+
+ Self {
+ constants,
+ constants_buffer,
+ instances,
+ instance_count: 0,
}
}
+
+ pub fn prepare(
+ &mut self,
+ device: &wgpu::Device,
+ queue: &wgpu::Queue,
+ instances: &[layer::Quad],
+ transformation: Transformation,
+ scale: f32,
+ ) {
+ #[cfg(feature = "tracing")]
+ let _ = info_span!("Wgpu::Quad", "PREPARE").entered();
+
+ let uniforms = Uniforms::new(transformation, scale);
+
+ queue.write_buffer(
+ &self.constants_buffer,
+ 0,
+ bytemuck::bytes_of(&uniforms),
+ );
+
+ let _ = self.instances.resize(device, instances.len());
+ self.instances.write(queue, 0, instances);
+ self.instance_count = instances.len();
+ }
+
+ pub fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
+ #[cfg(feature = "tracing")]
+ let _ = info_span!("Wgpu::Quad", "DRAW").entered();
+
+ render_pass.set_bind_group(0, &self.constants, &[]);
+ render_pass.set_vertex_buffer(1, self.instances.slice(..));
+
+ render_pass.draw_indexed(
+ 0..QUAD_INDICES.len() as u32,
+ 0,
+ 0..self.instance_count as u32,
+ );
+ }
}
#[repr(C)]
@@ -285,7 +297,7 @@ const QUAD_VERTS: [Vertex; 4] = [
},
];
-const MAX_INSTANCES: usize = 100_000;
+const INITIAL_INSTANCES: usize = 10_000;
#[repr(C)]
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs
index 5ef79499..bd9cf473 100644
--- a/wgpu/src/settings.rs
+++ b/wgpu/src/settings.rs
@@ -1,12 +1,12 @@
//! Configure a renderer.
-use std::fmt;
-
pub use crate::Antialiasing;
+use crate::Font;
+
/// The settings of a [`Backend`].
///
/// [`Backend`]: crate::Backend
-#[derive(Clone, Copy, PartialEq)]
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Settings {
/// The present mode of the [`Backend`].
///
@@ -16,42 +16,20 @@ pub struct Settings {
/// The internal graphics backend to use.
pub internal_backend: wgpu::Backends,
- /// The bytes of the font that will be used by default.
- ///
- /// If `None` is provided, a default system font will be chosen.
- pub default_font: Option<&'static [u8]>,
+ /// The default [`Font`] to use.
+ pub default_font: Font,
/// The default size of text.
///
/// By default, it will be set to `16.0`.
pub default_text_size: f32,
- /// If enabled, spread text workload in multiple threads when multiple cores
- /// are available.
- ///
- /// By default, it is disabled.
- pub text_multithreading: bool,
-
/// The antialiasing strategy that will be used for triangle primitives.
///
/// By default, it is `None`.
pub antialiasing: Option<Antialiasing>,
}
-impl fmt::Debug for Settings {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.debug_struct("Settings")
- .field("present_mode", &self.present_mode)
- .field("internal_backend", &self.internal_backend)
- // Instead of printing the font bytes, we simply show a `bool` indicating if using a default font or not.
- .field("default_font", &self.default_font.is_some())
- .field("default_text_size", &self.default_text_size)
- .field("text_multithreading", &self.text_multithreading)
- .field("antialiasing", &self.antialiasing)
- .finish()
- }
-}
-
impl Settings {
/// Creates new [`Settings`] using environment configuration.
///
@@ -81,9 +59,8 @@ impl Default for Settings {
Settings {
present_mode: wgpu::PresentMode::AutoVsync,
internal_backend: wgpu::Backends::all(),
- default_font: None,
- default_text_size: 20.0,
- text_multithreading: false,
+ default_font: Font::SansSerif,
+ default_text_size: 16.0,
antialiasing: None,
}
}
diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs
index e17b84c1..dea6ab18 100644
--- a/wgpu/src/text.rs
+++ b/wgpu/src/text.rs
@@ -1,128 +1,267 @@
-use crate::Transformation;
-
-use iced_graphics::font;
+pub use iced_native::text::Hit;
-use std::{cell::RefCell, collections::HashMap};
-use wgpu_glyph::ab_glyph;
+use iced_graphics::layer::Text;
+use iced_native::alignment;
+use iced_native::{Color, Font, Rectangle, Size};
-pub use iced_native::text::Hit;
+use rustc_hash::{FxHashMap, FxHashSet};
+use std::borrow::Cow;
+use std::cell::RefCell;
+use std::collections::hash_map;
+use std::hash::{BuildHasher, Hash, Hasher};
+use std::sync::Arc;
-#[derive(Debug)]
+#[allow(missing_debug_implementations)]
pub struct Pipeline {
- draw_brush: RefCell<wgpu_glyph::GlyphBrush<()>>,
- draw_font_map: RefCell<HashMap<String, wgpu_glyph::FontId>>,
- measure_brush: RefCell<glyph_brush::GlyphBrush<()>>,
+ system: Option<System>,
+ renderers: Vec<glyphon::TextRenderer>,
+ atlas: glyphon::TextAtlas,
+ prepare_layer: usize,
+}
+
+#[ouroboros::self_referencing]
+struct System {
+ fonts: glyphon::FontSystem,
+
+ #[borrows(fonts)]
+ #[not_covariant]
+ measurement_cache: RefCell<Cache<'this>>,
+
+ #[borrows(fonts)]
+ #[not_covariant]
+ render_cache: Cache<'this>,
}
impl Pipeline {
pub fn new(
device: &wgpu::Device,
+ queue: &wgpu::Queue,
format: wgpu::TextureFormat,
- default_font: Option<&[u8]>,
- multithreading: bool,
) -> Self {
- let default_font = default_font.map(|slice| slice.to_vec());
-
- // TODO: Font customization
- #[cfg(not(target_os = "ios"))]
- #[cfg(feature = "default_system_font")]
- let default_font = {
- default_font.or_else(|| {
- font::Source::new()
- .load(&[font::Family::SansSerif, font::Family::Serif])
- .ok()
- })
- };
+ Pipeline {
+ system: Some(
+ SystemBuilder {
+ fonts: glyphon::FontSystem::new_with_fonts(
+ [glyphon::fontdb::Source::Binary(Arc::new(
+ include_bytes!("../fonts/Iced-Icons.ttf")
+ .as_slice(),
+ ))]
+ .into_iter(),
+ ),
+ measurement_cache_builder: |_| RefCell::new(Cache::new()),
+ render_cache_builder: |_| Cache::new(),
+ }
+ .build(),
+ ),
+ renderers: Vec::new(),
+ atlas: glyphon::TextAtlas::new(device, queue, format),
+ prepare_layer: 0,
+ }
+ }
- let default_font =
- default_font.unwrap_or_else(|| font::FALLBACK.to_vec());
+ pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
+ let heads = self.system.take().unwrap().into_heads();
- let font = ab_glyph::FontArc::try_from_vec(default_font)
- .unwrap_or_else(|_| {
- log::warn!(
- "System font failed to load. Falling back to \
- embedded font..."
- );
+ let (locale, mut db) = heads.fonts.into_locale_and_db();
- ab_glyph::FontArc::try_from_slice(font::FALLBACK)
- .expect("Load fallback font")
- });
+ db.load_font_source(glyphon::fontdb::Source::Binary(Arc::new(
+ bytes.into_owned(),
+ )));
+
+ self.system = Some(
+ SystemBuilder {
+ fonts: glyphon::FontSystem::new_with_locale_and_db(locale, db),
+ measurement_cache_builder: |_| RefCell::new(Cache::new()),
+ render_cache_builder: |_| Cache::new(),
+ }
+ .build(),
+ );
+ }
- let draw_brush_builder =
- wgpu_glyph::GlyphBrushBuilder::using_font(font.clone())
- .initial_cache_size((2048, 2048))
- .draw_cache_multithread(multithreading);
+ pub fn prepare(
+ &mut self,
+ device: &wgpu::Device,
+ queue: &wgpu::Queue,
+ sections: &[Text<'_>],
+ bounds: Rectangle,
+ scale_factor: f32,
+ target_size: Size<u32>,
+ ) -> bool {
+ self.system.as_mut().unwrap().with_mut(|fields| {
+ if self.renderers.len() <= self.prepare_layer {
+ self.renderers
+ .push(glyphon::TextRenderer::new(device, queue));
+ }
- #[cfg(target_arch = "wasm32")]
- let draw_brush_builder = draw_brush_builder.draw_cache_align_4x4(true);
+ let renderer = &mut self.renderers[self.prepare_layer];
+
+ let keys: Vec<_> = sections
+ .iter()
+ .map(|section| {
+ let (key, _) = fields.render_cache.allocate(
+ fields.fonts,
+ Key {
+ content: section.content,
+ size: section.size * scale_factor,
+ font: section.font,
+ bounds: Size {
+ width: (section.bounds.width * scale_factor)
+ .ceil(),
+ height: (section.bounds.height * scale_factor)
+ .ceil(),
+ },
+ color: section.color,
+ },
+ );
+
+ key
+ })
+ .collect();
+
+ let bounds = glyphon::TextBounds {
+ left: (bounds.x * scale_factor) as i32,
+ top: (bounds.y * scale_factor) as i32,
+ right: ((bounds.x + bounds.width) * scale_factor) as i32,
+ bottom: ((bounds.y + bounds.height) * scale_factor) as i32,
+ };
+
+ let text_areas =
+ sections.iter().zip(keys.iter()).map(|(section, key)| {
+ let buffer = fields
+ .render_cache
+ .get(key)
+ .expect("Get cached buffer");
+
+ let x = section.bounds.x * scale_factor;
+ let y = section.bounds.y * scale_factor;
+
+ let (total_lines, max_width) = buffer
+ .layout_runs()
+ .enumerate()
+ .fold((0, 0.0), |(_, max), (i, buffer)| {
+ (i + 1, buffer.line_w.max(max))
+ });
+
+ let total_height =
+ total_lines as f32 * section.size * 1.2 * scale_factor;
+
+ let left = match section.horizontal_alignment {
+ alignment::Horizontal::Left => x,
+ alignment::Horizontal::Center => x - max_width / 2.0,
+ alignment::Horizontal::Right => x - max_width,
+ };
+
+ let top = match section.vertical_alignment {
+ alignment::Vertical::Top => y,
+ alignment::Vertical::Center => y - total_height / 2.0,
+ alignment::Vertical::Bottom => y - total_height,
+ };
+
+ glyphon::TextArea {
+ buffer,
+ left: left as i32,
+ top: top as i32,
+ bounds,
+ }
+ });
+
+ let result = renderer.prepare(
+ device,
+ queue,
+ &mut self.atlas,
+ glyphon::Resolution {
+ width: target_size.width,
+ height: target_size.height,
+ },
+ text_areas,
+ glyphon::Color::rgb(0, 0, 0),
+ &mut glyphon::SwashCache::new(fields.fonts),
+ );
+
+ match result {
+ Ok(()) => {
+ self.prepare_layer += 1;
+
+ true
+ }
+ Err(glyphon::PrepareError::AtlasFull(content_type)) => {
+ self.prepare_layer = 0;
+
+ #[allow(clippy::needless_bool)]
+ if self.atlas.grow(device, content_type) {
+ false
+ } else {
+ // If the atlas cannot grow, then all bets are off.
+ // Instead of panicking, we will just pray that the result
+ // will be somewhat readable...
+ true
+ }
+ }
+ }
+ })
+ }
- let draw_brush = draw_brush_builder.build(device, format);
+ pub fn render<'a>(
+ &'a self,
+ layer: usize,
+ bounds: Rectangle<u32>,
+ render_pass: &mut wgpu::RenderPass<'a>,
+ ) {
+ let renderer = &self.renderers[layer];
- let measure_brush =
- glyph_brush::GlyphBrushBuilder::using_font(font).build();
+ render_pass.set_scissor_rect(
+ bounds.x,
+ bounds.y,
+ bounds.width,
+ bounds.height,
+ );
- Pipeline {
- draw_brush: RefCell::new(draw_brush),
- draw_font_map: RefCell::new(HashMap::new()),
- measure_brush: RefCell::new(measure_brush),
- }
+ renderer
+ .render(&self.atlas, render_pass)
+ .expect("Render text");
}
- pub fn queue(&mut self, section: wgpu_glyph::Section<'_>) {
- self.draw_brush.borrow_mut().queue(section);
- }
+ pub fn end_frame(&mut self) {
+ self.atlas.trim();
- pub fn draw_queued(
- &mut self,
- device: &wgpu::Device,
- staging_belt: &mut wgpu::util::StagingBelt,
- encoder: &mut wgpu::CommandEncoder,
- target: &wgpu::TextureView,
- transformation: Transformation,
- region: wgpu_glyph::Region,
- ) {
- self.draw_brush
- .borrow_mut()
- .draw_queued_with_transform_and_scissoring(
- device,
- staging_belt,
- encoder,
- target,
- transformation.into(),
- region,
- )
- .expect("Draw text");
+ self.system
+ .as_mut()
+ .unwrap()
+ .with_render_cache_mut(|cache| cache.trim());
+
+ self.prepare_layer = 0;
}
pub fn measure(
&self,
content: &str,
size: f32,
- font: iced_native::Font,
- bounds: iced_native::Size,
+ font: Font,
+ bounds: Size,
) -> (f32, f32) {
- use wgpu_glyph::GlyphCruncher;
-
- let wgpu_glyph::FontId(font_id) = self.find_font(font);
-
- let section = wgpu_glyph::Section {
- bounds: (bounds.width, bounds.height),
- text: vec![wgpu_glyph::Text {
- text: content,
- scale: size.into(),
- font_id: wgpu_glyph::FontId(font_id),
- extra: wgpu_glyph::Extra::default(),
- }],
- ..Default::default()
- };
-
- if let Some(bounds) =
- self.measure_brush.borrow_mut().glyph_bounds(section)
- {
- (bounds.width().ceil(), bounds.height().ceil())
- } else {
- (0.0, 0.0)
- }
+ self.system.as_ref().unwrap().with(|fields| {
+ let mut measurement_cache = fields.measurement_cache.borrow_mut();
+
+ let (_, paragraph) = measurement_cache.allocate(
+ fields.fonts,
+ Key {
+ content,
+ size,
+ font,
+ bounds,
+ color: Color::BLACK,
+ },
+ );
+
+ let (total_lines, max_width) = paragraph
+ .layout_runs()
+ .enumerate()
+ .fold((0, 0.0), |(_, max), (i, buffer)| {
+ (i + 1, buffer.line_w.max(max))
+ });
+
+ (max_width, size * 1.2 * total_lines as f32)
+ })
}
pub fn hit_test(
@@ -132,134 +271,148 @@ impl Pipeline {
font: iced_native::Font,
bounds: iced_native::Size,
point: iced_native::Point,
- nearest_only: bool,
+ _nearest_only: bool,
) -> Option<Hit> {
- use wgpu_glyph::GlyphCruncher;
-
- let wgpu_glyph::FontId(font_id) = self.find_font(font);
-
- let section = wgpu_glyph::Section {
- bounds: (bounds.width, bounds.height),
- text: vec![wgpu_glyph::Text {
- text: content,
- scale: size.into(),
- font_id: wgpu_glyph::FontId(font_id),
- extra: wgpu_glyph::Extra::default(),
- }],
- ..Default::default()
- };
+ self.system.as_ref().unwrap().with(|fields| {
+ let mut measurement_cache = fields.measurement_cache.borrow_mut();
+
+ let (_, paragraph) = measurement_cache.allocate(
+ fields.fonts,
+ Key {
+ content,
+ size,
+ font,
+ bounds,
+ color: Color::BLACK,
+ },
+ );
+
+ let cursor = paragraph.hit(point.x, point.y)?;
+
+ Some(Hit::CharOffset(cursor.index))
+ })
+ }
- let mut mb = self.measure_brush.borrow_mut();
-
- // The underlying type is FontArc, so clones are cheap.
- use wgpu_glyph::ab_glyph::{Font, ScaleFont};
- let font = mb.fonts()[font_id].clone().into_scaled(size);
-
- // Implements an iterator over the glyph bounding boxes.
- let bounds = mb.glyphs(section).map(
- |wgpu_glyph::SectionGlyph {
- byte_index, glyph, ..
- }| {
- (
- *byte_index,
- iced_native::Rectangle::new(
- iced_native::Point::new(
- glyph.position.x - font.h_side_bearing(glyph.id),
- glyph.position.y - font.ascent(),
- ),
- iced_native::Size::new(
- font.h_advance(glyph.id),
- font.ascent() - font.descent(),
- ),
- ),
- )
- },
- );
+ pub fn trim_measurement_cache(&mut self) {
+ self.system
+ .as_mut()
+ .unwrap()
+ .with_measurement_cache_mut(|cache| cache.borrow_mut().trim());
+ }
+}
- // Implements computation of the character index based on the byte index
- // within the input string.
- let char_index = |byte_index| {
- let mut b_count = 0;
- for (i, utf8_len) in
- content.chars().map(|c| c.len_utf8()).enumerate()
- {
- if byte_index < (b_count + utf8_len) {
- return i;
- }
- b_count += utf8_len;
- }
+fn to_family(font: Font) -> glyphon::Family<'static> {
+ match font {
+ Font::Name(name) => glyphon::Family::Name(name),
+ Font::SansSerif => glyphon::Family::SansSerif,
+ Font::Serif => glyphon::Family::Serif,
+ Font::Cursive => glyphon::Family::Cursive,
+ Font::Fantasy => glyphon::Family::Fantasy,
+ Font::Monospace => glyphon::Family::Monospace,
+ }
+}
- byte_index
- };
+struct Cache<'a> {
+ entries: FxHashMap<KeyHash, glyphon::Buffer<'a>>,
+ recently_used: FxHashSet<KeyHash>,
+ hasher: HashBuilder,
+ trim_count: usize,
+}
- if !nearest_only {
- for (idx, bounds) in bounds.clone() {
- if bounds.contains(point) {
- return Some(Hit::CharOffset(char_index(idx)));
- }
- }
- }
+#[cfg(not(target_arch = "wasm32"))]
+type HashBuilder = twox_hash::RandomXxHashBuilder64;
- let nearest = bounds
- .map(|(index, bounds)| (index, bounds.center()))
- .min_by(|(_, center_a), (_, center_b)| {
- center_a
- .distance(point)
- .partial_cmp(&center_b.distance(point))
- .unwrap_or(std::cmp::Ordering::Greater)
- });
-
- nearest.map(|(idx, center)| {
- Hit::NearestCharOffset(char_index(idx), point - center)
- })
- }
+#[cfg(target_arch = "wasm32")]
+type HashBuilder = std::hash::BuildHasherDefault<twox_hash::XxHash64>;
- pub fn trim_measurement_cache(&mut self) {
- // TODO: We should probably use a `GlyphCalculator` for this. However,
- // it uses a lifetimed `GlyphCalculatorGuard` with side-effects on drop.
- // This makes stuff quite inconvenient. A manual method for trimming the
- // cache would make our lives easier.
- loop {
- let action = self
- .measure_brush
- .borrow_mut()
- .process_queued(|_, _| {}, |_| {});
-
- match action {
- Ok(_) => break,
- Err(glyph_brush::BrushError::TextureTooSmall { suggested }) => {
- let (width, height) = suggested;
-
- self.measure_brush
- .borrow_mut()
- .resize_texture(width, height);
- }
- }
+impl<'a> Cache<'a> {
+ const TRIM_INTERVAL: usize = 300;
+
+ fn new() -> Self {
+ Self {
+ entries: FxHashMap::default(),
+ recently_used: FxHashSet::default(),
+ hasher: HashBuilder::default(),
+ trim_count: 0,
}
}
- pub fn find_font(&self, font: iced_native::Font) -> wgpu_glyph::FontId {
- match font {
- iced_native::Font::Default => wgpu_glyph::FontId(0),
- iced_native::Font::External { name, bytes } => {
- if let Some(font_id) = self.draw_font_map.borrow().get(name) {
- return *font_id;
- }
+ fn get(&self, key: &KeyHash) -> Option<&glyphon::Buffer<'a>> {
+ self.entries.get(key)
+ }
- let font = ab_glyph::FontArc::try_from_slice(bytes)
- .expect("Load font");
+ fn allocate(
+ &mut self,
+ fonts: &'a glyphon::FontSystem,
+ key: Key<'_>,
+ ) -> (KeyHash, &mut glyphon::Buffer<'a>) {
+ let hash = {
+ let mut hasher = self.hasher.build_hasher();
+
+ key.content.hash(&mut hasher);
+ key.size.to_bits().hash(&mut hasher);
+ key.font.hash(&mut hasher);
+ key.bounds.width.to_bits().hash(&mut hasher);
+ key.bounds.height.to_bits().hash(&mut hasher);
+ key.color.into_rgba8().hash(&mut hasher);
+
+ hasher.finish()
+ };
- let _ = self.measure_brush.borrow_mut().add_font(font.clone());
+ if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) {
+ let metrics = glyphon::Metrics::new(key.size, key.size * 1.2);
+ let mut buffer = glyphon::Buffer::new(fonts, metrics);
+
+ buffer.set_size(
+ key.bounds.width,
+ key.bounds.height.max(key.size * 1.2),
+ );
+ buffer.set_text(
+ key.content,
+ glyphon::Attrs::new()
+ .family(to_family(key.font))
+ .color({
+ let [r, g, b, a] = key.color.into_linear();
+
+ glyphon::Color::rgba(
+ (r * 255.0) as u8,
+ (g * 255.0) as u8,
+ (b * 255.0) as u8,
+ (a * 255.0) as u8,
+ )
+ })
+ .monospaced(matches!(key.font, Font::Monospace)),
+ );
+
+ let _ = entry.insert(buffer);
+ }
- let font_id = self.draw_brush.borrow_mut().add_font(font);
+ let _ = self.recently_used.insert(hash);
- let _ = self
- .draw_font_map
- .borrow_mut()
- .insert(String::from(name), font_id);
+ (hash, self.entries.get_mut(&hash).unwrap())
+ }
- font_id
- }
+ fn trim(&mut self) {
+ if self.trim_count >= Self::TRIM_INTERVAL {
+ self.entries
+ .retain(|key, _| self.recently_used.contains(key));
+
+ self.recently_used.clear();
+
+ self.trim_count = 0;
+ } else {
+ self.trim_count += 1;
}
}
}
+
+#[derive(Debug, Clone, Copy)]
+struct Key<'a> {
+ content: &'a str,
+ size: f32,
+ font: Font,
+ bounds: Size,
+ color: Color,
+}
+
+type KeyHash = u64;
diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs
index efdd214b..4b4fa16d 100644
--- a/wgpu/src/triangle.rs
+++ b/wgpu/src/triangle.rs
@@ -14,50 +14,55 @@ use tracing::info_span;
#[derive(Debug)]
pub struct Pipeline {
blit: Option<msaa::Blit>,
- index_buffer: Buffer<u32>,
- index_strides: Vec<u32>,
solid: solid::Pipeline,
/// Gradients are currently not supported on WASM targets due to their need of storage buffers.
#[cfg(not(target_arch = "wasm32"))]
gradient: gradient::Pipeline,
+
+ layers: Vec<Layer>,
+ prepare_layer: usize,
}
-impl Pipeline {
- pub fn new(
+#[derive(Debug)]
+struct Layer {
+ index_buffer: Buffer<u32>,
+ index_strides: Vec<u32>,
+ solid: solid::Layer,
+
+ #[cfg(not(target_arch = "wasm32"))]
+ gradient: gradient::Layer,
+}
+
+impl Layer {
+ fn new(
device: &wgpu::Device,
- format: wgpu::TextureFormat,
- antialiasing: Option<settings::Antialiasing>,
- ) -> Pipeline {
- Pipeline {
- blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)),
+ solid: &solid::Pipeline,
+ #[cfg(not(target_arch = "wasm32"))] gradient: &gradient::Pipeline,
+ ) -> Self {
+ Self {
index_buffer: Buffer::new(
device,
- "iced_wgpu::triangle vertex buffer",
+ "iced_wgpu::triangle index buffer",
wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
),
index_strides: Vec::new(),
- solid: solid::Pipeline::new(device, format, antialiasing),
+ solid: solid::Layer::new(device, &solid.constants_layout),
#[cfg(not(target_arch = "wasm32"))]
- gradient: gradient::Pipeline::new(device, format, antialiasing),
+ gradient: gradient::Layer::new(device, &gradient.constants_layout),
}
}
- pub fn draw(
+ fn prepare(
&mut self,
device: &wgpu::Device,
- staging_belt: &mut wgpu::util::StagingBelt,
- encoder: &mut wgpu::CommandEncoder,
- target: &wgpu::TextureView,
- target_size: Size<u32>,
- transformation: Transformation,
- scale_factor: f32,
+ queue: &wgpu::Queue,
+ solid: &solid::Pipeline,
+ #[cfg(not(target_arch = "wasm32"))] gradient: &gradient::Pipeline,
meshes: &[Mesh<'_>],
+ transformation: Transformation,
) {
- #[cfg(feature = "tracing")]
- let _ = info_span!("Wgpu::Triangle", "DRAW").entered();
-
// Count the total amount of vertices & indices we need to handle
let count = mesh::attribute_count_of(meshes);
@@ -75,6 +80,7 @@ impl Pipeline {
.resize(device, count.gradient_vertices);
// Prepare dynamic buffers & data store for writing
+ self.index_buffer.clear();
self.index_strides.clear();
self.solid.vertices.clear();
self.solid.uniforms.clear();
@@ -99,13 +105,8 @@ impl Pipeline {
let transform =
transformation * Transformation::translate(origin.x, origin.y);
- let new_index_offset = self.index_buffer.write(
- device,
- staging_belt,
- encoder,
- index_offset,
- indices,
- );
+ let new_index_offset =
+ self.index_buffer.write(queue, index_offset, indices);
index_offset += new_index_offset;
self.index_strides.push(indices.len() as u32);
@@ -116,9 +117,7 @@ impl Pipeline {
self.solid.uniforms.push(&solid::Uniforms::new(transform));
let written_bytes = self.solid.vertices.write(
- device,
- staging_belt,
- encoder,
+ queue,
solid_vertex_offset,
&buffers.vertices,
);
@@ -130,9 +129,7 @@ impl Pipeline {
buffers, gradient, ..
} => {
let written_bytes = self.gradient.vertices.write(
- device,
- staging_belt,
- encoder,
+ queue,
gradient_vertex_offset,
&buffers.vertices,
);
@@ -196,14 +193,14 @@ impl Pipeline {
let uniforms_resized = self.solid.uniforms.resize(device);
if uniforms_resized {
- self.solid.bind_group = solid::Pipeline::bind_group(
+ self.solid.constants = solid::Layer::bind_group(
device,
self.solid.uniforms.raw(),
- &self.solid.bind_group_layout,
+ &solid.constants_layout,
)
}
- self.solid.uniforms.write(device, staging_belt, encoder);
+ self.solid.uniforms.write(queue);
}
#[cfg(not(target_arch = "wasm32"))]
@@ -218,22 +215,169 @@ impl Pipeline {
let storage_resized = self.gradient.storage.resize(device);
if uniforms_resized || storage_resized {
- self.gradient.bind_group = gradient::Pipeline::bind_group(
+ self.gradient.constants = gradient::Layer::bind_group(
device,
self.gradient.uniforms.raw(),
self.gradient.storage.raw(),
- &self.gradient.bind_group_layout,
+ &gradient.constants_layout,
);
}
// Write to GPU
- self.gradient.uniforms.write(device, staging_belt, encoder);
- self.gradient.storage.write(device, staging_belt, encoder);
+ self.gradient.uniforms.write(queue);
+ self.gradient.storage.write(queue);
// Cleanup
self.gradient.color_stop_offset = 0;
self.gradient.color_stops_pending_write.color_stops.clear();
}
+ }
+
+ fn render<'a>(
+ &'a self,
+ solid: &'a solid::Pipeline,
+ #[cfg(not(target_arch = "wasm32"))] gradient: &'a gradient::Pipeline,
+ meshes: &[Mesh<'_>],
+ scale_factor: f32,
+ render_pass: &mut wgpu::RenderPass<'a>,
+ ) {
+ let mut num_solids = 0;
+ #[cfg(not(target_arch = "wasm32"))]
+ let mut num_gradients = 0;
+ let mut last_is_solid = None;
+
+ for (index, mesh) in meshes.iter().enumerate() {
+ let clip_bounds = (mesh.clip_bounds() * scale_factor).snap();
+
+ render_pass.set_scissor_rect(
+ clip_bounds.x,
+ clip_bounds.y,
+ clip_bounds.width,
+ clip_bounds.height,
+ );
+
+ match mesh {
+ Mesh::Solid { .. } => {
+ if !last_is_solid.unwrap_or(false) {
+ render_pass.set_pipeline(&solid.pipeline);
+
+ last_is_solid = Some(true);
+ }
+
+ render_pass.set_bind_group(
+ 0,
+ &self.solid.constants,
+ &[self.solid.uniforms.offset_at_index(num_solids)],
+ );
+
+ render_pass.set_vertex_buffer(
+ 0,
+ self.solid.vertices.slice_from_index(num_solids),
+ );
+
+ num_solids += 1;
+ }
+ #[cfg(not(target_arch = "wasm32"))]
+ Mesh::Gradient { .. } => {
+ if last_is_solid.unwrap_or(true) {
+ render_pass.set_pipeline(&gradient.pipeline);
+
+ last_is_solid = Some(false);
+ }
+
+ render_pass.set_bind_group(
+ 0,
+ &self.gradient.constants,
+ &[self
+ .gradient
+ .uniforms
+ .offset_at_index(num_gradients)],
+ );
+
+ render_pass.set_vertex_buffer(
+ 0,
+ self.gradient.vertices.slice_from_index(num_gradients),
+ );
+
+ num_gradients += 1;
+ }
+ #[cfg(target_arch = "wasm32")]
+ Mesh::Gradient { .. } => {}
+ };
+
+ render_pass.set_index_buffer(
+ self.index_buffer.slice_from_index(index),
+ wgpu::IndexFormat::Uint32,
+ );
+
+ render_pass.draw_indexed(0..self.index_strides[index], 0, 0..1);
+ }
+ }
+}
+
+impl Pipeline {
+ pub fn new(
+ device: &wgpu::Device,
+ format: wgpu::TextureFormat,
+ antialiasing: Option<settings::Antialiasing>,
+ ) -> Pipeline {
+ Pipeline {
+ blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)),
+ solid: solid::Pipeline::new(device, format, antialiasing),
+
+ #[cfg(not(target_arch = "wasm32"))]
+ gradient: gradient::Pipeline::new(device, format, antialiasing),
+
+ layers: Vec::new(),
+ prepare_layer: 0,
+ }
+ }
+
+ pub fn prepare(
+ &mut self,
+ device: &wgpu::Device,
+ queue: &wgpu::Queue,
+ meshes: &[Mesh<'_>],
+ transformation: Transformation,
+ ) {
+ #[cfg(feature = "tracing")]
+ let _ = info_span!("Wgpu::Triangle", "PREPARE").entered();
+
+ if self.layers.len() <= self.prepare_layer {
+ self.layers.push(Layer::new(
+ device,
+ &self.solid,
+ #[cfg(not(target_arch = "wasm32"))]
+ &self.gradient,
+ ));
+ }
+
+ let layer = &mut self.layers[self.prepare_layer];
+ layer.prepare(
+ device,
+ queue,
+ &self.solid,
+ #[cfg(not(target_arch = "wasm32"))]
+ &self.gradient,
+ meshes,
+ transformation,
+ );
+
+ self.prepare_layer += 1;
+ }
+
+ pub fn render(
+ &mut self,
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ target: &wgpu::TextureView,
+ layer: usize,
+ target_size: Size<u32>,
+ meshes: &[Mesh<'_>],
+ scale_factor: f32,
+ ) {
+ #[cfg(feature = "tracing")]
+ let _ = info_span!("Wgpu::Triangle", "DRAW").entered();
// Configure render pass
{
@@ -268,87 +412,26 @@ impl Pipeline {
depth_stencil_attachment: None,
});
- let mut num_solids = 0;
- #[cfg(not(target_arch = "wasm32"))]
- let mut num_gradients = 0;
- let mut last_is_solid = None;
-
- for (index, mesh) in meshes.iter().enumerate() {
- let clip_bounds = (mesh.clip_bounds() * scale_factor).snap();
-
- render_pass.set_scissor_rect(
- clip_bounds.x,
- clip_bounds.y,
- clip_bounds.width,
- clip_bounds.height,
- );
-
- match mesh {
- Mesh::Solid { .. } => {
- if !last_is_solid.unwrap_or(false) {
- render_pass.set_pipeline(&self.solid.pipeline);
-
- last_is_solid = Some(true);
- }
-
- render_pass.set_bind_group(
- 0,
- &self.solid.bind_group,
- &[self.solid.uniforms.offset_at_index(num_solids)],
- );
-
- render_pass.set_vertex_buffer(
- 0,
- self.solid.vertices.slice_from_index(num_solids),
- );
-
- num_solids += 1;
- }
- #[cfg(not(target_arch = "wasm32"))]
- Mesh::Gradient { .. } => {
- if last_is_solid.unwrap_or(true) {
- render_pass.set_pipeline(&self.gradient.pipeline);
-
- last_is_solid = Some(false);
- }
-
- render_pass.set_bind_group(
- 0,
- &self.gradient.bind_group,
- &[self
- .gradient
- .uniforms
- .offset_at_index(num_gradients)],
- );
-
- render_pass.set_vertex_buffer(
- 0,
- self.gradient
- .vertices
- .slice_from_index(num_gradients),
- );
-
- num_gradients += 1;
- }
- #[cfg(target_arch = "wasm32")]
- Mesh::Gradient { .. } => {}
- };
-
- render_pass.set_index_buffer(
- self.index_buffer.slice_from_index(index),
- wgpu::IndexFormat::Uint32,
- );
+ let layer = &mut self.layers[layer];
- render_pass.draw_indexed(0..self.index_strides[index], 0, 0..1);
- }
+ layer.render(
+ &self.solid,
+ #[cfg(not(target_arch = "wasm32"))]
+ &self.gradient,
+ meshes,
+ scale_factor,
+ &mut render_pass,
+ );
}
- self.index_buffer.clear();
-
if let Some(blit) = &mut self.blit {
blit.draw(encoder, target);
}
}
+
+ pub fn end_frame(&mut self) {
+ self.prepare_layer = 0;
+ }
}
fn fragment_target(
@@ -390,10 +473,62 @@ mod solid {
#[derive(Debug)]
pub struct Pipeline {
pub pipeline: wgpu::RenderPipeline,
+ pub constants_layout: wgpu::BindGroupLayout,
+ }
+
+ #[derive(Debug)]
+ pub struct Layer {
pub vertices: Buffer<triangle::ColoredVertex2D>,
pub uniforms: dynamic::Buffer<Uniforms>,
- pub bind_group_layout: wgpu::BindGroupLayout,
- pub bind_group: wgpu::BindGroup,
+ pub constants: wgpu::BindGroup,
+ }
+
+ impl Layer {
+ pub fn new(
+ device: &wgpu::Device,
+ constants_layout: &wgpu::BindGroupLayout,
+ ) -> Self {
+ let vertices = Buffer::new(
+ device,
+ "iced_wgpu::triangle::solid vertex buffer",
+ wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
+ );
+
+ let uniforms = dynamic::Buffer::uniform(
+ device,
+ "iced_wgpu::triangle::solid uniforms",
+ );
+
+ let constants =
+ Self::bind_group(device, uniforms.raw(), constants_layout);
+
+ Self {
+ vertices,
+ uniforms,
+ constants,
+ }
+ }
+
+ pub fn bind_group(
+ device: &wgpu::Device,
+ buffer: &wgpu::Buffer,
+ layout: &wgpu::BindGroupLayout,
+ ) -> wgpu::BindGroup {
+ device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: Some("iced_wgpu::triangle::solid bind group"),
+ layout,
+ entries: &[wgpu::BindGroupEntry {
+ binding: 0,
+ resource: wgpu::BindingResource::Buffer(
+ wgpu::BufferBinding {
+ buffer,
+ offset: 0,
+ size: Some(Uniforms::min_size()),
+ },
+ ),
+ }],
+ })
+ }
}
#[derive(Debug, Clone, Copy, ShaderType)]
@@ -416,18 +551,7 @@ mod solid {
format: wgpu::TextureFormat,
antialiasing: Option<settings::Antialiasing>,
) -> Self {
- let vertices = Buffer::new(
- device,
- "iced_wgpu::triangle::solid vertex buffer",
- wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
- );
-
- let uniforms = dynamic::Buffer::uniform(
- device,
- "iced_wgpu::triangle::solid uniforms",
- );
-
- let bind_group_layout = device.create_bind_group_layout(
+ let constants_layout = device.create_bind_group_layout(
&wgpu::BindGroupLayoutDescriptor {
label: Some("iced_wgpu::triangle::solid bind group layout"),
entries: &[wgpu::BindGroupLayoutEntry {
@@ -443,13 +567,10 @@ mod solid {
},
);
- let bind_group =
- Self::bind_group(device, uniforms.raw(), &bind_group_layout);
-
let layout = device.create_pipeline_layout(
&wgpu::PipelineLayoutDescriptor {
label: Some("iced_wgpu::triangle::solid pipeline layout"),
- bind_group_layouts: &[&bind_group_layout],
+ bind_group_layouts: &[&constants_layout],
push_constant_ranges: &[],
},
);
@@ -501,33 +622,9 @@ mod solid {
Self {
pipeline,
- vertices,
- uniforms,
- bind_group_layout,
- bind_group,
+ constants_layout,
}
}
-
- pub fn bind_group(
- device: &wgpu::Device,
- buffer: &wgpu::Buffer,
- layout: &wgpu::BindGroupLayout,
- ) -> wgpu::BindGroup {
- device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: Some("iced_wgpu::triangle::solid bind group"),
- layout,
- entries: &[wgpu::BindGroupEntry {
- binding: 0,
- resource: wgpu::BindingResource::Buffer(
- wgpu::BufferBinding {
- buffer,
- offset: 0,
- size: Some(Uniforms::min_size()),
- },
- ),
- }],
- })
- }
}
}
@@ -545,15 +642,90 @@ mod gradient {
#[derive(Debug)]
pub struct Pipeline {
pub pipeline: wgpu::RenderPipeline,
+ pub constants_layout: wgpu::BindGroupLayout,
+ }
+
+ #[derive(Debug)]
+ pub struct Layer {
pub vertices: Buffer<Vertex2D>,
pub uniforms: dynamic::Buffer<Uniforms>,
pub storage: dynamic::Buffer<Storage>,
+ pub constants: wgpu::BindGroup,
pub color_stop_offset: i32,
//Need to store these and then write them all at once
//or else they will be padded to 256 and cause gaps in the storage buffer
pub color_stops_pending_write: Storage,
- pub bind_group_layout: wgpu::BindGroupLayout,
- pub bind_group: wgpu::BindGroup,
+ }
+
+ impl Layer {
+ pub fn new(
+ device: &wgpu::Device,
+ constants_layout: &wgpu::BindGroupLayout,
+ ) -> Self {
+ let vertices = Buffer::new(
+ device,
+ "iced_wgpu::triangle::gradient vertex buffer",
+ wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
+ );
+
+ let uniforms = dynamic::Buffer::uniform(
+ device,
+ "iced_wgpu::triangle::gradient uniforms",
+ );
+
+ // Note: with a WASM target storage buffers are not supported. Will need to use UBOs & static
+ // sized array (eg like the 32-sized array on OpenGL side right now) to make gradients work
+ let storage = dynamic::Buffer::storage(
+ device,
+ "iced_wgpu::triangle::gradient storage",
+ );
+
+ let constants = Self::bind_group(
+ device,
+ uniforms.raw(),
+ storage.raw(),
+ constants_layout,
+ );
+
+ Self {
+ vertices,
+ uniforms,
+ storage,
+ constants,
+ color_stop_offset: 0,
+ color_stops_pending_write: Storage {
+ color_stops: vec![],
+ },
+ }
+ }
+
+ pub fn bind_group(
+ device: &wgpu::Device,
+ uniform_buffer: &wgpu::Buffer,
+ storage_buffer: &wgpu::Buffer,
+ layout: &wgpu::BindGroupLayout,
+ ) -> wgpu::BindGroup {
+ device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: Some("iced_wgpu::triangle::gradient bind group"),
+ layout,
+ entries: &[
+ wgpu::BindGroupEntry {
+ binding: 0,
+ resource: wgpu::BindingResource::Buffer(
+ wgpu::BufferBinding {
+ buffer: uniform_buffer,
+ offset: 0,
+ size: Some(Uniforms::min_size()),
+ },
+ ),
+ },
+ wgpu::BindGroupEntry {
+ binding: 1,
+ resource: storage_buffer.as_entire_binding(),
+ },
+ ],
+ })
+ }
}
#[derive(Debug, ShaderType)]
@@ -584,25 +756,7 @@ mod gradient {
format: wgpu::TextureFormat,
antialiasing: Option<settings::Antialiasing>,
) -> Self {
- let vertices = Buffer::new(
- device,
- "iced_wgpu::triangle::gradient vertex buffer",
- wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
- );
-
- let uniforms = dynamic::Buffer::uniform(
- device,
- "iced_wgpu::triangle::gradient uniforms",
- );
-
- //Note: with a WASM target storage buffers are not supported. Will need to use UBOs & static
- // sized array (eg like the 32-sized array on OpenGL side right now) to make gradients work
- let storage = dynamic::Buffer::storage(
- device,
- "iced_wgpu::triangle::gradient storage",
- );
-
- let bind_group_layout = device.create_bind_group_layout(
+ let constants_layout = device.create_bind_group_layout(
&wgpu::BindGroupLayoutDescriptor {
label: Some(
"iced_wgpu::triangle::gradient bind group layout",
@@ -634,19 +788,12 @@ mod gradient {
},
);
- let bind_group = Pipeline::bind_group(
- device,
- uniforms.raw(),
- storage.raw(),
- &bind_group_layout,
- );
-
let layout = device.create_pipeline_layout(
&wgpu::PipelineLayoutDescriptor {
label: Some(
"iced_wgpu::triangle::gradient pipeline layout",
),
- bind_group_layouts: &[&bind_group_layout],
+ bind_group_layouts: &[&constants_layout],
push_constant_ranges: &[],
},
);
@@ -694,44 +841,8 @@ mod gradient {
Self {
pipeline,
- vertices,
- uniforms,
- storage,
- color_stop_offset: 0,
- color_stops_pending_write: Storage {
- color_stops: vec![],
- },
- bind_group_layout,
- bind_group,
+ constants_layout,
}
}
-
- pub fn bind_group(
- device: &wgpu::Device,
- uniform_buffer: &wgpu::Buffer,
- storage_buffer: &wgpu::Buffer,
- layout: &wgpu::BindGroupLayout,
- ) -> wgpu::BindGroup {
- device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: Some("iced_wgpu::triangle::gradient bind group"),
- layout,
- entries: &[
- wgpu::BindGroupEntry {
- binding: 0,
- resource: wgpu::BindingResource::Buffer(
- wgpu::BufferBinding {
- buffer: uniform_buffer,
- offset: 0,
- size: Some(Uniforms::min_size()),
- },
- ),
- },
- wgpu::BindGroupEntry {
- binding: 1,
- resource: storage_buffer.as_entire_binding(),
- },
- ],
- })
- }
}
}
diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs
index 6d0c36f6..365cb603 100644
--- a/wgpu/src/window/compositor.rs
+++ b/wgpu/src/window/compositor.rs
@@ -16,14 +16,11 @@ pub struct Compositor<Theme> {
adapter: wgpu::Adapter,
device: wgpu::Device,
queue: wgpu::Queue,
- staging_belt: wgpu::util::StagingBelt,
format: wgpu::TextureFormat,
theme: PhantomData<Theme>,
}
impl<Theme> Compositor<Theme> {
- const CHUNK_SIZE: u64 = 10 * 1024;
-
/// Requests a new [`Compositor`] with the given [`Settings`].
///
/// Returns `None` if no compatible graphics adapter could be found.
@@ -98,15 +95,12 @@ impl<Theme> Compositor<Theme> {
.next()
.await?;
- let staging_belt = wgpu::util::StagingBelt::new(Self::CHUNK_SIZE);
-
Some(Compositor {
instance,
settings,
adapter,
device,
queue,
- staging_belt,
format,
theme: PhantomData,
})
@@ -114,7 +108,7 @@ impl<Theme> Compositor<Theme> {
/// Creates a new rendering [`Backend`] for this [`Compositor`].
pub fn create_backend(&self) -> Backend {
- Backend::new(&self.device, self.settings, self.format)
+ Backend::new(&self.device, &self.queue, self.settings, self.format)
}
}
@@ -196,39 +190,12 @@ impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> {
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
- let _ =
- encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
- label: Some(
- "iced_wgpu::window::Compositor render pass",
- ),
- color_attachments: &[Some(
- wgpu::RenderPassColorAttachment {
- view,
- resolve_target: None,
- ops: wgpu::Operations {
- load: wgpu::LoadOp::Clear({
- let [r, g, b, a] =
- background_color.into_linear();
-
- wgpu::Color {
- r: f64::from(r),
- g: f64::from(g),
- b: f64::from(b),
- a: f64::from(a),
- }
- }),
- store: true,
- },
- },
- )],
- depth_stencil_attachment: None,
- });
-
renderer.with_primitives(|backend, primitives| {
backend.present(
&self.device,
- &mut self.staging_belt,
+ &self.queue,
&mut encoder,
+ Some(background_color),
view,
primitives,
viewport,
@@ -237,13 +204,9 @@ impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> {
});
// Submit work
- self.staging_belt.finish();
let _submission = self.queue.submit(Some(encoder.finish()));
frame.present();
- // Recall staging buffers
- self.staging_belt.recall();
-
Ok(())
}
Err(error) => match error {