Redesign iced_wgpu layering architecture

This commit is contained in:
Héctor Ramón Jiménez 2024-04-03 21:07:54 +02:00
parent 99a904112c
commit b05e61f5c8
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
36 changed files with 2781 additions and 2048 deletions

View file

@ -41,6 +41,3 @@ lyon.optional = true
resvg.workspace = true
resvg.optional = true
tracing.workspace = true
tracing.optional = true

View file

@ -1,432 +0,0 @@
use crate::buffer;
use crate::core::{Color, Size, Transformation};
use crate::graphics::backend;
use crate::graphics::color;
use crate::graphics::Viewport;
use crate::primitive::pipeline;
use crate::primitive::{self, Primitive};
use crate::quad;
use crate::text;
use crate::triangle;
use crate::window;
use crate::{Layer, Settings};
#[cfg(feature = "tracing")]
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
#[allow(missing_debug_implementations)]
pub struct Backend {
quad_pipeline: quad::Pipeline,
text_pipeline: text::Pipeline,
triangle_pipeline: triangle::Pipeline,
pipeline_storage: pipeline::Storage,
#[cfg(any(feature = "image", feature = "svg"))]
image_pipeline: image::Pipeline,
staging_belt: wgpu::util::StagingBelt,
}
impl Backend {
/// Creates a new [`Backend`].
pub fn new(
_adapter: &wgpu::Adapter,
device: &wgpu::Device,
queue: &wgpu::Queue,
settings: Settings,
format: wgpu::TextureFormat,
) -> Self {
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);
#[cfg(any(feature = "image", feature = "svg"))]
let image_pipeline = {
let backend = _adapter.get_info().backend;
image::Pipeline::new(device, format, backend)
};
Self {
quad_pipeline,
text_pipeline,
triangle_pipeline,
pipeline_storage: pipeline::Storage::default(),
#[cfg(any(feature = "image", feature = "svg"))]
image_pipeline,
// TODO: Resize belt smartly (?)
// It would be great if the `StagingBelt` API exposed methods
// for introspection to detect when a resize may be worth it.
staging_belt: wgpu::util::StagingBelt::new(
buffer::MAX_WRITE_SIZE as u64,
),
}
}
/// Draws the provided primitives in the given `TextureView`.
///
/// The text provided as overlay will be rendered on top of the primitives.
/// This is useful for rendering debug information.
pub fn present<T: AsRef<str>>(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
clear_color: Option<Color>,
format: wgpu::TextureFormat,
frame: &wgpu::TextureView,
primitives: &[Primitive],
viewport: &Viewport,
overlay_text: &[T],
) {
log::debug!("Drawing");
#[cfg(feature = "tracing")]
let _ = info_span!("Wgpu::Backend", "PRESENT").entered();
let target_size = viewport.physical_size();
let scale_factor = viewport.scale_factor() as f32;
let transformation = viewport.projection();
let mut layers = Layer::generate(primitives, viewport);
if !overlay_text.is_empty() {
layers.push(Layer::overlay(overlay_text, viewport));
}
self.prepare(
device,
queue,
format,
encoder,
scale_factor,
target_size,
transformation,
&layers,
);
self.staging_belt.finish();
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();
}
/// Recalls staging memory for future uploads.
///
/// This method should be called after the command encoder
/// has been submitted.
pub fn recall(&mut self) {
self.staging_belt.recall();
}
fn prepare(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
format: wgpu::TextureFormat,
encoder: &mut wgpu::CommandEncoder,
scale_factor: f32,
target_size: Size<u32>,
transformation: Transformation,
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,
encoder,
&mut self.staging_belt,
&layer.quads,
transformation,
scale_factor,
);
}
if !layer.meshes.is_empty() {
let scaled =
transformation * Transformation::scale(scale_factor);
self.triangle_pipeline.prepare(
device,
encoder,
&mut self.staging_belt,
&layer.meshes,
scaled,
);
}
#[cfg(any(feature = "image", feature = "svg"))]
{
if !layer.images.is_empty() {
let scaled =
transformation * Transformation::scale(scale_factor);
self.image_pipeline.prepare(
device,
encoder,
&mut self.staging_belt,
&layer.images,
scaled,
scale_factor,
);
}
}
if !layer.text.is_empty() {
self.text_pipeline.prepare(
device,
queue,
encoder,
&layer.text,
layer.bounds,
scale_factor,
target_size,
);
}
if !layer.pipelines.is_empty() {
for pipeline in &layer.pipelines {
pipeline.primitive.prepare(
format,
device,
queue,
pipeline.bounds,
target_size,
scale_factor,
&mut self.pipeline_storage,
);
}
}
}
}
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<'_>],
) {
use std::mem::ManuallyDrop;
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 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] =
color::pack(background_color).components();
wgpu::Color {
r: f64::from(r),
g: f64::from(g),
b: f64::from(b),
a: f64::from(a),
}
}),
None => wgpu::LoadOp::Load,
},
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
},
));
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.render(
quad_layer,
bounds,
&layer.quads,
&mut render_pass,
);
quad_layer += 1;
}
if !layer.meshes.is_empty() {
let _ = ManuallyDrop::into_inner(render_pass);
self.triangle_pipeline.render(
device,
encoder,
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 render pass"),
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view: target,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
},
)],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
},
));
}
#[cfg(any(feature = "image", feature = "svg"))]
{
if !layer.images.is_empty() {
self.image_pipeline.render(
image_layer,
bounds,
&mut render_pass,
);
image_layer += 1;
}
}
if !layer.text.is_empty() {
self.text_pipeline
.render(text_layer, bounds, &mut render_pass);
text_layer += 1;
}
if !layer.pipelines.is_empty() {
let _ = ManuallyDrop::into_inner(render_pass);
for pipeline in &layer.pipelines {
let viewport = (pipeline.viewport * scale_factor).snap();
if viewport.width < 1 || viewport.height < 1 {
continue;
}
pipeline.primitive.render(
&self.pipeline_storage,
target,
target_size,
viewport,
encoder,
);
}
render_pass = ManuallyDrop::new(encoder.begin_render_pass(
&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu render pass"),
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view: target,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
},
)],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
},
));
}
}
let _ = ManuallyDrop::into_inner(render_pass);
}
}
impl backend::Backend for Backend {
type Primitive = primitive::Custom;
type Compositor = window::Compositor;
}
impl backend::Text for Backend {
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: &crate::core::image::Handle) -> Size<u32> {
self.image_pipeline.dimensions(handle)
}
}
#[cfg(feature = "svg")]
impl backend::Svg for Backend {
fn viewport_dimensions(
&self,
handle: &crate::core::svg::Handle,
) -> Size<u32> {
self.image_pipeline.viewport_dimensions(handle)
}
}
#[cfg(feature = "geometry")]
impl crate::graphics::geometry::Backend for Backend {
type Frame = crate::geometry::Frame;
fn new_frame(&self, size: Size) -> Self::Frame {
crate::geometry::Frame::new(size)
}
}

79
wgpu/src/engine.rs Normal file
View file

@ -0,0 +1,79 @@
use crate::buffer;
use crate::graphics::Antialiasing;
use crate::primitive::pipeline;
use crate::quad;
use crate::text;
use crate::triangle;
#[allow(missing_debug_implementations)]
pub struct Engine {
pub(crate) quad_pipeline: quad::Pipeline,
pub(crate) text_pipeline: text::Pipeline,
pub(crate) triangle_pipeline: triangle::Pipeline,
pub(crate) _pipeline_storage: pipeline::Storage,
#[cfg(any(feature = "image", feature = "svg"))]
pub(crate) image_pipeline: crate::image::Pipeline,
pub(crate) staging_belt: wgpu::util::StagingBelt,
}
impl Engine {
pub fn new(
_adapter: &wgpu::Adapter,
device: &wgpu::Device,
queue: &wgpu::Queue,
format: wgpu::TextureFormat,
antialiasing: Option<Antialiasing>, // TODO: Initialize AA pipelines lazily
) -> Self {
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, antialiasing);
#[cfg(any(feature = "image", feature = "svg"))]
let image_pipeline = {
let backend = _adapter.get_info().backend;
crate::image::Pipeline::new(device, format, backend)
};
Self {
// TODO: Resize belt smartly (?)
// It would be great if the `StagingBelt` API exposed methods
// for introspection to detect when a resize may be worth it.
staging_belt: wgpu::util::StagingBelt::new(
buffer::MAX_WRITE_SIZE as u64,
),
quad_pipeline,
text_pipeline,
triangle_pipeline,
_pipeline_storage: pipeline::Storage::default(),
#[cfg(any(feature = "image", feature = "svg"))]
image_pipeline,
}
}
#[cfg(any(feature = "image", feature = "svg"))]
pub fn image_cache(&self) -> &crate::image::cache::Shared {
self.image_pipeline.cache()
}
pub fn submit(
&mut self,
queue: &wgpu::Queue,
encoder: wgpu::CommandEncoder,
) -> wgpu::SubmissionIndex {
self.staging_belt.finish();
let index = queue.submit(Some(encoder.finish()));
self.staging_belt.recall();
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();
index
}
}

View file

