summaryrefslogtreecommitdiffstats
path: root/wgpu/src
diff options
context:
space:
mode:
Diffstat (limited to 'wgpu/src')
-rw-r--r--wgpu/src/backend.rs415
-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/geometry.rs613
-rw-r--r--wgpu/src/image.rs283
-rw-r--r--wgpu/src/image/atlas.rs215
-rw-r--r--wgpu/src/image/atlas/allocation.rs3
-rw-r--r--wgpu/src/image/atlas/allocator.rs4
-rw-r--r--wgpu/src/image/atlas/entry.rs9
-rw-r--r--wgpu/src/image/raster.rs121
-rw-r--r--wgpu/src/image/vector.rs183
-rw-r--r--wgpu/src/layer.rs281
-rw-r--r--wgpu/src/layer/image.rs27
-rw-r--r--wgpu/src/layer/mesh.rs93
-rw-r--r--wgpu/src/layer/quad.rs30
-rw-r--r--wgpu/src/layer/text.rs34
-rw-r--r--wgpu/src/lib.rs16
-rw-r--r--wgpu/src/quad.rs233
-rw-r--r--wgpu/src/settings.rs55
-rw-r--r--wgpu/src/text.rs596
-rw-r--r--wgpu/src/triangle.rs655
-rw-r--r--wgpu/src/triangle/msaa.rs8
-rw-r--r--wgpu/src/window.rs3
-rw-r--r--wgpu/src/window/compositor.rs208
25 files changed, 3020 insertions, 1206 deletions
diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs
index 6a299425..def80a81 100644
--- a/wgpu/src/backend.rs
+++ b/wgpu/src/backend.rs
@@ -1,14 +1,11 @@
+use crate::core;
+use crate::core::{Color, Font, Point, Size};
+use crate::graphics::backend;
+use crate::graphics::{Primitive, Transformation, Viewport};
use crate::quad;
use crate::text;
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 crate::{Layer, Settings};
#[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();
+ }
+
+ 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);
- self.image_pipeline.draw(
+ quad_layer += 1;
+ }
+
+ 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::with_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
@@ -273,45 +352,59 @@ impl backend::Text for Backend {
&self,
contents: &str,
size: f32,
+ line_height: core::text::LineHeight,
font: Font,
bounds: Size,
+ shaping: core::text::Shaping,
) -> (f32, f32) {
- self.text_pipeline.measure(contents, size, font, bounds)
+ self.text_pipeline.measure(
+ contents,
+ size,
+ line_height,
+ font,
+ bounds,
+ shaping,
+ )
}
fn hit_test(
&self,
contents: &str,
size: f32,
+ line_height: core::text::LineHeight,
font: Font,
bounds: Size,
- point: iced_native::Point,
+ shaping: core::text::Shaping,
+ point: Point,
nearest_only: bool,
- ) -> Option<text::Hit> {
+ ) -> Option<core::text::Hit> {
self.text_pipeline.hit_test(
contents,
size,
+ line_height,
font,
bounds,
+ shaping,
point,
nearest_only,
)
}
+
+ fn load_font(&mut self, font: Cow<'static, [u8]>) {
+ self.text_pipeline.load_font(font);
+ }
}
#[cfg(feature = "image")]
impl backend::Image for Backend {
- fn dimensions(&self, handle: &iced_native::image::Handle) -> Size<u32> {
+ fn dimensions(&self, handle: &core::image::Handle) -> Size<u32> {
self.image_pipeline.dimensions(handle)
}
}
#[cfg(feature = "svg")]
impl backend::Svg for Backend {
- fn viewport_dimensions(
- &self,
- handle: &iced_native::svg::Handle,
- ) -> Size<u32> {
+ fn viewport_dimensions(&self, handle: &core::svg::Handle) -> Size<u32> {
self.image_pipeline.viewport_dimensions(handle)
}
}
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/geometry.rs b/wgpu/src/geometry.rs
new file mode 100644
index 00000000..7e17a7ad
--- /dev/null
+++ b/wgpu/src/geometry.rs
@@ -0,0 +1,613 @@
+//! Build and draw geometry.
+use crate::core::{Gradient, Point, Rectangle, Size, Vector};
+use crate::graphics::geometry::fill::{self, Fill};
+use crate::graphics::geometry::{
+ LineCap, LineDash, LineJoin, Path, Stroke, Style, Text,
+};
+use crate::graphics::primitive::{self, Primitive};
+
+use lyon::geom::euclid;
+use lyon::tessellation;
+use std::borrow::Cow;
+
+/// A frame for drawing some geometry.
+#[allow(missing_debug_implementations)]
+pub struct Frame {
+ size: Size,
+ buffers: BufferStack,
+ primitives: Vec<Primitive>,
+ transforms: Transforms,
+ fill_tessellator: tessellation::FillTessellator,
+ stroke_tessellator: tessellation::StrokeTessellator,
+}
+
+enum Buffer {
+ Solid(tessellation::VertexBuffers<primitive::ColoredVertex2D, u32>),
+ Gradient(
+ tessellation::VertexBuffers<primitive::Vertex2D, u32>,
+ Gradient,
+ ),
+}
+
+struct BufferStack {
+ stack: Vec<Buffer>,
+}
+
+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(gradient) => match self.stack.last() {
+ Some(Buffer::Gradient(_, last)) if gradient == last => {}
+ _ => {
+ self.stack.push(Buffer::Gradient(
+ tessellation::VertexBuffers::new(),
+ gradient.clone(),
+ ));
+ }
+ },
+ }
+
+ self.stack.last_mut().unwrap()
+ }
+
+ fn get_fill<'a>(
+ &'a mut self,
+ style: &Style,
+ ) -> Box<dyn tessellation::FillGeometryBuilder + 'a> {
+ match (style, self.get_mut(style)) {
+ (Style::Solid(color), Buffer::Solid(buffer)) => {
+ Box::new(tessellation::BuffersBuilder::new(
+ buffer,
+ TriangleVertex2DBuilder(color.into_linear()),
+ ))
+ }
+ (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new(
+ tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder),
+ ),
+ _ => unreachable!(),
+ }
+ }
+
+ fn get_stroke<'a>(
+ &'a mut self,
+ style: &Style,
+ ) -> Box<dyn tessellation::StrokeGeometryBuilder + 'a> {
+ match (style, self.get_mut(style)) {
+ (Style::Solid(color), Buffer::Solid(buffer)) => {
+ Box::new(tessellation::BuffersBuilder::new(
+ buffer,
+ TriangleVertex2DBuilder(color.into_linear()),
+ ))
+ }
+ (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new(
+ tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder),
+ ),
+ _ => unreachable!(),
+ }
+ }
+}
+
+#[derive(Debug)]
+struct Transforms {
+ previous: Vec<Transform>,
+ current: Transform,
+}
+
+#[derive(Debug, Clone, Copy)]
+struct Transform {
+ raw: lyon::math::Transform,
+ is_identity: bool,
+}
+
+impl Transform {
+ /// Transforms the given [Point] by the transformation matrix.
+ fn transform_point(&self, point: &mut Point) {
+ let transformed = self
+ .raw
+ .transform_point(euclid::Point2D::new(point.x, point.y));
+ point.x = transformed.x;
+ point.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 {
+ let (start, end) = match &mut gradient {
+ Gradient::Linear(linear) => (&mut linear.start, &mut linear.end),
+ };
+ self.transform_point(start);
+ self.transform_point(end);
+ gradient
+ }
+}
+
+impl Frame {
+ /// Creates a new empty [`Frame`] with the given dimensions.
+ ///
+ /// The default coordinate system of a [`Frame`] has its origin at the
+ /// top-left corner of its bounds.
+ pub fn new(size: Size) -> Frame {
+ Frame {
+ size,
+ buffers: BufferStack::new(),
+ primitives: Vec::new(),
+ transforms: Transforms {
+ previous: Vec::new(),
+ current: Transform {
+ raw: lyon::math::Transform::identity(),
+ is_identity: true,
+ },
+ },
+ fill_tessellator: tessellation::FillTessellator::new(),
+ stroke_tessellator: tessellation::StrokeTessellator::new(),
+ }
+ }
+
+ /// Returns the width of the [`Frame`].
+ #[inline]
+ pub fn width(&self) -> f32 {
+ self.size.width
+ }
+
+ /// Returns the height of the [`Frame`].
+ #[inline]
+ pub fn height(&self) -> f32 {
+ self.size.height
+ }
+
+ /// Returns the dimensions of the [`Frame`].
+ #[inline]
+ pub fn size(&self) -> Size {
+ self.size
+ }
+
+ /// Returns the coordinate of the center of the [`Frame`].
+ #[inline]
+ pub fn center(&self) -> Point {
+ Point::new(self.size.width / 2.0, self.size.height / 2.0)
+ }
+
+ /// Draws the given [`Path`] on the [`Frame`] by filling it with the
+ /// provided style.
+ pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
+ 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.raw);
+
+ self.fill_tessellator.tessellate_path(
+ path.raw(),
+ &options,
+ buffer.as_mut(),
+ )
+ }
+ .expect("Tessellate path.");
+ }
+
+ /// Draws an axis-aligned rectangle given its top-left corner coordinate and
+ /// its `Size` on the [`Frame`] by filling it with the provided style.
+ pub fn fill_rectangle(
+ &mut self,
+ top_left: Point,
+ size: Size,
+ fill: impl Into<Fill>,
+ ) {
+ 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.raw.transform_point(
+ lyon::math::Point::new(top_left.x, top_left.y),
+ );
+
+ let size =
+ self.transforms.current.raw.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");
+ }
+
+ /// Draws the stroke of the given [`Path`] on the [`Frame`] with the
+ /// provided style.
+ pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
+ 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.raw);
+
+ self.stroke_tessellator.tessellate_path(
+ path.raw(),
+ &options,
+ buffer.as_mut(),
+ )
+ }
+ .expect("Stroke path");
+ }
+
+ /// Draws the characters of the given [`Text`] on the [`Frame`], filling
+ /// them with the given color.
+ ///
+ /// __Warning:__ Text currently does not work well with rotations and scale
+ /// transforms! The position will be correctly transformed, but the
+ /// resulting glyphs will not be rotated or scaled properly.
+ ///
+ /// Additionally, all text will be rendered on top of all the layers of
+ /// a [`Canvas`]. Therefore, it is currently only meant to be used for
+ /// overlays, which is the most common use case.
+ ///
+ /// Support for vectorial text is planned, and should address all these
+ /// limitations.
+ ///
+ /// [`Canvas`]: crate::widget::Canvas
+ pub fn fill_text(&mut self, text: impl Into<Text>) {
+ let text = text.into();
+
+ let position = if self.transforms.current.is_identity {
+ text.position
+ } else {
+ let transformed = self.transforms.current.raw.transform_point(
+ lyon::math::Point::new(text.position.x, text.position.y),
+ );
+
+ Point::new(transformed.x, transformed.y)
+ };
+
+ // TODO: Use vectorial text instead of primitive
+ self.primitives.push(Primitive::Text {
+ content: text.content,
+ bounds: Rectangle {
+ x: position.x,
+ y: position.y,
+ width: f32::INFINITY,
+ height: f32::INFINITY,
+ },
+ color: text.color,
+ size: text.size,
+ line_height: text.line_height,
+ font: text.font,
+ horizontal_alignment: text.horizontal_alignment,
+ vertical_alignment: text.vertical_alignment,
+ shaping: text.shaping,
+ });
+ }
+
+ /// Stores the current transform of the [`Frame`] and executes the given
+ /// drawing operations, restoring the transform afterwards.
+ ///
+ /// This method is useful to compose transforms and perform drawing
+ /// operations in different coordinate systems.
+ #[inline]
+ pub fn with_save(&mut self, f: impl FnOnce(&mut Frame)) {
+ self.push_transform();
+
+ f(self);
+
+ self.pop_transform();
+ }
+
+ /// Pushes the current transform in the transform stack.
+ pub fn push_transform(&mut self) {
+ self.transforms.previous.push(self.transforms.current);
+ }
+
+ /// Pops a transform from the transform stack and sets it as the current transform.
+ pub fn pop_transform(&mut self) {
+ self.transforms.current = self.transforms.previous.pop().unwrap();
+ }
+
+ /// Executes the given drawing operations within a [`Rectangle`] region,
+ /// clipping any geometry that overflows its bounds. Any transformations
+ /// performed are local to the provided closure.
+ ///
+ /// This method is useful to perform drawing operations that need to be
+ /// clipped.
+ #[inline]
+ pub fn with_clip(&mut self, region: Rectangle, f: impl FnOnce(&mut Frame)) {
+ let mut frame = Frame::new(region.size());
+
+ f(&mut frame);
+
+ let origin = Point::new(region.x, region.y);
+
+ self.clip(frame, origin);
+ }
+
+ /// Draws the clipped contents of the given [`Frame`] with origin at the given [`Point`].
+ pub fn clip(&mut self, frame: Frame, at: Point) {
+ let size = frame.size();
+ let primitives = frame.into_primitives();
+ let translation = Vector::new(at.x, at.y);
+
+ let (text, meshes) = primitives
+ .into_iter()
+ .partition(|primitive| matches!(primitive, Primitive::Text { .. }));
+
+ self.primitives.push(Primitive::Group {
+ primitives: vec![
+ Primitive::Translate {
+ translation,
+ content: Box::new(Primitive::Group { primitives: meshes }),
+ },
+ Primitive::Translate {
+ translation,
+ content: Box::new(Primitive::Clip {
+ bounds: Rectangle::with_size(size),
+ content: Box::new(Primitive::Group {
+ primitives: text,
+ }),
+ }),
+ },
+ ],
+ });
+ }
+
+ /// Applies a translation to the current transform of the [`Frame`].
+ #[inline]
+ pub fn translate(&mut self, translation: Vector) {
+ self.transforms.current.raw = self
+ .transforms
+ .current
+ .raw
+ .pre_translate(lyon::math::Vector::new(
+ translation.x,
+ translation.y,
+ ));
+ self.transforms.current.is_identity = false;
+ }
+
+ /// Applies a rotation in radians to the current transform of the [`Frame`].
+ #[inline]
+ pub fn rotate(&mut self, angle: f32) {
+ self.transforms.current.raw = self
+ .transforms
+ .current
+ .raw
+ .pre_rotate(lyon::math::Angle::radians(angle));
+ self.transforms.current.is_identity = false;
+ }
+
+ /// Applies a scaling to the current transform of the [`Frame`].
+ #[inline]
+ pub fn scale(&mut self, scale: f32) {
+ self.transforms.current.raw =
+ self.transforms.current.raw.pre_scale(scale, scale);
+ self.transforms.current.is_identity = false;
+ }
+
+ /// Produces the [`Primitive`] representing everything drawn on the [`Frame`].
+ pub fn into_primitive(self) -> Primitive {
+ Primitive::Group {
+ primitives: self.into_primitives(),
+ }
+ }
+
+ fn into_primitives(mut self) -> Vec<Primitive> {
+ for buffer in self.buffers.stack {
+ match buffer {
+ Buffer::Solid(buffer) => {
+ if !buffer.indices.is_empty() {
+ self.primitives.push(Primitive::SolidMesh {
+ buffers: primitive::Mesh2D {
+ vertices: buffer.vertices,
+ indices: buffer.indices,
+ },
+ size: self.size,
+ })
+ }
+ }
+ Buffer::Gradient(buffer, gradient) => {
+ if !buffer.indices.is_empty() {
+ self.primitives.push(Primitive::GradientMesh {
+ buffers: primitive::Mesh2D {
+ vertices: buffer.vertices,
+ indices: buffer.indices,
+ },
+ size: self.size,
+ gradient,
+ })
+ }
+ }
+ }
+ }
+
+ self.primitives
+ }
+}
+
+struct Vertex2DBuilder;
+
+impl tessellation::FillVertexConstructor<primitive::Vertex2D>
+ for Vertex2DBuilder
+{
+ fn new_vertex(
+ &mut self,
+ vertex: tessellation::FillVertex<'_>,
+ ) -> primitive::Vertex2D {
+ let position = vertex.position();
+
+ primitive::Vertex2D {
+ position: [position.x, position.y],
+ }
+ }
+}
+
+impl tessellation::StrokeVertexConstructor<primitive::Vertex2D>
+ for Vertex2DBuilder
+{
+ fn new_vertex(
+ &mut self,
+ vertex: tessellation::StrokeVertex<'_, '_>,
+ ) -> primitive::Vertex2D {
+ let position = vertex.position();
+
+ primitive::Vertex2D {
+ position: [position.x, position.y],
+ }
+ }
+}
+
+struct TriangleVertex2DBuilder([f32; 4]);
+
+impl tessellation::FillVertexConstructor<primitive::ColoredVertex2D>
+ for TriangleVertex2DBuilder
+{
+ fn new_vertex(
+ &mut self,
+ vertex: tessellation::FillVertex<'_>,
+ ) -> primitive::ColoredVertex2D {
+ let position = vertex.position();
+
+ primitive::ColoredVertex2D {
+ position: [position.x, position.y],
+ color: self.0,
+ }
+ }
+}
+
+impl tessellation::StrokeVertexConstructor<primitive::ColoredVertex2D>
+ for TriangleVertex2DBuilder
+{
+ fn new_vertex(
+ &mut self,
+ vertex: tessellation::StrokeVertex<'_, '_>,
+ ) -> primitive::ColoredVertex2D {
+ let position = vertex.position();
+
+ primitive::ColoredVertex2D {
+ 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::{
+ walk_along_path, RepeatedPattern, WalkerEvent,
+ };
+ 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(0.01),
+ 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/wgpu/src/image.rs b/wgpu/src/image.rs
index 9f56c188..263bcfa2 100644
--- a/wgpu/src/image.rs
+++ b/wgpu/src/image.rs
@@ -1,16 +1,17 @@
mod atlas;
#[cfg(feature = "image")]
-use iced_graphics::image::raster;
+mod raster;
#[cfg(feature = "svg")]
-use iced_graphics::image::vector;
+mod vector;
-use crate::Transformation;
use atlas::Atlas;
-use iced_graphics::layer;
-use iced_native::{Rectangle, Size};
+use crate::core::{Rectangle, Size};
+use crate::graphics::Transformation;
+use crate::layer;
+use crate::Buffer;
use std::cell::RefCell;
use std::mem;
@@ -18,10 +19,10 @@ use std::mem;
use bytemuck::{Pod, Zeroable};
#[cfg(feature = "image")]
-use iced_native::image;
+use crate::core::image;
#[cfg(feature = "svg")]
-use iced_native::svg;
+use crate::core::svg;
#[cfg(feature = "tracing")]
use tracing::info_span;
@@ -29,20 +30,112 @@ use tracing::info_span;
#[derive(Debug)]
pub struct Pipeline {
#[cfg(feature = "image")]
- raster_cache: RefCell<raster::Cache<Atlas>>,
+ raster_cache: RefCell<raster::Cache>,
#[cfg(feature = "svg")]
- vector_cache: RefCell<vector::Cache<Atlas>>,
+ vector_cache: RefCell<vector::Cache>,
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 +179,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 +289,6 @@ impl Pipeline {
usage: wgpu::BufferUsages::INDEX,
});
- let instances = device.create_buffer(&wgpu::BufferDescriptor {
- label: Some("iced_wgpu::image instance buffer"),
- size: mem::size_of::<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 +310,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 +340,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();
@@ -308,8 +368,10 @@ impl Pipeline {
#[cfg(feature = "image")]
layer::Image::Raster { handle, bounds } => {
if let Some(atlas_entry) = raster_cache.upload(
+ device,
+ queue,
+ encoder,
handle,
- &mut (device, encoder),
&mut self.texture_atlas,
) {
add_instances(
@@ -332,11 +394,13 @@ impl Pipeline {
let size = [bounds.width, bounds.height];
if let Some(atlas_entry) = vector_cache.upload(
+ device,
+ queue,
+ encoder,
handle,
*color,
size,
_scale,
- &mut (device, encoder),
&mut self.texture_atlas,
) {
add_instances(
@@ -376,68 +440,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,
- );
+ let layer = &mut self.layers[self.prepare_layer];
+ layer.prepare(device, queue, instances, transformation);
- instances_buffer.copy_from_slice(bytemuck::cast_slice(
- &instances[i..i + amount],
- ));
-
- 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 +470,25 @@ 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(
- &mut self,
- device: &wgpu::Device,
- encoder: &mut wgpu::CommandEncoder,
- ) {
+ pub fn end_frame(&mut self) {
#[cfg(feature = "image")]
- self.raster_cache
- .borrow_mut()
- .trim(&mut self.texture_atlas, &mut (device, encoder));
+ self.raster_cache.borrow_mut().trim(&mut self.texture_atlas);
#[cfg(feature = "svg")]
- self.vector_cache
- .borrow_mut()
- .trim(&mut self.texture_atlas, &mut (device, encoder));
+ self.vector_cache.borrow_mut().trim(&mut self.texture_atlas);
+
+ self.prepare_layer = 0;
}
}
@@ -507,7 +526,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 a0fdf146..366fe623 100644
--- a/wgpu/src/image/atlas.rs
+++ b/wgpu/src/image/atlas.rs
@@ -12,8 +12,7 @@ use allocator::Allocator;
pub const SIZE: u32 = 2048;
-use iced_graphics::image;
-use iced_graphics::Size;
+use crate::core::Size;
#[derive(Debug)]
pub struct Atlas {
@@ -37,10 +36,10 @@ impl Atlas {
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
- view_formats: &[],
usage: wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::COPY_SRC
| wgpu::TextureUsages::TEXTURE_BINDING,
+ view_formats: &[],
});
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor {
@@ -63,6 +62,97 @@ impl Atlas {
self.layers.len()
}
+ pub fn upload(
+ &mut self,
+ device: &wgpu::Device,
+ queue: &wgpu::Queue,
+ encoder: &mut wgpu::CommandEncoder,
+ width: u32,
+ height: u32,
+ data: &[u8],
+ ) -> Option<Entry> {
+ 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);
+
+ entry
+ };
+
+ log::info!("Allocated atlas entry: {:?}", entry);
+
+ // It is a webgpu requirement that:
+ // BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0
+ // So we calculate padded_width by rounding width up to the next
+ // multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT.
+ let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
+ let padding = (align - (4 * width) % align) % align;
+ let padded_width = (4 * width + padding) as usize;
+ let padded_data_size = padded_width * height as usize;
+
+ let mut padded_data = vec![0; padded_data_size];
+
+ for row in 0..height as usize {
+ let offset = row * padded_width;
+
+ padded_data[offset..offset + 4 * width as usize].copy_from_slice(
+ &data[row * 4 * width as usize..(row + 1) * 4 * width as usize],
+ )
+ }
+
+ match &entry {
+ Entry::Contiguous(allocation) => {
+ self.upload_allocation(
+ &padded_data,
+ width,
+ height,
+ padding,
+ 0,
+ allocation,
+ queue,
+ );
+ }
+ Entry::Fragmented { fragments, .. } => {
+ for fragment in fragments {
+ let (x, y) = fragment.position;
+ let offset = (y * padded_width as u32 + 4 * x) as usize;
+
+ self.upload_allocation(
+ &padded_data,
+ width,
+ height,
+ padding,
+ offset,
+ &fragment.allocation,
+ queue,
+ );
+ }
+ }
+ }
+
+ log::info!("Current atlas: {:?}", self);
+
+ Some(entry)
+ }
+
+ pub fn remove(&mut self, entry: &Entry) {
+ log::info!("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<Entry> {
// Allocate one layer if texture fits perfectly
if width == SIZE && height == SIZE {
@@ -184,13 +274,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();
@@ -202,15 +292,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: Some(4 * image_width + padding),
- rows_per_image: Some(image_height),
- },
- },
+ queue.write_texture(
wgpu::ImageCopyTexture {
texture: &self.texture,
mip_level: 0,
@@ -221,6 +303,12 @@ impl Atlas {
},
aspect: wgpu::TextureAspect::default(),
},
+ data,
+ wgpu::ImageDataLayout {
+ offset: offset as u64,
+ bytes_per_row: Some(4 * image_width + padding),
+ rows_per_image: Some(image_height),
+ },
extent,
);
}
@@ -246,10 +334,10 @@ impl Atlas {
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
- view_formats: &[],
usage: wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::COPY_SRC
| wgpu::TextureUsages::TEXTURE_BINDING,
+ view_formats: &[],
});
let amount_to_copy = self.layers.len() - amount;
@@ -298,100 +386,3 @@ impl Atlas {
});
}
}
-
-impl image::Storage for Atlas {
- type Entry = Entry;
- type State<'a> = (&'a wgpu::Device, &'a mut wgpu::CommandEncoder);
-
- fn upload(
- &mut self,
- width: u32,
- height: u32,
- data: &[u8],
- (device, 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)?;
-
- // We grow the internal texture after allocating if necessary
- let new_layers = self.layers.len() - current_size;
- self.grow(new_layers, device, encoder);
-
- entry
- };
-
- log::info!("Allocated atlas entry: {:?}", entry);
-
- // It is a webgpu requirement that:
- // BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0
- // So we calculate padded_width by rounding width up to the next
- // multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT.
- let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
- let padding = (align - (4 * width) % align) % align;
- let padded_width = (4 * width + padding) as usize;
- let padded_data_size = padded_width * height as usize;
-
- let mut padded_data = vec![0; padded_data_size];
-
- for row in 0..height as usize {
- let offset = row * padded_width;
-
- padded_data[offset..offset + 4 * width as usize].copy_from_slice(
- &data[row * 4 * width as usize..(row + 1) * 4 * width as usize],
- )
- }
-
- 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,
- );
- }
- Entry::Fragmented { fragments, .. } => {
- for fragment in fragments {
- let (x, y) = fragment.position;
- let offset = (y * padded_width as u32 + 4 * x) as usize;
-
- self.upload_allocation(
- &buffer,
- width,
- height,
- padding,
- offset,
- &fragment.allocation,
- encoder,
- );
- }
- }
- }
-
- log::info!("Current atlas: {:?}", self);
-
- Some(entry)
- }
-
- fn remove(&mut self, entry: &Entry, _: &mut Self::State<'_>) {
- log::info!("Removing atlas entry: {:?}", entry);
-
- match entry {
- Entry::Contiguous(allocation) => {
- self.deallocate(allocation);
- }
- Entry::Fragmented { fragments, .. } => {
- for fragment in fragments {
- self.deallocate(&fragment.allocation);
- }
- }
- }
- }
-}
diff --git a/wgpu/src/image/atlas/allocation.rs b/wgpu/src/image/atlas/allocation.rs
index 43aba875..11289771 100644
--- a/wgpu/src/image/atlas/allocation.rs
+++ b/wgpu/src/image/atlas/allocation.rs
@@ -1,7 +1,6 @@
+use crate::core::Size;
use crate::image::atlas::{self, allocator};
-use iced_graphics::Size;
-
#[derive(Debug)]
pub enum Allocation {
Partial {
diff --git a/wgpu/src/image/atlas/allocator.rs b/wgpu/src/image/atlas/allocator.rs
index 03effdcb..204a5c26 100644
--- a/wgpu/src/image/atlas/allocator.rs
+++ b/wgpu/src/image/atlas/allocator.rs
@@ -46,10 +46,10 @@ impl Region {
(rectangle.min.x as u32, rectangle.min.y as u32)
}
- pub fn size(&self) -> iced_graphics::Size<u32> {
+ pub fn size(&self) -> crate::core::Size<u32> {
let size = self.allocation.rectangle.size();
- iced_graphics::Size::new(size.width as u32, size.height as u32)
+ crate::core::Size::new(size.width as u32, size.height as u32)
}
}
diff --git a/wgpu/src/image/atlas/entry.rs b/wgpu/src/image/atlas/entry.rs
index 69c05a50..7e4c92a2 100644
--- a/wgpu/src/image/atlas/entry.rs
+++ b/wgpu/src/image/atlas/entry.rs
@@ -1,8 +1,6 @@
+use crate::core::Size;
use crate::image::atlas;
-use iced_graphics::image;
-use iced_graphics::Size;
-
#[derive(Debug)]
pub enum Entry {
Contiguous(atlas::Allocation),
@@ -12,8 +10,9 @@ pub enum Entry {
},
}
-impl image::storage::Entry for Entry {
- fn size(&self) -> Size<u32> {
+impl Entry {
+ #[cfg(feature = "image")]
+ pub fn size(&self) -> Size<u32> {
match self {
Entry::Contiguous(allocation) => allocation.size(),
Entry::Fragmented { size, .. } => *size,
diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs
new file mode 100644
index 00000000..9b38dce4
--- /dev/null
+++ b/wgpu/src/image/raster.rs
@@ -0,0 +1,121 @@
+use crate::core::image;
+use crate::core::Size;
+use crate::graphics;
+use crate::graphics::image::image_rs;
+use crate::image::atlas::{self, Atlas};
+
+use std::collections::{HashMap, HashSet};
+
+/// Entry in cache corresponding to an image handle
+#[derive(Debug)]
+pub enum Memory {
+ /// Image data on host
+ Host(image_rs::ImageBuffer<image_rs::Rgba<u8>, Vec<u8>>),
+ /// Storage entry
+ Device(atlas::Entry),
+ /// Image not found
+ NotFound,
+ /// Invalid image data
+ Invalid,
+}
+
+impl Memory {
+ /// Width and height of image
+ pub fn dimensions(&self) -> Size<u32> {
+ match self {
+ Memory::Host(image) => {
+ let (width, height) = image.dimensions();
+
+ Size::new(width, height)
+ }
+ Memory::Device(entry) => entry.size(),
+ Memory::NotFound => Size::new(1, 1),
+ Memory::Invalid => Size::new(1, 1),
+ }
+ }
+}
+
+/// Caches image raster data
+#[derive(Debug, Default)]
+pub struct Cache {
+ map: HashMap<u64, Memory>,
+ hits: HashSet<u64>,
+}
+
+impl Cache {
+ /// Load image
+ pub fn load(&mut self, handle: &image::Handle) -> &mut Memory {
+ if self.contains(handle) {
+ return self.get(handle).unwrap();
+ }
+
+ let memory = match graphics::image::load(handle) {
+ Ok(image) => Memory::Host(image.to_rgba8()),
+ Err(image_rs::error::ImageError::IoError(_)) => Memory::NotFound,
+ Err(_) => Memory::Invalid,
+ };
+
+ self.insert(handle, memory);
+ self.get(handle).unwrap()
+ }
+
+ /// Load image and upload raster data
+ pub fn upload(
+ &mut self,
+ device: &wgpu::Device,
+ queue: &wgpu::Queue,
+ encoder: &mut wgpu::CommandEncoder,
+ handle: &image::Handle,
+ atlas: &mut Atlas,
+ ) -> Option<&atlas::Entry> {
+ let memory = self.load(handle);
+
+ if let Memory::Host(image) = memory {
+ let (width, height) = image.dimensions();
+
+ let entry =
+ atlas.upload(device, queue, encoder, width, height, image)?;
+
+ *memory = Memory::Device(entry);
+ }
+
+ if let Memory::Device(allocation) = memory {
+ Some(allocation)
+ } else {
+ None
+ }
+ }
+
+ /// Trim cache misses from cache
+ pub fn trim(&mut self, atlas: &mut Atlas) {
+ let hits = &self.hits;
+
+ self.map.retain(|k, memory| {
+ let retain = hits.contains(k);
+
+ if !retain {
+ if let Memory::Device(entry) = memory {
+ atlas.remove(entry);
+ }
+ }
+
+ retain
+ });
+
+ self.hits.clear();
+ }
+
+ fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory> {
+ let _ = self.hits.insert(handle.id());
+
+ self.map.get_mut(&handle.id())
+ }
+
+ fn insert(&mut self, handle: &image::Handle, memory: Memory) {
+ let _ = self.map.insert(handle.id(), memory);
+ }
+
+ fn contains(&self, handle: &image::Handle) -> bool {
+ self.map.contains_key(&handle.id())
+ }
+}
diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs
new file mode 100644
index 00000000..58bdf64a
--- /dev/null
+++ b/wgpu/src/image/vector.rs
@@ -0,0 +1,183 @@
+use crate::core::svg;
+use crate::core::{Color, Size};
+use crate::image::atlas::{self, Atlas};
+
+use resvg::tiny_skia;
+use resvg::usvg;
+use std::collections::{HashMap, HashSet};
+use std::fs;
+
+/// 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<u32> {
+ 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: HashMap<u64, Svg>,
+ rasterized: HashMap<(u64, u32, u32, ColorFilter), atlas::Entry>,
+ svg_hits: HashSet<u64>,
+ rasterized_hits: HashSet<(u64, u32, u32, ColorFilter)>,
+}
+
+type ColorFilter = Option<[u8; 4]>;
+
+impl Cache {
+ /// Load svg
+ pub fn load(&mut self, handle: &svg::Handle) -> &Svg {
+ use usvg::TreeParsing;
+
+ if self.svgs.contains_key(&handle.id()) {
+ return self.svgs.get(&handle.id()).unwrap();
+ }
+
+ let svg = match handle.data() {
+ svg::Data::Path(path) => {
+ let tree = fs::read_to_string(path).ok().and_then(|contents| {
+ usvg::Tree::from_str(&contents, &usvg::Options::default())
+ .ok()
+ });
+
+ tree.map(Svg::Loaded).unwrap_or(Svg::NotFound)
+ }
+ svg::Data::Bytes(bytes) => {
+ match usvg::Tree::from_data(bytes, &usvg::Options::default()) {
+ Ok(tree) => Svg::Loaded(tree),
+ Err(_) => Svg::NotFound,
+ }
+ }
+ };
+
+ 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,
+ queue: &wgpu::Queue,
+ encoder: &mut wgpu::CommandEncoder,
+ handle: &svg::Handle,
+ color: Option<Color>,
+ [width, height]: [f32; 2],
+ scale: f32,
+ atlas: &mut Atlas,
+ ) -> Option<&atlas::Entry> {
+ let id = handle.id();
+
+ let (width, height) = (
+ (scale * width).ceil() as u32,
+ (scale * 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)?;
+
+ resvg::render(
+ tree,
+ if width > height {
+ resvg::FitTo::Width(width)
+ } else {
+ resvg::FitTo::Height(height)
+ },
+ tiny_skia::Transform::default(),
+ img.as_mut(),
+ )?;
+
+ 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, queue, encoder, width, height, &rgba)?;
+
+ log::debug!("allocating {} {}x{}", id, width, height);
+
+ let _ = self.svg_hits.insert(id);
+ let _ = self.rasterized_hits.insert(key);
+ let _ = self.rasterized.insert(key, allocation);
+
+ self.rasterized.get(&key)
+ }
+ Svg::NotFound => None,
+ }
+ }
+
+ /// Load svg and upload raster data
+ pub fn trim(&mut self, atlas: &mut Atlas) {
+ 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();
+ }
+}
+
+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/wgpu/src/layer.rs b/wgpu/src/layer.rs
new file mode 100644
index 00000000..8af72b9d
--- /dev/null
+++ b/wgpu/src/layer.rs
@@ -0,0 +1,281 @@
+//! Organize rendering primitives into a flattened list of layers.
+mod image;
+mod quad;
+mod text;
+
+pub mod mesh;
+
+pub use image::Image;
+pub use mesh::Mesh;
+pub use quad::Quad;
+pub use text::Text;
+
+use crate::core;
+use crate::core::alignment;
+use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector};
+use crate::graphics::{Primitive, Viewport};
+
+/// A group of primitives that should be clipped together.
+#[derive(Debug)]
+pub struct Layer<'a> {
+ /// The clipping bounds of the [`Layer`].
+ pub bounds: Rectangle,
+
+ /// The quads of the [`Layer`].
+ pub quads: Vec<Quad>,
+
+ /// The triangle meshes of the [`Layer`].
+ pub meshes: Vec<Mesh<'a>>,
+
+ /// The text of the [`Layer`].
+ pub text: Vec<Text<'a>>,
+
+ /// The images of the [`Layer`].
+ pub images: Vec<Image>,
+}
+
+impl<'a> Layer<'a> {
+ /// Creates a new [`Layer`] with the given clipping bounds.
+ pub fn new(bounds: Rectangle) -> Self {
+ Self {
+ bounds,
+ quads: Vec::new(),
+ meshes: Vec::new(),
+ text: Vec::new(),
+ images: Vec::new(),
+ }
+ }
+
+ /// Creates a new [`Layer`] for the provided overlay text.
+ ///
+ /// This can be useful for displaying debug information.
+ pub fn overlay(lines: &'a [impl AsRef<str>], viewport: &Viewport) -> Self {
+ let mut overlay =
+ Layer::new(Rectangle::with_size(viewport.logical_size()));
+
+ for (i, line) in lines.iter().enumerate() {
+ let text = Text {
+ content: line.as_ref(),
+ bounds: Rectangle::new(
+ Point::new(11.0, 11.0 + 25.0 * i as f32),
+ Size::INFINITY,
+ ),
+ color: Color::new(0.9, 0.9, 0.9, 1.0),
+ size: 20.0,
+ line_height: core::text::LineHeight::default(),
+ font: Font::MONOSPACE,
+ horizontal_alignment: alignment::Horizontal::Left,
+ vertical_alignment: alignment::Vertical::Top,
+ shaping: core::text::Shaping::Basic,
+ };
+
+ overlay.text.push(text);
+
+ overlay.text.push(Text {
+ bounds: text.bounds + Vector::new(-1.0, -1.0),
+ color: Color::BLACK,
+ ..text
+ });
+ }
+
+ overlay
+ }
+
+ /// Distributes the given [`Primitive`] and generates a list of layers based
+ /// on its contents.
+ pub fn generate(
+ primitives: &'a [Primitive],
+ viewport: &Viewport,
+ ) -> Vec<Self> {
+ let first_layer =
+ Layer::new(Rectangle::with_size(viewport.logical_size()));
+
+ let mut layers = vec![first_layer];
+
+ for primitive in primitives {
+ Self::process_primitive(
+ &mut layers,
+ Vector::new(0.0, 0.0),
+ primitive,
+ 0,
+ );
+ }
+
+ layers
+ }
+
+ fn process_primitive(
+ layers: &mut Vec<Self>,
+ translation: Vector,
+ primitive: &'a Primitive,
+ current_layer: usize,
+ ) {
+ match primitive {
+ Primitive::Text {
+ content,
+ bounds,
+ size,
+ line_height,
+ color,
+ font,
+ horizontal_alignment,
+ vertical_alignment,
+ shaping,
+ } => {
+ let layer = &mut layers[current_layer];
+
+ layer.text.push(Text {
+ content,
+ bounds: *bounds + translation,
+ size: *size,
+ line_height: *line_height,
+ color: *color,
+ font: *font,
+ horizontal_alignment: *horizontal_alignment,
+ vertical_alignment: *vertical_alignment,
+ shaping: *shaping,
+ });
+ }
+ Primitive::Quad {
+ bounds,
+ background,
+ border_radius,
+ border_width,
+ border_color,
+ } => {
+ let layer = &mut layers[current_layer];
+
+ // TODO: Move some of these computations to the GPU (?)
+ layer.quads.push(Quad {
+ position: [
+ bounds.x + translation.x,
+ bounds.y + translation.y,
+ ],
+ size: [bounds.width, bounds.height],
+ color: match background {
+ Background::Color(color) => color.into_linear(),
+ },
+ border_radius: *border_radius,
+ border_width: *border_width,
+ border_color: border_color.into_linear(),
+ });
+ }
+ Primitive::Image { handle, bounds } => {
+ let layer = &mut layers[current_layer];
+
+ layer.images.push(Image::Raster {
+ handle: handle.clone(),
+ bounds: *bounds + translation,
+ });
+ }
+ Primitive::Svg {
+ handle,
+ color,
+ bounds,
+ } => {
+ let layer = &mut layers[current_layer];
+
+ layer.images.push(Image::Vector {
+ handle: handle.clone(),
+ color: *color,
+ bounds: *bounds + translation,
+ });
+ }
+ Primitive::SolidMesh { buffers, size } => {
+ let layer = &mut layers[current_layer];
+
+ let bounds = Rectangle::new(
+ Point::new(translation.x, translation.y),
+ *size,
+ );
+
+ // Only draw visible content
+ if let Some(clip_bounds) = layer.bounds.intersection(&bounds) {
+ layer.meshes.push(Mesh::Solid {
+ origin: Point::new(translation.x, translation.y),
+ buffers,
+ clip_bounds,
+ });
+ }
+ }
+ Primitive::GradientMesh {
+ buffers,
+ size,
+ gradient,
+ } => {
+ let layer = &mut layers[current_layer];
+
+ let bounds = Rectangle::new(
+ Point::new(translation.x, translation.y),
+ *size,
+ );
+
+ // Only draw visible content
+ if let Some(clip_bounds) = layer.bounds.intersection(&bounds) {
+ layer.meshes.push(Mesh::Gradient {
+ origin: Point::new(translation.x, translation.y),
+ buffers,
+ clip_bounds,
+ gradient,
+ });
+ }
+ }
+ Primitive::Group { primitives } => {
+ // TODO: Inspect a bit and regroup (?)
+ for primitive in primitives {
+ Self::process_primitive(
+ layers,
+ translation,
+ primitive,
+ current_layer,
+ )
+ }
+ }
+ Primitive::Clip { bounds, content } => {
+ let layer = &mut layers[current_layer];
+ let translated_bounds = *bounds + translation;
+
+ // Only draw visible content
+ if let Some(clip_bounds) =
+ layer.bounds.intersection(&translated_bounds)
+ {
+ let clip_layer = Layer::new(clip_bounds);
+ layers.push(clip_layer);
+
+ Self::process_primitive(
+ layers,
+ translation,
+ content,
+ layers.len() - 1,
+ );
+ }
+ }
+ Primitive::Translate {
+ translation: new_translation,
+ content,
+ } => {
+ Self::process_primitive(
+ layers,
+ translation + *new_translation,
+ content,
+ current_layer,
+ );
+ }
+ Primitive::Cache { content } => {
+ Self::process_primitive(
+ layers,
+ translation,
+ content,
+ current_layer,
+ );
+ }
+ _ => {
+ // Not supported!
+ log::warn!(
+ "Unsupported primitive in `iced_wgpu`: {:?}",
+ primitive
+ );
+ }
+ }
+ }
+}
diff --git a/wgpu/src/layer/image.rs b/wgpu/src/layer/image.rs
new file mode 100644
index 00000000..0de589f8
--- /dev/null
+++ b/wgpu/src/layer/image.rs
@@ -0,0 +1,27 @@
+use crate::core::image;
+use crate::core::svg;
+use crate::core::{Color, Rectangle};
+
+/// A raster or vector image.
+#[derive(Debug, Clone)]
+pub enum Image {
+ /// A raster image.
+ Raster {
+ /// The handle of a raster image.
+ handle: image::Handle,
+
+ /// The bounds of the image.
+ bounds: Rectangle,
+ },
+ /// A vector image.
+ Vector {
+ /// The handle of a vector image.
+ handle: svg::Handle,
+
+ /// The [`Color`] filter
+ color: Option<Color>,
+
+ /// The bounds of the image.
+ bounds: Rectangle,
+ },
+}
diff --git a/wgpu/src/layer/mesh.rs b/wgpu/src/layer/mesh.rs
new file mode 100644
index 00000000..9dd14391
--- /dev/null
+++ b/wgpu/src/layer/mesh.rs
@@ -0,0 +1,93 @@
+//! A collection of triangle primitives.
+use crate::core::{Gradient, Point, Rectangle};
+use crate::graphics::primitive;
+
+/// A mesh of triangles.
+#[derive(Debug, Clone, Copy)]
+pub enum Mesh<'a> {
+ /// A mesh of triangles with a solid color.
+ Solid {
+ /// The origin of the vertices of the [`Mesh`].
+ origin: Point,
+
+ /// The vertex and index buffers of the [`Mesh`].
+ buffers: &'a primitive::Mesh2D<primitive::ColoredVertex2D>,
+
+ /// The clipping bounds of the [`Mesh`].
+ clip_bounds: Rectangle<f32>,
+ },
+ /// A mesh of triangles with a gradient color.
+ Gradient {
+ /// The origin of the vertices of the [`Mesh`].
+ origin: Point,
+
+ /// The vertex and index buffers of the [`Mesh`].
+ buffers: &'a primitive::Mesh2D<primitive::Vertex2D>,
+
+ /// The clipping bounds of the [`Mesh`].
+ clip_bounds: Rectangle<f32>,
+
+ /// The gradient to apply to the [`Mesh`].
+ gradient: &'a Gradient,
+ },
+}
+
+impl Mesh<'_> {
+ /// Returns the origin of the [`Mesh`].
+ pub fn origin(&self) -> Point {
+ match self {
+ Self::Solid { origin, .. } | Self::Gradient { origin, .. } => {
+ *origin
+ }
+ }
+ }
+
+ /// Returns the indices of the [`Mesh`].
+ pub fn indices(&self) -> &[u32] {
+ match self {
+ Self::Solid { buffers, .. } => &buffers.indices,
+ Self::Gradient { buffers, .. } => &buffers.indices,
+ }
+ }
+
+ /// Returns the clip bounds of the [`Mesh`].
+ pub fn clip_bounds(&self) -> Rectangle<f32> {
+ match self {
+ Self::Solid { clip_bounds, .. }
+ | Self::Gradient { clip_bounds, .. } => *clip_bounds,
+ }
+ }
+}
+
+/// The result of counting the attributes of a set of meshes.
+#[derive(Debug, Clone, Copy, Default)]
+pub struct AttributeCount {
+ /// The total amount of solid vertices.
+ pub solid_vertices: usize,
+
+ /// The total amount of gradient vertices.
+ pub gradient_vertices: usize,
+
+ /// The total amount of indices.
+ pub indices: usize,
+}
+
+/// Returns the number of total vertices & total indices of all [`Mesh`]es.
+pub fn attribute_count_of<'a>(meshes: &'a [Mesh<'a>]) -> AttributeCount {
+ meshes
+ .iter()
+ .fold(AttributeCount::default(), |mut count, mesh| {
+ match mesh {
+ Mesh::Solid { buffers, .. } => {
+ count.solid_vertices += buffers.vertices.len();
+ count.indices += buffers.indices.len();
+ }
+ Mesh::Gradient { buffers, .. } => {
+ count.gradient_vertices += buffers.vertices.len();
+ count.indices += buffers.indices.len();
+ }
+ }
+
+ count
+ })
+}
diff --git a/wgpu/src/layer/quad.rs b/wgpu/src/layer/quad.rs
new file mode 100644
index 00000000..0d8bde9d
--- /dev/null
+++ b/wgpu/src/layer/quad.rs
@@ -0,0 +1,30 @@
+/// A colored rectangle with a border.
+///
+/// This type can be directly uploaded to GPU memory.
+#[derive(Debug, Clone, Copy)]
+#[repr(C)]
+pub struct Quad {
+ /// The position of the [`Quad`].
+ pub position: [f32; 2],
+
+ /// The size of the [`Quad`].
+ pub size: [f32; 2],
+
+ /// The color of the [`Quad`], in __linear RGB__.
+ pub color: [f32; 4],
+
+ /// The border color of the [`Quad`], in __linear RGB__.
+ pub border_color: [f32; 4],
+
+ /// The border radius of the [`Quad`].
+ pub border_radius: [f32; 4],
+
+ /// The border width of the [`Quad`].
+ pub border_width: f32,
+}
+
+#[allow(unsafe_code)]
+unsafe impl bytemuck::Zeroable for Quad {}
+
+#[allow(unsafe_code)]
+unsafe impl bytemuck::Pod for Quad {}
diff --git a/wgpu/src/layer/text.rs b/wgpu/src/layer/text.rs
new file mode 100644
index 00000000..ba1bdca8
--- /dev/null
+++ b/wgpu/src/layer/text.rs
@@ -0,0 +1,34 @@
+use crate::core::alignment;
+use crate::core::text;
+use crate::core::{Color, Font, Rectangle};
+
+/// A paragraph of text.
+#[derive(Debug, Clone, Copy)]
+pub struct Text<'a> {
+ /// The content of the [`Text`].
+ pub content: &'a str,
+
+ /// The layout bounds of the [`Text`].
+ pub bounds: Rectangle,
+
+ /// The color of the [`Text`], in __linear RGB_.
+ pub color: Color,
+
+ /// The size of the [`Text`] in logical pixels.
+ pub size: f32,
+
+ /// The line height of the [`Text`].
+ pub line_height: text::LineHeight,
+
+ /// The font of the [`Text`].
+ pub font: Font,
+
+ /// The horizontal alignment of the [`Text`].
+ pub horizontal_alignment: alignment::Horizontal,
+
+ /// The vertical alignment of the [`Text`].
+ pub vertical_alignment: alignment::Vertical,
+
+ /// The shaping strategy of the text.
+ pub shaping: text::Shaping,
+}
diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs
index 969e3199..4a92c345 100644
--- a/wgpu/src/lib.rs
+++ b/wgpu/src/lib.rs
@@ -37,24 +37,29 @@
#![forbid(rust_2018_idioms)]
#![allow(clippy::inherent_to_string, clippy::type_complexity)]
#![cfg_attr(docsrs, feature(doc_cfg))]
-
+pub mod layer;
pub mod settings;
pub mod window;
+#[cfg(feature = "geometry")]
+pub mod geometry;
+
mod backend;
mod buffer;
mod quad;
mod text;
mod triangle;
-pub use iced_graphics::{Antialiasing, Color, Error, Primitive, Viewport};
-pub use iced_native::Theme;
+pub use iced_graphics as graphics;
+pub use iced_graphics::core;
+
pub use wgpu;
pub use backend::Backend;
+pub use layer::Layer;
pub use settings::Settings;
-pub(crate) use iced_graphics::Transformation;
+use buffer::Buffer;
#[cfg(any(feature = "image", feature = "svg"))]
mod image;
@@ -63,5 +68,4 @@ mod image;
///
/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
/// [`iced`]: https://github.com/iced-rs/iced
-pub type Renderer<Theme = iced_native::Theme> =
- iced_graphics::Renderer<Backend, Theme>;
+pub type Renderer<Theme> = iced_graphics::Renderer<Backend, Theme>;
diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs
index 1343181e..8fa7359e 100644
--- a/wgpu/src/quad.rs
+++ b/wgpu/src/quad.rs
@@ -1,6 +1,7 @@
-use crate::Transformation;
-use iced_graphics::layer;
-use iced_native::Rectangle;
+use crate::core::Rectangle;
+use crate::graphics::Transformation;
+use crate::layer;
+use crate::Buffer;
use bytemuck::{Pod, Zeroable};
use std::mem;
@@ -12,11 +13,11 @@ use tracing::info_span;
#[derive(Debug)]
pub struct Pipeline {
pipeline: wgpu::RenderPipeline,
- constants: wgpu::BindGroup,
- constants_buffer: wgpu::Buffer,
+ constant_layout: wgpu::BindGroupLayout,
vertices: wgpu::Buffer,
indices: wgpu::Buffer,
- instances: wgpu::Buffer,
+ layers: Vec<Layer>,
+ prepare_layer: usize,
}
impl Pipeline {
@@ -38,22 +39,6 @@ impl Pipeline {
}],
});
- let constants_buffer = device.create_buffer(&wgpu::BufferDescriptor {
- label: Some("iced_wgpu::quad uniforms buffer"),
- size: mem::size_of::<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 +133,146 @@ impl Pipeline {
usage: wgpu::BufferUsages::INDEX,
});
- let instances = device.create_buffer(&wgpu::BufferDescriptor {
- label: Some("iced_wgpu::quad instance buffer"),
- size: mem::size_of::<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 +298,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..266a2c87 100644
--- a/wgpu/src/settings.rs
+++ b/wgpu/src/settings.rs
@@ -1,12 +1,11 @@
//! Configure a renderer.
-use std::fmt;
-
-pub use crate::Antialiasing;
+use crate::core::Font;
+use crate::graphics::Antialiasing;
/// 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 +15,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.
///
@@ -69,7 +46,7 @@ impl Settings {
/// - `primary`
pub fn from_env() -> Self {
Settings {
- internal_backend: backend_from_env()
+ internal_backend: wgpu::util::backend_bits_from_env()
.unwrap_or(wgpu::Backends::all()),
..Self::default()
}
@@ -81,25 +58,9 @@ 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::default(),
+ default_text_size: 16.0,
antialiasing: None,
}
}
}
-
-fn backend_from_env() -> Option<wgpu::Backends> {
- std::env::var("WGPU_BACKEND").ok().map(|backend| {
- match backend.to_lowercase().as_str() {
- "vulkan" => wgpu::Backends::VULKAN,
- "metal" => wgpu::Backends::METAL,
- "dx12" => wgpu::Backends::DX12,
- "dx11" => wgpu::Backends::DX11,
- "gl" => wgpu::Backends::GL,
- "webgpu" => wgpu::Backends::BROWSER_WEBGPU,
- "primary" => wgpu::Backends::PRIMARY,
- other => panic!("Unknown backend: {other}"),
- }
- })
-}
diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs
index e17b84c1..714e0400 100644
--- a/wgpu/src/text.rs
+++ b/wgpu/src/text.rs
@@ -1,265 +1,439 @@
-use crate::Transformation;
-
-use iced_graphics::font;
-
-use std::{cell::RefCell, collections::HashMap};
-use wgpu_glyph::ab_glyph;
-
-pub use iced_native::text::Hit;
-
-#[derive(Debug)]
+use crate::core::alignment;
+use crate::core::font::{self, Font};
+use crate::core::text::{Hit, LineHeight, Shaping};
+use crate::core::{Pixels, Point, Rectangle, Size};
+use crate::layer::Text;
+
+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;
+
+#[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<()>>,
+ font_system: RefCell<glyphon::FontSystem>,
+ renderers: Vec<glyphon::TextRenderer>,
+ atlas: glyphon::TextAtlas,
+ prepare_layer: usize,
+ measurement_cache: RefCell<Cache>,
+ render_cache: Cache,
}
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 {
+ font_system: RefCell::new(glyphon::FontSystem::new_with_fonts(
+ [glyphon::fontdb::Source::Binary(Arc::new(
+ include_bytes!("../fonts/Iced-Icons.ttf").as_slice(),
+ ))]
+ .into_iter(),
+ )),
+ renderers: Vec::new(),
+ atlas: glyphon::TextAtlas::new(device, queue, format),
+ prepare_layer: 0,
+ measurement_cache: RefCell::new(Cache::new()),
+ render_cache: Cache::new(),
+ }
+ }
- let default_font =
- default_font.unwrap_or_else(|| font::FALLBACK.to_vec());
+ pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
+ self.font_system.get_mut().db_mut().load_font_source(
+ glyphon::fontdb::Source::Binary(Arc::new(bytes.into_owned())),
+ );
+ }
- 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..."
+ pub fn prepare(
+ &mut self,
+ device: &wgpu::Device,
+ queue: &wgpu::Queue,
+ sections: &[Text<'_>],
+ bounds: Rectangle,
+ scale_factor: f32,
+ target_size: Size<u32>,
+ ) -> bool {
+ if self.renderers.len() <= self.prepare_layer {
+ self.renderers.push(glyphon::TextRenderer::new(
+ &mut self.atlas,
+ device,
+ Default::default(),
+ None,
+ ));
+ }
+
+ let font_system = self.font_system.get_mut();
+ let renderer = &mut self.renderers[self.prepare_layer];
+
+ let keys: Vec<_> = sections
+ .iter()
+ .map(|section| {
+ let (key, _) = self.render_cache.allocate(
+ font_system,
+ Key {
+ content: section.content,
+ size: section.size * scale_factor,
+ line_height: f32::from(
+ section
+ .line_height
+ .to_absolute(Pixels(section.size)),
+ ) * scale_factor,
+ font: section.font,
+ bounds: Size {
+ width: (section.bounds.width * scale_factor).ceil(),
+ height: (section.bounds.height * scale_factor)
+ .ceil(),
+ },
+ shaping: section.shaping,
+ },
);
- ab_glyph::FontArc::try_from_slice(font::FALLBACK)
- .expect("Load fallback font")
- });
+ key
+ })
+ .collect();
+
+ let bounds = bounds * scale_factor;
+
+ let text_areas =
+ sections
+ .iter()
+ .zip(keys.iter())
+ .filter_map(|(section, key)| {
+ let buffer =
+ self.render_cache.get(key).expect("Get cached buffer");
+
+ 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 * buffer.metrics().line_height;
+
+ let x = section.bounds.x * scale_factor;
+ let y = section.bounds.y * 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,
+ };
+
+ let section_bounds = Rectangle {
+ x: left,
+ y: top,
+ width: section.bounds.width * scale_factor,
+ height: section.bounds.height * scale_factor,
+ };
+
+ let clip_bounds = bounds.intersection(&section_bounds)?;
+
+ // TODO: Subpixel glyph positioning
+ let left = left.round() as i32;
+ let top = top.round() as i32;
+
+ Some(glyphon::TextArea {
+ buffer,
+ left,
+ top,
+ bounds: glyphon::TextBounds {
+ left: clip_bounds.x as i32,
+ top: clip_bounds.y as i32,
+ right: (clip_bounds.x + clip_bounds.width) as i32,
+ bottom: (clip_bounds.y + clip_bounds.height) as i32,
+ },
+ default_color: {
+ let [r, g, b, a] = section.color.into_linear();
+
+ glyphon::Color::rgba(
+ (r * 255.0) as u8,
+ (g * 255.0) as u8,
+ (b * 255.0) as u8,
+ (a * 255.0) as u8,
+ )
+ },
+ })
+ });
+
+ let result = renderer.prepare(
+ device,
+ queue,
+ font_system,
+ &mut self.atlas,
+ glyphon::Resolution {
+ width: target_size.width,
+ height: target_size.height,
+ },
+ text_areas,
+ &mut glyphon::SwashCache::new(),
+ );
- let draw_brush_builder =
- wgpu_glyph::GlyphBrushBuilder::using_font(font.clone())
- .initial_cache_size((2048, 2048))
- .draw_cache_multithread(multithreading);
+ match result {
+ Ok(()) => {
+ self.prepare_layer += 1;
- #[cfg(target_arch = "wasm32")]
- let draw_brush_builder = draw_brush_builder.draw_cache_align_4x4(true);
+ 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();
+ self.render_cache.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.prepare_layer = 0;
}
pub fn measure(
&self,
content: &str,
size: f32,
- font: iced_native::Font,
- bounds: iced_native::Size,
+ line_height: LineHeight,
+ font: Font,
+ bounds: Size,
+ shaping: Shaping,
) -> (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()
- };
+ let mut measurement_cache = self.measurement_cache.borrow_mut();
+
+ let line_height = f32::from(line_height.to_absolute(Pixels(size)));
+
+ let (_, paragraph) = measurement_cache.allocate(
+ &mut self.font_system.borrow_mut(),
+ Key {
+ content,
+ size,
+ line_height,
+ font,
+ bounds,
+ shaping,
+ },
+ );
- if let Some(bounds) =
- self.measure_brush.borrow_mut().glyph_bounds(section)
- {
- (bounds.width().ceil(), bounds.height().ceil())
- } else {
- (0.0, 0.0)
- }
+ 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, line_height * total_lines as f32)
}
pub fn hit_test(
&self,
content: &str,
size: f32,
- font: iced_native::Font,
- bounds: iced_native::Size,
- point: iced_native::Point,
- nearest_only: bool,
+ line_height: LineHeight,
+ font: Font,
+ bounds: Size,
+ shaping: Shaping,
+ point: Point,
+ _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()
- };
-
- 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(),
- ),
- ),
- )
+ let mut measurement_cache = self.measurement_cache.borrow_mut();
+
+ let line_height = f32::from(line_height.to_absolute(Pixels(size)));
+
+ let (_, paragraph) = measurement_cache.allocate(
+ &mut self.font_system.borrow_mut(),
+ Key {
+ content,
+ size,
+ line_height,
+ font,
+ bounds,
+ shaping,
},
);
- // 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;
- }
+ let cursor = paragraph.hit(point.x, point.y)?;
- byte_index
- };
+ Some(Hit::CharOffset(cursor.index))
+ }
- if !nearest_only {
- for (idx, bounds) in bounds.clone() {
- if bounds.contains(point) {
- return Some(Hit::CharOffset(char_index(idx)));
- }
- }
- }
+ pub fn trim_measurement_cache(&mut self) {
+ self.measurement_cache.borrow_mut().trim();
+ }
+}
- 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)
- });
+fn to_family(family: font::Family) -> glyphon::Family<'static> {
+ match family {
+ font::Family::Name(name) => glyphon::Family::Name(name),
+ font::Family::SansSerif => glyphon::Family::SansSerif,
+ font::Family::Serif => glyphon::Family::Serif,
+ font::Family::Cursive => glyphon::Family::Cursive,
+ font::Family::Fantasy => glyphon::Family::Fantasy,
+ font::Family::Monospace => glyphon::Family::Monospace,
+ }
+}
- nearest.map(|(idx, center)| {
- Hit::NearestCharOffset(char_index(idx), point - center)
- })
+fn to_weight(weight: font::Weight) -> glyphon::Weight {
+ match weight {
+ font::Weight::Thin => glyphon::Weight::THIN,
+ font::Weight::ExtraLight => glyphon::Weight::EXTRA_LIGHT,
+ font::Weight::Light => glyphon::Weight::LIGHT,
+ font::Weight::Normal => glyphon::Weight::NORMAL,
+ font::Weight::Medium => glyphon::Weight::MEDIUM,
+ font::Weight::Semibold => glyphon::Weight::SEMIBOLD,
+ font::Weight::Bold => glyphon::Weight::BOLD,
+ font::Weight::ExtraBold => glyphon::Weight::EXTRA_BOLD,
+ font::Weight::Black => glyphon::Weight::BLACK,
}
+}
- 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);
- }
- }
- }
+fn to_stretch(stretch: font::Stretch) -> glyphon::Stretch {
+ match stretch {
+ font::Stretch::UltraCondensed => glyphon::Stretch::UltraCondensed,
+ font::Stretch::ExtraCondensed => glyphon::Stretch::ExtraCondensed,
+ font::Stretch::Condensed => glyphon::Stretch::Condensed,
+ font::Stretch::SemiCondensed => glyphon::Stretch::SemiCondensed,
+ font::Stretch::Normal => glyphon::Stretch::Normal,
+ font::Stretch::SemiExpanded => glyphon::Stretch::SemiExpanded,
+ font::Stretch::Expanded => glyphon::Stretch::Expanded,
+ font::Stretch::ExtraExpanded => glyphon::Stretch::ExtraExpanded,
+ font::Stretch::UltraExpanded => glyphon::Stretch::UltraExpanded,
+ }
+}
+
+fn to_shaping(shaping: Shaping) -> glyphon::Shaping {
+ match shaping {
+ Shaping::Basic => glyphon::Shaping::Basic,
+ Shaping::Advanced => glyphon::Shaping::Advanced,
}
+}
- 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;
- }
+struct Cache {
+ entries: FxHashMap<KeyHash, glyphon::Buffer>,
+ recently_used: FxHashSet<KeyHash>,
+ hasher: HashBuilder,
+}
- let font = ab_glyph::FontArc::try_from_slice(bytes)
- .expect("Load font");
+#[cfg(not(target_arch = "wasm32"))]
+type HashBuilder = twox_hash::RandomXxHashBuilder64;
- let _ = self.measure_brush.borrow_mut().add_font(font.clone());
+#[cfg(target_arch = "wasm32")]
+type HashBuilder = std::hash::BuildHasherDefault<twox_hash::XxHash64>;
- let font_id = self.draw_brush.borrow_mut().add_font(font);
+impl Cache {
+ fn new() -> Self {
+ Self {
+ entries: FxHashMap::default(),
+ recently_used: FxHashSet::default(),
+ hasher: HashBuilder::default(),
+ }
+ }
- let _ = self
- .draw_font_map
- .borrow_mut()
- .insert(String::from(name), font_id);
+ fn get(&self, key: &KeyHash) -> Option<&glyphon::Buffer> {
+ self.entries.get(key)
+ }
- font_id
- }
+ fn allocate(
+ &mut self,
+ font_system: &mut glyphon::FontSystem,
+ key: Key<'_>,
+ ) -> (KeyHash, &mut glyphon::Buffer) {
+ let hash = {
+ let mut hasher = self.hasher.build_hasher();
+
+ key.content.hash(&mut hasher);
+ key.size.to_bits().hash(&mut hasher);
+ key.line_height.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.shaping.hash(&mut hasher);
+
+ hasher.finish()
+ };
+
+ if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) {
+ let metrics = glyphon::Metrics::new(key.size, key.line_height);
+ let mut buffer = glyphon::Buffer::new(font_system, metrics);
+
+ buffer.set_size(
+ font_system,
+ key.bounds.width,
+ key.bounds.height.max(key.line_height),
+ );
+ buffer.set_text(
+ font_system,
+ key.content,
+ glyphon::Attrs::new()
+ .family(to_family(key.font.family))
+ .weight(to_weight(key.font.weight))
+ .stretch(to_stretch(key.font.stretch)),
+ to_shaping(key.shaping),
+ );
+
+ let _ = entry.insert(buffer);
}
+
+ let _ = self.recently_used.insert(hash);
+
+ (hash, self.entries.get_mut(&hash).unwrap())
+ }
+
+ fn trim(&mut self) {
+ self.entries
+ .retain(|key, _| self.recently_used.contains(key));
+
+ self.recently_used.clear();
}
}
+
+#[derive(Debug, Clone, Copy)]
+struct Key<'a> {
+ content: &'a str,
+ size: f32,
+ line_height: f32,
+ font: Font,
+ bounds: Size,
+ shaping: Shaping,
+}
+
+type KeyHash = u64;
diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs
index 162428f0..eb15a458 100644
--- a/wgpu/src/triangle.rs
+++ b/wgpu/src/triangle.rs
@@ -2,62 +2,68 @@
mod msaa;
use crate::buffer::r#static::Buffer;
-use crate::settings;
-use crate::Transformation;
+use crate::core::Size;
+use crate::graphics::{Antialiasing, Transformation};
+use crate::layer::mesh::{self, Mesh};
+
+#[cfg(not(target_arch = "wasm32"))]
+use crate::core::Gradient;
-use iced_graphics::layer::mesh::{self, Mesh};
-use iced_graphics::triangle::ColoredVertex2D;
-use iced_graphics::Size;
#[cfg(feature = "tracing")]
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 +81,7 @@ impl Pipeline {
.resize(device, count.gradient_vertices);
// Prepare dynamic buffers & data store for writing
+ self.index_buffer.clear();
self.index_strides.clear();
self.solid.vertices.clear();
self.solid.uniforms.clear();
@@ -99,13 +106,8 @@ impl Pipeline {
let transform =
transformation * Transformation::translate(origin.x, origin.y);
- let new_index_offset = self.index_buffer.write(
- device,
- staging_belt,
- encoder,
- index_offset,
- indices,
- );
+ let new_index_offset =
+ self.index_buffer.write(queue, index_offset, indices);
index_offset += new_index_offset;
self.index_strides.push(indices.len() as u32);
@@ -116,9 +118,7 @@ impl Pipeline {
self.solid.uniforms.push(&solid::Uniforms::new(transform));
let written_bytes = self.solid.vertices.write(
- device,
- staging_belt,
- encoder,
+ queue,
solid_vertex_offset,
&buffers.vertices,
);
@@ -130,9 +130,7 @@ impl Pipeline {
buffers, gradient, ..
} => {
let written_bytes = self.gradient.vertices.write(
- device,
- staging_belt,
- encoder,
+ queue,
gradient_vertex_offset,
&buffers.vertices,
);
@@ -140,7 +138,7 @@ impl Pipeline {
gradient_vertex_offset += written_bytes;
match gradient {
- iced_graphics::Gradient::Linear(linear) => {
+ Gradient::Linear(linear) => {
use glam::{IVec4, Vec4};
let start_offset = self.gradient.color_stop_offset;
@@ -196,14 +194,14 @@ impl Pipeline {
let uniforms_resized = self.solid.uniforms.resize(device);
if uniforms_resized {
- self.solid.bind_group = solid::Pipeline::bind_group(
+ self.solid.constants = solid::Layer::bind_group(
device,
self.solid.uniforms.raw(),
- &self.solid.bind_group_layout,
+ &solid.constants_layout,
)
}
- self.solid.uniforms.write(device, staging_belt, encoder);
+ self.solid.uniforms.write(queue);
}
#[cfg(not(target_arch = "wasm32"))]
@@ -218,22 +216,169 @@ impl Pipeline {
let storage_resized = self.gradient.storage.resize(device);
if uniforms_resized || storage_resized {
- self.gradient.bind_group = gradient::Pipeline::bind_group(
+ self.gradient.constants = gradient::Layer::bind_group(
device,
self.gradient.uniforms.raw(),
self.gradient.storage.raw(),
- &self.gradient.bind_group_layout,
+ &gradient.constants_layout,
);
}
// Write to GPU
- self.gradient.uniforms.write(device, staging_belt, encoder);
- self.gradient.storage.write(device, staging_belt, encoder);
+ self.gradient.uniforms.write(queue);
+ self.gradient.storage.write(queue);
// Cleanup
self.gradient.color_stop_offset = 0;
self.gradient.color_stops_pending_write.color_stops.clear();
}
+ }
+
+ fn render<'a>(
+ &'a 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<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 +413,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);
- }
+ let layer = &mut self.layers[layer];
- 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,
- );
-
- 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(
@@ -370,7 +454,7 @@ fn primitive_state() -> wgpu::PrimitiveState {
}
fn multisample_state(
- antialiasing: Option<settings::Antialiasing>,
+ antialiasing: Option<Antialiasing>,
) -> wgpu::MultisampleState {
wgpu::MultisampleState {
count: antialiasing.map(|a| a.sample_count()).unwrap_or(1),
@@ -382,18 +466,71 @@ fn multisample_state(
mod solid {
use crate::buffer::dynamic;
use crate::buffer::r#static::Buffer;
- use crate::settings;
+ use crate::graphics::primitive;
+ use crate::graphics::{Antialiasing, Transformation};
use crate::triangle;
+
use encase::ShaderType;
- use iced_graphics::Transformation;
#[derive(Debug)]
pub struct Pipeline {
pub pipeline: wgpu::RenderPipeline,
- pub vertices: Buffer<triangle::ColoredVertex2D>,
+ pub constants_layout: wgpu::BindGroupLayout,
+ }
+
+ #[derive(Debug)]
+ pub struct Layer {
+ pub vertices: Buffer<primitive::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)]
@@ -414,20 +551,9 @@ mod solid {
pub fn new(
device: &wgpu::Device,
format: wgpu::TextureFormat,
- antialiasing: Option<settings::Antialiasing>,
+ antialiasing: Option<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 +569,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: &[],
},
);
@@ -475,7 +598,7 @@ mod solid {
entry_point: "vs_main",
buffers: &[wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<
- triangle::ColoredVertex2D,
+ primitive::ColoredVertex2D,
>()
as u64,
step_mode: wgpu::VertexStepMode::Vertex,
@@ -501,33 +624,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()),
- },
- ),
- }],
- })
- }
}
}
@@ -535,25 +634,100 @@ mod solid {
mod gradient {
use crate::buffer::dynamic;
use crate::buffer::r#static::Buffer;
- use crate::settings;
+ use crate::graphics::Antialiasing;
use crate::triangle;
use encase::ShaderType;
use glam::{IVec4, Vec4};
- use iced_graphics::triangle::Vertex2D;
+ use iced_graphics::primitive;
#[derive(Debug)]
pub struct Pipeline {
pub pipeline: wgpu::RenderPipeline,
- pub vertices: Buffer<Vertex2D>,
+ pub constants_layout: wgpu::BindGroupLayout,
+ }
+
+ #[derive(Debug)]
+ pub struct Layer {
+ pub vertices: Buffer<primitive::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)]
@@ -582,27 +756,9 @@ mod gradient {
pub(super) fn new(
device: &wgpu::Device,
format: wgpu::TextureFormat,
- antialiasing: Option<settings::Antialiasing>,
+ antialiasing: Option<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 +790,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: &[],
},
);
@@ -654,7 +803,7 @@ mod gradient {
let shader =
device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some(
- "iced_wgpu triangle gradient create shader module",
+ "iced_wgpu::triangle::gradient create shader module",
),
source: wgpu::ShaderSource::Wgsl(
std::borrow::Cow::Borrowed(include_str!(
@@ -663,75 +812,43 @@ mod gradient {
),
});
- 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: "vs_main",
- buffers: &[wgpu::VertexBufferLayout {
- array_stride: std::mem::size_of::<Vertex2D>()
- as u64,
- step_mode: wgpu::VertexStepMode::Vertex,
- attributes: &wgpu::vertex_attr_array!(
- // Position
- 0 => Float32x2,
- ),
- }],
+ 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: "vs_main",
+ buffers: &[wgpu::VertexBufferLayout {
+ array_stride: std::mem::size_of::<
+ primitive::Vertex2D,
+ >(
+ )
+ as u64,
+ step_mode: wgpu::VertexStepMode::Vertex,
+ attributes: &wgpu::vertex_attr_array!(
+ // Position
+ 0 => Float32x2,
+ ),
+ }],
+ },
+ fragment: Some(wgpu::FragmentState {
+ module: &shader,
+ entry_point: "fs_main",
+ targets: &[triangle::fragment_target(format)],
+ }),
+ primitive: triangle::primitive_state(),
+ depth_stencil: None,
+ multisample: triangle::multisample_state(antialiasing),
+ multiview: None,
},
- fragment: Some(wgpu::FragmentState {
- module: &shader,
- entry_point: "fs_main",
- targets: &[triangle::fragment_target(format)],
- }),
- primitive: triangle::primitive_state(),
- depth_stencil: None,
- multisample: triangle::multisample_state(antialiasing),
- multiview: None,
- },
- );
+ );
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/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs
index d24f8e1a..4afbdb32 100644
--- a/wgpu/src/triangle/msaa.rs
+++ b/wgpu/src/triangle/msaa.rs
@@ -1,4 +1,4 @@
-use crate::settings;
+use crate::graphics;
#[derive(Debug)]
pub struct Blit {
@@ -14,7 +14,7 @@ impl Blit {
pub fn new(
device: &wgpu::Device,
format: wgpu::TextureFormat,
- antialiasing: settings::Antialiasing,
+ antialiasing: graphics::Antialiasing,
) -> Blit {
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
@@ -222,8 +222,8 @@ impl Targets {
sample_count,
dimension: wgpu::TextureDimension::D2,
format,
- view_formats: &[],
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
+ view_formats: &[],
});
let resolve = device.create_texture(&wgpu::TextureDescriptor {
@@ -233,9 +233,9 @@ impl Targets {
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format,
- view_formats: &[],
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::TEXTURE_BINDING,
+ view_formats: &[],
});
let attachment =
diff --git a/wgpu/src/window.rs b/wgpu/src/window.rs
index aac5fb9e..9545a14e 100644
--- a/wgpu/src/window.rs
+++ b/wgpu/src/window.rs
@@ -1,4 +1,5 @@
//! Display rendering results on windows.
-mod compositor;
+pub mod compositor;
pub use compositor::Compositor;
+pub use wgpu::Surface;
diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs
index 53af19bf..500458e8 100644
--- a/wgpu/src/window/compositor.rs
+++ b/wgpu/src/window/compositor.rs
@@ -1,9 +1,12 @@
-use crate::{Backend, Color, Error, Renderer, Settings, Viewport};
+//! Connect a window with a renderer.
+use crate::core::Color;
+use crate::graphics;
+use crate::graphics::compositor;
+use crate::graphics::{Error, Primitive, Viewport};
+use crate::{Backend, Renderer, Settings};
use futures::stream::{self, StreamExt};
-use iced_graphics::compositor;
-use iced_native::futures;
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
use std::marker::PhantomData;
@@ -16,14 +19,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.
@@ -53,11 +53,12 @@ impl<Theme> Compositor<Theme> {
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
- power_preference: if settings.antialiasing.is_none() {
- wgpu::PowerPreference::LowPower
- } else {
- wgpu::PowerPreference::HighPerformance
- },
+ power_preference: wgpu::util::power_preference_from_env()
+ .unwrap_or(if settings.antialiasing.is_none() {
+ wgpu::PowerPreference::LowPower
+ } else {
+ wgpu::PowerPreference::HighPerformance
+ }),
compatible_surface: compatible_surface.as_ref(),
force_fallback_adapter: false,
})
@@ -112,15 +113,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,
})
@@ -128,11 +126,82 @@ 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)
+ }
+}
+
+/// Creates a [`Compositor`] and its [`Backend`] for the given [`Settings`] and
+/// window.
+pub fn new<Theme, W: HasRawWindowHandle + HasRawDisplayHandle>(
+ settings: Settings,
+ compatible_window: Option<&W>,
+) -> Result<(Compositor<Theme>, Backend), Error> {
+ let compositor = futures::executor::block_on(Compositor::request(
+ settings,
+ compatible_window,
+ ))
+ .ok_or(Error::GraphicsAdapterNotFound)?;
+
+ let backend = compositor.create_backend();
+
+ Ok((compositor, backend))
+}
+
+/// Presents the given primitives with the given [`Compositor`] and [`Backend`].
+pub fn present<Theme, T: AsRef<str>>(
+ compositor: &mut Compositor<Theme>,
+ backend: &mut Backend,
+ surface: &mut wgpu::Surface,
+ primitives: &[Primitive],
+ viewport: &Viewport,
+ background_color: Color,
+ overlay: &[T],
+) -> Result<(), compositor::SurfaceError> {
+ match surface.get_current_texture() {
+ Ok(frame) => {
+ let mut encoder = compositor.device.create_command_encoder(
+ &wgpu::CommandEncoderDescriptor {
+ label: Some("iced_wgpu encoder"),
+ },
+ );
+
+ let view = &frame
+ .texture
+ .create_view(&wgpu::TextureViewDescriptor::default());
+
+ backend.present(
+ &compositor.device,
+ &compositor.queue,
+ &mut encoder,
+ Some(background_color),
+ view,
+ primitives,
+ viewport,
+ overlay,
+ );
+
+ // Submit work
+ let _submission = compositor.queue.submit(Some(encoder.finish()));
+ 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)
+ }
+ },
}
}
-impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> {
+impl<Theme> graphics::Compositor for Compositor<Theme> {
type Settings = Settings;
type Renderer = Renderer<Theme>;
type Surface = wgpu::Surface;
@@ -141,13 +210,7 @@ impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> {
settings: Self::Settings,
compatible_window: Option<&W>,
) -> Result<(Self, Self::Renderer), Error> {
- let compositor = futures::executor::block_on(Self::request(
- settings,
- compatible_window,
- ))
- .ok_or(Error::GraphicsAdapterNotFound)?;
-
- let backend = compositor.create_backend();
+ let (compositor, backend) = new(settings, compatible_window)?;
Ok((compositor, Renderer::new(backend)))
}
@@ -155,13 +218,16 @@ impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> {
fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>(
&mut self,
window: &W,
+ width: u32,
+ height: u32,
) -> wgpu::Surface {
#[allow(unsafe_code)]
- unsafe {
- self.instance
- .create_surface(window)
- .expect("Create surface")
- }
+ let mut surface = unsafe { self.instance.create_surface(window) }
+ .expect("Create surface");
+
+ self.configure_surface(&mut surface, width, height);
+
+ surface
}
fn configure_surface(
@@ -201,80 +267,16 @@ impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> {
background_color: Color,
overlay: &[T],
) -> Result<(), compositor::SurfaceError> {
- match surface.get_current_texture() {
- Ok(frame) => {
- let mut encoder = self.device.create_command_encoder(
- &wgpu::CommandEncoderDescriptor {
- label: Some("iced_wgpu encoder"),
- },
- );
-
- let view = &frame
- .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,
- &mut encoder,
- view,
- primitives,
- viewport,
- overlay,
- );
- });
-
- // 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 {
- 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)
- }
- },
- }
+ renderer.with_primitives(|backend, primitives| {
+ present(
+ self,
+ backend,
+ surface,
+ primitives,
+ viewport,
+ background_color,
+ overlay,
+ )
+ })
}
}