@ -10,31 +10,82 @@ use crate::graphics::geometry::{
};
use crate::graphics::gradient::{self, Gradient};
use crate::graphics::mesh::{self, Mesh};
use crate::primitive::{self, Primitive};
use crate::graphics::{self, Cached};
use crate::layer;
use crate::text;
use lyon::geom::euclid;
use lyon::tessellation;
use std::borrow::Cow;
use std::cell::RefCell;
use std::rc::Rc;
/// A frame for drawing some geometry.
#[allow(missing_debug_implementations)]
pub struct Frame {
size: Size,
buffers: BufferStack,
primitives: Vec<Primitive>,
layers: Vec<layer::Live>,
text: text::Batch,
transforms: Transforms,
fill_tessellator: tessellation::FillTessellator,
stroke_tessellator: tessellation::StrokeTessellator,
}
pub enum Geometry {
Live(Vec<layer::Live>),
Cached(Rc<[Rc<RefCell<layer::Cached>>]>),
}
impl Cached for Geometry {
type Cache = Rc<[Rc<RefCell<layer::Cached>>]>;
fn load(cache: &Self::Cache) -> Self {
Geometry::Cached(cache.clone())
}
fn cache(self, previous: Option<Self::Cache>) -> Self::Cache {
match self {
Self::Live(live) => {
let mut layers = live.into_iter();
let mut new: Vec<_> = previous
.map(|previous| {
previous
.iter()
.cloned()
.zip(layers.by_ref())
.map(|(cached, live)| {
cached.borrow_mut().update(live);
cached
})
.collect()
})
.unwrap_or_default();
new.extend(
layers
.map(layer::Live::into_cached)
.map(RefCell::new)
.map(Rc::new),
);
Rc::from(new)
}
Self::Cached(cache) => cache,
}
}
}
impl Frame {
/// Creates a new [`Frame`] with the given [`Size`].
pub fn new(size: Size) -> Frame {
Frame {
size,
buffers: BufferStack::new(),
primitives: Vec::new(),
layers: Vec::new(),
text: text::Batch::new(),
transforms: Transforms {
previous: Vec::new(),
current: Transform(lyon::math::Transform::identity()),
@ -44,49 +95,54 @@ impl Frame {
}
}
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::Custom(
primitive::Custom::Mesh(Mesh::Solid {
buffers: mesh::Indexed {
vertices: buffer.vertices,
indices: buffer.indices,
},
size: self.size,
}),
));
}
}
Buffer::Gradient(buffer) => {
if !buffer.indices.is_empty() {
self.primitives.push(Primitive::Custom(
primitive::Custom::Mesh(Mesh::Gradient {
buffers: mesh::Indexed {
vertices: buffer.vertices,
indices: buffer.indices,
},
size: self.size,
}),
));
}
}
}
fn into_layers(mut self) -> Vec<layer::Live> {
if !self.text.is_empty() || !self.buffers.stack.is_empty() {
let clip_bounds = Rectangle::with_size(self.size);
let transformation = Transformation::IDENTITY;
// TODO: Generate different meshes for different transformations (?)
// Instead of transforming each path
let meshes = self
.buffers
.stack
.into_iter()
.map(|buffer| match buffer {
Buffer::Solid(buffer) => Mesh::Solid {
buffers: mesh::Indexed {
vertices: buffer.vertices,
indices: buffer.indices,
},
transformation: Transformation::IDENTITY,
size: self.size,
},
Buffer::Gradient(buffer) => Mesh::Gradient {
buffers: mesh::Indexed {
vertices: buffer.vertices,
indices: buffer.indices,
},
transformation: Transformation::IDENTITY,
size: self.size,
},
})
.collect();
let layer = layer::Live {
bounds: Some(clip_bounds),
transformation,
meshes,
text: self.text,
..layer::Live::default()
};
self.layers.push(layer);
}
self.primitives
self.layers
}
}
impl geometry::frame::Backend for Frame {
type Geometry = Primitive;
/// 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.
type Geometry = Geometry;
#[inline]
fn width(&self) -> f32 {
@ -246,8 +302,7 @@ impl geometry::frame::Backend for Frame {
height: f32::INFINITY,
};
// TODO: Honor layering!
self.primitives.push(Primitive::Text {
self.text.push(graphics::Text::Cached {
content: text.content,
bounds,
color: text.color,
@ -313,37 +368,17 @@ impl geometry::frame::Backend for Frame {
}
fn paste(&mut self, frame: Frame, at: Point) {
let size = frame.size();
let primitives = frame.into_primitives();
let transformation = Transformation::translate(at.x, at.y);
let translation = Transformation::translate(at.x, at.y);
let (text, meshes) = primitives
.into_iter()
.partition(|primitive| matches!(primitive, Primitive::Text { .. }));
self.primitives.push(Primitive::Group {
primitives: vec![
Primitive::Transform {
transformation,
content: Box::new(Primitive::Group { primitives: meshes }),
},
Primitive::Transform {
transformation,
content: Box::new(Primitive::Clip {
bounds: Rectangle::with_size(size),
content: Box::new(Primitive::Group {
primitives: text,
}),
}),
},
],
});
self.layers
.extend(frame.into_layers().into_iter().map(|mut layer| {
layer.transformation = layer.transformation * translation;
layer
}));
}
fn into_geometry(self) -> Self::Geometry {
Primitive::Group {
primitives: self.into_primitives(),
}
Geometry::Live(self.into_layers())
}
}

107
wgpu/src/image/cache.rs Normal file
View file

@ -0,0 +1,107 @@
use crate::core::{self, Size};
use crate::image::atlas::{self, Atlas};
use std::cell::{RefCell, RefMut};
use std::rc::Rc;
#[derive(Debug)]
pub struct Cache {
atlas: Atlas,
#[cfg(feature = "image")]
raster: crate::image::raster::Cache,
#[cfg(feature = "svg")]
vector: crate::image::vector::Cache,
}
impl Cache {
pub fn new(device: &wgpu::Device, backend: wgpu::Backend) -> Self {
Self {
atlas: Atlas::new(device, backend),
#[cfg(feature = "image")]
raster: crate::image::raster::Cache::default(),
#[cfg(feature = "svg")]
vector: crate::image::vector::Cache::default(),
}
}
pub fn layer_count(&self) -> usize {
self.atlas.layer_count()
}
#[cfg(feature = "image")]
pub fn measure_image(&mut self, handle: &core::image::Handle) -> Size<u32> {
self.raster.load(handle).dimensions()
}
#[cfg(feature = "svg")]
pub fn measure_svg(&mut self, handle: &core::svg::Handle) -> Size<u32> {
self.vector.load(handle).viewport_dimensions()
}
#[cfg(feature = "image")]
pub fn upload_raster(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
handle: &core::image::Handle,
) -> Option<&atlas::Entry> {
self.raster.upload(device, encoder, handle, &mut self.atlas)
}
#[cfg(feature = "svg")]
pub fn upload_vector(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
handle: &core::svg::Handle,
color: Option<core::Color>,
size: [f32; 2],
scale: f32,
) -> Option<&atlas::Entry> {
self.vector.upload(
device,
encoder,
handle,
color,
size,
scale,
&mut self.atlas,
)
}
pub fn create_bind_group(
&self,
device: &wgpu::Device,
layout: &wgpu::BindGroupLayout,
) -> wgpu::BindGroup {
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("iced_wgpu::image texture atlas bind group"),
layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(self.atlas.view()),
}],
})
}
pub fn trim(&mut self) {
#[cfg(feature = "image")]
self.raster.trim(&mut self.atlas);
#[cfg(feature = "svg")]
self.vector.trim(&mut self.atlas);
}
}
#[derive(Debug, Clone)]
pub struct Shared(Rc<RefCell<Cache>>);
impl Shared {
pub fn new(cache: Cache) -> Self {
Self(Rc::new(RefCell::new(cache)))
}
pub fn lock(&self) -> RefMut<'_, Cache> {
self.0.borrow_mut()
}
}

View file

@ -1,3 +1,6 @@
pub(crate) mod cache;
pub(crate) use cache::Cache;
mod atlas;
#[cfg(feature = "image")]
@ -6,46 +9,331 @@ mod raster;
#[cfg(feature = "svg")]
mod vector;
use atlas::Atlas;
use crate::core::image;
use crate::core::{Rectangle, Size, Transformation};
use crate::layer;
use crate::Buffer;
use std::cell::RefCell;
use bytemuck::{Pod, Zeroable};
use std::mem;
use bytemuck::{Pod, Zeroable};
pub use crate::graphics::Image;
#[cfg(feature = "image")]
use crate::core::image;
#[cfg(feature = "svg")]
use crate::core::svg;
#[cfg(feature = "tracing")]
use tracing::info_span;
pub type Batch = Vec<Image>;
#[derive(Debug)]
pub struct Pipeline {
#[cfg(feature = "image")]
raster_cache: RefCell<raster::Cache>,
#[cfg(feature = "svg")]
vector_cache: RefCell<vector::Cache>,
pipeline: wgpu::RenderPipeline,
nearest_sampler: wgpu::Sampler,
linear_sampler: wgpu::Sampler,
texture: wgpu::BindGroup,
texture_version: usize,
texture_atlas: Atlas,
texture_layout: wgpu::BindGroupLayout,
constant_layout: wgpu::BindGroupLayout,
cache: cache::Shared,
layers: Vec<Layer>,
prepare_layer: usize,
}
impl Pipeline {
pub fn new(
device: &wgpu::Device,
format: wgpu::TextureFormat,
backend: wgpu::Backend,
) -> Self {
let nearest_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
min_filter: wgpu::FilterMode::Nearest,
mag_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
let linear_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
min_filter: wgpu::FilterMode::Linear,
mag_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Linear,
..Default::default()
});
let constant_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("iced_wgpu::image constants layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: wgpu::BufferSize::new(
mem::size_of::<Uniforms>() as u64,
),
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(
wgpu::SamplerBindingType::Filtering,
),
count: None,
},
],
});
let texture_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("iced_wgpu::image texture atlas layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float {
filterable: true,
},
view_dimension: wgpu::TextureViewDimension::D2Array,
multisampled: false,
},
count: None,
}],
});
let layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("iced_wgpu::image pipeline layout"),
push_constant_ranges: &[],
bind_group_layouts: &[&constant_layout, &texture_layout],
});
let shader =
device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("iced_wgpu image shader"),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
concat!(
include_str!("../shader/vertex.wgsl"),
"\n",
include_str!("../shader/image.wgsl"),
),
)),
});
let pipeline =
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("iced_wgpu::image pipeline"),
layout: Some(&layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[wgpu::VertexBufferLayout {
array_stride: mem::size_of::<Instance>() as u64,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &wgpu::vertex_attr_array!(
// Position
0 => Float32x2,
// Scale
1 => Float32x2,
// Atlas position
2 => Float32x2,
// Atlas scale
3 => Float32x2,
// Layer
4 => Sint32,
),
}],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[Some(wgpu::ColorTargetState {
format,
blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
}),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
front_face: wgpu::FrontFace::Cw,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
});
let cache = Cache::new(device, backend);
let texture = cache.create_bind_group(device, &texture_layout);
Pipeline {
pipeline,
nearest_sampler,
linear_sampler,
texture,
texture_version: cache.layer_count(),
texture_layout,
constant_layout,
cache: cache::Shared::new(cache),
layers: Vec::new(),
prepare_layer: 0,
}
}
pub fn cache(&self) -> &cache::Shared {
&self.cache
}
pub fn prepare(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
images: &Batch,
transformation: Transformation,
scale: f32,
) {
let transformation = transformation * Transformation::scale(scale);
let nearest_instances: &mut Vec<Instance> = &mut Vec::new();
let linear_instances: &mut Vec<Instance> = &mut Vec::new();
let mut cache = self.cache.lock();
for image in images {
match &image {
#[cfg(feature = "image")]
Image::Raster {
handle,
filter_method,
bounds,
} => {
if let Some(atlas_entry) =
cache.upload_raster(device, encoder, handle)
{
add_instances(
[bounds.x, bounds.y],
[bounds.width, bounds.height],
atlas_entry,
match filter_method {
image::FilterMethod::Nearest => {
nearest_instances
}
image::FilterMethod::Linear => linear_instances,
},
);
}
}
#[cfg(not(feature = "image"))]
Image::Raster { .. } => {}
#[cfg(feature = "svg")]
Image::Vector {
handle,
color,
bounds,
} => {
let size = [bounds.width, bounds.height];
if let Some(atlas_entry) = cache.upload_vector(
device, encoder, handle, *color, size, scale,
) {
add_instances(
[bounds.x, bounds.y],
size,
atlas_entry,
nearest_instances,
);
}
}
#[cfg(not(feature = "svg"))]
Image::Vector { .. } => {}
}
}
if nearest_instances.is_empty() && linear_instances.is_empty() {
return;
}
let texture_version = cache.layer_count();
if self.texture_version != texture_version {
log::info!("Atlas has grown. Recreating bind group...");
self.texture =
cache.create_bind_group(device, &self.texture_layout);
self.texture_version = texture_version;
}
if self.layers.len() <= self.prepare_layer {
self.layers.push(Layer::new(
device,
&self.constant_layout,
&self.nearest_sampler,
&self.linear_sampler,
));
}
let layer = &mut self.layers[self.prepare_layer];
layer.prepare(
device,
encoder,
belt,
nearest_instances,
linear_instances,
transformation,
);
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,
);
render_pass.set_bind_group(1, &self.texture, &[]);
layer.render(render_pass);
}
}
pub fn end_frame(&mut self) {
self.cache.lock().trim();
self.prepare_layer = 0;
}
}
#[derive(Debug)]
struct Layer {
uniforms: wgpu::Buffer,
@ -194,367 +482,6 @@ impl Data {
}
}
impl Pipeline {
pub fn new(
device: &wgpu::Device,
format: wgpu::TextureFormat,
backend: wgpu::Backend,
) -> Self {
let nearest_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
min_filter: wgpu::FilterMode::Nearest,
mag_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
let linear_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
min_filter: wgpu::FilterMode::Linear,
mag_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Linear,
..Default::default()
});
let constant_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("iced_wgpu::image constants layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: wgpu::BufferSize::new(
mem::size_of::<Uniforms>() as u64,
),
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(
wgpu::SamplerBindingType::Filtering,
),
count: None,
},
],
});
let texture_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("iced_wgpu::image texture atlas layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float {
filterable: true,
},
view_dimension: wgpu::TextureViewDimension::D2Array,
multisampled: false,
},
count: None,
}],
});
let layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("iced_wgpu::image pipeline layout"),
push_constant_ranges: &[],
bind_group_layouts: &[&constant_layout, &texture_layout],
});
let shader =
device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("iced_wgpu image shader"),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
concat!(
include_str!("shader/vertex.wgsl"),
"\n",
include_str!("shader/image.wgsl"),
),
)),
});
let pipeline =
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("iced_wgpu::image pipeline"),
layout: Some(&layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[wgpu::VertexBufferLayout {
array_stride: mem::size_of::<Instance>() as u64,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &wgpu::vertex_attr_array!(
// Position
0 => Float32x2,
// Scale
1 => Float32x2,
// Atlas position
2 => Float32x2,
// Atlas scale
3 => Float32x2,
// Layer
4 => Sint32,
),
}],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[Some(wgpu::ColorTargetState {
format,
blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
}),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
front_face: wgpu::FrontFace::Cw,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
});
let texture_atlas = Atlas::new(device, backend);
let texture = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("iced_wgpu::image texture atlas bind group"),
layout: &texture_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(
texture_atlas.view(),
),
}],
});
Pipeline {
#[cfg(feature = "image")]
raster_cache: RefCell::new(raster::Cache::default()),
#[cfg(feature = "svg")]
vector_cache: RefCell::new(vector::Cache::default()),
pipeline,
nearest_sampler,
linear_sampler,
texture,
texture_version: texture_atlas.layer_count(),
texture_atlas,
texture_layout,
constant_layout,
layers: Vec::new(),
prepare_layer: 0,
}
}
#[cfg(feature = "image")]
pub fn dimensions(&self, handle: &image::Handle) -> Size<u32> {
let mut cache = self.raster_cache.borrow_mut();
let memory = cache.load(handle);
memory.dimensions()
}
#[cfg(feature = "svg")]
pub fn viewport_dimensions(&self, handle: &svg::Handle) -> Size<u32> {
let mut cache = self.vector_cache.borrow_mut();
let svg = cache.load(handle);
svg.viewport_dimensions()
}
pub fn prepare(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
images: &[layer::Image],
transformation: Transformation,
_scale: f32,
) {
#[cfg(feature = "tracing")]
let _ = info_span!("Wgpu::Image", "PREPARE").entered();
#[cfg(feature = "tracing")]
let _ = info_span!("Wgpu::Image", "DRAW").entered();
let nearest_instances: &mut Vec<Instance> = &mut Vec::new();
let linear_instances: &mut Vec<Instance> = &mut Vec::new();
#[cfg(feature = "image")]
let mut raster_cache = self.raster_cache.borrow_mut();
#[cfg(feature = "svg")]
let mut vector_cache = self.vector_cache.borrow_mut();
for image in images {
match &image {
#[cfg(feature = "image")]
layer::Image::Raster {
handle,
filter_method,
bounds,
} => {
if let Some(atlas_entry) = raster_cache.upload(
device,
encoder,
handle,
&mut self.texture_atlas,
) {
add_instances(
[bounds.x, bounds.y],
[bounds.width, bounds.height],
atlas_entry,
match filter_method {
image::FilterMethod::Nearest => {
nearest_instances
}
image::FilterMethod::Linear => linear_instances,
},
);
}
}
#[cfg(not(feature = "image"))]
layer::Image::Raster { .. } => {}
#[cfg(feature = "svg")]
layer::Image::Vector {
handle,
color,
bounds,
} => {
let size = [bounds.width, bounds.height];
if let Some(atlas_entry) = vector_cache.upload(
device,
encoder,
handle,
*color,
size,
_scale,
&mut self.texture_atlas,
) {
add_instances(
[bounds.x, bounds.y],
size,
atlas_entry,
nearest_instances,
);
}
}
#[cfg(not(feature = "svg"))]
layer::Image::Vector { .. } => {}
}
}
if nearest_instances.is_empty() && linear_instances.is_empty() {
return;
}
let texture_version = self.texture_atlas.layer_count();
if self.texture_version != texture_version {
log::info!("Atlas has grown. Recreating bind group...");
self.texture =
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("iced_wgpu::image texture atlas bind group"),
layout: &self.texture_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(
self.texture_atlas.view(),
),
}],
});
self.texture_version = texture_version;
}
if self.layers.len() <= self.prepare_layer {
self.layers.push(Layer::new(
device,
&self.constant_layout,
&self.nearest_sampler,
&self.linear_sampler,
));
}
let layer = &mut self.layers[self.prepare_layer];
layer.prepare(
device,
encoder,
belt,
nearest_instances,
linear_instances,
transformation,
);
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,
);
render_pass.set_bind_group(1, &self.texture, &[]);
layer.render(render_pass);
}
}
pub fn end_frame(&mut self) {
#[cfg(feature = "image")]
self.raster_cache.borrow_mut().trim(&mut self.texture_atlas);
#[cfg(feature = "svg")]
self.vector_cache.borrow_mut().trim(&mut self.texture_atlas);
self.prepare_layer = 0;
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
struct Instance {

10
wgpu/src/image/null.rs Normal file
View file

@ -0,0 +1,10 @@
pub use crate::graphics::Image;
#[derive(Default)]
pub struct Batch;
impl Batch {
pub fn push(&mut self, _image: Image) {}
pub fn clear(&mut self) {}
}

View file

@ -1,343 +1,326 @@
//! Organize rendering primitives into a flattened list of layers.
mod image;
mod pipeline;
mod text;
pub mod mesh;
pub use image::Image;
pub use mesh::Mesh;
pub use pipeline::Pipeline;
pub use text::Text;
use crate::core;
use crate::core::alignment;
use crate::core::{
Color, Font, Pixels, Point, Rectangle, Size, Transformation, Vector,
};
use crate::graphics;
use crate::core::renderer;
use crate::core::{Background, Color, Point, Rectangle, Transformation};
use crate::graphics::color;
use crate::graphics::Viewport;
use crate::primitive::{self, Primitive};
use crate::graphics::text::{Editor, Paragraph};
use crate::graphics::Mesh;
use crate::image::{self, Image};
use crate::quad::{self, Quad};
use crate::text::{self, Text};
use crate::triangle;
/// A group of primitives that should be clipped together.
#[derive(Debug)]
pub struct Layer<'a> {
/// The clipping bounds of the [`Layer`].
pub bounds: Rectangle,
use std::cell::{self, RefCell};
use std::rc::Rc;
/// The quads of the [`Layer`].
pub quads: quad::Batch,
/// 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>,
/// The custom pipelines of this [`Layer`].
pub pipelines: Vec<Pipeline>,
pub enum Layer<'a> {
Live(&'a Live),
Cached(cell::Ref<'a, Cached>),
}
impl<'a> Layer<'a> {
/// Creates a new [`Layer`] with the given clipping bounds.
pub fn new(bounds: Rectangle) -> Self {
pub enum LayerMut<'a> {
Live(&'a mut Live),
Cached(cell::RefMut<'a, Cached>),
}
pub struct Stack {
live: Vec<Live>,
cached: Vec<Rc<RefCell<Cached>>>,
order: Vec<Kind>,
transformations: Vec<Transformation>,
previous: Vec<usize>,
current: usize,
live_count: usize,
}
impl Stack {
pub fn new() -> Self {
Self {
bounds,
quads: quad::Batch::default(),
meshes: Vec::new(),
text: Vec::new(),
images: Vec::new(),
pipelines: Vec::new(),
live: vec![Live::default()],
cached: Vec::new(),
order: vec![Kind::Live],
transformations: vec![Transformation::IDENTITY],
previous: Vec::new(),
current: 0,
live_count: 1,
}
}
/// 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()));
pub fn draw_quad(&mut self, quad: renderer::Quad, background: Background) {
let transformation = self.transformations.last().unwrap();
let bounds = quad.bounds * *transformation;
for (i, line) in lines.iter().enumerate() {
let text = text::Cached {
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: Pixels(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,
clip_bounds: Rectangle::with_size(Size::INFINITY),
};
let quad = Quad {
position: [bounds.x, bounds.y],
size: [bounds.width, bounds.height],
border_color: color::pack(quad.border.color),
border_radius: quad.border.radius.into(),
border_width: quad.border.width,
shadow_color: color::pack(quad.shadow.color),
shadow_offset: quad.shadow.offset.into(),
shadow_blur_radius: quad.shadow.blur_radius,
};
overlay.text.push(Text::Cached(text.clone()));
overlay.text.push(Text::Cached(text::Cached {
bounds: text.bounds + Vector::new(-1.0, -1.0),
color: Color::BLACK,
..text
}));
}
overlay
self.live[self.current].quads.add(quad, &background);
}
/// 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,
Transformation::IDENTITY,
primitive,
0,
);
}
layers
}
fn process_primitive(
layers: &mut Vec<Self>,
transformation: Transformation,
primitive: &'a Primitive,
current_layer: usize,
pub fn draw_paragraph(
&mut self,
paragraph: &Paragraph,
position: Point,
color: Color,
clip_bounds: Rectangle,
) {
match primitive {
Primitive::Paragraph {
paragraph,
position,
color,
clip_bounds,
} => {
let layer = &mut layers[current_layer];
let paragraph = Text::Paragraph {
paragraph: paragraph.downgrade(),
position,
color,
clip_bounds,
transformation: self.transformations.last().copied().unwrap(),
};
layer.text.push(Text::Paragraph {
paragraph: paragraph.clone(),
position: *position,
color: *color,
clip_bounds: *clip_bounds,
transformation,
});
}
Primitive::Editor {
editor,
position,
color,
clip_bounds,
} => {
let layer = &mut layers[current_layer];
self.live[self.current].text.push(paragraph);
}
layer.text.push(Text::Editor {
editor: editor.clone(),
position: *position,
color: *color,
clip_bounds: *clip_bounds,
transformation,
});
pub fn draw_editor(
&mut self,
editor: &Editor,
position: Point,
color: Color,
clip_bounds: Rectangle,
) {
let paragraph = Text::Editor {
editor: editor.downgrade(),
position,
color,
clip_bounds,
transformation: self.transformations.last().copied().unwrap(),
};
self.live[self.current].text.push(paragraph);
}
pub fn draw_text(
&mut self,
text: crate::core::Text,
position: Point,
color: Color,
clip_bounds: Rectangle,
) {
let transformation = self.transformation();
let paragraph = Text::Cached {
content: text.content,
bounds: Rectangle::new(position, text.bounds) * transformation,
color,
size: text.size * transformation.scale_factor(),
line_height: text.line_height,
font: text.font,
horizontal_alignment: text.horizontal_alignment,
vertical_alignment: text.vertical_alignment,
shaping: text.shaping,
clip_bounds: clip_bounds * transformation,
};
self.live[self.current].text.push(paragraph);
}
pub fn draw_image(
&mut self,
handle: crate::core::image::Handle,
filter_method: crate::core::image::FilterMethod,
bounds: Rectangle,
) {
let image = Image::Raster {
handle,
filter_method,
bounds: bounds * self.transformation(),
};
self.live[self.current].images.push(image);
}
pub fn draw_svg(
&mut self,
handle: crate::core::svg::Handle,
color: Option<Color>,
bounds: Rectangle,
) {
let svg = Image::Vector {
handle,
color,
bounds: bounds * self.transformation(),
};
self.live[self.current].images.push(svg);
}
pub fn draw_mesh(&mut self, mut mesh: Mesh) {
match &mut mesh {
Mesh::Solid { transformation, .. }
| Mesh::Gradient { transformation, .. } => {
*transformation = *transformation * self.transformation();
}
Primitive::Text {
content,
}
self.live[self.current].meshes.push(mesh);
}
pub fn draw_layer(&mut self, mut layer: Live) {
layer.transformation = layer.transformation * self.transformation();
if self.live_count == self.live.len() {
self.live.push(layer);
} else {
self.live[self.live_count] = layer;
}
self.live_count += 1;
self.order.push(Kind::Live);
}
pub fn draw_cached_layer(&mut self, layer: &Rc<RefCell<Cached>>) {
{
let mut layer = layer.borrow_mut();
layer.transformation = self.transformation() * layer.transformation;
}
self.cached.push(layer.clone());
self.order.push(Kind::Cache);
}
pub fn push_clip(&mut self, bounds: Option<Rectangle>) {
self.previous.push(self.current);
self.order.push(Kind::Live);
self.current = self.live_count;
self.live_count += 1;
let bounds = bounds.map(|bounds| bounds * self.transformation());
if self.current == self.live.len() {
self.live.push(Live {
bounds,
size,
line_height,
color,
font,
horizontal_alignment,
vertical_alignment,
shaping,
clip_bounds,
} => {
let layer = &mut layers[current_layer];
..Live::default()
});
} else {
self.live[self.current].bounds = bounds;
}
}
layer.text.push(Text::Cached(text::Cached {
content,
bounds: *bounds + transformation.translation(),
size: *size * transformation.scale_factor(),
line_height: *line_height,
color: *color,
font: *font,
horizontal_alignment: *horizontal_alignment,
vertical_alignment: *vertical_alignment,
shaping: *shaping,
clip_bounds: *clip_bounds * transformation,
}));
pub fn pop_clip(&mut self) {
self.current = self.previous.pop().unwrap();
}
pub fn push_transformation(&mut self, transformation: Transformation) {
self.transformations
.push(self.transformation() * transformation);
}
pub fn pop_transformation(&mut self) {
let _ = self.transformations.pop();
}
fn transformation(&self) -> Transformation {
self.transformations.last().copied().unwrap()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = LayerMut<'_>> {
let mut live = self.live.iter_mut();
let mut cached = self.cached.iter_mut();
self.order.iter().map(move |kind| match kind {
Kind::Live => LayerMut::Live(live.next().unwrap()),
Kind::Cache => {
LayerMut::Cached(cached.next().unwrap().borrow_mut())
}
graphics::Primitive::RawText(raw) => {
let layer = &mut layers[current_layer];
})
}
layer.text.push(Text::Raw {
raw: raw.clone(),
transformation,
});
}
Primitive::Quad {
bounds,
background,
border,
shadow,
} => {
let layer = &mut layers[current_layer];
let bounds = *bounds * transformation;
pub fn iter(&self) -> impl Iterator<Item = Layer<'_>> {
let mut live = self.live.iter();
let mut cached = self.cached.iter();
let quad = Quad {
position: [bounds.x, bounds.y],
size: [bounds.width, bounds.height],
border_color: color::pack(border.color),
border_radius: border.radius.into(),
border_width: border.width,
shadow_color: shadow.color.into_linear(),
shadow_offset: shadow.offset.into(),
shadow_blur_radius: shadow.blur_radius,
};
self.order.iter().map(move |kind| match kind {
Kind::Live => Layer::Live(live.next().unwrap()),
Kind::Cache => Layer::Cached(cached.next().unwrap().borrow()),
})
}
layer.quads.add(quad, background);
}
Primitive::Image {
handle,
filter_method,
bounds,
} => {
let layer = &mut layers[current_layer];
pub fn clear(&mut self) {
for live in &mut self.live[..self.live_count] {
live.bounds = None;
live.transformation = Transformation::IDENTITY;
layer.images.push(Image::Raster {
handle: handle.clone(),
filter_method: *filter_method,
bounds: *bounds * transformation,
});
}
Primitive::Svg {
handle,
color,
bounds,
} => {
let layer = &mut layers[current_layer];
live.quads.clear();
live.meshes.clear();
live.text.clear();
live.images.clear();
}
layer.images.push(Image::Vector {
handle: handle.clone(),
color: *color,
bounds: *bounds * transformation,
});
}
Primitive::Group { primitives } => {
// TODO: Inspect a bit and regroup (?)
for primitive in primitives {
Self::process_primitive(
layers,
transformation,
primitive,
current_layer,
);
}
}
Primitive::Clip { bounds, content } => {
let layer = &mut layers[current_layer];
let translated_bounds = *bounds * transformation;
self.current = 0;
self.live_count = 1;
// 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.order.clear();
self.order.push(Kind::Live);
Self::process_primitive(
layers,
transformation,
content,
layers.len() - 1,
);
}
}
Primitive::Transform {
transformation: new_transformation,
content,
} => {
Self::process_primitive(
layers,
transformation * *new_transformation,
content,
current_layer,
);
}
Primitive::Cache { content } => {
Self::process_primitive(
layers,
transformation,
content,
current_layer,
);
}
Primitive::Custom(custom) => match custom {
primitive::Custom::Mesh(mesh) => match mesh {
graphics::Mesh::Solid { buffers, size } => {
let layer = &mut layers[current_layer];
self.cached.clear();
self.previous.clear();
}
}
let bounds =
Rectangle::with_size(*size) * transformation;
impl Default for Stack {
fn default() -> Self {
Self::new()
}
}
// Only draw visible content
if let Some(clip_bounds) =
layer.bounds.intersection(&bounds)
{
layer.meshes.push(Mesh::Solid {
transformation,
buffers,
clip_bounds,
});
}
}
graphics::Mesh::Gradient { buffers, size } => {
let layer = &mut layers[current_layer];
#[derive(Default)]
pub struct Live {
pub bounds: Option<Rectangle>,
pub transformation: Transformation,
pub quads: quad::Batch,
pub meshes: triangle::Batch,
pub text: text::Batch,
pub images: image::Batch,
}
let bounds =
Rectangle::with_size(*size) * transformation;
// Only draw visible content
if let Some(clip_bounds) =
layer.bounds.intersection(&bounds)
{
layer.meshes.push(Mesh::Gradient {
transformation,
buffers,
clip_bounds,
});
}
}
},
primitive::Custom::Pipeline(pipeline) => {
let layer = &mut layers[current_layer];
let bounds = pipeline.bounds * transformation;
if let Some(clip_bounds) =
layer.bounds.intersection(&bounds)
{
layer.pipelines.push(Pipeline {
bounds,
viewport: clip_bounds,
primitive: pipeline.primitive.clone(),
});
}
}
},
impl Live {
pub fn into_cached(self) -> Cached {
Cached {
bounds: self.bounds,
transformation: self.transformation,
last_transformation: None,
quads: quad::Cache::Staged(self.quads),
meshes: triangle::Cache::Staged(self.meshes),
text: text::Cache::Staged(self.text),
images: self.images,
}
}
}
#[derive(Default)]
pub struct Cached {
pub bounds: Option<Rectangle>,
pub transformation: Transformation,
pub last_transformation: Option<Transformation>,
pub quads: quad::Cache,
pub meshes: triangle::Cache,
pub text: text::Cache,
pub images: image::Batch,
}
impl Cached {
pub fn update(&mut self, live: Live) {
self.bounds = live.bounds;
self.transformation = live.transformation;
self.quads.update(live.quads);
self.meshes.update(live.meshes);
self.text.update(live.text);
self.images = live.images;
}
}
enum Kind {
Live,
Cache,
}

View file

@ -1,30 +0,0 @@
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 filter method of a raster image.
filter_method: image::FilterMethod,
/// 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,
},
}

View file

@ -1,97 +0,0 @@
//! A collection of triangle primitives.
use crate::core::{Rectangle, Transformation};
use crate::graphics::mesh;
/// A mesh of triangles.
#[derive(Debug, Clone, Copy)]
pub enum Mesh<'a> {
/// A mesh of triangles with a solid color.
Solid {
/// The [`Transformation`] for the vertices of the [`Mesh`].
transformation: Transformation,
/// The vertex and index buffers of the [`Mesh`].
buffers: &'a mesh::Indexed<mesh::SolidVertex2D>,
/// The clipping bounds of the [`Mesh`].
clip_bounds: Rectangle<f32>,
},
/// A mesh of triangles with a gradient color.
Gradient {
/// The [`Transformation`] for the vertices of the [`Mesh`].
transformation: Transformation,
/// The vertex and index buffers of the [`Mesh`].
buffers: &'a mesh::Indexed<mesh::GradientVertex2D>,
/// The clipping bounds of the [`Mesh`].
clip_bounds: Rectangle<f32>,
},
}
impl Mesh<'_> {
/// Returns the origin of the [`Mesh`].
pub fn transformation(&self) -> Transformation {
match self {
Self::Solid { transformation, .. }
| Self::Gradient { transformation, .. } => *transformation,
}
}
/// 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 solid meshes.
pub solids: usize,
/// The total amount of gradient vertices.
pub gradient_vertices: usize,
/// The total amount of gradient meshes.
pub gradients: 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.solids += 1;
count.solid_vertices += buffers.vertices.len();
count.indices += buffers.indices.len();
}
Mesh::Gradient { buffers, .. } => {
count.gradients += 1;
count.gradient_vertices += buffers.vertices.len();
count.indices += buffers.indices.len();
}
}
count
})
}

View file

@ -1,17 +0,0 @@
use crate::core::Rectangle;
use crate::primitive::pipeline::Primitive;
use std::sync::Arc;
#[derive(Clone, Debug)]
/// A custom primitive which can be used to render primitives associated with a custom pipeline.
pub struct Pipeline {
/// The bounds of the [`Pipeline`].
pub bounds: Rectangle,
/// The viewport of the [`Pipeline`].
pub viewport: Rectangle,
/// The [`Primitive`] to render.
pub primitive: Arc<dyn Primitive>,
}

View file

@ -1,70 +0,0 @@
use crate::core::alignment;
use crate::core::text;
use crate::core::{Color, Font, Pixels, Point, Rectangle, Transformation};
use crate::graphics;
use crate::graphics::text::editor;
use crate::graphics::text::paragraph;
/// A text primitive.
#[derive(Debug, Clone)]
pub enum Text<'a> {
/// A paragraph.
#[allow(missing_docs)]
Paragraph {
paragraph: paragraph::Weak,
position: Point,
color: Color,
clip_bounds: Rectangle,
transformation: Transformation,
},
/// An editor.
#[allow(missing_docs)]
Editor {
editor: editor::Weak,
position: Point,
color: Color,
clip_bounds: Rectangle,
transformation: Transformation,
},
/// Some cached text.
Cached(Cached<'a>),
/// Some raw text.
#[allow(missing_docs)]
Raw {
raw: graphics::text::Raw,
transformation: Transformation,
},
}
#[derive(Debug, Clone)]
pub struct Cached<'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: Pixels,
/// 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,
/// The clip bounds of the text.
pub clip_bounds: Rectangle,
}

View file

@ -22,8 +22,8 @@
)]
#![forbid(rust_2018_idioms)]
#![deny(
missing_debug_implementations,
missing_docs,
// missing_debug_implementations,
//missing_docs,
unsafe_code,
unused_results,
rustdoc::broken_intra_doc_links
@ -37,13 +37,21 @@ pub mod window;
#[cfg(feature = "geometry")]
pub mod geometry;
mod backend;
mod buffer;
mod color;
mod engine;
mod quad;
mod text;
mod triangle;
#[cfg(any(feature = "image", feature = "svg"))]
#[path = "image/mod.rs"]
mod image;
#[cfg(not(any(feature = "image", feature = "svg")))]
#[path = "image/null.rs"]
mod image;
use buffer::Buffer;
pub use iced_graphics as graphics;
@ -51,16 +59,570 @@ pub use iced_graphics::core;
pub use wgpu;
pub use backend::Backend;
pub use layer::Layer;
pub use engine::Engine;
pub use layer::{Layer, LayerMut};
pub use primitive::Primitive;
pub use settings::Settings;
#[cfg(any(feature = "image", feature = "svg"))]
mod image;
#[cfg(feature = "geometry")]
pub use geometry::Geometry;
use crate::core::{
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
};
use crate::graphics::text::{Editor, Paragraph};
use crate::graphics::Viewport;
use std::borrow::Cow;
/// A [`wgpu`] graphics renderer for [`iced`].
///
/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
/// [`iced`]: https://github.com/iced-rs/iced
pub type Renderer = iced_graphics::Renderer<Backend>;
#[allow(missing_debug_implementations)]
pub struct Renderer {
default_font: Font,
default_text_size: Pixels,
layers: layer::Stack,
// TODO: Centralize all the image feature handling
#[cfg(any(feature = "svg", feature = "image"))]
image_cache: image::cache::Shared,
}
impl Renderer {
pub fn new(settings: Settings, _engine: &Engine) -> Self {
Self {
default_font: settings.default_font,
default_text_size: settings.default_text_size,
layers: layer::Stack::new(),
#[cfg(any(feature = "svg", feature = "image"))]
image_cache: _engine.image_cache().clone(),
}
}
pub fn draw_primitive(&mut self, _primitive: Primitive) {}
pub fn present<T: AsRef<str>>(
&mut self,
engine: &mut Engine,
device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
clear_color: Option<Color>,
format: wgpu::TextureFormat,
frame: &wgpu::TextureView,
viewport: &Viewport,
overlay: &[T],
) {
let target_size = viewport.physical_size();
let scale_factor = viewport.scale_factor() as f32;
let transformation = viewport.projection();
for line in overlay {
println!("{}", line.as_ref());
}
self.prepare(
engine,
device,
queue,
format,
encoder,
scale_factor,
target_size,
transformation,
);
self.render(
engine,
device,
encoder,
frame,
clear_color,
scale_factor,
target_size,
);
}
fn prepare(
&mut self,
engine: &mut Engine,
device: &wgpu::Device,
queue: &wgpu::Queue,
_format: wgpu::TextureFormat,
encoder: &mut wgpu::CommandEncoder,
scale_factor: f32,
target_size: Size<u32>,
transformation: Transformation,
) {
for layer in self.layers.iter_mut() {
match layer {
LayerMut::Live(live) => {
if !live.quads.is_empty() {
engine.quad_pipeline.prepare_batch(
device,
encoder,
&mut engine.staging_belt,
&live.quads,
transformation,
scale_factor,
);
}
if !live.meshes.is_empty() {
engine.triangle_pipeline.prepare_batch(
device,
encoder,
&mut engine.staging_belt,
&live.meshes,
transformation
* Transformation::scale(scale_factor),
);
}
if !live.text.is_empty() {
engine.text_pipeline.prepare_batch(
device,
queue,
encoder,
&live.text,
live.bounds.unwrap_or(Rectangle::with_size(
Size::INFINITY,
)),
scale_factor,
target_size,
);
}
#[cfg(any(feature = "svg", feature = "image"))]
if !live.images.is_empty() {
engine.image_pipeline.prepare(
device,
encoder,
&mut engine.staging_belt,
&live.images,
transformation,
scale_factor,
);
}
}
LayerMut::Cached(mut cached) => {
if !cached.quads.is_empty() {
engine.quad_pipeline.prepare_cache(
device,
encoder,
&mut engine.staging_belt,
&mut cached.quads,
transformation,
scale_factor,
);
}
if !cached.meshes.is_empty() {
engine.triangle_pipeline.prepare_cache(
device,
encoder,
&mut engine.staging_belt,
&mut cached.meshes,
transformation
* Transformation::scale(scale_factor),
);
}
if !cached.text.is_empty() {
let bounds = cached
.bounds
.unwrap_or(Rectangle::with_size(Size::INFINITY));
engine.text_pipeline.prepare_cache(
device,
queue,
encoder,
&mut cached.text,
bounds,
scale_factor,
target_size,
);
}
#[cfg(any(feature = "svg", feature = "image"))]
if !cached.images.is_empty() {
engine.image_pipeline.prepare(
device,
encoder,
&mut engine.staging_belt,
&cached.images,
transformation,
scale_factor,
);
}
}
}
}
}
fn render(
&mut self,
engine: &mut Engine,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
frame: &wgpu::TextureView,
clear_color: Option<Color>,
scale_factor: f32,
target_size: Size<u32>,
) {
use std::mem::ManuallyDrop;
let mut render_pass = ManuallyDrop::new(encoder.begin_render_pass(
&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu render pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: frame,
resolve_target: None,
ops: wgpu::Operations {
load: match clear_color {
Some(background_color) => wgpu::LoadOp::Clear({
let [r, g, b, a] =
graphics::color::pack(background_color)
.components();
wgpu::Color {
r: f64::from(r),
g: f64::from(g),
b: f64::from(b),
a: f64::from(a),
}
}),
None => wgpu::LoadOp::Load,
},
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
},
));
let mut quad_layer = 0;
let mut mesh_layer = 0;
let mut text_layer = 0;
#[cfg(any(feature = "svg", feature = "image"))]
let mut image_layer = 0;
// TODO: Can we avoid collecting here?
let layers: Vec<_> = self.layers.iter().collect();
for layer in &layers {
match layer {
Layer::Live(live) => {
let bounds = live
.bounds
.map(|bounds| bounds * scale_factor)
.map(Rectangle::snap)
.unwrap_or(Rectangle::with_size(target_size));
if !live.quads.is_empty() {
engine.quad_pipeline.render_batch(
quad_layer,
bounds,
&live.quads,
&mut render_pass,
);
quad_layer += 1;
}
if !live.meshes.is_empty() {
let _ = ManuallyDrop::into_inner(render_pass);
engine.triangle_pipeline.render_batch(
device,
encoder,
frame,
mesh_layer,
target_size,
&live.meshes,
bounds,
scale_factor,
);
mesh_layer += 1;
render_pass =
ManuallyDrop::new(encoder.begin_render_pass(
&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu render pass"),
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view: frame,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
},
)],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
},
));
}
if !live.text.is_empty() {
engine.text_pipeline.render_batch(
text_layer,
bounds,
&mut render_pass,
);
text_layer += 1;
}
#[cfg(any(feature = "svg", feature = "image"))]
if !live.images.is_empty() {
engine.image_pipeline.render(
image_layer,
bounds,
&mut render_pass,
);
image_layer += 1;
}
}
Layer::Cached(cached) => {
let bounds = cached
.bounds
.map(|bounds| bounds * scale_factor)
.map(Rectangle::snap)
.unwrap_or(Rectangle::with_size(target_size));
if !cached.quads.is_empty() {
engine.quad_pipeline.render_cache(
&cached.quads,
bounds,
&mut render_pass,
);
}
if !cached.meshes.is_empty() {
let _ = ManuallyDrop::into_inner(render_pass);
engine.triangle_pipeline.render_cache(
device,
encoder,
frame,
target_size,
&cached.meshes,
bounds,
scale_factor,
);
render_pass =
ManuallyDrop::new(encoder.begin_render_pass(
&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu render pass"),
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view: frame,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
},
)],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
},
));
}
if !cached.text.is_empty() {
engine.text_pipeline.render_cache(
&cached.text,
bounds,
&mut render_pass,
);
}
#[cfg(any(feature = "svg", feature = "image"))]
if !cached.images.is_empty() {
engine.image_pipeline.render(
image_layer,
bounds,
&mut render_pass,
);
image_layer += 1;
}
}
}
}
let _ = ManuallyDrop::into_inner(render_pass);
}
}
impl core::Renderer for Renderer {
fn start_layer(&mut self, bounds: Rectangle) {
self.layers.push_clip(Some(bounds));
}
fn end_layer(&mut self, _bounds: Rectangle) {
self.layers.pop_clip();
}
fn start_transformation(&mut self, transformation: Transformation) {
self.layers.push_transformation(transformation);
}
fn end_transformation(&mut self, _transformation: Transformation) {
self.layers.pop_transformation();
}
fn fill_quad(
&mut self,
quad: core::renderer::Quad,
background: impl Into<Background>,
) {
self.layers.draw_quad(quad, background.into());
}
fn clear(&mut self) {
self.layers.clear();
}
}
impl core::text::Renderer for Renderer {
type Font = Font;
type Paragraph = Paragraph;
type Editor = Editor;
const ICON_FONT: Font = Font::with_name("Iced-Icons");
const CHECKMARK_ICON: char = '\u{f00c}';
const ARROW_DOWN_ICON: char = '\u{e800}';
fn default_font(&self) -> Self::Font {
self.default_font
}
fn default_size(&self) -> Pixels {
self.default_text_size
}
fn load_font(&mut self, font: Cow<'static, [u8]>) {
graphics::text::font_system()
.write()
.expect("Write font system")
.load_font(font);
// TODO: Invalidate buffer cache
}
fn fill_paragraph(
&mut self,
text: &Self::Paragraph,
position: Point,
color: Color,
clip_bounds: Rectangle,
) {
self.layers
.draw_paragraph(text, position, color, clip_bounds);
}
fn fill_editor(
&mut self,
editor: &Self::Editor,
position: Point,
color: Color,
clip_bounds: Rectangle,
) {
self.layers
.draw_editor(editor, position, color, clip_bounds);
}
fn fill_text(
&mut self,
text: core::Text,
position: Point,
color: Color,
clip_bounds: Rectangle,
) {
self.layers.draw_text(text, position, color, clip_bounds);
}
}
#[cfg(feature = "image")]
impl core::image::Renderer for Renderer {
type Handle = core::image::Handle;
fn measure_image(&self, handle: &Self::Handle) -> Size<u32> {
self.image_cache.lock().measure_image(handle)
}
fn draw_image(
&mut self,
handle: Self::Handle,
filter_method: core::image::FilterMethod,
bounds: Rectangle,
) {
self.layers.draw_image(handle, filter_method, bounds);
}
}
#[cfg(feature = "svg")]
impl core::svg::Renderer for Renderer {
fn measure_svg(&self, handle: &core::svg::Handle) -> Size<u32> {
self.image_cache.lock().measure_svg(handle)
}
fn draw_svg(
&mut self,
handle: core::svg::Handle,
color_filter: Option<Color>,
bounds: Rectangle,
) {
self.layers.draw_svg(handle, color_filter, bounds);
}
}
impl graphics::mesh::Renderer for Renderer {
fn draw_mesh(&mut self, mesh: graphics::Mesh) {
self.layers.draw_mesh(mesh);
}
}
#[cfg(feature = "geometry")]
impl graphics::geometry::Renderer for Renderer {
type Geometry = Geometry;
type Frame = geometry::Frame;
fn new_frame(&self, size: Size) -> Self::Frame {
geometry::Frame::new(size)
}
fn draw_geometry(&mut self, geometry: Self::Geometry) {
match geometry {
Geometry::Live(layers) => {
for layer in layers {
self.layers.draw_layer(layer);
}
}
Geometry::Cached(layers) => {
for layer in layers.as_ref() {
self.layers.draw_cached_layer(layer);
}
}
}
}
}
impl graphics::compositor::Default for crate::Renderer {
type Compositor = window::Compositor;
}

View file

@ -3,8 +3,7 @@ pub mod pipeline;
pub use pipeline::Pipeline;
use crate::core::Rectangle;
use crate::graphics::{Damage, Mesh};
use crate::graphics::Mesh;
use std::fmt::Debug;
@ -19,20 +18,3 @@ pub enum Custom {
/// A custom pipeline primitive.
Pipeline(Pipeline),
}
impl Damage for Custom {
fn bounds(&self) -> Rectangle {
match self {
Self::Mesh(mesh) => mesh.bounds(),
Self::Pipeline(pipeline) => pipeline.bounds,
}
}
}
impl TryFrom<Mesh> for Custom {
type Error = &'static str;
fn try_from(mesh: Mesh) -> Result<Self, Self::Error> {
Ok(Custom::Mesh(mesh))
}
}

View file

@ -12,11 +12,37 @@ use bytemuck::{Pod, Zeroable};
use std::mem;
#[cfg(feature = "tracing")]
use tracing::info_span;
const INITIAL_INSTANCES: usize = 2_000;
/// The properties of a quad.
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
#[repr(C)]
pub struct Quad {
/// The position of the [`Quad`].
pub position: [f32; 2],
/// The size of the [`Quad`].
pub size: [f32; 2],
/// The border color of the [`Quad`], in __linear RGB__.
pub border_color: color::Packed,
/// The border radii of the [`Quad`].
pub border_radius: [f32; 4],
/// The border width of the [`Quad`].
pub border_width: f32,
/// The shadow color of the [`Quad`].
pub shadow_color: color::Packed,
/// The shadow offset of the [`Quad`].
pub shadow_offset: [f32; 2],
/// The shadow blur radius of the [`Quad`].
pub shadow_blur_radius: f32,
}
#[derive(Debug)]
pub struct Pipeline {
solid: solid::Pipeline,
@ -54,7 +80,7 @@ impl Pipeline {
}
}
pub fn prepare(
pub fn prepare_batch(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
@ -73,7 +99,64 @@ impl Pipeline {
self.prepare_layer += 1;
}
pub fn render<'a>(
pub fn prepare_cache(
&self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
cache: &mut Cache,
transformation: Transformation,
scale: f32,
) {
match cache {
Cache::Staged(_) => {
let Cache::Staged(batch) =
std::mem::replace(cache, Cache::Staged(Batch::default()))
else {
unreachable!()
};
let mut layer = Layer::new(device, &self.constant_layout);
layer.prepare(
device,
encoder,
belt,
&batch,
transformation,
scale,
);
*cache = Cache::Uploaded {
layer,
batch,
needs_reupload: false,
}
}
Cache::Uploaded {
batch,
layer,
needs_reupload,
} => {
if *needs_reupload {
layer.prepare(
device,
encoder,
belt,
batch,
transformation,
scale,
);
*needs_reupload = false;
} else {
layer.update(device, encoder, belt, transformation, scale);
}
}
}
}
pub fn render_batch<'a>(
&'a self,
layer: usize,
bounds: Rectangle<u32>,
@ -81,38 +164,59 @@ impl Pipeline {
render_pass: &mut wgpu::RenderPass<'a>,
) {
if let Some(layer) = self.layers.get(layer) {
render_pass.set_scissor_rect(
bounds.x,
bounds.y,
bounds.width,
bounds.height,
);
self.render(bounds, layer, &quads.order, render_pass);
}
}
let mut solid_offset = 0;
let mut gradient_offset = 0;
pub fn render_cache<'a>(
&'a self,
cache: &'a Cache,
bounds: Rectangle<u32>,
render_pass: &mut wgpu::RenderPass<'a>,
) {
if let Cache::Uploaded { layer, batch, .. } = cache {
self.render(bounds, layer, &batch.order, render_pass);
}
}
for (kind, count) in &quads.order {
match kind {
Kind::Solid => {
self.solid.render(
render_pass,
&layer.constants,
&layer.solid,
solid_offset..(solid_offset + count),
);
fn render<'a>(
&'a self,
bounds: Rectangle<u32>,
layer: &'a Layer,
order: &Order,
render_pass: &mut wgpu::RenderPass<'a>,
) {
render_pass.set_scissor_rect(
bounds.x,
bounds.y,
bounds.width,
bounds.height,
);
solid_offset += count;
}
Kind::Gradient => {
self.gradient.render(
render_pass,
&layer.constants,
&layer.gradient,
gradient_offset..(gradient_offset + count),
);
let mut solid_offset = 0;
let mut gradient_offset = 0;
gradient_offset += count;
}
for (kind, count) in order {
match kind {
Kind::Solid => {
self.solid.render(
render_pass,
&layer.constants,
&layer.solid,
solid_offset..(solid_offset + count),
);
solid_offset += count;
}
Kind::Gradient => {
self.gradient.render(
render_pass,
&layer.constants,
&layer.gradient,
gradient_offset..(gradient_offset + count),
);
gradient_offset += count;
}
}
}
@ -124,7 +228,49 @@ impl Pipeline {
}
#[derive(Debug)]
struct Layer {
pub enum Cache {
Staged(Batch),
Uploaded {
batch: Batch,
layer: Layer,
needs_reupload: bool,
},
}
impl Cache {
pub fn is_empty(&self) -> bool {
match self {
Cache::Staged(batch) | Cache::Uploaded { batch, .. } => {
batch.is_empty()
}
}
}
pub fn update(&mut self, new_batch: Batch) {
match self {
Self::Staged(batch) => {
*batch = new_batch;
}
Self::Uploaded {
batch,
needs_reupload,
..
} => {
*batch = new_batch;
*needs_reupload = true;
}
}
}
}
impl Default for Cache {
fn default() -> Self {
Self::Staged(Batch::default())
}
}
#[derive(Debug)]
pub struct Layer {
constants: wgpu::BindGroup,
constants_buffer: wgpu::Buffer,
solid: solid::Layer,
@ -169,9 +315,26 @@ impl Layer {
transformation: Transformation,
scale: f32,
) {
#[cfg(feature = "tracing")]
let _ = info_span!("Wgpu::Quad", "PREPARE").entered();
self.update(device, encoder, belt, transformation, scale);
if !quads.solids.is_empty() {
self.solid.prepare(device, encoder, belt, &quads.solids);
}
if !quads.gradients.is_empty() {
self.gradient
.prepare(device, encoder, belt, &quads.gradients);
}
}
pub fn update(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
transformation: Transformation,
scale: f32,
) {
let uniforms = Uniforms::new(transformation, scale);
let bytes = bytemuck::bytes_of(&uniforms);
@ -183,47 +346,9 @@ impl Layer {
device,
)
.copy_from_slice(bytes);
if !quads.solids.is_empty() {
self.solid.prepare(device, encoder, belt, &quads.solids);
}
if !quads.gradients.is_empty() {
self.gradient
.prepare(device, encoder, belt, &quads.gradients);
}
}
}
/// The properties of a quad.
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
#[repr(C)]
pub struct Quad {
/// The position of the [`Quad`].
pub position: [f32; 2],
/// The size of the [`Quad`].
pub size: [f32; 2],
/// The border color of the [`Quad`], in __linear RGB__.
pub border_color: color::Packed,
/// The border radii of the [`Quad`].
pub border_radius: [f32; 4],
/// The border width of the [`Quad`].
pub border_width: f32,
/// The shadow color of the [`Quad`].
pub shadow_color: [f32; 4],
/// The shadow offset of the [`Quad`].
pub shadow_offset: [f32; 2],
/// The shadow blur radius of the [`Quad`].
pub shadow_blur_radius: f32,
}
/// A group of [`Quad`]s rendered together.
#[derive(Default, Debug)]
pub struct Batch {
@ -233,10 +358,13 @@ pub struct Batch {
/// The gradient quads of the [`Layer`].
gradients: Vec<Gradient>,
/// The quad order of the [`Layer`]; stored as a tuple of the quad type & its count.
order: Vec<(Kind, usize)>,
/// The quad order of the [`Layer`].
order: Order,
}
/// The quad order of a [`Layer`]; stored as a tuple of the quad type & its count.
type Order = Vec<(Kind, usize)>;
impl Batch {
/// Returns true if there are no quads of any type in [`Quads`].
pub fn is_empty(&self) -> bool {
@ -276,6 +404,12 @@ impl Batch {
}
}
}
pub fn clear(&mut self) {
self.solids.clear();
self.gradients.clear();
self.order.clear();
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]

View file

@ -1,20 +1,67 @@
use crate::core::alignment;
use crate::core::{Rectangle, Size, Transformation};
use crate::graphics::color;
use crate::graphics::text::cache::{self, Cache};
use crate::graphics::text::cache::{self, Cache as BufferCache};
use crate::graphics::text::{font_system, to_color, Editor, Paragraph};
use crate::layer::Text;
use std::borrow::Cow;
use std::cell::RefCell;
use std::sync::Arc;
pub use crate::graphics::Text;
pub type Batch = Vec<Text>;
#[allow(missing_debug_implementations)]
pub struct Pipeline {
renderers: Vec<glyphon::TextRenderer>,
format: wgpu::TextureFormat,
atlas: glyphon::TextAtlas,
renderers: Vec<glyphon::TextRenderer>,
prepare_layer: usize,
cache: RefCell<Cache>,
cache: BufferCache,
}
pub enum Cache {
Staged(Batch),
Uploaded {
batch: Batch,
renderer: glyphon::TextRenderer,
atlas: Option<glyphon::TextAtlas>,
buffer_cache: Option<BufferCache>,
scale_factor: f32,
target_size: Size<u32>,
needs_reupload: bool,
},
}
impl Cache {
pub fn is_empty(&self) -> bool {
match self {
Cache::Staged(batch) | Cache::Uploaded { batch, .. } => {
batch.is_empty()
}
}
}
pub fn update(&mut self, new_batch: Batch) {
match self {
Self::Staged(batch) => {
*batch = new_batch;
}
Self::Uploaded {
batch,
needs_reupload,
..
} => {
*batch = new_batch;
*needs_reupload = true;
}
}
}
}
impl Default for Cache {
fn default() -> Self {
Self::Staged(Batch::default())
}
}
impl Pipeline {
@ -24,6 +71,7 @@ impl Pipeline {
format: wgpu::TextureFormat,
) -> Self {
Pipeline {
format,
renderers: Vec::new(),
atlas: glyphon::TextAtlas::with_color_mode(
device,
@ -36,25 +84,16 @@ impl Pipeline {
},
),
prepare_layer: 0,
cache: RefCell::new(Cache::new()),
cache: BufferCache::new(),
}
}
pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
font_system()
.write()
.expect("Write font system")
.load_font(bytes);
self.cache = RefCell::new(Cache::new());
}
pub fn prepare(
pub fn prepare_batch(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
sections: &[Text<'_>],
sections: &Batch,
layer_bounds: Rectangle,
scale_factor: f32,
target_size: Size<u32>,
@ -68,210 +107,18 @@ impl Pipeline {
));
}
let mut font_system = font_system().write().expect("Write font system");
let font_system = font_system.raw();
let renderer = &mut self.renderers[self.prepare_layer];
let cache = self.cache.get_mut();
enum Allocation {
Paragraph(Paragraph),
Editor(Editor),
Cache(cache::KeyHash),
Raw(Arc<glyphon::Buffer>),
}
let allocations: Vec<_> = sections
.iter()
.map(|section| match section {
Text::Paragraph { paragraph, .. } => {
paragraph.upgrade().map(Allocation::Paragraph)
}
Text::Editor { editor, .. } => {
editor.upgrade().map(Allocation::Editor)
}
Text::Cached(text) => {
let (key, _) = cache.allocate(
font_system,
cache::Key {
content: text.content,
size: text.size.into(),
line_height: f32::from(
text.line_height.to_absolute(text.size),
),
font: text.font,
bounds: Size {
width: text.bounds.width,
height: text.bounds.height,
},
shaping: text.shaping,
},
);
Some(Allocation::Cache(key))
}
Text::Raw { raw, .. } => {
raw.buffer.upgrade().map(Allocation::Raw)
}
})
.collect();
let layer_bounds = layer_bounds * scale_factor;
let text_areas = sections.iter().zip(allocations.iter()).filter_map(
|(section, allocation)| {
let (
buffer,
bounds,
horizontal_alignment,
vertical_alignment,
color,
clip_bounds,
transformation,
) = match section {
Text::Paragraph {
position,
color,
clip_bounds,
transformation,
..
} => {
use crate::core::text::Paragraph as _;
let Some(Allocation::Paragraph(paragraph)) = allocation
else {
return None;
};
(
paragraph.buffer(),
Rectangle::new(*position, paragraph.min_bounds()),
paragraph.horizontal_alignment(),
paragraph.vertical_alignment(),
*color,
*clip_bounds,
*transformation,
)
}
Text::Editor {
position,
color,
clip_bounds,
transformation,
..
} => {
use crate::core::text::Editor as _;
let Some(Allocation::Editor(editor)) = allocation
else {
return None;
};
(
editor.buffer(),
Rectangle::new(*position, editor.bounds()),
alignment::Horizontal::Left,
alignment::Vertical::Top,
*color,
*clip_bounds,
*transformation,
)
}
Text::Cached(text) => {
let Some(Allocation::Cache(key)) = allocation else {
return None;
};
let entry = cache.get(key).expect("Get cached buffer");
(
&entry.buffer,
Rectangle::new(
text.bounds.position(),
entry.min_bounds,
),
text.horizontal_alignment,
text.vertical_alignment,
text.color,
text.clip_bounds,
Transformation::IDENTITY,
)
}
Text::Raw {
raw,
transformation,
} => {
let Some(Allocation::Raw(buffer)) = allocation else {
return None;
};
let (width, height) = buffer.size();
(
buffer.as_ref(),
Rectangle::new(
raw.position,
Size::new(width, height),
),
alignment::Horizontal::Left,
alignment::Vertical::Top,
raw.color,
raw.clip_bounds,
*transformation,
)
}
};
let bounds = bounds * transformation * scale_factor;
let left = match horizontal_alignment {
alignment::Horizontal::Left => bounds.x,
alignment::Horizontal::Center => {
bounds.x - bounds.width / 2.0
}
alignment::Horizontal::Right => bounds.x - bounds.width,
};
let top = match vertical_alignment {
alignment::Vertical::Top => bounds.y,
alignment::Vertical::Center => {
bounds.y - bounds.height / 2.0
}
alignment::Vertical::Bottom => bounds.y - bounds.height,
};
let clip_bounds = layer_bounds.intersection(
&(clip_bounds * transformation * scale_factor),
)?;
Some(glyphon::TextArea {
buffer,
left,
top,
scale: scale_factor * transformation.scale_factor(),
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: to_color(color),
})
},
);
let result = renderer.prepare(
let result = prepare(
device,
queue,
encoder,
font_system,
renderer,
&mut self.atlas,
glyphon::Resolution {
width: target_size.width,
height: target_size.height,
},
text_areas,
&mut glyphon::SwashCache::new(),
&mut self.cache,
sections,
layer_bounds,
scale_factor,
target_size,
);
match result {
@ -286,7 +133,109 @@ impl Pipeline {
}
}
pub fn render<'a>(
pub fn prepare_cache(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
cache: &mut Cache,
layer_bounds: Rectangle,
new_scale_factor: f32,
new_target_size: Size<u32>,
) {
match cache {
Cache::Staged(_) => {
let Cache::Staged(batch) =
std::mem::replace(cache, Cache::Staged(Batch::default()))
else {
unreachable!()
};
// TODO: Find a better heuristic (?)
let (mut atlas, mut buffer_cache) = if batch.len() > 10 {
(
Some(glyphon::TextAtlas::with_color_mode(
device,
queue,
self.format,
if color::GAMMA_CORRECTION {
glyphon::ColorMode::Accurate
} else {
glyphon::ColorMode::Web
},
)),
Some(BufferCache::new()),
)
} else {
(None, None)
};
let mut renderer = glyphon::TextRenderer::new(
atlas.as_mut().unwrap_or(&mut self.atlas),
device,
wgpu::MultisampleState::default(),
None,
);
let _ = prepare(
device,
queue,
encoder,
&mut renderer,
atlas.as_mut().unwrap_or(&mut self.atlas),
buffer_cache.as_mut().unwrap_or(&mut self.cache),
&batch,
layer_bounds,
new_scale_factor,
new_target_size,
);
*cache = Cache::Uploaded {
batch,
needs_reupload: false,
renderer,
atlas,
buffer_cache,
scale_factor: new_scale_factor,
target_size: new_target_size,
}
}
Cache::Uploaded {
batch,
needs_reupload,
renderer,
atlas,
buffer_cache,
scale_factor,
target_size,
} => {
if *needs_reupload
|| atlas.is_none()
|| buffer_cache.is_none()
|| new_scale_factor != *scale_factor
|| new_target_size != *target_size
{
let _ = prepare(
device,
queue,
encoder,
renderer,
atlas.as_mut().unwrap_or(&mut self.atlas),
buffer_cache.as_mut().unwrap_or(&mut self.cache),
batch,
layer_bounds,
*scale_factor,
*target_size,
);
*scale_factor = new_scale_factor;
*target_size = new_target_size;
}
}
}
}
pub fn render_batch<'a>(
&'a self,
layer: usize,
bounds: Rectangle<u32>,
@ -306,10 +255,251 @@ impl Pipeline {
.expect("Render text");
}
pub fn render_cache<'a>(
&'a self,
cache: &'a Cache,
bounds: Rectangle<u32>,
render_pass: &mut wgpu::RenderPass<'a>,
) {
let Cache::Uploaded {
renderer, atlas, ..
} = cache
else {
return;
};
render_pass.set_scissor_rect(
bounds.x,
bounds.y,
bounds.width,
bounds.height,
);
renderer
.render(atlas.as_ref().unwrap_or(&self.atlas), render_pass)
.expect("Render text");
}
pub fn end_frame(&mut self) {
self.atlas.trim();
self.cache.get_mut().trim();
self.cache.trim();
self.prepare_layer = 0;
}
}
fn prepare(
device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
renderer: &mut glyphon::TextRenderer,
atlas: &mut glyphon::TextAtlas,
buffer_cache: &mut BufferCache,
sections: &Batch,
layer_bounds: Rectangle,
scale_factor: f32,
target_size: Size<u32>,
) -> Result<(), glyphon::PrepareError> {
let mut font_system = font_system().write().expect("Write font system");
let font_system = font_system.raw();
enum Allocation {
Paragraph(Paragraph),
Editor(Editor),
Cache(cache::KeyHash),
Raw(Arc<glyphon::Buffer>),
}
let allocations: Vec<_> = sections
.iter()
.map(|section| match section {
Text::Paragraph { paragraph, .. } => {
paragraph.upgrade().map(Allocation::Paragraph)
}
Text::Editor { editor, .. } => {
editor.upgrade().map(Allocation::Editor)
}
Text::Cached {
content,
bounds,
size,
line_height,
font,
shaping,
..
} => {
let (key, _) = buffer_cache.allocate(
font_system,
cache::Key {
content,
size: (*size).into(),
line_height: f32::from(line_height.to_absolute(*size)),
font: *font,
bounds: Size {
width: bounds.width,
height: bounds.height,
},
shaping: *shaping,
},
);
Some(Allocation::Cache(key))
}
Text::Raw { raw, .. } => raw.buffer.upgrade().map(Allocation::Raw),
})
.collect();
let layer_bounds = layer_bounds * scale_factor;
let text_areas = sections.iter().zip(allocations.iter()).filter_map(
|(section, allocation)| {
let (
buffer,
bounds,
horizontal_alignment,
vertical_alignment,
color,
clip_bounds,
transformation,
) = match section {
Text::Paragraph {
position,
color,
clip_bounds,
transformation,
..
} => {
use crate::core::text::Paragraph as _;
let Some(Allocation::Paragraph(paragraph)) = allocation
else {
return None;
};
(
paragraph.buffer(),
Rectangle::new(*position, paragraph.min_bounds()),
paragraph.horizontal_alignment(),
paragraph.vertical_alignment(),
*color,
*clip_bounds,
*transformation,
)
}
Text::Editor {
position,
color,
clip_bounds,
transformation,
..
} => {
use crate::core::text::Editor as _;
let Some(Allocation::Editor(editor)) = allocation else {
return None;
};
(
editor.buffer(),
Rectangle::new(*position, editor.bounds()),
alignment::Horizontal::Left,
alignment::Vertical::Top,
*color,
*clip_bounds,
*transformation,
)
}
Text::Cached {
bounds,
horizontal_alignment,
vertical_alignment,
color,
clip_bounds,
..
} => {
let Some(Allocation::Cache(key)) = allocation else {
return None;
};
let entry =
buffer_cache.get(key).expect("Get cached buffer");
(
&entry.buffer,
Rectangle::new(bounds.position(), entry.min_bounds),
*horizontal_alignment,
*vertical_alignment,
*color,
*clip_bounds,
Transformation::IDENTITY,
)
}
Text::Raw {
raw,
transformation,
} => {
let Some(Allocation::Raw(buffer)) = allocation else {
return None;
};
let (width, height) = buffer.size();
(
buffer.as_ref(),
Rectangle::new(raw.position, Size::new(width, height)),
alignment::Horizontal::Left,
alignment::Vertical::Top,
raw.color,
raw.clip_bounds,
*transformation,
)
}
};
let bounds = bounds * transformation * scale_factor;
let left = match horizontal_alignment {
alignment::Horizontal::Left => bounds.x,
alignment::Horizontal::Center => bounds.x - bounds.width / 2.0,
alignment::Horizontal::Right => bounds.x - bounds.width,
};
let top = match vertical_alignment {
alignment::Vertical::Top => bounds.y,
alignment::Vertical::Center => bounds.y - bounds.height / 2.0,
alignment::Vertical::Bottom => bounds.y - bounds.height,
};
let clip_bounds = layer_bounds
.intersection(&(clip_bounds * transformation * scale_factor))?;
Some(glyphon::TextArea {
buffer,
left,
top,
scale: scale_factor * transformation.scale_factor(),
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: to_color(color),
})
},
);
renderer.prepare(
device,
queue,
encoder,
font_system,
atlas,
glyphon::Resolution {
width: target_size.width,
height: target_size.height,
},
text_areas,
&mut glyphon::SwashCache::new(),
)
}

View file

@ -1,14 +1,16 @@
//! Draw meshes of triangles.
mod msaa;
use crate::core::{Size, Transformation};
use crate::core::{Rectangle, Size, Transformation};
use crate::graphics::mesh::{self, Mesh};
use crate::graphics::Antialiasing;
use crate::layer::mesh::{self, Mesh};
use crate::Buffer;
const INITIAL_INDEX_COUNT: usize = 1_000;
const INITIAL_VERTEX_COUNT: usize = 1_000;
pub type Batch = Vec<Mesh>;
#[derive(Debug)]
pub struct Pipeline {
blit: Option<msaa::Blit>,
@ -18,8 +20,270 @@ pub struct Pipeline {
prepare_layer: usize,
}
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),
gradient: gradient::Pipeline::new(device, format, antialiasing),
layers: Vec::new(),
prepare_layer: 0,
}
}
pub fn prepare_batch(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
meshes: &Batch,
transformation: Transformation,
) {
if self.layers.len() <= self.prepare_layer {
self.layers
.push(Layer::new(device, &self.solid, &self.gradient));
}
let layer = &mut self.layers[self.prepare_layer];
layer.prepare(
device,
encoder,
belt,
&self.solid,
&self.gradient,
meshes,
transformation,
);
self.prepare_layer += 1;
}
pub fn prepare_cache(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
cache: &mut Cache,
transformation: Transformation,
) {
match cache {
Cache::Staged(_) => {
let Cache::Staged(batch) =
std::mem::replace(cache, Cache::Staged(Batch::default()))
else {
unreachable!()
};
let mut layer = Layer::new(device, &self.solid, &self.gradient);
layer.prepare(
device,
encoder,
belt,
&self.solid,
&self.gradient,
&batch,
transformation,
);
*cache = Cache::Uploaded {
layer,
batch,
needs_reupload: false,
}
}
Cache::Uploaded {
batch,
layer,
needs_reupload,
} => {
if *needs_reupload {
layer.prepare(
device,
encoder,
belt,
&self.solid,
&self.gradient,
batch,
transformation,
);
*needs_reupload = false;
}
}
}
}
pub fn render_batch(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
target: &wgpu::TextureView,
layer: usize,
target_size: Size<u32>,
meshes: &Batch,
bounds: Rectangle<u32>,
scale_factor: f32,
) {
Self::render(
device,
encoder,
target,
self.blit.as_mut(),
&self.solid,
&self.gradient,
&self.layers[layer],
target_size,
meshes,
bounds,
scale_factor,
);
}
pub fn render_cache(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
target: &wgpu::TextureView,
target_size: Size<u32>,
cache: &Cache,
bounds: Rectangle<u32>,
scale_factor: f32,
) {
let Cache::Uploaded { batch, layer, .. } = cache else {
return;
};
Self::render(
device,
encoder,
target,
self.blit.as_mut(),
&self.solid,
&self.gradient,
layer,
target_size,
batch,
bounds,
scale_factor,
);
}
fn render(
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
target: &wgpu::TextureView,
mut blit: Option<&mut msaa::Blit>,
solid: &solid::Pipeline,
gradient: &gradient::Pipeline,
layer: &Layer,
target_size: Size<u32>,
meshes: &Batch,
bounds: Rectangle<u32>,
scale_factor: f32,
) {
{
let (attachment, resolve_target, load) = if let Some(blit) =
&mut blit
{
let (attachment, resolve_target) =
blit.targets(device, target_size.width, target_size.height);
(
attachment,
Some(resolve_target),
wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
)
} else {
(target, None, wgpu::LoadOp::Load)
};
let mut render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu.triangle.render_pass"),
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view: attachment,
resolve_target,
ops: wgpu::Operations {
load,
store: wgpu::StoreOp::Store,
},
},
)],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
layer.render(
solid,
gradient,
meshes,
bounds,
scale_factor,
&mut render_pass,
);
}
if let Some(blit) = blit {
blit.draw(encoder, target);
}
}
pub fn end_frame(&mut self) {
self.prepare_layer = 0;
}
}
#[derive(Debug)]
struct Layer {
pub enum Cache {
Staged(Batch),
Uploaded {
batch: Batch,
layer: Layer,
needs_reupload: bool,
},
}
impl Cache {
pub fn is_empty(&self) -> bool {
match self {
Cache::Staged(batch) | Cache::Uploaded { batch, .. } => {
batch.is_empty()
}
}
}
pub fn update(&mut self, new_batch: Batch) {
match self {
Self::Staged(batch) => {
*batch = new_batch;
}
Self::Uploaded {
batch,
needs_reupload,
..
} => {
*batch = new_batch;
*needs_reupload = true;
}
}
}
}
impl Default for Cache {
fn default() -> Self {
Self::Staged(Batch::default())
}
}
#[derive(Debug)]
pub struct Layer {
index_buffer: Buffer<u32>,
index_strides: Vec<u32>,
solid: solid::Layer,
@ -52,7 +316,7 @@ impl Layer {
belt: &mut wgpu::util::StagingBelt,
solid: &solid::Pipeline,
gradient: &gradient::Pipeline,
meshes: &[Mesh<'_>],
meshes: &Batch,
transformation: Transformation,
) {
// Count the total amount of vertices & indices we need to handle
@ -100,7 +364,6 @@ impl Layer {
for mesh in meshes {
let indices = mesh.indices();
let uniforms =
Uniforms::new(transformation * mesh.transformation());
@ -157,7 +420,8 @@ impl Layer {
&'a self,
solid: &'a solid::Pipeline,
gradient: &'a gradient::Pipeline,
meshes: &[Mesh<'_>],
meshes: &Batch,
layer_bounds: Rectangle<u32>,
scale_factor: f32,
render_pass: &mut wgpu::RenderPass<'a>,
) {
@ -166,7 +430,12 @@ impl Layer {
let mut last_is_solid = None;
for (index, mesh) in meshes.iter().enumerate() {
let clip_bounds = (mesh.clip_bounds() * scale_factor).snap();
let Some(clip_bounds) = Rectangle::<f32>::from(layer_bounds)
.intersection(&(mesh.clip_bounds() * scale_factor))
.map(Rectangle::snap)
else {
continue;
};
if clip_bounds.width < 1 || clip_bounds.height < 1 {
continue;
@ -234,119 +503,6 @@ impl Layer {
}
}
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),
gradient: gradient::Pipeline::new(device, format, antialiasing),
layers: Vec::new(),
prepare_layer: 0,
}
}
pub fn prepare(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
meshes: &[Mesh<'_>],
transformation: Transformation,
) {
#[cfg(feature = "tracing")]
let _ = tracing::info_span!("Wgpu::Triangle", "PREPARE").entered();
if self.layers.len() <= self.prepare_layer {
self.layers
.push(Layer::new(device, &self.solid, &self.gradient));
}
let layer = &mut self.layers[self.prepare_layer];
layer.prepare(
device,
encoder,
belt,
&self.solid,
&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 _ = tracing::info_span!("Wgpu::Triangle", "DRAW").entered();
{
let (attachment, resolve_target, load) = if let Some(blit) =
&mut self.blit
{
let (attachment, resolve_target) =
blit.targets(device, target_size.width, target_size.height);
(
attachment,
Some(resolve_target),
wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
)
} else {
(target, None, wgpu::LoadOp::Load)
};
let mut render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu.triangle.render_pass"),
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view: attachment,
resolve_target,
ops: wgpu::Operations {
load,
store: wgpu::StoreOp::Store,
},
},
)],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
let layer = &mut self.layers[layer];
layer.render(
&self.solid,
&self.gradient,
meshes,
scale_factor,
&mut render_pass,
);
}
if let Some(blit) = &mut self.blit {
blit.draw(encoder, target);
}
}
pub fn end_frame(&mut self) {
self.prepare_layer = 0;
}
}
fn fragment_target(
texture_format: wgpu::TextureFormat,
) -> wgpu::ColorTargetState {

View file

@ -4,18 +4,19 @@ use crate::graphics::color;
use crate::graphics::compositor;
use crate::graphics::error;
use crate::graphics::{self, Viewport};
use crate::{Backend, Primitive, Renderer, Settings};
use crate::{Engine, Renderer, Settings};
/// A window graphics backend for iced powered by `wgpu`.
#[allow(missing_debug_implementations)]
pub struct Compositor {
settings: Settings,
instance: wgpu::Instance,
adapter: wgpu::Adapter,
device: wgpu::Device,
queue: wgpu::Queue,
format: wgpu::TextureFormat,
alpha_mode: wgpu::CompositeAlphaMode,
engine: Engine,
settings: Settings,
}
/// A compositor error.
@ -167,15 +168,24 @@ impl Compositor {
match result {
Ok((device, queue)) => {
let engine = Engine::new(
&adapter,
&device,
&queue,
format,
settings.antialiasing,
);
return Ok(Compositor {
instance,
settings,
adapter,
device,
queue,
format,
alpha_mode,
})
engine,
settings,
});
}
Err(error) => {
errors.push((required_limits, error));
@ -185,17 +195,6 @@ impl Compositor {
Err(Error::RequestDeviceFailed(errors))
}
/// Creates a new rendering [`Backend`] for this [`Compositor`].
pub fn create_backend(&self) -> Backend {
Backend::new(
&self.adapter,
&self.device,
&self.queue,
self.settings,
self.format,
)
}
}
/// Creates a [`Compositor`] and its [`Backend`] for the given [`Settings`] and
@ -210,9 +209,8 @@ pub async fn new<W: compositor::Window>(
/// Presents the given primitives with the given [`Compositor`] and [`Backend`].
pub fn present<T: AsRef<str>>(
compositor: &mut Compositor,
backend: &mut Backend,
renderer: &mut Renderer,
surface: &mut wgpu::Surface<'static>,
primitives: &[Primitive],
viewport: &Viewport,
background_color: Color,
overlay: &[T],
@ -229,21 +227,21 @@ pub fn present<T: AsRef<str>>(
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
backend.present(
renderer.present(
&mut compositor.engine,
&compositor.device,
&compositor.queue,
&mut encoder,
Some(background_color),
frame.texture.format(),
view,
primitives,
viewport,
overlay,
);
// Submit work
let _submission = compositor.queue.submit(Some(encoder.finish()));
backend.recall();
let _ = compositor.engine.submit(&compositor.queue, encoder);
// Present the frame
frame.present();
Ok(())
@ -292,11 +290,7 @@ impl graphics::Compositor for Compositor {
}
fn create_renderer(&self) -> Self::Renderer {
Renderer::new(
self.create_backend(),
self.settings.default_font,
self.settings.default_text_size,
)
Renderer::new(self.settings, &self.engine)
}
fn create_surface<W: compositor::Window>(
@ -328,7 +322,7 @@ impl graphics::Compositor for Compositor {
&wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: self.format,
present_mode: self.settings.present_mode,
present_mode: wgpu::PresentMode::Immediate,
width,
height,
alpha_mode: self.alpha_mode,
@ -355,17 +349,7 @@ impl graphics::Compositor for Compositor {
background_color: Color,
overlay: &[T],
) -> Result<(), compositor::SurfaceError> {
renderer.with_primitives(|backend, primitives| {
present(
self,
backend,
surface,
primitives,
viewport,
background_color,
overlay,
)
})
present(self, renderer, surface, viewport, background_color, overlay)
}
fn screenshot<T: AsRef<str>>(
@ -376,16 +360,7 @@ impl graphics::Compositor for Compositor {
background_color: Color,
overlay: &[T],
) -> Vec<u8> {
renderer.with_primitives(|backend, primitives| {
screenshot(
self,
backend,
primitives,
viewport,
background_color,
overlay,
)
})
screenshot(self, renderer, viewport, background_color, overlay)
}
}
@ -393,19 +368,12 @@ impl graphics::Compositor for Compositor {
///
/// Returns RGBA bytes of the texture data.
pub fn screenshot<T: AsRef<str>>(
compositor: &Compositor,
backend: &mut Backend,
primitives: &[Primitive],
compositor: &mut Compositor,
renderer: &mut Renderer,
viewport: &Viewport,
background_color: Color,
overlay: &[T],
) -> Vec<u8> {
let mut encoder = compositor.device.create_command_encoder(
&wgpu::CommandEncoderDescriptor {
label: Some("iced_wgpu.offscreen.encoder"),
},
);
let dimensions = BufferDimensions::new(viewport.physical_size());
let texture_extent = wgpu::Extent3d {
@ -429,14 +397,20 @@ pub fn screenshot<T: AsRef<str>>(
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
backend.present(
let mut encoder = compositor.device.create_command_encoder(
&wgpu::CommandEncoderDescriptor {
label: Some("iced_wgpu.offscreen.encoder"),
},
);
renderer.present(
&mut compositor.engine,
&compositor.device,
&compositor.queue,
&mut encoder,
Some(background_color),
texture.format(),
&view,
primitives,
viewport,
overlay,
);
@ -474,7 +448,7 @@ pub fn screenshot<T: AsRef<str>>(
texture_extent,
);
let index = compositor.queue.submit(Some(encoder.finish()));
let index = compositor.engine.submit(&compositor.queue, encoder);
let slice = output_buffer.slice(..);
slice.map_async(wgpu::MapMode::Read, |_| {});