Merge branch 'master' into beacon
This commit is contained in:
commit
aaf396256e
284 changed files with 18747 additions and 15450 deletions
|
|
@ -1,394 +0,0 @@
|
|||
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::{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,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(
|
||||
&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,
|
||||
) {
|
||||
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 layers = Layer::generate(primitives, viewport);
|
||||
|
||||
self.prepare(
|
||||
device,
|
||||
queue,
|
||||
format,
|
||||
encoder,
|
||||
scale_factor,
|
||||
target_size,
|
||||
transformation,
|
||||
&layers,
|
||||
);
|
||||
|
||||
self.render(
|
||||
device,
|
||||
encoder,
|
||||
frame,
|
||||
clear_color,
|
||||
scale_factor,
|
||||
target_size,
|
||||
&layers,
|
||||
);
|
||||
|
||||
self.quad_pipeline.end_frame();
|
||||
self.text_pipeline.end_frame();
|
||||
self.triangle_pipeline.end_frame();
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
self.image_pipeline.end_frame();
|
||||
}
|
||||
|
||||
fn prepare(
|
||||
&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,
|
||||
queue,
|
||||
&layer.quads,
|
||||
transformation,
|
||||
scale_factor,
|
||||
);
|
||||
}
|
||||
|
||||
if !layer.meshes.is_empty() {
|
||||
let scaled =
|
||||
transformation * Transformation::scale(scale_factor);
|
||||
|
||||
self.triangle_pipeline.prepare(
|
||||
device,
|
||||
queue,
|
||||
&layer.meshes,
|
||||
scaled,
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
{
|
||||
if !layer.images.is_empty() {
|
||||
let scaled =
|
||||
transformation * Transformation::scale(scale_factor);
|
||||
|
||||
self.image_pipeline.prepare(
|
||||
device,
|
||||
queue,
|
||||
_encoder,
|
||||
&layer.images,
|
||||
scaled,
|
||||
scale_factor,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if !layer.text.is_empty() {
|
||||
self.text_pipeline.prepare(
|
||||
device,
|
||||
queue,
|
||||
&layer.text,
|
||||
layer.bounds,
|
||||
scale_factor,
|
||||
target_size,
|
||||
);
|
||||
}
|
||||
|
||||
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 crate::graphics::Backend for Backend {
|
||||
type Primitive = primitive::Custom;
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,13 @@
|
|||
use std::marker::PhantomData;
|
||||
use std::num::NonZeroU64;
|
||||
use std::ops::RangeBounds;
|
||||
|
||||
pub const MAX_WRITE_SIZE: usize = 100 * 1024;
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
const MAX_WRITE_SIZE_U64: NonZeroU64 =
|
||||
unsafe { NonZeroU64::new_unchecked(MAX_WRITE_SIZE as u64) };
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Buffer<T> {
|
||||
label: &'static str,
|
||||
|
|
@ -61,12 +68,46 @@ impl<T: bytemuck::Pod> Buffer<T> {
|
|||
/// Returns the size of the written bytes.
|
||||
pub fn write(
|
||||
&mut self,
|
||||
queue: &wgpu::Queue,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
offset: usize,
|
||||
contents: &[T],
|
||||
) -> usize {
|
||||
let bytes: &[u8] = bytemuck::cast_slice(contents);
|
||||
queue.write_buffer(&self.raw, offset as u64, bytes);
|
||||
let mut bytes_written = 0;
|
||||
|
||||
// Split write into multiple chunks if necessary
|
||||
while bytes_written + MAX_WRITE_SIZE < bytes.len() {
|
||||
belt.write_buffer(
|
||||
encoder,
|
||||
&self.raw,
|
||||
(offset + bytes_written) as u64,
|
||||
MAX_WRITE_SIZE_U64,
|
||||
device,
|
||||
)
|
||||
.copy_from_slice(
|
||||
&bytes[bytes_written..bytes_written + MAX_WRITE_SIZE],
|
||||
);
|
||||
|
||||
bytes_written += MAX_WRITE_SIZE;
|
||||
}
|
||||
|
||||
// There will always be some bytes left, since the previous
|
||||
// loop guarantees `bytes_written < bytes.len()`
|
||||
let bytes_left = ((bytes.len() - bytes_written) as u64)
|
||||
.try_into()
|
||||
.expect("non-empty write");
|
||||
|
||||
// Write them
|
||||
belt.write_buffer(
|
||||
encoder,
|
||||
&self.raw,
|
||||
(offset + bytes_written) as u64,
|
||||
bytes_left,
|
||||
device,
|
||||
)
|
||||
.copy_from_slice(&bytes[bytes_written..]);
|
||||
|
||||
self.offsets.push(offset as u64);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
pub fn convert(
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
|
|
@ -15,28 +17,58 @@ pub fn convert(
|
|||
..wgpu::SamplerDescriptor::default()
|
||||
});
|
||||
|
||||
//sampler in 0
|
||||
let sampler_layout =
|
||||
#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
|
||||
#[repr(C)]
|
||||
struct Ratio {
|
||||
u: f32,
|
||||
v: f32,
|
||||
}
|
||||
|
||||
let ratio = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("iced-wgpu::triangle::msaa ratio"),
|
||||
contents: bytemuck::bytes_of(&Ratio { u: 1.0, v: 1.0 }),
|
||||
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM,
|
||||
});
|
||||
|
||||
let constant_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("iced_wgpu.offscreen.blit.sampler_layout"),
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(
|
||||
wgpu::SamplerBindingType::NonFiltering,
|
||||
),
|
||||
count: None,
|
||||
}],
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(
|
||||
wgpu::SamplerBindingType::NonFiltering,
|
||||
),
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let sampler_bind_group =
|
||||
let constant_bind_group =
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu.offscreen.sampler.bind_group"),
|
||||
layout: &sampler_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Sampler(&sampler),
|
||||
}],
|
||||
layout: &constant_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Sampler(&sampler),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: ratio.as_entire_binding(),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let texture_layout =
|
||||
|
|
@ -59,7 +91,7 @@ pub fn convert(
|
|||
let pipeline_layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("iced_wgpu.offscreen.blit.pipeline_layout"),
|
||||
bind_group_layouts: &[&sampler_layout, &texture_layout],
|
||||
bind_group_layouts: &[&constant_layout, &texture_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
|
|
@ -152,16 +184,9 @@ pub fn convert(
|
|||
});
|
||||
|
||||
pass.set_pipeline(&pipeline);
|
||||
pass.set_bind_group(0, &sampler_bind_group, &[]);
|
||||
pass.set_bind_group(0, &constant_bind_group, &[]);
|
||||
pass.set_bind_group(1, &texture_bind_group, &[]);
|
||||
pass.draw(0..6, 0..1);
|
||||
|
||||
texture
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
#[repr(C)]
|
||||
struct Vertex {
|
||||
ndc: [f32; 2],
|
||||
uv: [f32; 2],
|
||||
}
|
||||
|
|
|
|||
87
wgpu/src/engine.rs
Normal file
87
wgpu/src/engine.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
use crate::buffer;
|
||||
use crate::graphics::Antialiasing;
|
||||
use crate::primitive;
|
||||
use crate::quad;
|
||||
use crate::text;
|
||||
use crate::triangle;
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Engine {
|
||||
pub(crate) staging_belt: wgpu::util::StagingBelt,
|
||||
pub(crate) format: wgpu::TextureFormat,
|
||||
|
||||
pub(crate) quad_pipeline: quad::Pipeline,
|
||||
pub(crate) text_pipeline: text::Pipeline,
|
||||
pub(crate) triangle_pipeline: triangle::Pipeline,
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
pub(crate) image_pipeline: crate::image::Pipeline,
|
||||
pub(crate) primitive_storage: primitive::Storage,
|
||||
}
|
||||
|
||||
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,
|
||||
),
|
||||
format,
|
||||
|
||||
quad_pipeline,
|
||||
text_pipeline,
|
||||
triangle_pipeline,
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
image_pipeline,
|
||||
|
||||
primitive_storage: primitive::Storage::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
pub fn create_image_cache(
|
||||
&self,
|
||||
device: &wgpu::Device,
|
||||
) -> crate::image::Cache {
|
||||
self.image_pipeline.create_cache(device)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +1,358 @@
|
|||
//! Build and draw geometry.
|
||||
use crate::core::text::LineHeight;
|
||||
use crate::core::{Pixels, Point, Rectangle, Size, Transformation, Vector};
|
||||
use crate::core::{
|
||||
Pixels, Point, Radians, Rectangle, Size, Transformation, Vector,
|
||||
};
|
||||
use crate::graphics::cache::{self, Cached};
|
||||
use crate::graphics::color;
|
||||
use crate::graphics::geometry::fill::{self, Fill};
|
||||
use crate::graphics::geometry::{
|
||||
LineCap, LineDash, LineJoin, Path, Stroke, Style, Text,
|
||||
self, LineCap, LineDash, LineJoin, Path, Stroke, Style,
|
||||
};
|
||||
use crate::graphics::gradient::{self, Gradient};
|
||||
use crate::graphics::mesh::{self, Mesh};
|
||||
use crate::primitive::{self, Primitive};
|
||||
use crate::graphics::{self, Text};
|
||||
use crate::text;
|
||||
use crate::triangle;
|
||||
|
||||
use lyon::geom::euclid;
|
||||
use lyon::tessellation;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Geometry {
|
||||
Live { meshes: Vec<Mesh>, text: Vec<Text> },
|
||||
Cached(Cache),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Cache {
|
||||
pub meshes: Option<triangle::Cache>,
|
||||
pub text: Option<text::Cache>,
|
||||
}
|
||||
|
||||
impl Cached for Geometry {
|
||||
type Cache = Cache;
|
||||
|
||||
fn load(cache: &Self::Cache) -> Self {
|
||||
Geometry::Cached(cache.clone())
|
||||
}
|
||||
|
||||
fn cache(
|
||||
self,
|
||||
group: cache::Group,
|
||||
previous: Option<Self::Cache>,
|
||||
) -> Self::Cache {
|
||||
match self {
|
||||
Self::Live { meshes, text } => {
|
||||
if let Some(mut previous) = previous {
|
||||
if let Some(cache) = &mut previous.meshes {
|
||||
cache.update(meshes);
|
||||
} else {
|
||||
previous.meshes = triangle::Cache::new(meshes);
|
||||
}
|
||||
|
||||
if let Some(cache) = &mut previous.text {
|
||||
cache.update(text);
|
||||
} else {
|
||||
previous.text = text::Cache::new(group, text);
|
||||
}
|
||||
|
||||
previous
|
||||
} else {
|
||||
Cache {
|
||||
meshes: triangle::Cache::new(meshes),
|
||||
text: text::Cache::new(group, text),
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::Cached(cache) => cache,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A frame for drawing some geometry.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Frame {
|
||||
size: Size,
|
||||
clip_bounds: Rectangle,
|
||||
buffers: BufferStack,
|
||||
primitives: Vec<Primitive>,
|
||||
meshes: Vec<Mesh>,
|
||||
text: Vec<Text>,
|
||||
transforms: Transforms,
|
||||
fill_tessellator: tessellation::FillTessellator,
|
||||
stroke_tessellator: tessellation::StrokeTessellator,
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
/// Creates a new [`Frame`] with the given [`Size`].
|
||||
pub fn new(size: Size) -> Frame {
|
||||
Self::with_clip(Rectangle::with_size(size))
|
||||
}
|
||||
|
||||
/// Creates a new [`Frame`] with the given clip bounds.
|
||||
pub fn with_clip(bounds: Rectangle) -> Frame {
|
||||
Frame {
|
||||
clip_bounds: bounds,
|
||||
buffers: BufferStack::new(),
|
||||
meshes: Vec::new(),
|
||||
text: Vec::new(),
|
||||
transforms: Transforms {
|
||||
previous: Vec::new(),
|
||||
current: Transform(lyon::math::Transform::translation(
|
||||
bounds.x, bounds.y,
|
||||
)),
|
||||
},
|
||||
fill_tessellator: tessellation::FillTessellator::new(),
|
||||
stroke_tessellator: tessellation::StrokeTessellator::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl geometry::frame::Backend for Frame {
|
||||
type Geometry = Geometry;
|
||||
|
||||
#[inline]
|
||||
fn width(&self) -> f32 {
|
||||
self.clip_bounds.width
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn height(&self) -> f32 {
|
||||
self.clip_bounds.height
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> Size {
|
||||
self.clip_bounds.size()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn center(&self) -> Point {
|
||||
Point::new(self.clip_bounds.width / 2.0, self.clip_bounds.height / 2.0)
|
||||
}
|
||||
|
||||
fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
|
||||
let Fill { style, rule } = fill.into();
|
||||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get_fill(&self.transforms.current.transform_style(style));
|
||||
|
||||
let options = tessellation::FillOptions::default()
|
||||
.with_fill_rule(into_fill_rule(rule));
|
||||
|
||||
if self.transforms.current.is_identity() {
|
||||
self.fill_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
} else {
|
||||
let path = path.transform(&self.transforms.current.0);
|
||||
|
||||
self.fill_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
}
|
||||
.expect("Tessellate path.");
|
||||
}
|
||||
|
||||
fn fill_rectangle(
|
||||
&mut self,
|
||||
top_left: Point,
|
||||
size: Size,
|
||||
fill: impl Into<Fill>,
|
||||
) {
|
||||
let Fill { style, rule } = fill.into();
|
||||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get_fill(&self.transforms.current.transform_style(style));
|
||||
|
||||
let top_left = self
|
||||
.transforms
|
||||
.current
|
||||
.0
|
||||
.transform_point(lyon::math::Point::new(top_left.x, top_left.y));
|
||||
|
||||
let size =
|
||||
self.transforms.current.0.transform_vector(
|
||||
lyon::math::Vector::new(size.width, size.height),
|
||||
);
|
||||
|
||||
let options = tessellation::FillOptions::default()
|
||||
.with_fill_rule(into_fill_rule(rule));
|
||||
|
||||
self.fill_tessellator
|
||||
.tessellate_rectangle(
|
||||
&lyon::math::Box2D::new(top_left, top_left + size),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
.expect("Fill rectangle");
|
||||
}
|
||||
|
||||
fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
|
||||
let stroke = stroke.into();
|
||||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get_stroke(&self.transforms.current.transform_style(stroke.style));
|
||||
|
||||
let mut options = tessellation::StrokeOptions::default();
|
||||
options.line_width = stroke.width;
|
||||
options.start_cap = into_line_cap(stroke.line_cap);
|
||||
options.end_cap = into_line_cap(stroke.line_cap);
|
||||
options.line_join = into_line_join(stroke.line_join);
|
||||
|
||||
let path = if stroke.line_dash.segments.is_empty() {
|
||||
Cow::Borrowed(path)
|
||||
} else {
|
||||
Cow::Owned(dashed(path, stroke.line_dash))
|
||||
};
|
||||
|
||||
if self.transforms.current.is_identity() {
|
||||
self.stroke_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
} else {
|
||||
let path = path.transform(&self.transforms.current.0);
|
||||
|
||||
self.stroke_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
}
|
||||
.expect("Stroke path");
|
||||
}
|
||||
|
||||
fn fill_text(&mut self, text: impl Into<geometry::Text>) {
|
||||
let text = text.into();
|
||||
|
||||
let (scale_x, scale_y) = self.transforms.current.scale();
|
||||
|
||||
if self.transforms.current.is_scale_translation()
|
||||
&& scale_x == scale_y
|
||||
&& scale_x > 0.0
|
||||
&& scale_y > 0.0
|
||||
{
|
||||
let (position, size, line_height) =
|
||||
if self.transforms.current.is_identity() {
|
||||
(text.position, text.size, text.line_height)
|
||||
} else {
|
||||
let position =
|
||||
self.transforms.current.transform_point(text.position);
|
||||
|
||||
let size = Pixels(text.size.0 * scale_y);
|
||||
|
||||
let line_height = match text.line_height {
|
||||
LineHeight::Absolute(size) => {
|
||||
LineHeight::Absolute(Pixels(size.0 * scale_y))
|
||||
}
|
||||
LineHeight::Relative(factor) => {
|
||||
LineHeight::Relative(factor)
|
||||
}
|
||||
};
|
||||
|
||||
(position, size, line_height)
|
||||
};
|
||||
|
||||
let bounds = Rectangle {
|
||||
x: position.x,
|
||||
y: position.y,
|
||||
width: f32::INFINITY,
|
||||
height: f32::INFINITY,
|
||||
};
|
||||
|
||||
self.text.push(graphics::Text::Cached {
|
||||
content: text.content,
|
||||
bounds,
|
||||
color: text.color,
|
||||
size,
|
||||
line_height: line_height.to_absolute(size),
|
||||
font: text.font,
|
||||
horizontal_alignment: text.horizontal_alignment,
|
||||
vertical_alignment: text.vertical_alignment,
|
||||
shaping: text.shaping,
|
||||
clip_bounds: self.clip_bounds,
|
||||
});
|
||||
} else {
|
||||
text.draw_with(|path, color| self.fill(&path, color));
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn translate(&mut self, translation: Vector) {
|
||||
self.transforms.current.0 =
|
||||
self.transforms
|
||||
.current
|
||||
.0
|
||||
.pre_translate(lyon::math::Vector::new(
|
||||
translation.x,
|
||||
translation.y,
|
||||
));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn rotate(&mut self, angle: impl Into<Radians>) {
|
||||
self.transforms.current.0 = self
|
||||
.transforms
|
||||
.current
|
||||
.0
|
||||
.pre_rotate(lyon::math::Angle::radians(angle.into().0));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn scale(&mut self, scale: impl Into<f32>) {
|
||||
let scale = scale.into();
|
||||
|
||||
self.scale_nonuniform(Vector { x: scale, y: scale });
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
|
||||
let scale = scale.into();
|
||||
|
||||
self.transforms.current.0 =
|
||||
self.transforms.current.0.pre_scale(scale.x, scale.y);
|
||||
}
|
||||
|
||||
fn push_transform(&mut self) {
|
||||
self.transforms.previous.push(self.transforms.current);
|
||||
}
|
||||
|
||||
fn pop_transform(&mut self) {
|
||||
self.transforms.current = self.transforms.previous.pop().unwrap();
|
||||
}
|
||||
|
||||
fn draft(&mut self, clip_bounds: Rectangle) -> Frame {
|
||||
Frame::with_clip(clip_bounds)
|
||||
}
|
||||
|
||||
fn paste(&mut self, frame: Frame, _at: Point) {
|
||||
self.meshes
|
||||
.extend(frame.buffers.into_meshes(frame.clip_bounds));
|
||||
|
||||
self.text.extend(frame.text);
|
||||
}
|
||||
|
||||
fn into_geometry(mut self) -> Self::Geometry {
|
||||
self.meshes
|
||||
.extend(self.buffers.into_meshes(self.clip_bounds));
|
||||
|
||||
Geometry::Live {
|
||||
meshes: self.meshes,
|
||||
text: self.text,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Buffer {
|
||||
Solid(tessellation::VertexBuffers<mesh::SolidVertex2D, u32>),
|
||||
Gradient(tessellation::VertexBuffers<mesh::GradientVertex2D, u32>),
|
||||
|
|
@ -107,6 +435,34 @@ impl BufferStack {
|
|||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_meshes(self, clip_bounds: Rectangle) -> impl Iterator<Item = Mesh> {
|
||||
self.stack
|
||||
.into_iter()
|
||||
.filter_map(move |buffer| match buffer {
|
||||
Buffer::Solid(buffer) if !buffer.indices.is_empty() => {
|
||||
Some(Mesh::Solid {
|
||||
buffers: mesh::Indexed {
|
||||
vertices: buffer.vertices,
|
||||
indices: buffer.indices,
|
||||
},
|
||||
clip_bounds,
|
||||
transformation: Transformation::IDENTITY,
|
||||
})
|
||||
}
|
||||
Buffer::Gradient(buffer) if !buffer.indices.is_empty() => {
|
||||
Some(Mesh::Gradient {
|
||||
buffers: mesh::Indexed {
|
||||
vertices: buffer.vertices,
|
||||
indices: buffer.indices,
|
||||
},
|
||||
clip_bounds,
|
||||
transformation: Transformation::IDENTITY,
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -163,386 +519,6 @@ impl Transform {
|
|||
gradient
|
||||
}
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
/// Creates a new empty [`Frame`] with the given dimensions.
|
||||
///
|
||||
/// The default coordinate system of a [`Frame`] has its origin at the
|
||||
/// top-left corner of its bounds.
|
||||
pub fn new(size: Size) -> Frame {
|
||||
Frame {
|
||||
size,
|
||||
buffers: BufferStack::new(),
|
||||
primitives: Vec::new(),
|
||||
transforms: Transforms {
|
||||
previous: Vec::new(),
|
||||
current: Transform(lyon::math::Transform::identity()),
|
||||
},
|
||||
fill_tessellator: tessellation::FillTessellator::new(),
|
||||
stroke_tessellator: tessellation::StrokeTessellator::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the width of the [`Frame`].
|
||||
#[inline]
|
||||
pub fn width(&self) -> f32 {
|
||||
self.size.width
|
||||
}
|
||||
|
||||
/// Returns the height of the [`Frame`].
|
||||
#[inline]
|
||||
pub fn height(&self) -> f32 {
|
||||
self.size.height
|
||||
}
|
||||
|
||||
/// Returns the dimensions of the [`Frame`].
|
||||
#[inline]
|
||||
pub fn size(&self) -> Size {
|
||||
self.size
|
||||
}
|
||||
|
||||
/// Returns the coordinate of the center of the [`Frame`].
|
||||
#[inline]
|
||||
pub fn center(&self) -> Point {
|
||||
Point::new(self.size.width / 2.0, self.size.height / 2.0)
|
||||
}
|
||||
|
||||
/// Draws the given [`Path`] on the [`Frame`] by filling it with the
|
||||
/// provided style.
|
||||
pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
|
||||
let Fill { style, rule } = fill.into();
|
||||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get_fill(&self.transforms.current.transform_style(style));
|
||||
|
||||
let options = tessellation::FillOptions::default()
|
||||
.with_fill_rule(into_fill_rule(rule));
|
||||
|
||||
if self.transforms.current.is_identity() {
|
||||
self.fill_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
} else {
|
||||
let path = path.transform(&self.transforms.current.0);
|
||||
|
||||
self.fill_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
}
|
||||
.expect("Tessellate path.");
|
||||
}
|
||||
|
||||
/// Draws an axis-aligned rectangle given its top-left corner coordinate and
|
||||
/// its `Size` on the [`Frame`] by filling it with the provided style.
|
||||
pub fn fill_rectangle(
|
||||
&mut self,
|
||||
top_left: Point,
|
||||
size: Size,
|
||||
fill: impl Into<Fill>,
|
||||
) {
|
||||
let Fill { style, rule } = fill.into();
|
||||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get_fill(&self.transforms.current.transform_style(style));
|
||||
|
||||
let top_left = self
|
||||
.transforms
|
||||
.current
|
||||
.0
|
||||
.transform_point(lyon::math::Point::new(top_left.x, top_left.y));
|
||||
|
||||
let size =
|
||||
self.transforms.current.0.transform_vector(
|
||||
lyon::math::Vector::new(size.width, size.height),
|
||||
);
|
||||
|
||||
let options = tessellation::FillOptions::default()
|
||||
.with_fill_rule(into_fill_rule(rule));
|
||||
|
||||
self.fill_tessellator
|
||||
.tessellate_rectangle(
|
||||
&lyon::math::Box2D::new(top_left, top_left + size),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
.expect("Fill rectangle");
|
||||
}
|
||||
|
||||
/// Draws the stroke of the given [`Path`] on the [`Frame`] with the
|
||||
/// provided style.
|
||||
pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
|
||||
let stroke = stroke.into();
|
||||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get_stroke(&self.transforms.current.transform_style(stroke.style));
|
||||
|
||||
let mut options = tessellation::StrokeOptions::default();
|
||||
options.line_width = stroke.width;
|
||||
options.start_cap = into_line_cap(stroke.line_cap);
|
||||
options.end_cap = into_line_cap(stroke.line_cap);
|
||||
options.line_join = into_line_join(stroke.line_join);
|
||||
|
||||
let path = if stroke.line_dash.segments.is_empty() {
|
||||
Cow::Borrowed(path)
|
||||
} else {
|
||||
Cow::Owned(dashed(path, stroke.line_dash))
|
||||
};
|
||||
|
||||
if self.transforms.current.is_identity() {
|
||||
self.stroke_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
} else {
|
||||
let path = path.transform(&self.transforms.current.0);
|
||||
|
||||
self.stroke_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
}
|
||||
.expect("Stroke path");
|
||||
}
|
||||
|
||||
/// Draws the characters of the given [`Text`] on the [`Frame`], filling
|
||||
/// them with the given color.
|
||||
///
|
||||
/// __Warning:__ Text currently does not work well with rotations and scale
|
||||
/// transforms! The position will be correctly transformed, but the
|
||||
/// resulting glyphs will not be rotated or scaled properly.
|
||||
///
|
||||
/// Additionally, all text will be rendered on top of all the layers of
|
||||
/// a `Canvas`. Therefore, it is currently only meant to be used for
|
||||
/// overlays, which is the most common use case.
|
||||
///
|
||||
/// Support for vectorial text is planned, and should address all these
|
||||
/// limitations.
|
||||
pub fn fill_text(&mut self, text: impl Into<Text>) {
|
||||
let text = text.into();
|
||||
|
||||
let (scale_x, scale_y) = self.transforms.current.scale();
|
||||
|
||||
if self.transforms.current.is_scale_translation()
|
||||
&& scale_x == scale_y
|
||||
&& scale_x > 0.0
|
||||
&& scale_y > 0.0
|
||||
{
|
||||
let (position, size, line_height) =
|
||||
if self.transforms.current.is_identity() {
|
||||
(text.position, text.size, text.line_height)
|
||||
} else {
|
||||
let position =
|
||||
self.transforms.current.transform_point(text.position);
|
||||
|
||||
let size = Pixels(text.size.0 * scale_y);
|
||||
|
||||
let line_height = match text.line_height {
|
||||
LineHeight::Absolute(size) => {
|
||||
LineHeight::Absolute(Pixels(size.0 * scale_y))
|
||||
}
|
||||
LineHeight::Relative(factor) => {
|
||||
LineHeight::Relative(factor)
|
||||
}
|
||||
};
|
||||
|
||||
(position, size, line_height)
|
||||
};
|
||||
|
||||
let bounds = Rectangle {
|
||||
x: position.x,
|
||||
y: position.y,
|
||||
width: f32::INFINITY,
|
||||
height: f32::INFINITY,
|
||||
};
|
||||
|
||||
// TODO: Honor layering!
|
||||
self.primitives.push(Primitive::Text {
|
||||
content: text.content,
|
||||
bounds,
|
||||
color: text.color,
|
||||
size,
|
||||
line_height,
|
||||
font: text.font,
|
||||
horizontal_alignment: text.horizontal_alignment,
|
||||
vertical_alignment: text.vertical_alignment,
|
||||
shaping: text.shaping,
|
||||
clip_bounds: Rectangle::with_size(Size::INFINITY),
|
||||
});
|
||||
} else {
|
||||
text.draw_with(|path, color| self.fill(&path, color));
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the current transform of the [`Frame`] and executes the given
|
||||
/// drawing operations, restoring the transform afterwards.
|
||||
///
|
||||
/// This method is useful to compose transforms and perform drawing
|
||||
/// operations in different coordinate systems.
|
||||
#[inline]
|
||||
pub fn with_save<R>(&mut self, f: impl FnOnce(&mut Frame) -> R) -> R {
|
||||
self.push_transform();
|
||||
|
||||
let result = f(self);
|
||||
|
||||
self.pop_transform();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Pushes the current transform in the transform stack.
|
||||
pub fn push_transform(&mut self) {
|
||||
self.transforms.previous.push(self.transforms.current);
|
||||
}
|
||||
|
||||
/// Pops a transform from the transform stack and sets it as the current transform.
|
||||
pub fn pop_transform(&mut self) {
|
||||
self.transforms.current = self.transforms.previous.pop().unwrap();
|
||||
}
|
||||
|
||||
/// Executes the given drawing operations within a [`Rectangle`] region,
|
||||
/// clipping any geometry that overflows its bounds. Any transformations
|
||||
/// performed are local to the provided closure.
|
||||
///
|
||||
/// This method is useful to perform drawing operations that need to be
|
||||
/// clipped.
|
||||
#[inline]
|
||||
pub fn with_clip<R>(
|
||||
&mut self,
|
||||
region: Rectangle,
|
||||
f: impl FnOnce(&mut Frame) -> R,
|
||||
) -> R {
|
||||
let mut frame = Frame::new(region.size());
|
||||
|
||||
let result = f(&mut frame);
|
||||
|
||||
let origin = Point::new(region.x, region.y);
|
||||
|
||||
self.clip(frame, origin);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Draws the clipped contents of the given [`Frame`] with origin at the given [`Point`].
|
||||
pub fn clip(&mut self, frame: Frame, at: Point) {
|
||||
let size = frame.size();
|
||||
let primitives = frame.into_primitives();
|
||||
let transformation = 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,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
/// Applies a translation to the current transform of the [`Frame`].
|
||||
#[inline]
|
||||
pub fn translate(&mut self, translation: Vector) {
|
||||
self.transforms.current.0 =
|
||||
self.transforms
|
||||
.current
|
||||
.0
|
||||
.pre_translate(lyon::math::Vector::new(
|
||||
translation.x,
|
||||
translation.y,
|
||||
));
|
||||
}
|
||||
|
||||
/// Applies a rotation in radians to the current transform of the [`Frame`].
|
||||
#[inline]
|
||||
pub fn rotate(&mut self, angle: f32) {
|
||||
self.transforms.current.0 = self
|
||||
.transforms
|
||||
.current
|
||||
.0
|
||||
.pre_rotate(lyon::math::Angle::radians(angle));
|
||||
}
|
||||
|
||||
/// Applies a uniform scaling to the current transform of the [`Frame`].
|
||||
#[inline]
|
||||
pub fn scale(&mut self, scale: impl Into<f32>) {
|
||||
let scale = scale.into();
|
||||
|
||||
self.scale_nonuniform(Vector { x: scale, y: scale });
|
||||
}
|
||||
|
||||
/// Applies a non-uniform scaling to the current transform of the [`Frame`].
|
||||
#[inline]
|
||||
pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
|
||||
let scale = scale.into();
|
||||
|
||||
self.transforms.current.0 =
|
||||
self.transforms.current.0.pre_scale(scale.x, scale.y);
|
||||
}
|
||||
|
||||
/// Produces the [`Primitive`] representing everything drawn on the [`Frame`].
|
||||
pub fn into_primitive(self) -> Primitive {
|
||||
Primitive::Group {
|
||||
primitives: self.into_primitives(),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_primitives(mut self) -> Vec<Primitive> {
|
||||
for buffer in self.buffers.stack {
|
||||
match buffer {
|
||||
Buffer::Solid(buffer) => {
|
||||
if !buffer.indices.is_empty() {
|
||||
self.primitives.push(Primitive::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,
|
||||
}),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.primitives
|
||||
}
|
||||
}
|
||||
|
||||
struct GradientVertex2DBuilder {
|
||||
gradient: gradient::Packed,
|
||||
}
|
||||
|
|
@ -649,7 +625,9 @@ pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path {
|
|||
let mut draw_line = false;
|
||||
|
||||
walk_along_path(
|
||||
path.raw().iter().flattened(0.01),
|
||||
path.raw().iter().flattened(
|
||||
lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE,
|
||||
),
|
||||
0.0,
|
||||
lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE,
|
||||
&mut RepeatedPattern {
|
||||
|
|
|
|||
|
|
@ -15,15 +15,23 @@ pub const SIZE: u32 = 2048;
|
|||
use crate::core::Size;
|
||||
use crate::graphics::color;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Atlas {
|
||||
texture: wgpu::Texture,
|
||||
texture_view: wgpu::TextureView,
|
||||
texture_bind_group: wgpu::BindGroup,
|
||||
texture_layout: Arc<wgpu::BindGroupLayout>,
|
||||
layers: Vec<Layer>,
|
||||
}
|
||||
|
||||
impl Atlas {
|
||||
pub fn new(device: &wgpu::Device, backend: wgpu::Backend) -> Self {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
backend: wgpu::Backend,
|
||||
texture_layout: Arc<wgpu::BindGroupLayout>,
|
||||
) -> Self {
|
||||
let layers = match backend {
|
||||
// On the GL backend we start with 2 layers, to help wgpu figure
|
||||
// out that this texture is `GL_TEXTURE_2D_ARRAY` rather than `GL_TEXTURE_2D`
|
||||
|
|
@ -60,15 +68,27 @@ impl Atlas {
|
|||
..Default::default()
|
||||
});
|
||||
|
||||
let texture_bind_group =
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::image texture atlas bind group"),
|
||||
layout: &texture_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(&texture_view),
|
||||
}],
|
||||
});
|
||||
|
||||
Atlas {
|
||||
texture,
|
||||
texture_view,
|
||||
texture_bind_group,
|
||||
texture_layout,
|
||||
layers,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view(&self) -> &wgpu::TextureView {
|
||||
&self.texture_view
|
||||
pub fn bind_group(&self) -> &wgpu::BindGroup {
|
||||
&self.texture_bind_group
|
||||
}
|
||||
|
||||
pub fn layer_count(&self) -> usize {
|
||||
|
|
@ -94,7 +114,7 @@ impl Atlas {
|
|||
entry
|
||||
};
|
||||
|
||||
log::info!("Allocated atlas entry: {entry:?}");
|
||||
log::debug!("Allocated atlas entry: {entry:?}");
|
||||
|
||||
// It is a webgpu requirement that:
|
||||
// BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0
|
||||
|
|
@ -147,13 +167,20 @@ impl Atlas {
|
|||
}
|
||||
}
|
||||
|
||||
log::info!("Current atlas: {self:?}");
|
||||
if log::log_enabled!(log::Level::Debug) {
|
||||
log::debug!(
|
||||
"Atlas layers: {} (busy: {}, allocations: {})",
|
||||
self.layer_count(),
|
||||
self.layers.iter().filter(|layer| !layer.is_empty()).count(),
|
||||
self.layers.iter().map(Layer::allocations).sum::<usize>(),
|
||||
);
|
||||
}
|
||||
|
||||
Some(entry)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, entry: &Entry) {
|
||||
log::info!("Removing atlas entry: {entry:?}");
|
||||
log::debug!("Removing atlas entry: {entry:?}");
|
||||
|
||||
match entry {
|
||||
Entry::Contiguous(allocation) => {
|
||||
|
|
@ -266,7 +293,7 @@ impl Atlas {
|
|||
}
|
||||
|
||||
fn deallocate(&mut self, allocation: &Allocation) {
|
||||
log::info!("Deallocating atlas: {allocation:?}");
|
||||
log::debug!("Deallocating atlas: {allocation:?}");
|
||||
|
||||
match allocation {
|
||||
Allocation::Full { layer } => {
|
||||
|
|
@ -414,5 +441,17 @@ impl Atlas {
|
|||
dimension: Some(wgpu::TextureViewDimension::D2Array),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
self.texture_bind_group =
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::image texture atlas bind group"),
|
||||
layout: &self.texture_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(
|
||||
&self.texture_view,
|
||||
),
|
||||
}],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,10 @@ impl Allocator {
|
|||
pub fn is_empty(&self) -> bool {
|
||||
self.allocations == 0
|
||||
}
|
||||
|
||||
pub fn allocations(&self) -> usize {
|
||||
self.allocations
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Region {
|
||||
|
|
|
|||
|
|
@ -11,4 +11,12 @@ impl Layer {
|
|||
pub fn is_empty(&self) -> bool {
|
||||
matches!(self, Layer::Empty)
|
||||
}
|
||||
|
||||
pub fn allocations(&self) -> usize {
|
||||
match self {
|
||||
Layer::Empty => 0,
|
||||
Layer::Busy(allocator) => allocator.allocations(),
|
||||
Layer::Full => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
86
wgpu/src/image/cache.rs
Normal file
86
wgpu/src/image/cache.rs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
use crate::core::{self, Size};
|
||||
use crate::image::atlas::{self, Atlas};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
#[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,
|
||||
layout: Arc<wgpu::BindGroupLayout>,
|
||||
) -> Self {
|
||||
Self {
|
||||
atlas: Atlas::new(device, backend, layout),
|
||||
#[cfg(feature = "image")]
|
||||
raster: crate::image::raster::Cache::default(),
|
||||
#[cfg(feature = "svg")]
|
||||
vector: crate::image::vector::Cache::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bind_group(&self) -> &wgpu::BindGroup {
|
||||
self.atlas.bind_group()
|
||||
}
|
||||
|
||||
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 trim(&mut self) {
|
||||
#[cfg(feature = "image")]
|
||||
self.raster.trim(&mut self.atlas);
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
self.vector.trim(&mut self.atlas);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,6 @@
|
|||
pub(crate) mod cache;
|
||||
pub(crate) use cache::Cache;
|
||||
|
||||
mod atlas;
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
|
|
@ -6,175 +9,30 @@ mod raster;
|
|||
#[cfg(feature = "svg")]
|
||||
mod vector;
|
||||
|
||||
use atlas::Atlas;
|
||||
|
||||
use crate::core::{Rectangle, Size, Transformation};
|
||||
use crate::layer;
|
||||
use crate::Buffer;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::mem;
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
use crate::core::image;
|
||||
use std::mem;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
use crate::core::svg;
|
||||
pub use crate::graphics::Image;
|
||||
|
||||
#[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,
|
||||
backend: wgpu::Backend,
|
||||
nearest_sampler: wgpu::Sampler,
|
||||
linear_sampler: wgpu::Sampler,
|
||||
texture: wgpu::BindGroup,
|
||||
texture_version: usize,
|
||||
texture_atlas: Atlas,
|
||||
texture_layout: wgpu::BindGroupLayout,
|
||||
texture_layout: Arc<wgpu::BindGroupLayout>,
|
||||
constant_layout: wgpu::BindGroupLayout,
|
||||
|
||||
layers: Vec<Layer>,
|
||||
prepare_layer: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Layer {
|
||||
uniforms: wgpu::Buffer,
|
||||
nearest: Data,
|
||||
linear: Data,
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
fn new(
|
||||
device: &wgpu::Device,
|
||||
constant_layout: &wgpu::BindGroupLayout,
|
||||
nearest_sampler: &wgpu::Sampler,
|
||||
linear_sampler: &wgpu::Sampler,
|
||||
) -> Self {
|
||||
let uniforms = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("iced_wgpu::image uniforms buffer"),
|
||||
size: mem::size_of::<Uniforms>() as u64,
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let nearest =
|
||||
Data::new(device, constant_layout, nearest_sampler, &uniforms);
|
||||
|
||||
let linear =
|
||||
Data::new(device, constant_layout, linear_sampler, &uniforms);
|
||||
|
||||
Self {
|
||||
uniforms,
|
||||
nearest,
|
||||
linear,
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
nearest_instances: &[Instance],
|
||||
linear_instances: &[Instance],
|
||||
transformation: Transformation,
|
||||
) {
|
||||
queue.write_buffer(
|
||||
&self.uniforms,
|
||||
0,
|
||||
bytemuck::bytes_of(&Uniforms {
|
||||
transform: transformation.into(),
|
||||
}),
|
||||
);
|
||||
|
||||
self.nearest.upload(device, queue, nearest_instances);
|
||||
self.linear.upload(device, queue, linear_instances);
|
||||
}
|
||||
|
||||
fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
|
||||
self.nearest.render(render_pass);
|
||||
self.linear.render(render_pass);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Data {
|
||||
constants: wgpu::BindGroup,
|
||||
instances: Buffer<Instance>,
|
||||
instance_count: usize,
|
||||
}
|
||||
|
||||
impl Data {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
constant_layout: &wgpu::BindGroupLayout,
|
||||
sampler: &wgpu::Sampler,
|
||||
uniforms: &wgpu::Buffer,
|
||||
) -> Self {
|
||||
let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::image constants bind group"),
|
||||
layout: constant_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(
|
||||
wgpu::BufferBinding {
|
||||
buffer: uniforms,
|
||||
offset: 0,
|
||||
size: None,
|
||||
},
|
||||
),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(sampler),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let instances = Buffer::new(
|
||||
device,
|
||||
"iced_wgpu::image instance buffer",
|
||||
Instance::INITIAL,
|
||||
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
);
|
||||
|
||||
Self {
|
||||
constants,
|
||||
instances,
|
||||
instance_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn upload(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
instances: &[Instance],
|
||||
) {
|
||||
let _ = self.instances.resize(device, instances.len());
|
||||
let _ = self.instances.write(queue, 0, instances);
|
||||
|
||||
self.instance_count = instances.len();
|
||||
}
|
||||
|
||||
fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
|
||||
render_pass.set_bind_group(0, &self.constants, &[]);
|
||||
render_pass.set_vertex_buffer(0, self.instances.slice(..));
|
||||
|
||||
render_pass.draw(0..6, 0..self.instance_count as u32);
|
||||
}
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
|
|
@ -257,9 +115,9 @@ impl Pipeline {
|
|||
label: Some("iced_wgpu image shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
|
||||
concat!(
|
||||
include_str!("shader/vertex.wgsl"),
|
||||
include_str!("../shader/vertex.wgsl"),
|
||||
"\n",
|
||||
include_str!("shader/image.wgsl"),
|
||||
include_str!("../shader/image.wgsl"),
|
||||
),
|
||||
)),
|
||||
});
|
||||
|
|
@ -277,14 +135,20 @@ impl Pipeline {
|
|||
attributes: &wgpu::vertex_attr_array!(
|
||||
// Position
|
||||
0 => Float32x2,
|
||||
// Scale
|
||||
// Center
|
||||
1 => Float32x2,
|
||||
// Atlas position
|
||||
// Scale
|
||||
2 => Float32x2,
|
||||
// Rotation
|
||||
3 => Float32,
|
||||
// Opacity
|
||||
4 => Float32,
|
||||
// Atlas position
|
||||
5 => Float32x2,
|
||||
// Atlas scale
|
||||
3 => Float32x2,
|
||||
6 => Float32x2,
|
||||
// Layer
|
||||
4 => Sint32,
|
||||
7 => Sint32,
|
||||
),
|
||||
}],
|
||||
},
|
||||
|
|
@ -322,137 +186,95 @@ impl Pipeline {
|
|||
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,
|
||||
backend,
|
||||
nearest_sampler,
|
||||
linear_sampler,
|
||||
texture,
|
||||
texture_version: texture_atlas.layer_count(),
|
||||
texture_atlas,
|
||||
texture_layout,
|
||||
texture_layout: Arc::new(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 create_cache(&self, device: &wgpu::Device) -> Cache {
|
||||
Cache::new(device, self.backend, self.texture_layout.clone())
|
||||
}
|
||||
|
||||
pub fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
images: &[layer::Image],
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
cache: &mut Cache,
|
||||
images: &Batch,
|
||||
transformation: Transformation,
|
||||
_scale: f32,
|
||||
scale: f32,
|
||||
) {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _ = info_span!("Wgpu::Image", "PREPARE").entered();
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
let _ = info_span!("Wgpu::Image", "DRAW").entered();
|
||||
let transformation = transformation * Transformation::scale(scale);
|
||||
|
||||
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 {
|
||||
Image::Raster {
|
||||
handle,
|
||||
filter_method,
|
||||
bounds,
|
||||
rotation,
|
||||
opacity,
|
||||
} => {
|
||||
if let Some(atlas_entry) = raster_cache.upload(
|
||||
device,
|
||||
encoder,
|
||||
handle,
|
||||
&mut self.texture_atlas,
|
||||
) {
|
||||
if let Some(atlas_entry) =
|
||||
cache.upload_raster(device, encoder, handle)
|
||||
{
|
||||
add_instances(
|
||||
[bounds.x, bounds.y],
|
||||
[bounds.width, bounds.height],
|
||||
f32::from(*rotation),
|
||||
*opacity,
|
||||
atlas_entry,
|
||||
match filter_method {
|
||||
image::FilterMethod::Nearest => {
|
||||
crate::core::image::FilterMethod::Nearest => {
|
||||
nearest_instances
|
||||
}
|
||||
image::FilterMethod::Linear => linear_instances,
|
||||
crate::core::image::FilterMethod::Linear => {
|
||||
linear_instances
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "image"))]
|
||||
layer::Image::Raster { .. } => {}
|
||||
Image::Raster { .. } => {}
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
layer::Image::Vector {
|
||||
Image::Vector {
|
||||
handle,
|
||||
color,
|
||||
bounds,
|
||||
rotation,
|
||||
opacity,
|
||||
} => {
|
||||
let size = [bounds.width, bounds.height];
|
||||
|
||||
if let Some(atlas_entry) = vector_cache.upload(
|
||||
device,
|
||||
encoder,
|
||||
handle,
|
||||
*color,
|
||||
size,
|
||||
_scale,
|
||||
&mut self.texture_atlas,
|
||||
if let Some(atlas_entry) = cache.upload_vector(
|
||||
device, encoder, handle, *color, size, scale,
|
||||
) {
|
||||
add_instances(
|
||||
[bounds.x, bounds.y],
|
||||
size,
|
||||
f32::from(*rotation),
|
||||
*opacity,
|
||||
atlas_entry,
|
||||
nearest_instances,
|
||||
);
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "svg"))]
|
||||
layer::Image::Vector { .. } => {}
|
||||
Image::Vector { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -460,26 +282,6 @@ impl Pipeline {
|
|||
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,
|
||||
|
|
@ -493,7 +295,8 @@ impl Pipeline {
|
|||
|
||||
layer.prepare(
|
||||
device,
|
||||
queue,
|
||||
encoder,
|
||||
belt,
|
||||
nearest_instances,
|
||||
linear_instances,
|
||||
transformation,
|
||||
|
|
@ -504,6 +307,7 @@ impl Pipeline {
|
|||
|
||||
pub fn render<'a>(
|
||||
&'a self,
|
||||
cache: &'a Cache,
|
||||
layer: usize,
|
||||
bounds: Rectangle<u32>,
|
||||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
|
|
@ -518,28 +322,173 @@ impl Pipeline {
|
|||
bounds.height,
|
||||
);
|
||||
|
||||
render_pass.set_bind_group(1, &self.texture, &[]);
|
||||
render_pass.set_bind_group(1, cache.bind_group(), &[]);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Layer {
|
||||
uniforms: wgpu::Buffer,
|
||||
nearest: Data,
|
||||
linear: Data,
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
fn new(
|
||||
device: &wgpu::Device,
|
||||
constant_layout: &wgpu::BindGroupLayout,
|
||||
nearest_sampler: &wgpu::Sampler,
|
||||
linear_sampler: &wgpu::Sampler,
|
||||
) -> Self {
|
||||
let uniforms = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("iced_wgpu::image uniforms buffer"),
|
||||
size: mem::size_of::<Uniforms>() as u64,
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let nearest =
|
||||
Data::new(device, constant_layout, nearest_sampler, &uniforms);
|
||||
|
||||
let linear =
|
||||
Data::new(device, constant_layout, linear_sampler, &uniforms);
|
||||
|
||||
Self {
|
||||
uniforms,
|
||||
nearest,
|
||||
linear,
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
nearest_instances: &[Instance],
|
||||
linear_instances: &[Instance],
|
||||
transformation: Transformation,
|
||||
) {
|
||||
let uniforms = Uniforms {
|
||||
transform: transformation.into(),
|
||||
};
|
||||
|
||||
let bytes = bytemuck::bytes_of(&uniforms);
|
||||
|
||||
belt.write_buffer(
|
||||
encoder,
|
||||
&self.uniforms,
|
||||
0,
|
||||
(bytes.len() as u64).try_into().expect("Sized uniforms"),
|
||||
device,
|
||||
)
|
||||
.copy_from_slice(bytes);
|
||||
|
||||
self.nearest
|
||||
.upload(device, encoder, belt, nearest_instances);
|
||||
|
||||
self.linear.upload(device, encoder, belt, linear_instances);
|
||||
}
|
||||
|
||||
fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
|
||||
self.nearest.render(render_pass);
|
||||
self.linear.render(render_pass);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Data {
|
||||
constants: wgpu::BindGroup,
|
||||
instances: Buffer<Instance>,
|
||||
instance_count: usize,
|
||||
}
|
||||
|
||||
impl Data {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
constant_layout: &wgpu::BindGroupLayout,
|
||||
sampler: &wgpu::Sampler,
|
||||
uniforms: &wgpu::Buffer,
|
||||
) -> Self {
|
||||
let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::image constants bind group"),
|
||||
layout: constant_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(
|
||||
wgpu::BufferBinding {
|
||||
buffer: uniforms,
|
||||
offset: 0,
|
||||
size: None,
|
||||
},
|
||||
),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(sampler),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let instances = Buffer::new(
|
||||
device,
|
||||
"iced_wgpu::image instance buffer",
|
||||
Instance::INITIAL,
|
||||
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
);
|
||||
|
||||
Self {
|
||||
constants,
|
||||
instances,
|
||||
instance_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn upload(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
instances: &[Instance],
|
||||
) {
|
||||
self.instance_count = instances.len();
|
||||
|
||||
if self.instance_count == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let _ = self.instances.resize(device, instances.len());
|
||||
let _ = self.instances.write(device, encoder, belt, 0, instances);
|
||||
}
|
||||
|
||||
fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
|
||||
if self.instance_count == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
render_pass.set_bind_group(0, &self.constants, &[]);
|
||||
render_pass.set_vertex_buffer(0, self.instances.slice(..));
|
||||
|
||||
render_pass.draw(0..6, 0..self.instance_count as u32);
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
struct Instance {
|
||||
_position: [f32; 2],
|
||||
_center: [f32; 2],
|
||||
_size: [f32; 2],
|
||||
_rotation: f32,
|
||||
_opacity: f32,
|
||||
_position_in_atlas: [f32; 2],
|
||||
_size_in_atlas: [f32; 2],
|
||||
_layer: u32,
|
||||
|
|
@ -558,12 +507,27 @@ struct Uniforms {
|
|||
fn add_instances(
|
||||
image_position: [f32; 2],
|
||||
image_size: [f32; 2],
|
||||
rotation: f32,
|
||||
opacity: f32,
|
||||
entry: &atlas::Entry,
|
||||
instances: &mut Vec<Instance>,
|
||||
) {
|
||||
let center = [
|
||||
image_position[0] + image_size[0] / 2.0,
|
||||
image_position[1] + image_size[1] / 2.0,
|
||||
];
|
||||
|
||||
match entry {
|
||||
atlas::Entry::Contiguous(allocation) => {
|
||||
add_instance(image_position, image_size, allocation, instances);
|
||||
add_instance(
|
||||
image_position,
|
||||
center,
|
||||
image_size,
|
||||
rotation,
|
||||
opacity,
|
||||
allocation,
|
||||
instances,
|
||||
);
|
||||
}
|
||||
atlas::Entry::Fragmented { fragments, size } => {
|
||||
let scaling_x = image_size[0] / size.width as f32;
|
||||
|
|
@ -589,7 +553,10 @@ fn add_instances(
|
|||
fragment_height as f32 * scaling_y,
|
||||
];
|
||||
|
||||
add_instance(position, size, allocation, instances);
|
||||
add_instance(
|
||||
position, center, size, rotation, opacity, allocation,
|
||||
instances,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -598,7 +565,10 @@ fn add_instances(
|
|||
#[inline]
|
||||
fn add_instance(
|
||||
position: [f32; 2],
|
||||
center: [f32; 2],
|
||||
size: [f32; 2],
|
||||
rotation: f32,
|
||||
opacity: f32,
|
||||
allocation: &atlas::Allocation,
|
||||
instances: &mut Vec<Instance>,
|
||||
) {
|
||||
|
|
@ -608,7 +578,10 @@ fn add_instance(
|
|||
|
||||
let instance = Instance {
|
||||
_position: position,
|
||||
_center: center,
|
||||
_size: size,
|
||||
_rotation: rotation,
|
||||
_opacity: opacity,
|
||||
_position_in_atlas: [
|
||||
(x as f32 + 0.5) / atlas::SIZE as f32,
|
||||
(y as f32 + 0.5) / atlas::SIZE as f32,
|
||||
10
wgpu/src/image/null.rs
Normal file
10
wgpu/src/image/null.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
pub use crate::graphics::Image;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Batch;
|
||||
|
||||
impl Batch {
|
||||
pub fn push(&mut self, _image: Image) {}
|
||||
|
||||
pub fn clear(&mut self) {}
|
||||
}
|
||||
|
|
@ -4,13 +4,13 @@ use crate::graphics;
|
|||
use crate::graphics::image::image_rs;
|
||||
use crate::image::atlas::{self, Atlas};
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
/// Entry in cache corresponding to an image handle
|
||||
#[derive(Debug)]
|
||||
pub enum Memory {
|
||||
/// Image data on host
|
||||
Host(image_rs::ImageBuffer<image_rs::Rgba<u8>, Vec<u8>>),
|
||||
Host(image_rs::ImageBuffer<image_rs::Rgba<u8>, image::Bytes>),
|
||||
/// Storage entry
|
||||
Device(atlas::Entry),
|
||||
/// Image not found
|
||||
|
|
@ -38,8 +38,9 @@ impl Memory {
|
|||
/// Caches image raster data
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Cache {
|
||||
map: HashMap<u64, Memory>,
|
||||
hits: HashSet<u64>,
|
||||
map: FxHashMap<image::Id, Memory>,
|
||||
hits: FxHashSet<image::Id>,
|
||||
should_trim: bool,
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
|
|
@ -50,11 +51,13 @@ impl Cache {
|
|||
}
|
||||
|
||||
let memory = match graphics::image::load(handle) {
|
||||
Ok(image) => Memory::Host(image.to_rgba8()),
|
||||
Ok(image) => Memory::Host(image),
|
||||
Err(image_rs::error::ImageError::IoError(_)) => Memory::NotFound,
|
||||
Err(_) => Memory::Invalid,
|
||||
};
|
||||
|
||||
self.should_trim = true;
|
||||
|
||||
self.insert(handle, memory);
|
||||
self.get(handle).unwrap()
|
||||
}
|
||||
|
|
@ -86,6 +89,11 @@ impl Cache {
|
|||
|
||||
/// Trim cache misses from cache
|
||||
pub fn trim(&mut self, atlas: &mut Atlas) {
|
||||
// Only trim if new entries have landed in the `Cache`
|
||||
if !self.should_trim {
|
||||
return;
|
||||
}
|
||||
|
||||
let hits = &self.hits;
|
||||
|
||||
self.map.retain(|k, memory| {
|
||||
|
|
@ -101,6 +109,7 @@ impl Cache {
|
|||
});
|
||||
|
||||
self.hits.clear();
|
||||
self.should_trim = false;
|
||||
}
|
||||
|
||||
fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory> {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use crate::image::atlas::{self, Atlas};
|
|||
|
||||
use resvg::tiny_skia;
|
||||
use resvg::usvg::{self, TreeTextToPath};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use std::fs;
|
||||
|
||||
/// Entry in cache corresponding to an svg handle
|
||||
|
|
@ -33,10 +33,11 @@ impl Svg {
|
|||
/// Caches svg vector and raster data
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Cache {
|
||||
svgs: HashMap<u64, Svg>,
|
||||
rasterized: HashMap<(u64, u32, u32, ColorFilter), atlas::Entry>,
|
||||
svg_hits: HashSet<u64>,
|
||||
rasterized_hits: HashSet<(u64, u32, u32, ColorFilter)>,
|
||||
svgs: FxHashMap<u64, Svg>,
|
||||
rasterized: FxHashMap<(u64, u32, u32, ColorFilter), atlas::Entry>,
|
||||
svg_hits: FxHashSet<u64>,
|
||||
rasterized_hits: FxHashSet<(u64, u32, u32, ColorFilter)>,
|
||||
should_trim: bool,
|
||||
}
|
||||
|
||||
type ColorFilter = Option<[u8; 4]>;
|
||||
|
|
@ -76,6 +77,8 @@ impl Cache {
|
|||
}
|
||||
}
|
||||
|
||||
self.should_trim = true;
|
||||
|
||||
let _ = self.svgs.insert(handle.id(), svg);
|
||||
self.svgs.get(&handle.id()).unwrap()
|
||||
}
|
||||
|
|
@ -176,6 +179,10 @@ impl Cache {
|
|||
|
||||
/// Load svg and upload raster data
|
||||
pub fn trim(&mut self, atlas: &mut Atlas) {
|
||||
if !self.should_trim {
|
||||
return;
|
||||
}
|
||||
|
||||
let svg_hits = &self.svg_hits;
|
||||
let rasterized_hits = &self.rasterized_hits;
|
||||
|
||||
|
|
@ -191,6 +198,7 @@ impl Cache {
|
|||
});
|
||||
self.svg_hits.clear();
|
||||
self.rasterized_hits.clear();
|
||||
self.should_trim = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,343 +1,302 @@
|
|||
//! 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,
|
||||
renderer, Background, Color, Point, Radians, Rectangle, Transformation,
|
||||
};
|
||||
use crate::graphics;
|
||||
use crate::graphics::color;
|
||||
use crate::graphics::Viewport;
|
||||
use crate::graphics::layer;
|
||||
use crate::graphics::text::{Editor, Paragraph};
|
||||
use crate::graphics::Mesh;
|
||||
use crate::image::{self, Image};
|
||||
use crate::primitive::{self, Primitive};
|
||||
use crate::quad::{self, Quad};
|
||||
use crate::text::{self, Text};
|
||||
use crate::triangle;
|
||||
|
||||
pub type Stack = layer::Stack<Layer>;
|
||||
|
||||
/// A group of primitives that should be clipped together.
|
||||
#[derive(Debug)]
|
||||
pub struct Layer<'a> {
|
||||
/// The clipping bounds of the [`Layer`].
|
||||
pub struct Layer {
|
||||
pub bounds: Rectangle,
|
||||
|
||||
/// 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 triangles: triangle::Batch,
|
||||
pub primitives: primitive::Batch,
|
||||
pub text: text::Batch,
|
||||
pub images: image::Batch,
|
||||
pending_meshes: Vec<Mesh>,
|
||||
pending_text: Vec<Text>,
|
||||
}
|
||||
|
||||
impl<'a> Layer<'a> {
|
||||
/// Creates a new [`Layer`] with the given clipping bounds.
|
||||
pub fn new(bounds: Rectangle) -> Self {
|
||||
impl Layer {
|
||||
pub fn draw_quad(
|
||||
&mut self,
|
||||
quad: renderer::Quad,
|
||||
background: Background,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
let bounds = quad.bounds * transformation;
|
||||
|
||||
let quad = Quad {
|
||||
position: [bounds.x, bounds.y],
|
||||
size: [bounds.width, bounds.height],
|
||||
border_color: color::pack(quad.border.color),
|
||||
border_radius: quad.border.radius.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,
|
||||
};
|
||||
|
||||
self.quads.add(quad, &background);
|
||||
}
|
||||
|
||||
pub fn draw_paragraph(
|
||||
&mut self,
|
||||
paragraph: &Paragraph,
|
||||
position: Point,
|
||||
color: Color,
|
||||
clip_bounds: Rectangle,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
let paragraph = Text::Paragraph {
|
||||
paragraph: paragraph.downgrade(),
|
||||
position,
|
||||
color,
|
||||
clip_bounds,
|
||||
transformation,
|
||||
};
|
||||
|
||||
self.pending_text.push(paragraph);
|
||||
}
|
||||
|
||||
pub fn draw_editor(
|
||||
&mut self,
|
||||
editor: &Editor,
|
||||
position: Point,
|
||||
color: Color,
|
||||
clip_bounds: Rectangle,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
let editor = Text::Editor {
|
||||
editor: editor.downgrade(),
|
||||
position,
|
||||
color,
|
||||
clip_bounds,
|
||||
transformation,
|
||||
};
|
||||
|
||||
self.pending_text.push(editor);
|
||||
}
|
||||
|
||||
pub fn draw_text(
|
||||
&mut self,
|
||||
text: crate::core::Text,
|
||||
position: Point,
|
||||
color: Color,
|
||||
clip_bounds: Rectangle,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
let text = Text::Cached {
|
||||
content: text.content,
|
||||
bounds: Rectangle::new(position, text.bounds) * transformation,
|
||||
color,
|
||||
size: text.size * transformation.scale_factor(),
|
||||
line_height: text.line_height.to_absolute(text.size)
|
||||
* transformation.scale_factor(),
|
||||
font: text.font,
|
||||
horizontal_alignment: text.horizontal_alignment,
|
||||
vertical_alignment: text.vertical_alignment,
|
||||
shaping: text.shaping,
|
||||
clip_bounds: clip_bounds * transformation,
|
||||
};
|
||||
|
||||
self.pending_text.push(text);
|
||||
}
|
||||
|
||||
pub fn draw_image(
|
||||
&mut self,
|
||||
handle: crate::core::image::Handle,
|
||||
filter_method: crate::core::image::FilterMethod,
|
||||
bounds: Rectangle,
|
||||
transformation: Transformation,
|
||||
rotation: Radians,
|
||||
opacity: f32,
|
||||
) {
|
||||
let image = Image::Raster {
|
||||
handle,
|
||||
filter_method,
|
||||
bounds: bounds * transformation,
|
||||
rotation,
|
||||
opacity,
|
||||
};
|
||||
|
||||
self.images.push(image);
|
||||
}
|
||||
|
||||
pub fn draw_svg(
|
||||
&mut self,
|
||||
handle: crate::core::svg::Handle,
|
||||
color: Option<Color>,
|
||||
bounds: Rectangle,
|
||||
transformation: Transformation,
|
||||
rotation: Radians,
|
||||
opacity: f32,
|
||||
) {
|
||||
let svg = Image::Vector {
|
||||
handle,
|
||||
color,
|
||||
bounds: bounds * transformation,
|
||||
rotation,
|
||||
opacity,
|
||||
};
|
||||
|
||||
self.images.push(svg);
|
||||
}
|
||||
|
||||
pub fn draw_mesh(
|
||||
&mut self,
|
||||
mut mesh: Mesh,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
match &mut mesh {
|
||||
Mesh::Solid {
|
||||
transformation: local_transformation,
|
||||
..
|
||||
}
|
||||
| Mesh::Gradient {
|
||||
transformation: local_transformation,
|
||||
..
|
||||
} => {
|
||||
*local_transformation = *local_transformation * transformation;
|
||||
}
|
||||
}
|
||||
|
||||
self.pending_meshes.push(mesh);
|
||||
}
|
||||
|
||||
pub fn draw_mesh_group(
|
||||
&mut self,
|
||||
meshes: Vec<Mesh>,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
self.flush_meshes();
|
||||
|
||||
self.triangles.push(triangle::Item::Group {
|
||||
meshes,
|
||||
transformation,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn draw_mesh_cache(
|
||||
&mut self,
|
||||
cache: triangle::Cache,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
self.flush_meshes();
|
||||
|
||||
self.triangles.push(triangle::Item::Cached {
|
||||
cache,
|
||||
transformation,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn draw_text_group(
|
||||
&mut self,
|
||||
text: Vec<Text>,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
self.flush_text();
|
||||
|
||||
self.text.push(text::Item::Group {
|
||||
text,
|
||||
transformation,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn draw_text_cache(
|
||||
&mut self,
|
||||
cache: text::Cache,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
self.flush_text();
|
||||
|
||||
self.text.push(text::Item::Cached {
|
||||
cache,
|
||||
transformation,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn draw_primitive(
|
||||
&mut self,
|
||||
bounds: Rectangle,
|
||||
primitive: Box<dyn Primitive>,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
let bounds = bounds * transformation;
|
||||
|
||||
self.primitives
|
||||
.push(primitive::Instance { bounds, primitive });
|
||||
}
|
||||
|
||||
fn flush_meshes(&mut self) {
|
||||
if !self.pending_meshes.is_empty() {
|
||||
self.triangles.push(triangle::Item::Group {
|
||||
transformation: Transformation::IDENTITY,
|
||||
meshes: self.pending_meshes.drain(..).collect(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_text(&mut self) {
|
||||
if !self.pending_text.is_empty() {
|
||||
self.text.push(text::Item::Group {
|
||||
transformation: Transformation::IDENTITY,
|
||||
text: self.pending_text.drain(..).collect(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl graphics::Layer for Layer {
|
||||
fn with_bounds(bounds: Rectangle) -> Self {
|
||||
Self {
|
||||
bounds,
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) {
|
||||
self.flush_meshes();
|
||||
self.flush_text();
|
||||
}
|
||||
|
||||
fn resize(&mut self, bounds: Rectangle) {
|
||||
self.bounds = bounds;
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.bounds = Rectangle::INFINITE;
|
||||
|
||||
self.quads.clear();
|
||||
self.triangles.clear();
|
||||
self.primitives.clear();
|
||||
self.text.clear();
|
||||
self.images.clear();
|
||||
self.pending_meshes.clear();
|
||||
self.pending_text.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Layer {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bounds: Rectangle::INFINITE,
|
||||
quads: quad::Batch::default(),
|
||||
meshes: Vec::new(),
|
||||
text: Vec::new(),
|
||||
images: Vec::new(),
|
||||
pipelines: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`Layer`] for the provided overlay text.
|
||||
///
|
||||
/// This can be useful for displaying debug information.
|
||||
pub fn overlay(lines: &'a [impl AsRef<str>], viewport: &Viewport) -> Self {
|
||||
let mut overlay =
|
||||
Layer::new(Rectangle::with_size(viewport.logical_size()));
|
||||
|
||||
for (i, line) in lines.iter().enumerate() {
|
||||
let text = text::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),
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
/// 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,
|
||||
) {
|
||||
match primitive {
|
||||
Primitive::Paragraph {
|
||||
paragraph,
|
||||
position,
|
||||
color,
|
||||
clip_bounds,
|
||||
} => {
|
||||
let layer = &mut layers[current_layer];
|
||||
|
||||
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];
|
||||
|
||||
layer.text.push(Text::Editor {
|
||||
editor: editor.clone(),
|
||||
position: *position,
|
||||
color: *color,
|
||||
clip_bounds: *clip_bounds,
|
||||
transformation,
|
||||
});
|
||||
}
|
||||
Primitive::Text {
|
||||
content,
|
||||
bounds,
|
||||
size,
|
||||
line_height,
|
||||
color,
|
||||
font,
|
||||
horizontal_alignment,
|
||||
vertical_alignment,
|
||||
shaping,
|
||||
clip_bounds,
|
||||
} => {
|
||||
let layer = &mut layers[current_layer];
|
||||
|
||||
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,
|
||||
}));
|
||||
}
|
||||
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;
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
layer.quads.add(quad, background);
|
||||
}
|
||||
Primitive::Image {
|
||||
handle,
|
||||
filter_method,
|
||||
bounds,
|
||||
} => {
|
||||
let layer = &mut layers[current_layer];
|
||||
|
||||
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];
|
||||
|
||||
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;
|
||||
|
||||
// Only draw visible content
|
||||
if let Some(clip_bounds) =
|
||||
layer.bounds.intersection(&translated_bounds)
|
||||
{
|
||||
let clip_layer = Layer::new(clip_bounds);
|
||||
layers.push(clip_layer);
|
||||
|
||||
Self::process_primitive(
|
||||
layers,
|
||||
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];
|
||||
|
||||
let bounds =
|
||||
Rectangle::with_size(*size) * transformation;
|
||||
|
||||
// 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];
|
||||
|
||||
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(),
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
triangles: triangle::Batch::default(),
|
||||
primitives: primitive::Batch::default(),
|
||||
text: text::Batch::default(),
|
||||
images: image::Batch::default(),
|
||||
pending_meshes: Vec::new(),
|
||||
pending_text: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
|
@ -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>,
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
536
wgpu/src/lib.rs
536
wgpu/src/lib.rs
|
|
@ -20,15 +20,8 @@
|
|||
#![doc(
|
||||
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
|
||||
)]
|
||||
#![forbid(rust_2018_idioms)]
|
||||
#![deny(
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
unsafe_code,
|
||||
unused_results,
|
||||
rustdoc::broken_intra_doc_links
|
||||
)]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![allow(missing_docs)]
|
||||
pub mod layer;
|
||||
pub mod primitive;
|
||||
pub mod settings;
|
||||
|
|
@ -37,13 +30,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 +52,525 @@ pub use iced_graphics::core;
|
|||
|
||||
pub use wgpu;
|
||||
|
||||
pub use backend::Backend;
|
||||
pub use engine::Engine;
|
||||
pub use layer::Layer;
|
||||
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;
|
||||
|
||||
/// 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,
|
||||
|
||||
triangle_storage: triangle::Storage,
|
||||
text_storage: text::Storage,
|
||||
text_viewport: text::Viewport,
|
||||
|
||||
// TODO: Centralize all the image feature handling
|
||||
#[cfg(any(feature = "svg", feature = "image"))]
|
||||
image_cache: std::cell::RefCell<image::Cache>,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
engine: &Engine,
|
||||
default_font: Font,
|
||||
default_text_size: Pixels,
|
||||
) -> Self {
|
||||
Self {
|
||||
default_font,
|
||||
default_text_size,
|
||||
layers: layer::Stack::new(),
|
||||
|
||||
triangle_storage: triangle::Storage::new(),
|
||||
text_storage: text::Storage::new(),
|
||||
text_viewport: engine.text_pipeline.create_viewport(device),
|
||||
|
||||
#[cfg(any(feature = "svg", feature = "image"))]
|
||||
image_cache: std::cell::RefCell::new(
|
||||
engine.create_image_cache(device),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn present(
|
||||
&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,
|
||||
) {
|
||||
self.prepare(engine, device, queue, format, encoder, viewport);
|
||||
self.render(engine, encoder, frame, clear_color, viewport);
|
||||
|
||||
self.triangle_storage.trim();
|
||||
self.text_storage.trim();
|
||||
|
||||
#[cfg(any(feature = "svg", feature = "image"))]
|
||||
self.image_cache.borrow_mut().trim();
|
||||
}
|
||||
|
||||
fn prepare(
|
||||
&mut self,
|
||||
engine: &mut Engine,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
_format: wgpu::TextureFormat,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
viewport: &Viewport,
|
||||
) {
|
||||
let scale_factor = viewport.scale_factor() as f32;
|
||||
|
||||
self.text_viewport.update(queue, viewport.physical_size());
|
||||
|
||||
for layer in self.layers.iter_mut() {
|
||||
if !layer.quads.is_empty() {
|
||||
engine.quad_pipeline.prepare(
|
||||
device,
|
||||
encoder,
|
||||
&mut engine.staging_belt,
|
||||
&layer.quads,
|
||||
viewport.projection(),
|
||||
scale_factor,
|
||||
);
|
||||
}
|
||||
|
||||
if !layer.triangles.is_empty() {
|
||||
engine.triangle_pipeline.prepare(
|
||||
device,
|
||||
encoder,
|
||||
&mut engine.staging_belt,
|
||||
&mut self.triangle_storage,
|
||||
&layer.triangles,
|
||||
Transformation::scale(scale_factor),
|
||||
viewport.physical_size(),
|
||||
);
|
||||
}
|
||||
|
||||
if !layer.primitives.is_empty() {
|
||||
for instance in &layer.primitives {
|
||||
instance.primitive.prepare(
|
||||
device,
|
||||
queue,
|
||||
engine.format,
|
||||
&mut engine.primitive_storage,
|
||||
&instance.bounds,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if !layer.text.is_empty() {
|
||||
engine.text_pipeline.prepare(
|
||||
device,
|
||||
queue,
|
||||
&self.text_viewport,
|
||||
encoder,
|
||||
&mut self.text_storage,
|
||||
&layer.text,
|
||||
layer.bounds,
|
||||
Transformation::scale(scale_factor),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "svg", feature = "image"))]
|
||||
if !layer.images.is_empty() {
|
||||
engine.image_pipeline.prepare(
|
||||
device,
|
||||
encoder,
|
||||
&mut engine.staging_belt,
|
||||
&mut self.image_cache.borrow_mut(),
|
||||
&layer.images,
|
||||
viewport.projection(),
|
||||
scale_factor,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render(
|
||||
&mut self,
|
||||
engine: &mut Engine,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
frame: &wgpu::TextureView,
|
||||
clear_color: Option<Color>,
|
||||
viewport: &Viewport,
|
||||
) {
|
||||
use std::mem::ManuallyDrop;
|
||||
|
||||
let mut render_pass = ManuallyDrop::new(encoder.begin_render_pass(
|
||||
&wgpu::RenderPassDescriptor {
|
||||
label: Some("iced_wgpu render pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: frame,
|
||||
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;
|
||||
#[cfg(any(feature = "svg", feature = "image"))]
|
||||
let image_cache = self.image_cache.borrow();
|
||||
|
||||
let scale_factor = viewport.scale_factor() as f32;
|
||||
let physical_bounds = Rectangle::<f32>::from(Rectangle::with_size(
|
||||
viewport.physical_size(),
|
||||
));
|
||||
|
||||
let scale = Transformation::scale(scale_factor);
|
||||
|
||||
for layer in self.layers.iter() {
|
||||
let Some(physical_bounds) =
|
||||
physical_bounds.intersection(&(layer.bounds * scale))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(scissor_rect) = physical_bounds.snap() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !layer.quads.is_empty() {
|
||||
engine.quad_pipeline.render(
|
||||
quad_layer,
|
||||
scissor_rect,
|
||||
&layer.quads,
|
||||
&mut render_pass,
|
||||
);
|
||||
|
||||
quad_layer += 1;
|
||||
}
|
||||
|
||||
if !layer.triangles.is_empty() {
|
||||
let _ = ManuallyDrop::into_inner(render_pass);
|
||||
|
||||
mesh_layer += engine.triangle_pipeline.render(
|
||||
encoder,
|
||||
frame,
|
||||
&self.triangle_storage,
|
||||
mesh_layer,
|
||||
&layer.triangles,
|
||||
physical_bounds,
|
||||
scale,
|
||||
);
|
||||
|
||||
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 !layer.primitives.is_empty() {
|
||||
let _ = ManuallyDrop::into_inner(render_pass);
|
||||
|
||||
for instance in &layer.primitives {
|
||||
if let Some(clip_bounds) = (instance.bounds * scale)
|
||||
.intersection(&physical_bounds)
|
||||
.and_then(Rectangle::snap)
|
||||
{
|
||||
instance.primitive.render(
|
||||
encoder,
|
||||
&engine.primitive_storage,
|
||||
frame,
|
||||
&clip_bounds,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render_pass = ManuallyDrop::new(encoder.begin_render_pass(
|
||||
&wgpu::RenderPassDescriptor {
|
||||
label: Some("iced_wgpu render pass"),
|
||||
color_attachments: &[Some(
|
||||
wgpu::RenderPassColorAttachment {
|
||||
view: frame,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
},
|
||||
)],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
if !layer.text.is_empty() {
|
||||
text_layer += engine.text_pipeline.render(
|
||||
&self.text_viewport,
|
||||
&self.text_storage,
|
||||
text_layer,
|
||||
&layer.text,
|
||||
scissor_rect,
|
||||
&mut render_pass,
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "svg", feature = "image"))]
|
||||
if !layer.images.is_empty() {
|
||||
engine.image_pipeline.render(
|
||||
&image_cache,
|
||||
image_layer,
|
||||
scissor_rect,
|
||||
&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(bounds);
|
||||
}
|
||||
|
||||
fn end_layer(&mut self) {
|
||||
self.layers.pop_clip();
|
||||
}
|
||||
|
||||
fn start_transformation(&mut self, transformation: Transformation) {
|
||||
self.layers.push_transformation(transformation);
|
||||
}
|
||||
|
||||
fn end_transformation(&mut self) {
|
||||
self.layers.pop_transformation();
|
||||
}
|
||||
|
||||
fn fill_quad(
|
||||
&mut self,
|
||||
quad: core::renderer::Quad,
|
||||
background: impl Into<Background>,
|
||||
) {
|
||||
let (layer, transformation) = self.layers.current_mut();
|
||||
layer.draw_quad(quad, background.into(), transformation);
|
||||
}
|
||||
|
||||
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 fill_paragraph(
|
||||
&mut self,
|
||||
text: &Self::Paragraph,
|
||||
position: Point,
|
||||
color: Color,
|
||||
clip_bounds: Rectangle,
|
||||
) {
|
||||
let (layer, transformation) = self.layers.current_mut();
|
||||
|
||||
layer.draw_paragraph(
|
||||
text,
|
||||
position,
|
||||
color,
|
||||
clip_bounds,
|
||||
transformation,
|
||||
);
|
||||
}
|
||||
|
||||
fn fill_editor(
|
||||
&mut self,
|
||||
editor: &Self::Editor,
|
||||
position: Point,
|
||||
color: Color,
|
||||
clip_bounds: Rectangle,
|
||||
) {
|
||||
let (layer, transformation) = self.layers.current_mut();
|
||||
layer.draw_editor(editor, position, color, clip_bounds, transformation);
|
||||
}
|
||||
|
||||
fn fill_text(
|
||||
&mut self,
|
||||
text: core::Text,
|
||||
position: Point,
|
||||
color: Color,
|
||||
clip_bounds: Rectangle,
|
||||
) {
|
||||
let (layer, transformation) = self.layers.current_mut();
|
||||
layer.draw_text(text, position, color, clip_bounds, transformation);
|
||||
}
|
||||
}
|
||||
|
||||
#[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.borrow_mut().measure_image(handle)
|
||||
}
|
||||
|
||||
fn draw_image(
|
||||
&mut self,
|
||||
handle: Self::Handle,
|
||||
filter_method: core::image::FilterMethod,
|
||||
bounds: Rectangle,
|
||||
rotation: core::Radians,
|
||||
opacity: f32,
|
||||
) {
|
||||
let (layer, transformation) = self.layers.current_mut();
|
||||
layer.draw_image(
|
||||
handle,
|
||||
filter_method,
|
||||
bounds,
|
||||
transformation,
|
||||
rotation,
|
||||
opacity,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
impl core::svg::Renderer for Renderer {
|
||||
fn measure_svg(&self, handle: &core::svg::Handle) -> Size<u32> {
|
||||
self.image_cache.borrow_mut().measure_svg(handle)
|
||||
}
|
||||
|
||||
fn draw_svg(
|
||||
&mut self,
|
||||
handle: core::svg::Handle,
|
||||
color_filter: Option<Color>,
|
||||
bounds: Rectangle,
|
||||
rotation: core::Radians,
|
||||
opacity: f32,
|
||||
) {
|
||||
let (layer, transformation) = self.layers.current_mut();
|
||||
layer.draw_svg(
|
||||
handle,
|
||||
color_filter,
|
||||
bounds,
|
||||
transformation,
|
||||
rotation,
|
||||
opacity,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl graphics::mesh::Renderer for Renderer {
|
||||
fn draw_mesh(&mut self, mesh: graphics::Mesh) {
|
||||
let (layer, transformation) = self.layers.current_mut();
|
||||
layer.draw_mesh(mesh, transformation);
|
||||
}
|
||||
}
|
||||
|
||||
#[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) {
|
||||
let (layer, transformation) = self.layers.current_mut();
|
||||
|
||||
match geometry {
|
||||
Geometry::Live { meshes, text } => {
|
||||
layer.draw_mesh_group(meshes, transformation);
|
||||
layer.draw_text_group(text, transformation);
|
||||
}
|
||||
Geometry::Cached(cache) => {
|
||||
if let Some(meshes) = cache.meshes {
|
||||
layer.draw_mesh_cache(meshes, transformation);
|
||||
}
|
||||
|
||||
if let Some(text) = cache.text {
|
||||
layer.draw_text_cache(text, transformation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl primitive::Renderer for Renderer {
|
||||
fn draw_primitive(&mut self, bounds: Rectangle, primitive: impl Primitive) {
|
||||
let (layer, transformation) = self.layers.current_mut();
|
||||
layer.draw_primitive(bounds, Box::new(primitive), transformation);
|
||||
}
|
||||
}
|
||||
|
||||
impl graphics::compositor::Default for crate::Renderer {
|
||||
type Compositor = window::Compositor;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,95 @@
|
|||
//! Draw using different graphical primitives.
|
||||
pub mod pipeline;
|
||||
|
||||
pub use pipeline::Pipeline;
|
||||
|
||||
use crate::core::Rectangle;
|
||||
use crate::graphics::{Damage, Mesh};
|
||||
//! Draw custom primitives.
|
||||
use crate::core::{self, Rectangle};
|
||||
use crate::graphics::Viewport;
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::any::{Any, TypeId};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// The graphical primitives supported by `iced_wgpu`.
|
||||
pub type Primitive = crate::graphics::Primitive<Custom>;
|
||||
/// A batch of primitives.
|
||||
pub type Batch = Vec<Instance>;
|
||||
|
||||
/// The custom primitives supported by `iced_wgpu`.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Custom {
|
||||
/// A mesh primitive.
|
||||
Mesh(Mesh),
|
||||
/// A custom pipeline primitive.
|
||||
Pipeline(Pipeline),
|
||||
/// A set of methods which allows a [`Primitive`] to be rendered.
|
||||
pub trait Primitive: Debug + Send + Sync + 'static {
|
||||
/// Processes the [`Primitive`], allowing for GPU buffer allocation.
|
||||
fn prepare(
|
||||
&self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
format: wgpu::TextureFormat,
|
||||
storage: &mut Storage,
|
||||
bounds: &Rectangle,
|
||||
viewport: &Viewport,
|
||||
);
|
||||
|
||||
/// Renders the [`Primitive`].
|
||||
fn render(
|
||||
&self,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
storage: &Storage,
|
||||
target: &wgpu::TextureView,
|
||||
clip_bounds: &Rectangle<u32>,
|
||||
);
|
||||
}
|
||||
|
||||
impl Damage for Custom {
|
||||
fn bounds(&self) -> Rectangle {
|
||||
match self {
|
||||
Self::Mesh(mesh) => mesh.bounds(),
|
||||
Self::Pipeline(pipeline) => pipeline.bounds,
|
||||
#[derive(Debug)]
|
||||
/// An instance of a specific [`Primitive`].
|
||||
pub struct Instance {
|
||||
/// The bounds of the [`Instance`].
|
||||
pub bounds: Rectangle,
|
||||
|
||||
/// The [`Primitive`] to render.
|
||||
pub primitive: Box<dyn Primitive>,
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
/// Creates a new [`Instance`] with the given [`Primitive`].
|
||||
pub fn new(bounds: Rectangle, primitive: impl Primitive) -> Self {
|
||||
Instance {
|
||||
bounds,
|
||||
primitive: Box::new(primitive),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A renderer than can draw custom primitives.
|
||||
pub trait Renderer: core::Renderer {
|
||||
/// Draws a custom primitive.
|
||||
fn draw_primitive(&mut self, bounds: Rectangle, primitive: impl Primitive);
|
||||
}
|
||||
|
||||
/// Stores custom, user-provided types.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Storage {
|
||||
pipelines: FxHashMap<TypeId, Box<dyn Any + Send>>,
|
||||
}
|
||||
|
||||
impl Storage {
|
||||
/// Returns `true` if `Storage` contains a type `T`.
|
||||
pub fn has<T: 'static>(&self) -> bool {
|
||||
self.pipelines.contains_key(&TypeId::of::<T>())
|
||||
}
|
||||
|
||||
/// Inserts the data `T` in to [`Storage`].
|
||||
pub fn store<T: 'static + Send>(&mut self, data: T) {
|
||||
let _ = self.pipelines.insert(TypeId::of::<T>(), Box::new(data));
|
||||
}
|
||||
|
||||
/// Returns a reference to the data with type `T` if it exists in [`Storage`].
|
||||
pub fn get<T: 'static>(&self) -> Option<&T> {
|
||||
self.pipelines.get(&TypeId::of::<T>()).map(|pipeline| {
|
||||
pipeline
|
||||
.downcast_ref::<T>()
|
||||
.expect("Value with this type does not exist in Storage.")
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the data with type `T` if it exists in [`Storage`].
|
||||
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
|
||||
self.pipelines.get_mut(&TypeId::of::<T>()).map(|pipeline| {
|
||||
pipeline
|
||||
.downcast_mut::<T>()
|
||||
.expect("Value with this type does not exist in Storage.")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,116 +0,0 @@
|
|||
//! Draw primitives using custom pipelines.
|
||||
use crate::core::{Rectangle, Size};
|
||||
|
||||
use std::any::{Any, TypeId};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
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 [`Primitive`] to render.
|
||||
pub primitive: Arc<dyn Primitive>,
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
/// Creates a new [`Pipeline`] with the given [`Primitive`].
|
||||
pub fn new(bounds: Rectangle, primitive: impl Primitive) -> Self {
|
||||
Pipeline {
|
||||
bounds,
|
||||
primitive: Arc::new(primitive),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Pipeline {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.primitive.type_id() == other.primitive.type_id()
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of methods which allows a [`Primitive`] to be rendered.
|
||||
pub trait Primitive: Debug + Send + Sync + 'static {
|
||||
/// Processes the [`Primitive`], allowing for GPU buffer allocation.
|
||||
fn prepare(
|
||||
&self,
|
||||
format: wgpu::TextureFormat,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
bounds: Rectangle,
|
||||
target_size: Size<u32>,
|
||||
scale_factor: f32,
|
||||
storage: &mut Storage,
|
||||
);
|
||||
|
||||
/// Renders the [`Primitive`].
|
||||
fn render(
|
||||
&self,
|
||||
storage: &Storage,
|
||||
target: &wgpu::TextureView,
|
||||
target_size: Size<u32>,
|
||||
viewport: Rectangle<u32>,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
);
|
||||
}
|
||||
|
||||
/// A renderer than can draw custom pipeline primitives.
|
||||
pub trait Renderer: crate::core::Renderer {
|
||||
/// Draws a custom pipeline primitive.
|
||||
fn draw_pipeline_primitive(
|
||||
&mut self,
|
||||
bounds: Rectangle,
|
||||
primitive: impl Primitive,
|
||||
);
|
||||
}
|
||||
|
||||
impl Renderer for crate::Renderer {
|
||||
fn draw_pipeline_primitive(
|
||||
&mut self,
|
||||
bounds: Rectangle,
|
||||
primitive: impl Primitive,
|
||||
) {
|
||||
self.draw_primitive(super::Primitive::Custom(super::Custom::Pipeline(
|
||||
Pipeline::new(bounds, primitive),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores custom, user-provided pipelines.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Storage {
|
||||
pipelines: HashMap<TypeId, Box<dyn Any + Send>>,
|
||||
}
|
||||
|
||||
impl Storage {
|
||||
/// Returns `true` if `Storage` contains a pipeline with type `T`.
|
||||
pub fn has<T: 'static>(&self) -> bool {
|
||||
self.pipelines.get(&TypeId::of::<T>()).is_some()
|
||||
}
|
||||
|
||||
/// Inserts the pipeline `T` in to [`Storage`].
|
||||
pub fn store<T: 'static + Send>(&mut self, pipeline: T) {
|
||||
let _ = self.pipelines.insert(TypeId::of::<T>(), Box::new(pipeline));
|
||||
}
|
||||
|
||||
/// Returns a reference to pipeline with type `T` if it exists in [`Storage`].
|
||||
pub fn get<T: 'static>(&self) -> Option<&T> {
|
||||
self.pipelines.get(&TypeId::of::<T>()).map(|pipeline| {
|
||||
pipeline
|
||||
.downcast_ref::<T>()
|
||||
.expect("Pipeline with this type does not exist in Storage.")
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to pipeline `T` if it exists in [`Storage`].
|
||||
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
|
||||
self.pipelines.get_mut(&TypeId::of::<T>()).map(|pipeline| {
|
||||
pipeline
|
||||
.downcast_mut::<T>()
|
||||
.expect("Pipeline with this type does not exist in Storage.")
|
||||
})
|
||||
}
|
||||
}
|
||||
118
wgpu/src/quad.rs
118
wgpu/src/quad.rs
|
|
@ -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,
|
||||
|
|
@ -57,7 +83,8 @@ impl Pipeline {
|
|||
pub fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
quads: &Batch,
|
||||
transformation: Transformation,
|
||||
scale: f32,
|
||||
|
|
@ -67,7 +94,7 @@ impl Pipeline {
|
|||
}
|
||||
|
||||
let layer = &mut self.layers[self.prepare_layer];
|
||||
layer.prepare(device, queue, quads, transformation, scale);
|
||||
layer.prepare(device, encoder, belt, quads, transformation, scale);
|
||||
|
||||
self.prepare_layer += 1;
|
||||
}
|
||||
|
|
@ -123,7 +150,7 @@ impl Pipeline {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Layer {
|
||||
pub struct Layer {
|
||||
constants: wgpu::BindGroup,
|
||||
constants_buffer: wgpu::Buffer,
|
||||
solid: solid::Layer,
|
||||
|
|
@ -162,56 +189,46 @@ impl Layer {
|
|||
pub fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
quads: &Batch,
|
||||
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);
|
||||
|
||||
queue.write_buffer(
|
||||
belt.write_buffer(
|
||||
encoder,
|
||||
&self.constants_buffer,
|
||||
0,
|
||||
bytemuck::bytes_of(&uniforms),
|
||||
);
|
||||
|
||||
self.solid.prepare(device, queue, &quads.solids);
|
||||
self.gradient.prepare(device, queue, &quads.gradients);
|
||||
(bytes.len() as u64).try_into().expect("Sized uniforms"),
|
||||
device,
|
||||
)
|
||||
.copy_from_slice(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
|
|
@ -221,10 +238,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 {
|
||||
|
|
@ -264,6 +284,12 @@ impl Batch {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.solids.clear();
|
||||
self.gradients.clear();
|
||||
self.order.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
|
|
|
|||
|
|
@ -46,11 +46,12 @@ impl Layer {
|
|||
pub fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
instances: &[Gradient],
|
||||
) {
|
||||
let _ = self.instances.resize(device, instances.len());
|
||||
let _ = self.instances.write(queue, 0, instances);
|
||||
let _ = self.instances.write(device, encoder, belt, 0, instances);
|
||||
|
||||
self.instance_count = instances.len();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,11 +40,12 @@ impl Layer {
|
|||
pub fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
instances: &[Solid],
|
||||
) {
|
||||
let _ = self.instances.resize(device, instances.len());
|
||||
let _ = self.instances.write(queue, 0, instances);
|
||||
let _ = self.instances.write(device, encoder, belt, 0, instances);
|
||||
|
||||
self.instance_count = instances.len();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
//! Configure a renderer.
|
||||
use crate::core::{Font, Pixels};
|
||||
use crate::graphics::Antialiasing;
|
||||
use crate::graphics::{self, Antialiasing};
|
||||
|
||||
/// The settings of a [`Backend`].
|
||||
/// The settings of a [`Renderer`].
|
||||
///
|
||||
/// [`Backend`]: crate::Backend
|
||||
/// [`Renderer`]: crate::Renderer
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Settings {
|
||||
/// The present mode of the [`Backend`].
|
||||
/// The present mode of the [`Renderer`].
|
||||
///
|
||||
/// [`Backend`]: crate::Backend
|
||||
/// [`Renderer`]: crate::Renderer
|
||||
pub present_mode: wgpu::PresentMode,
|
||||
|
||||
/// The internal graphics backend to use.
|
||||
pub internal_backend: wgpu::Backends,
|
||||
/// The graphics backends to use.
|
||||
pub backends: wgpu::Backends,
|
||||
|
||||
/// The default [`Font`] to use.
|
||||
pub default_font: Font,
|
||||
|
|
@ -29,38 +29,51 @@ pub struct Settings {
|
|||
pub antialiasing: Option<Antialiasing>,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
/// Creates new [`Settings`] using environment configuration.
|
||||
///
|
||||
/// Specifically:
|
||||
///
|
||||
/// - The `internal_backend` can be configured using the `WGPU_BACKEND`
|
||||
/// environment variable. If the variable is not set, the primary backend
|
||||
/// will be used. The following values are allowed:
|
||||
/// - `vulkan`
|
||||
/// - `metal`
|
||||
/// - `dx12`
|
||||
/// - `dx11`
|
||||
/// - `gl`
|
||||
/// - `webgpu`
|
||||
/// - `primary`
|
||||
pub fn from_env() -> Self {
|
||||
Settings {
|
||||
internal_backend: wgpu::util::backend_bits_from_env()
|
||||
.unwrap_or(wgpu::Backends::all()),
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Settings {
|
||||
Settings {
|
||||
present_mode: wgpu::PresentMode::AutoVsync,
|
||||
internal_backend: wgpu::Backends::all(),
|
||||
backends: wgpu::Backends::all(),
|
||||
default_font: Font::default(),
|
||||
default_text_size: Pixels(16.0),
|
||||
antialiasing: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<graphics::Settings> for Settings {
|
||||
fn from(settings: graphics::Settings) -> Self {
|
||||
Self {
|
||||
default_font: settings.default_font,
|
||||
default_text_size: settings.default_text_size,
|
||||
antialiasing: settings.antialiasing,
|
||||
..Settings::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtains a [`wgpu::PresentMode`] from the current environment
|
||||
/// configuration, if set.
|
||||
///
|
||||
/// The value returned by this function can be changed by setting
|
||||
/// the `ICED_PRESENT_MODE` env variable. The possible values are:
|
||||
///
|
||||
/// - `vsync` → [`wgpu::PresentMode::AutoVsync`]
|
||||
/// - `no_vsync` → [`wgpu::PresentMode::AutoNoVsync`]
|
||||
/// - `immediate` → [`wgpu::PresentMode::Immediate`]
|
||||
/// - `fifo` → [`wgpu::PresentMode::Fifo`]
|
||||
/// - `fifo_relaxed` → [`wgpu::PresentMode::FifoRelaxed`]
|
||||
/// - `mailbox` → [`wgpu::PresentMode::Mailbox`]
|
||||
pub fn present_mode_from_env() -> Option<wgpu::PresentMode> {
|
||||
let present_mode = std::env::var("ICED_PRESENT_MODE").ok()?;
|
||||
|
||||
match present_mode.to_lowercase().as_str() {
|
||||
"vsync" => Some(wgpu::PresentMode::AutoVsync),
|
||||
"no_vsync" => Some(wgpu::PresentMode::AutoNoVsync),
|
||||
"immediate" => Some(wgpu::PresentMode::Immediate),
|
||||
"fifo" => Some(wgpu::PresentMode::Fifo),
|
||||
"fifo_relaxed" => Some(wgpu::PresentMode::FifoRelaxed),
|
||||
"mailbox" => Some(wgpu::PresentMode::Mailbox),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,14 @@
|
|||
var<private> positions: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
|
||||
vec2<f32>(-1.0, 1.0),
|
||||
vec2<f32>(-1.0, -1.0),
|
||||
vec2<f32>(1.0, -1.0),
|
||||
vec2<f32>(-1.0, 1.0),
|
||||
vec2<f32>(1.0, 1.0),
|
||||
vec2<f32>(1.0, -1.0)
|
||||
);
|
||||
|
||||
var<private> uvs: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
|
||||
vec2<f32>(0.0, 0.0),
|
||||
vec2<f32>(0.0, 1.0),
|
||||
vec2<f32>(1.0, 0.0),
|
||||
vec2<f32>(1.0, 1.0),
|
||||
vec2<f32>(0.0, 0.0),
|
||||
vec2<f32>(1.0, 0.0),
|
||||
vec2<f32>(0.0, 1.0),
|
||||
vec2<f32>(1.0, 1.0)
|
||||
);
|
||||
|
||||
@group(0) @binding(0) var u_sampler: sampler;
|
||||
@group(0) @binding(1) var<uniform> u_ratio: vec2<f32>;
|
||||
@group(1) @binding(0) var u_texture: texture_2d<f32>;
|
||||
|
||||
struct VertexInput {
|
||||
|
|
@ -30,9 +22,11 @@ struct VertexOutput {
|
|||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
let uv = uvs[input.vertex_index];
|
||||
|
||||
var out: VertexOutput;
|
||||
out.uv = uvs[input.vertex_index];
|
||||
out.position = vec4<f32>(positions[input.vertex_index], 0.0, 1.0);
|
||||
out.uv = uv * u_ratio;
|
||||
out.position = vec4<f32>(uv * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 0.0, 1.0);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,40 +9,55 @@ struct Globals {
|
|||
struct VertexInput {
|
||||
@builtin(vertex_index) vertex_index: u32,
|
||||
@location(0) pos: vec2<f32>,
|
||||
@location(1) scale: vec2<f32>,
|
||||
@location(2) atlas_pos: vec2<f32>,
|
||||
@location(3) atlas_scale: vec2<f32>,
|
||||
@location(4) layer: i32,
|
||||
@location(1) center: vec2<f32>,
|
||||
@location(2) scale: vec2<f32>,
|
||||
@location(3) rotation: f32,
|
||||
@location(4) opacity: f32,
|
||||
@location(5) atlas_pos: vec2<f32>,
|
||||
@location(6) atlas_scale: vec2<f32>,
|
||||
@location(7) layer: i32,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
@location(1) layer: f32, // this should be an i32, but naga currently reads that as requiring interpolation.
|
||||
@location(2) opacity: f32,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
|
||||
let v_pos = vertex_position(input.vertex_index);
|
||||
// Generate a vertex position in the range [0, 1] from the vertex index.
|
||||
var v_pos = vertex_position(input.vertex_index);
|
||||
|
||||
// Map the vertex position to the atlas texture.
|
||||
out.uv = vec2<f32>(v_pos * input.atlas_scale + input.atlas_pos);
|
||||
out.layer = f32(input.layer);
|
||||
out.opacity = input.opacity;
|
||||
|
||||
var transform: mat4x4<f32> = mat4x4<f32>(
|
||||
vec4<f32>(input.scale.x, 0.0, 0.0, 0.0),
|
||||
vec4<f32>(0.0, input.scale.y, 0.0, 0.0),
|
||||
// Calculate the vertex position and move the center to the origin
|
||||
v_pos = input.pos + v_pos * input.scale - input.center;
|
||||
|
||||
// Apply the rotation around the center of the image
|
||||
let cos_rot = cos(input.rotation);
|
||||
let sin_rot = sin(input.rotation);
|
||||
let rotate = mat4x4<f32>(
|
||||
vec4<f32>(cos_rot, sin_rot, 0.0, 0.0),
|
||||
vec4<f32>(-sin_rot, cos_rot, 0.0, 0.0),
|
||||
vec4<f32>(0.0, 0.0, 1.0, 0.0),
|
||||
vec4<f32>(input.pos, 0.0, 1.0)
|
||||
vec4<f32>(0.0, 0.0, 0.0, 1.0)
|
||||
);
|
||||
|
||||
out.position = globals.transform * transform * vec4<f32>(v_pos, 0.0, 1.0);
|
||||
// Calculate the final position of the vertex
|
||||
out.position = globals.transform * (vec4<f32>(input.center, 0.0, 0.0) + rotate * vec4<f32>(v_pos, 0.0, 1.0));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
|
||||
return textureSample(u_texture, u_sampler, input.uv, i32(input.layer));
|
||||
// Sample the texture at the given UV coordinate and layer.
|
||||
return textureSample(u_texture, u_sampler, input.uv, i32(input.layer)) * vec4<f32>(1.0, 1.0, 1.0, input.opacity);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ fn rounded_box_sdf(to_center: vec2<f32>, size: vec2<f32>, radius: f32) -> f32 {
|
|||
return length(max(abs(to_center) - size + vec2<f32>(radius, radius), vec2<f32>(0.0, 0.0))) - radius;
|
||||
}
|
||||
|
||||
// Based on the fragement position and the center of the quad, select one of the 4 radi.
|
||||
// Based on the fragment position and the center of the quad, select one of the 4 radi.
|
||||
// Order matches CSS border radius attribute:
|
||||
// radi.x = top-left, radi.y = top-right, radi.z = bottom-right, radi.w = bottom-left
|
||||
fn select_border_radius(radi: vec4<f32>, position: vec2<f32>, center: vec2<f32>) -> f32 {
|
||||
|
|
|
|||
|
|
@ -107,13 +107,19 @@ fn solid_fs_main(
|
|||
let quad_color = vec4<f32>(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha);
|
||||
|
||||
if input.shadow_color.a > 0.0 {
|
||||
let shadow_distance = rounded_box_sdf(input.position.xy - input.pos - input.shadow_offset - (input.scale / 2.0), input.scale / 2.0, border_radius);
|
||||
let shadow_radius = select_border_radius(
|
||||
input.border_radius,
|
||||
input.position.xy - input.shadow_offset,
|
||||
(input.pos + input.scale * 0.5).xy
|
||||
);
|
||||
let shadow_distance = max(rounded_box_sdf(input.position.xy - input.pos - input.shadow_offset - (input.scale / 2.0), input.scale / 2.0, shadow_radius), 0.);
|
||||
|
||||
let shadow_alpha = 1.0 - smoothstep(-input.shadow_blur_radius, input.shadow_blur_radius, shadow_distance);
|
||||
let shadow_color = input.shadow_color;
|
||||
let base_color = select(
|
||||
let base_color = mix(
|
||||
vec4<f32>(shadow_color.x, shadow_color.y, shadow_color.z, 0.0),
|
||||
quad_color,
|
||||
quad_color.a > 0.0
|
||||
quad_color.a
|
||||
);
|
||||
|
||||
return mix(base_color, shadow_color, (1.0 - radius_alpha) * shadow_alpha);
|
||||
|
|
|
|||
836
wgpu/src/text.rs
836
wgpu/src/text.rs
|
|
@ -1,20 +1,285 @@
|
|||
use crate::core::alignment;
|
||||
use crate::core::{Rectangle, Size, Transformation};
|
||||
use crate::graphics::cache;
|
||||
use crate::graphics::color;
|
||||
use crate::graphics::text::cache::{self, Cache};
|
||||
use crate::graphics::text::cache::{self as text_cache, 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 rustc_hash::FxHashMap;
|
||||
use std::collections::hash_map;
|
||||
use std::rc::{self, Rc};
|
||||
use std::sync::atomic::{self, AtomicU64};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use crate::graphics::Text;
|
||||
|
||||
const COLOR_MODE: glyphon::ColorMode = if color::GAMMA_CORRECTION {
|
||||
glyphon::ColorMode::Accurate
|
||||
} else {
|
||||
glyphon::ColorMode::Web
|
||||
};
|
||||
|
||||
pub type Batch = Vec<Item>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Item {
|
||||
Group {
|
||||
transformation: Transformation,
|
||||
text: Vec<Text>,
|
||||
},
|
||||
Cached {
|
||||
transformation: Transformation,
|
||||
cache: Cache,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Cache {
|
||||
id: Id,
|
||||
group: cache::Group,
|
||||
text: Rc<[Text]>,
|
||||
version: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Id(u64);
|
||||
|
||||
impl Cache {
|
||||
pub fn new(group: cache::Group, text: Vec<Text>) -> Option<Self> {
|
||||
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
if text.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Self {
|
||||
id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)),
|
||||
group,
|
||||
text: Rc::from(text),
|
||||
version: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update(&mut self, text: Vec<Text>) {
|
||||
if self.text.is_empty() && text.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.text = Rc::from(text);
|
||||
self.version += 1;
|
||||
}
|
||||
}
|
||||
|
||||
struct Upload {
|
||||
renderer: glyphon::TextRenderer,
|
||||
buffer_cache: BufferCache,
|
||||
transformation: Transformation,
|
||||
version: usize,
|
||||
group_version: usize,
|
||||
text: rc::Weak<[Text]>,
|
||||
_atlas: rc::Weak<()>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Storage {
|
||||
groups: FxHashMap<cache::Group, Group>,
|
||||
uploads: FxHashMap<Id, Upload>,
|
||||
}
|
||||
|
||||
struct Group {
|
||||
atlas: glyphon::TextAtlas,
|
||||
version: usize,
|
||||
should_trim: bool,
|
||||
handle: Rc<()>, // Keeps track of active uploads
|
||||
}
|
||||
|
||||
impl Storage {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn get(&self, cache: &Cache) -> Option<(&glyphon::TextAtlas, &Upload)> {
|
||||
if cache.text.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.groups
|
||||
.get(&cache.group)
|
||||
.map(|group| &group.atlas)
|
||||
.zip(self.uploads.get(&cache.id))
|
||||
}
|
||||
|
||||
fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
viewport: &glyphon::Viewport,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
format: wgpu::TextureFormat,
|
||||
state: &glyphon::Cache,
|
||||
cache: &Cache,
|
||||
new_transformation: Transformation,
|
||||
bounds: Rectangle,
|
||||
) {
|
||||
let group_count = self.groups.len();
|
||||
|
||||
let group = self.groups.entry(cache.group).or_insert_with(|| {
|
||||
log::debug!(
|
||||
"New text atlas: {:?} (total: {})",
|
||||
cache.group,
|
||||
group_count + 1
|
||||
);
|
||||
|
||||
Group {
|
||||
atlas: glyphon::TextAtlas::with_color_mode(
|
||||
device, queue, state, format, COLOR_MODE,
|
||||
),
|
||||
version: 0,
|
||||
should_trim: false,
|
||||
handle: Rc::new(()),
|
||||
}
|
||||
});
|
||||
|
||||
match self.uploads.entry(cache.id) {
|
||||
hash_map::Entry::Occupied(entry) => {
|
||||
let upload = entry.into_mut();
|
||||
|
||||
if upload.version != cache.version
|
||||
|| upload.group_version != group.version
|
||||
|| upload.transformation != new_transformation
|
||||
{
|
||||
if !cache.text.is_empty() {
|
||||
let _ = prepare(
|
||||
device,
|
||||
queue,
|
||||
viewport,
|
||||
encoder,
|
||||
&mut upload.renderer,
|
||||
&mut group.atlas,
|
||||
&mut upload.buffer_cache,
|
||||
&cache.text,
|
||||
bounds,
|
||||
new_transformation,
|
||||
);
|
||||
}
|
||||
|
||||
// Only trim if glyphs have changed
|
||||
group.should_trim =
|
||||
group.should_trim || upload.version != cache.version;
|
||||
|
||||
upload.text = Rc::downgrade(&cache.text);
|
||||
upload.version = cache.version;
|
||||
upload.group_version = group.version;
|
||||
upload.transformation = new_transformation;
|
||||
|
||||
upload.buffer_cache.trim();
|
||||
}
|
||||
}
|
||||
hash_map::Entry::Vacant(entry) => {
|
||||
let mut renderer = glyphon::TextRenderer::new(
|
||||
&mut group.atlas,
|
||||
device,
|
||||
wgpu::MultisampleState::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
let mut buffer_cache = BufferCache::new();
|
||||
|
||||
if !cache.text.is_empty() {
|
||||
let _ = prepare(
|
||||
device,
|
||||
queue,
|
||||
viewport,
|
||||
encoder,
|
||||
&mut renderer,
|
||||
&mut group.atlas,
|
||||
&mut buffer_cache,
|
||||
&cache.text,
|
||||
bounds,
|
||||
new_transformation,
|
||||
);
|
||||
}
|
||||
|
||||
let _ = entry.insert(Upload {
|
||||
renderer,
|
||||
buffer_cache,
|
||||
transformation: new_transformation,
|
||||
version: 0,
|
||||
group_version: group.version,
|
||||
text: Rc::downgrade(&cache.text),
|
||||
_atlas: Rc::downgrade(&group.handle),
|
||||
});
|
||||
|
||||
group.should_trim = cache.group.is_singleton();
|
||||
|
||||
log::debug!(
|
||||
"New text upload: {} (total: {})",
|
||||
cache.id.0,
|
||||
self.uploads.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trim(&mut self) {
|
||||
self.uploads
|
||||
.retain(|_id, upload| upload.text.strong_count() > 0);
|
||||
|
||||
self.groups.retain(|id, group| {
|
||||
let active_uploads = Rc::weak_count(&group.handle);
|
||||
|
||||
if active_uploads == 0 {
|
||||
log::debug!("Dropping text atlas: {id:?}");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if group.should_trim {
|
||||
log::trace!("Trimming text atlas: {id:?}");
|
||||
|
||||
group.atlas.trim();
|
||||
group.should_trim = false;
|
||||
|
||||
// We only need to worry about glyph fighting
|
||||
// when the atlas may be shared by multiple
|
||||
// uploads.
|
||||
if !id.is_singleton() {
|
||||
log::debug!(
|
||||
"Invalidating text atlas: {id:?} \
|
||||
(uploads: {active_uploads})"
|
||||
);
|
||||
|
||||
group.version += 1;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Viewport(glyphon::Viewport);
|
||||
|
||||
impl Viewport {
|
||||
pub fn update(&mut self, queue: &wgpu::Queue, resolution: Size<u32>) {
|
||||
self.0.update(
|
||||
queue,
|
||||
glyphon::Resolution {
|
||||
width: resolution.width,
|
||||
height: resolution.height,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Pipeline {
|
||||
renderers: Vec<glyphon::TextRenderer>,
|
||||
state: glyphon::Cache,
|
||||
format: wgpu::TextureFormat,
|
||||
atlas: glyphon::TextAtlas,
|
||||
renderers: Vec<glyphon::TextRenderer>,
|
||||
prepare_layer: usize,
|
||||
cache: RefCell<Cache>,
|
||||
cache: BufferCache,
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
|
|
@ -23,274 +288,102 @@ impl Pipeline {
|
|||
queue: &wgpu::Queue,
|
||||
format: wgpu::TextureFormat,
|
||||
) -> Self {
|
||||
let state = glyphon::Cache::new(device);
|
||||
let atlas = glyphon::TextAtlas::with_color_mode(
|
||||
device, queue, &state, format, COLOR_MODE,
|
||||
);
|
||||
|
||||
Pipeline {
|
||||
state,
|
||||
format,
|
||||
renderers: Vec::new(),
|
||||
atlas: glyphon::TextAtlas::with_color_mode(
|
||||
device,
|
||||
queue,
|
||||
format,
|
||||
if color::GAMMA_CORRECTION {
|
||||
glyphon::ColorMode::Accurate
|
||||
} else {
|
||||
glyphon::ColorMode::Web
|
||||
},
|
||||
),
|
||||
atlas,
|
||||
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(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
sections: &[Text<'_>],
|
||||
viewport: &Viewport,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
storage: &mut Storage,
|
||||
batch: &Batch,
|
||||
layer_bounds: Rectangle,
|
||||
scale_factor: f32,
|
||||
target_size: Size<u32>,
|
||||
layer_transformation: Transformation,
|
||||
) {
|
||||
if self.renderers.len() <= self.prepare_layer {
|
||||
self.renderers.push(glyphon::TextRenderer::new(
|
||||
&mut self.atlas,
|
||||
device,
|
||||
wgpu::MultisampleState::default(),
|
||||
None,
|
||||
));
|
||||
}
|
||||
for item in batch {
|
||||
match item {
|
||||
Item::Group {
|
||||
transformation,
|
||||
text,
|
||||
} => {
|
||||
if self.renderers.len() <= self.prepare_layer {
|
||||
self.renderers.push(glyphon::TextRenderer::new(
|
||||
&mut self.atlas,
|
||||
device,
|
||||
wgpu::MultisampleState::default(),
|
||||
None,
|
||||
));
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
let renderer = &mut self.renderers[self.prepare_layer];
|
||||
let result = prepare(
|
||||
device,
|
||||
queue,
|
||||
&viewport.0,
|
||||
encoder,
|
||||
renderer,
|
||||
&mut self.atlas,
|
||||
&mut self.cache,
|
||||
text,
|
||||
layer_bounds * layer_transformation,
|
||||
layer_transformation * *transformation,
|
||||
);
|
||||
|
||||
Some(Allocation::Cache(key))
|
||||
match result {
|
||||
Ok(()) => {
|
||||
self.prepare_layer += 1;
|
||||
}
|
||||
Err(glyphon::PrepareError::AtlasFull) => {
|
||||
// If the atlas cannot grow, then all bets are off.
|
||||
// Instead of panicking, we will just pray that the result
|
||||
// will be somewhat readable...
|
||||
}
|
||||
}
|
||||
}
|
||||
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,
|
||||
Item::Cached {
|
||||
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(
|
||||
device,
|
||||
queue,
|
||||
font_system,
|
||||
&mut self.atlas,
|
||||
glyphon::Resolution {
|
||||
width: target_size.width,
|
||||
height: target_size.height,
|
||||
},
|
||||
text_areas,
|
||||
&mut glyphon::SwashCache::new(),
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(()) => {
|
||||
self.prepare_layer += 1;
|
||||
}
|
||||
Err(glyphon::PrepareError::AtlasFull) => {
|
||||
// If the atlas cannot grow, then all bets are off.
|
||||
// Instead of panicking, we will just pray that the result
|
||||
// will be somewhat readable...
|
||||
cache,
|
||||
} => {
|
||||
storage.prepare(
|
||||
device,
|
||||
queue,
|
||||
&viewport.0,
|
||||
encoder,
|
||||
self.format,
|
||||
&self.state,
|
||||
cache,
|
||||
layer_transformation * *transformation,
|
||||
layer_bounds * layer_transformation,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render<'a>(
|
||||
&'a self,
|
||||
layer: usize,
|
||||
viewport: &'a Viewport,
|
||||
storage: &'a Storage,
|
||||
start: usize,
|
||||
batch: &'a Batch,
|
||||
bounds: Rectangle<u32>,
|
||||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
) {
|
||||
let renderer = &self.renderers[layer];
|
||||
) -> usize {
|
||||
let mut layer_count = 0;
|
||||
|
||||
render_pass.set_scissor_rect(
|
||||
bounds.x,
|
||||
|
|
@ -299,15 +392,252 @@ impl Pipeline {
|
|||
bounds.height,
|
||||
);
|
||||
|
||||
renderer
|
||||
.render(&self.atlas, render_pass)
|
||||
.expect("Render text");
|
||||
for item in batch {
|
||||
match item {
|
||||
Item::Group { .. } => {
|
||||
let renderer = &self.renderers[start + layer_count];
|
||||
|
||||
renderer
|
||||
.render(&self.atlas, &viewport.0, render_pass)
|
||||
.expect("Render text");
|
||||
|
||||
layer_count += 1;
|
||||
}
|
||||
Item::Cached { cache, .. } => {
|
||||
if let Some((atlas, upload)) = storage.get(cache) {
|
||||
upload
|
||||
.renderer
|
||||
.render(atlas, &viewport.0, render_pass)
|
||||
.expect("Render cached text");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layer_count
|
||||
}
|
||||
|
||||
pub fn create_viewport(&self, device: &wgpu::Device) -> Viewport {
|
||||
Viewport(glyphon::Viewport::new(device, &self.state))
|
||||
}
|
||||
|
||||
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,
|
||||
viewport: &glyphon::Viewport,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
renderer: &mut glyphon::TextRenderer,
|
||||
atlas: &mut glyphon::TextAtlas,
|
||||
buffer_cache: &mut BufferCache,
|
||||
sections: &[Text],
|
||||
layer_bounds: Rectangle,
|
||||
layer_transformation: Transformation,
|
||||
) -> 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(text_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,
|
||||
text_cache::Key {
|
||||
content,
|
||||
size: f32::from(*size),
|
||||
line_height: f32::from(*line_height),
|
||||
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 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 * layer_transformation;
|
||||
|
||||
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 * layer_transformation),
|
||||
)?;
|
||||
|
||||
Some(glyphon::TextArea {
|
||||
buffer,
|
||||
left,
|
||||
top,
|
||||
scale: transformation.scale_factor()
|
||||
* layer_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,
|
||||
viewport,
|
||||
text_areas,
|
||||
&mut glyphon::SwashCache::new(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,158 @@
|
|||
//! 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;
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::collections::hash_map;
|
||||
use std::rc::{self, Rc};
|
||||
use std::sync::atomic::{self, AtomicU64};
|
||||
|
||||
const INITIAL_INDEX_COUNT: usize = 1_000;
|
||||
const INITIAL_VERTEX_COUNT: usize = 1_000;
|
||||
|
||||
pub type Batch = Vec<Item>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Item {
|
||||
Group {
|
||||
transformation: Transformation,
|
||||
meshes: Vec<Mesh>,
|
||||
},
|
||||
Cached {
|
||||
transformation: Transformation,
|
||||
cache: Cache,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Cache {
|
||||
id: Id,
|
||||
batch: Rc<[Mesh]>,
|
||||
version: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Id(u64);
|
||||
|
||||
impl Cache {
|
||||
pub fn new(meshes: Vec<Mesh>) -> Option<Self> {
|
||||
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
if meshes.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Self {
|
||||
id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)),
|
||||
batch: Rc::from(meshes),
|
||||
version: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update(&mut self, meshes: Vec<Mesh>) {
|
||||
self.batch = Rc::from(meshes);
|
||||
self.version += 1;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Upload {
|
||||
layer: Layer,
|
||||
transformation: Transformation,
|
||||
version: usize,
|
||||
batch: rc::Weak<[Mesh]>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Storage {
|
||||
uploads: FxHashMap<Id, Upload>,
|
||||
}
|
||||
|
||||
impl Storage {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn get(&self, cache: &Cache) -> Option<&Upload> {
|
||||
if cache.batch.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.uploads.get(&cache.id)
|
||||
}
|
||||
|
||||
fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
solid: &solid::Pipeline,
|
||||
gradient: &gradient::Pipeline,
|
||||
cache: &Cache,
|
||||
new_transformation: Transformation,
|
||||
) {
|
||||
match self.uploads.entry(cache.id) {
|
||||
hash_map::Entry::Occupied(entry) => {
|
||||
let upload = entry.into_mut();
|
||||
|
||||
if !cache.batch.is_empty()
|
||||
&& (upload.version != cache.version
|
||||
|| upload.transformation != new_transformation)
|
||||
{
|
||||
upload.layer.prepare(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
solid,
|
||||
gradient,
|
||||
&cache.batch,
|
||||
new_transformation,
|
||||
);
|
||||
|
||||
upload.batch = Rc::downgrade(&cache.batch);
|
||||
upload.version = cache.version;
|
||||
upload.transformation = new_transformation;
|
||||
}
|
||||
}
|
||||
hash_map::Entry::Vacant(entry) => {
|
||||
let mut layer = Layer::new(device, solid, gradient);
|
||||
|
||||
layer.prepare(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
solid,
|
||||
gradient,
|
||||
&cache.batch,
|
||||
new_transformation,
|
||||
);
|
||||
|
||||
let _ = entry.insert(Upload {
|
||||
layer,
|
||||
transformation: new_transformation,
|
||||
version: 0,
|
||||
batch: Rc::downgrade(&cache.batch),
|
||||
});
|
||||
|
||||
log::debug!(
|
||||
"New mesh upload: {} (total: {})",
|
||||
cache.id.0,
|
||||
self.uploads.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trim(&mut self) {
|
||||
self.uploads
|
||||
.retain(|_id, upload| upload.batch.strong_count() > 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Pipeline {
|
||||
blit: Option<msaa::Blit>,
|
||||
|
|
@ -18,8 +162,198 @@ 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(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
storage: &mut Storage,
|
||||
items: &[Item],
|
||||
scale: Transformation,
|
||||
target_size: Size<u32>,
|
||||
) {
|
||||
let projection = if let Some(blit) = &mut self.blit {
|
||||
blit.prepare(device, encoder, belt, target_size) * scale
|
||||
} else {
|
||||
Transformation::orthographic(target_size.width, target_size.height)
|
||||
* scale
|
||||
};
|
||||
|
||||
for item in items {
|
||||
match item {
|
||||
Item::Group {
|
||||
transformation,
|
||||
meshes,
|
||||
} => {
|
||||
if self.layers.len() <= self.prepare_layer {
|
||||
self.layers.push(Layer::new(
|
||||
device,
|
||||
&self.solid,
|
||||
&self.gradient,
|
||||
));
|
||||
}
|
||||
|
||||
let layer = &mut self.layers[self.prepare_layer];
|
||||
layer.prepare(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
&self.solid,
|
||||
&self.gradient,
|
||||
meshes,
|
||||
projection * *transformation,
|
||||
);
|
||||
|
||||
self.prepare_layer += 1;
|
||||
}
|
||||
Item::Cached {
|
||||
transformation,
|
||||
cache,
|
||||
} => {
|
||||
storage.prepare(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
&self.solid,
|
||||
&self.gradient,
|
||||
cache,
|
||||
projection * *transformation,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
&mut self,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
target: &wgpu::TextureView,
|
||||
storage: &Storage,
|
||||
start: usize,
|
||||
batch: &Batch,
|
||||
bounds: Rectangle,
|
||||
screen_transformation: Transformation,
|
||||
) -> usize {
|
||||
let mut layer_count = 0;
|
||||
|
||||
let items = batch.iter().filter_map(|item| match item {
|
||||
Item::Group {
|
||||
transformation,
|
||||
meshes,
|
||||
} => {
|
||||
let layer = &self.layers[start + layer_count];
|
||||
layer_count += 1;
|
||||
|
||||
Some((
|
||||
layer,
|
||||
meshes.as_slice(),
|
||||
screen_transformation * *transformation,
|
||||
))
|
||||
}
|
||||
Item::Cached {
|
||||
transformation,
|
||||
cache,
|
||||
} => {
|
||||
let upload = storage.get(cache)?;
|
||||
|
||||
Some((
|
||||
&upload.layer,
|
||||
&cache.batch,
|
||||
screen_transformation * *transformation,
|
||||
))
|
||||
}
|
||||
});
|
||||
|
||||
render(
|
||||
encoder,
|
||||
target,
|
||||
self.blit.as_mut(),
|
||||
&self.solid,
|
||||
&self.gradient,
|
||||
bounds,
|
||||
items,
|
||||
);
|
||||
|
||||
layer_count
|
||||
}
|
||||
|
||||
pub fn end_frame(&mut self) {
|
||||
self.prepare_layer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn render<'a>(
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
target: &wgpu::TextureView,
|
||||
mut blit: Option<&mut msaa::Blit>,
|
||||
solid: &solid::Pipeline,
|
||||
gradient: &gradient::Pipeline,
|
||||
bounds: Rectangle,
|
||||
group: impl Iterator<Item = (&'a Layer, &'a [Mesh], Transformation)>,
|
||||
) {
|
||||
{
|
||||
let (attachment, resolve_target, load) = if let Some(blit) = &mut blit {
|
||||
let (attachment, resolve_target) = blit.targets();
|
||||
|
||||
(
|
||||
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,
|
||||
});
|
||||
|
||||
for (layer, meshes, transformation) in group {
|
||||
layer.render(
|
||||
solid,
|
||||
gradient,
|
||||
meshes,
|
||||
bounds,
|
||||
transformation,
|
||||
&mut render_pass,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(blit) = blit {
|
||||
blit.draw(encoder, target);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Layer {
|
||||
pub struct Layer {
|
||||
index_buffer: Buffer<u32>,
|
||||
index_strides: Vec<u32>,
|
||||
solid: solid::Layer,
|
||||
|
|
@ -48,10 +382,11 @@ impl Layer {
|
|||
fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
solid: &solid::Pipeline,
|
||||
gradient: &gradient::Pipeline,
|
||||
meshes: &[Mesh<'_>],
|
||||
meshes: &[Mesh],
|
||||
transformation: Transformation,
|
||||
) {
|
||||
// Count the total amount of vertices & indices we need to handle
|
||||
|
|
@ -103,33 +438,47 @@ impl Layer {
|
|||
let uniforms =
|
||||
Uniforms::new(transformation * mesh.transformation());
|
||||
|
||||
index_offset +=
|
||||
self.index_buffer.write(queue, index_offset, indices);
|
||||
index_offset += self.index_buffer.write(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
index_offset,
|
||||
indices,
|
||||
);
|
||||
|
||||
self.index_strides.push(indices.len() as u32);
|
||||
|
||||
match mesh {
|
||||
Mesh::Solid { buffers, .. } => {
|
||||
solid_vertex_offset += self.solid.vertices.write(
|
||||
queue,
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
solid_vertex_offset,
|
||||
&buffers.vertices,
|
||||
);
|
||||
|
||||
solid_uniform_offset += self.solid.uniforms.write(
|
||||
queue,
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
solid_uniform_offset,
|
||||
&[uniforms],
|
||||
);
|
||||
}
|
||||
Mesh::Gradient { buffers, .. } => {
|
||||
gradient_vertex_offset += self.gradient.vertices.write(
|
||||
queue,
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
gradient_vertex_offset,
|
||||
&buffers.vertices,
|
||||
);
|
||||
|
||||
gradient_uniform_offset += self.gradient.uniforms.write(
|
||||
queue,
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
gradient_uniform_offset,
|
||||
&[uniforms],
|
||||
);
|
||||
|
|
@ -142,8 +491,9 @@ impl Layer {
|
|||
&'a self,
|
||||
solid: &'a solid::Pipeline,
|
||||
gradient: &'a gradient::Pipeline,
|
||||
meshes: &[Mesh<'_>],
|
||||
scale_factor: f32,
|
||||
meshes: &[Mesh],
|
||||
bounds: Rectangle,
|
||||
transformation: Transformation,
|
||||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
) {
|
||||
let mut num_solids = 0;
|
||||
|
|
@ -151,11 +501,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();
|
||||
|
||||
if clip_bounds.width < 1 || clip_bounds.height < 1 {
|
||||
let Some(clip_bounds) = bounds
|
||||
.intersection(&(mesh.clip_bounds() * transformation))
|
||||
.and_then(Rectangle::snap)
|
||||
else {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
render_pass.set_scissor_rect(
|
||||
clip_bounds.x,
|
||||
|
|
@ -219,117 +570,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,
|
||||
queue: &wgpu::Queue,
|
||||
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,
|
||||
queue,
|
||||
&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 {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,18 @@
|
|||
use crate::core::{Size, Transformation};
|
||||
use crate::graphics;
|
||||
|
||||
use std::num::NonZeroU64;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Blit {
|
||||
format: wgpu::TextureFormat,
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
constants: wgpu::BindGroup,
|
||||
ratio: wgpu::Buffer,
|
||||
texture_layout: wgpu::BindGroupLayout,
|
||||
sample_count: u32,
|
||||
targets: Option<Targets>,
|
||||
last_region: Option<Size<u32>>,
|
||||
}
|
||||
|
||||
impl Blit {
|
||||
|
|
@ -19,27 +24,52 @@ impl Blit {
|
|||
let sampler =
|
||||
device.create_sampler(&wgpu::SamplerDescriptor::default());
|
||||
|
||||
let ratio = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("iced-wgpu::triangle::msaa ratio"),
|
||||
size: std::mem::size_of::<Ratio>() as u64,
|
||||
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let constant_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("iced_wgpu::triangle:msaa uniforms layout"),
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(
|
||||
wgpu::SamplerBindingType::NonFiltering,
|
||||
),
|
||||
count: None,
|
||||
}],
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(
|
||||
wgpu::SamplerBindingType::NonFiltering,
|
||||
),
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let constant_bind_group =
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::triangle::msaa uniforms bind group"),
|
||||
layout: &constant_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Sampler(&sampler),
|
||||
}],
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Sampler(&sampler),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: ratio.as_entire_binding(),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let texture_layout =
|
||||
|
|
@ -112,43 +142,61 @@ impl Blit {
|
|||
format,
|
||||
pipeline,
|
||||
constants: constant_bind_group,
|
||||
ratio,
|
||||
texture_layout,
|
||||
sample_count: antialiasing.sample_count(),
|
||||
targets: None,
|
||||
last_region: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn targets(
|
||||
pub fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> (&wgpu::TextureView, &wgpu::TextureView) {
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
region_size: Size<u32>,
|
||||
) -> Transformation {
|
||||
match &mut self.targets {
|
||||
None => {
|
||||
Some(targets)
|
||||
if region_size.width <= targets.size.width
|
||||
&& region_size.height <= targets.size.height => {}
|
||||
_ => {
|
||||
self.targets = Some(Targets::new(
|
||||
device,
|
||||
self.format,
|
||||
&self.texture_layout,
|
||||
self.sample_count,
|
||||
width,
|
||||
height,
|
||||
region_size,
|
||||
));
|
||||
}
|
||||
Some(targets) => {
|
||||
if targets.width != width || targets.height != height {
|
||||
self.targets = Some(Targets::new(
|
||||
device,
|
||||
self.format,
|
||||
&self.texture_layout,
|
||||
self.sample_count,
|
||||
width,
|
||||
height,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let targets = self.targets.as_mut().unwrap();
|
||||
|
||||
if Some(region_size) != self.last_region {
|
||||
let ratio = Ratio {
|
||||
u: region_size.width as f32 / targets.size.width as f32,
|
||||
v: region_size.height as f32 / targets.size.height as f32,
|
||||
};
|
||||
|
||||
belt.write_buffer(
|
||||
encoder,
|
||||
&self.ratio,
|
||||
0,
|
||||
NonZeroU64::new(std::mem::size_of::<Ratio>() as u64)
|
||||
.expect("non-empty ratio"),
|
||||
device,
|
||||
)
|
||||
.copy_from_slice(bytemuck::bytes_of(&ratio));
|
||||
|
||||
self.last_region = Some(region_size);
|
||||
}
|
||||
|
||||
Transformation::orthographic(targets.size.width, targets.size.height)
|
||||
}
|
||||
|
||||
pub fn targets(&self) -> (&wgpu::TextureView, &wgpu::TextureView) {
|
||||
let targets = self.targets.as_ref().unwrap();
|
||||
|
||||
(&targets.attachment, &targets.resolve)
|
||||
|
|
@ -191,8 +239,7 @@ struct Targets {
|
|||
attachment: wgpu::TextureView,
|
||||
resolve: wgpu::TextureView,
|
||||
bind_group: wgpu::BindGroup,
|
||||
width: u32,
|
||||
height: u32,
|
||||
size: Size<u32>,
|
||||
}
|
||||
|
||||
impl Targets {
|
||||
|
|
@ -201,12 +248,11 @@ impl Targets {
|
|||
format: wgpu::TextureFormat,
|
||||
texture_layout: &wgpu::BindGroupLayout,
|
||||
sample_count: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
size: Size<u32>,
|
||||
) -> Targets {
|
||||
let extent = wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
|
||||
|
|
@ -252,8 +298,14 @@ impl Targets {
|
|||
attachment,
|
||||
resolve,
|
||||
bind_group,
|
||||
width,
|
||||
height,
|
||||
size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
#[repr(C)]
|
||||
struct Ratio {
|
||||
u: f32,
|
||||
v: f32,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,49 @@
|
|||
//! Connect a window with a renderer.
|
||||
use crate::core::{Color, Size};
|
||||
use crate::graphics;
|
||||
use crate::graphics::color;
|
||||
use crate::graphics::compositor;
|
||||
use crate::graphics::{Error, Viewport};
|
||||
use crate::{Backend, Primitive, Renderer, Settings};
|
||||
use crate::graphics::error;
|
||||
use crate::graphics::{self, Viewport};
|
||||
use crate::settings::{self, Settings};
|
||||
use crate::{Engine, Renderer};
|
||||
|
||||
/// 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.
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// The surface creation failed.
|
||||
#[error("the surface creation failed: {0}")]
|
||||
SurfaceCreationFailed(#[from] wgpu::CreateSurfaceError),
|
||||
/// The surface is not compatible.
|
||||
#[error("the surface is not compatible")]
|
||||
IncompatibleSurface,
|
||||
/// No adapter was found for the options requested.
|
||||
#[error("no adapter was found for the options requested: {0:?}")]
|
||||
NoAdapterFound(String),
|
||||
/// No device request succeeded.
|
||||
#[error("no device request succeeded: {0:?}")]
|
||||
RequestDeviceFailed(Vec<(wgpu::Limits, wgpu::RequestDeviceError)>),
|
||||
}
|
||||
|
||||
impl From<Error> for graphics::Error {
|
||||
fn from(error: Error) -> Self {
|
||||
Self::GraphicsAdapterNotFound {
|
||||
backend: "wgpu",
|
||||
reason: error::Reason::RequestFailed(error.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Compositor {
|
||||
|
|
@ -25,9 +53,9 @@ impl Compositor {
|
|||
pub async fn request<W: compositor::Window>(
|
||||
settings: Settings,
|
||||
compatible_window: Option<W>,
|
||||
) -> Option<Self> {
|
||||
) -> Result<Self, Error> {
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
backends: settings.internal_backend,
|
||||
backends: settings.backends,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
|
|
@ -36,7 +64,7 @@ impl Compositor {
|
|||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if log::max_level() >= log::LevelFilter::Info {
|
||||
let available_adapters: Vec<_> = instance
|
||||
.enumerate_adapters(settings.internal_backend)
|
||||
.enumerate_adapters(settings.backends)
|
||||
.iter()
|
||||
.map(wgpu::Adapter::get_info)
|
||||
.collect();
|
||||
|
|
@ -47,23 +75,27 @@ impl Compositor {
|
|||
let compatible_surface = compatible_window
|
||||
.and_then(|window| instance.create_surface(window).ok());
|
||||
|
||||
let adapter_options = wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::util::power_preference_from_env()
|
||||
.unwrap_or(if settings.antialiasing.is_none() {
|
||||
wgpu::PowerPreference::LowPower
|
||||
} else {
|
||||
wgpu::PowerPreference::HighPerformance
|
||||
}),
|
||||
compatible_surface: compatible_surface.as_ref(),
|
||||
force_fallback_adapter: false,
|
||||
};
|
||||
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::util::power_preference_from_env()
|
||||
.unwrap_or(if settings.antialiasing.is_none() {
|
||||
wgpu::PowerPreference::LowPower
|
||||
} else {
|
||||
wgpu::PowerPreference::HighPerformance
|
||||
}),
|
||||
compatible_surface: compatible_surface.as_ref(),
|
||||
force_fallback_adapter: false,
|
||||
})
|
||||
.await?;
|
||||
.request_adapter(&adapter_options)
|
||||
.await
|
||||
.ok_or(Error::NoAdapterFound(format!("{:?}", adapter_options)))?;
|
||||
|
||||
log::info!("Selected: {:#?}", adapter.get_info());
|
||||
|
||||
let (format, alpha_mode) =
|
||||
compatible_surface.as_ref().and_then(|surface| {
|
||||
let (format, alpha_mode) = compatible_surface
|
||||
.as_ref()
|
||||
.and_then(|surface| {
|
||||
let capabilities = surface.get_capabilities(&adapter);
|
||||
|
||||
let mut formats = capabilities.formats.iter().copied();
|
||||
|
|
@ -90,12 +122,17 @@ impl Compositor {
|
|||
.contains(&wgpu::CompositeAlphaMode::PostMultiplied)
|
||||
{
|
||||
wgpu::CompositeAlphaMode::PostMultiplied
|
||||
} else if alpha_modes
|
||||
.contains(&wgpu::CompositeAlphaMode::PreMultiplied)
|
||||
{
|
||||
wgpu::CompositeAlphaMode::PreMultiplied
|
||||
} else {
|
||||
wgpu::CompositeAlphaMode::Auto
|
||||
};
|
||||
|
||||
format.zip(Some(preferred_alpha))
|
||||
})?;
|
||||
})
|
||||
.ok_or(Error::IncompatibleSurface)?;
|
||||
|
||||
log::info!(
|
||||
"Selected format: {format:?} with alpha mode: {alpha_mode:?}"
|
||||
|
|
@ -109,74 +146,71 @@ impl Compositor {
|
|||
let limits =
|
||||
[wgpu::Limits::default(), wgpu::Limits::downlevel_defaults()];
|
||||
|
||||
let mut limits = limits.into_iter().map(|limits| wgpu::Limits {
|
||||
let limits = limits.into_iter().map(|limits| wgpu::Limits {
|
||||
max_bind_groups: 2,
|
||||
..limits
|
||||
});
|
||||
|
||||
let (device, queue) =
|
||||
loop {
|
||||
let required_limits = limits.next()?;
|
||||
let device = adapter.request_device(
|
||||
let mut errors = Vec::new();
|
||||
|
||||
for required_limits in limits {
|
||||
let result = adapter
|
||||
.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
label: Some(
|
||||
"iced_wgpu::window::compositor device descriptor",
|
||||
),
|
||||
required_features: wgpu::Features::empty(),
|
||||
required_limits,
|
||||
required_limits: required_limits.clone(),
|
||||
},
|
||||
None,
|
||||
).await.ok();
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Some(device) = device {
|
||||
break Some(device);
|
||||
match result {
|
||||
Ok((device, queue)) => {
|
||||
let engine = Engine::new(
|
||||
&adapter,
|
||||
&device,
|
||||
&queue,
|
||||
format,
|
||||
settings.antialiasing,
|
||||
);
|
||||
|
||||
return Ok(Compositor {
|
||||
instance,
|
||||
adapter,
|
||||
device,
|
||||
queue,
|
||||
format,
|
||||
alpha_mode,
|
||||
engine,
|
||||
settings,
|
||||
});
|
||||
}
|
||||
}?;
|
||||
Err(error) => {
|
||||
errors.push((required_limits, error));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(Compositor {
|
||||
instance,
|
||||
settings,
|
||||
adapter,
|
||||
device,
|
||||
queue,
|
||||
format,
|
||||
alpha_mode,
|
||||
})
|
||||
}
|
||||
|
||||
/// 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,
|
||||
)
|
||||
Err(Error::RequestDeviceFailed(errors))
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [`Compositor`] and its [`Backend`] for the given [`Settings`] and
|
||||
/// window.
|
||||
pub fn new<W: compositor::Window>(
|
||||
/// Creates a [`Compositor`] with the given [`Settings`] and window.
|
||||
pub async fn new<W: compositor::Window>(
|
||||
settings: Settings,
|
||||
compatible_window: W,
|
||||
) -> Result<Compositor, Error> {
|
||||
let compositor = futures::executor::block_on(Compositor::request(
|
||||
settings,
|
||||
Some(compatible_window),
|
||||
))
|
||||
.ok_or(Error::GraphicsAdapterNotFound)?;
|
||||
|
||||
Ok(compositor)
|
||||
Compositor::request(settings, Some(compatible_window)).await
|
||||
}
|
||||
|
||||
/// Presents the given primitives with the given [`Compositor`] and [`Backend`].
|
||||
/// Presents the given primitives with the given [`Compositor`].
|
||||
pub fn present(
|
||||
compositor: &mut Compositor,
|
||||
backend: &mut Backend,
|
||||
renderer: &mut Renderer,
|
||||
surface: &mut wgpu::Surface<'static>,
|
||||
primitives: &[Primitive],
|
||||
viewport: &Viewport,
|
||||
background_color: Color,
|
||||
) -> Result<(), compositor::SurfaceError> {
|
||||
|
|
@ -192,19 +226,20 @@ pub fn present(
|
|||
.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,
|
||||
);
|
||||
|
||||
// Submit work
|
||||
let _submission = compositor.queue.submit(Some(encoder.finish()));
|
||||
let _ = compositor.engine.submit(&compositor.queue, encoder);
|
||||
|
||||
// Present the frame
|
||||
frame.present();
|
||||
|
||||
Ok(())
|
||||
|
|
@ -225,20 +260,41 @@ pub fn present(
|
|||
}
|
||||
|
||||
impl graphics::Compositor for Compositor {
|
||||
type Settings = Settings;
|
||||
type Renderer = Renderer;
|
||||
type Surface = wgpu::Surface<'static>;
|
||||
|
||||
fn new<W: compositor::Window>(
|
||||
settings: Self::Settings,
|
||||
async fn with_backend<W: compositor::Window>(
|
||||
settings: graphics::Settings,
|
||||
compatible_window: W,
|
||||
) -> Result<Self, Error> {
|
||||
new(settings, compatible_window)
|
||||
backend: Option<&str>,
|
||||
) -> Result<Self, graphics::Error> {
|
||||
match backend {
|
||||
None | Some("wgpu") => {
|
||||
let mut settings = Settings::from(settings);
|
||||
|
||||
if let Some(backends) = wgpu::util::backend_bits_from_env() {
|
||||
settings.backends = backends;
|
||||
}
|
||||
|
||||
if let Some(present_mode) = settings::present_mode_from_env() {
|
||||
settings.present_mode = present_mode;
|
||||
}
|
||||
|
||||
Ok(new(settings, compatible_window).await?)
|
||||
}
|
||||
Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound {
|
||||
backend: "wgpu",
|
||||
reason: error::Reason::DidNotMatch {
|
||||
preferred_backend: backend.to_owned(),
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_renderer(&self) -> Self::Renderer {
|
||||
Renderer::new(
|
||||
self.create_backend(),
|
||||
&self.device,
|
||||
&self.engine,
|
||||
self.settings.default_font,
|
||||
self.settings.default_text_size,
|
||||
)
|
||||
|
|
@ -278,7 +334,7 @@ impl graphics::Compositor for Compositor {
|
|||
height,
|
||||
alpha_mode: self.alpha_mode,
|
||||
view_formats: vec![],
|
||||
desired_maximum_frame_latency: 2,
|
||||
desired_maximum_frame_latency: 1,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -299,16 +355,7 @@ impl graphics::Compositor for Compositor {
|
|||
viewport: &Viewport,
|
||||
background_color: Color,
|
||||
) -> Result<(), compositor::SurfaceError> {
|
||||
renderer.with_primitives(|backend, primitives| {
|
||||
present(
|
||||
self,
|
||||
backend,
|
||||
surface,
|
||||
primitives,
|
||||
viewport,
|
||||
background_color,
|
||||
)
|
||||
})
|
||||
present(self, renderer, surface, viewport, background_color)
|
||||
}
|
||||
|
||||
fn screenshot(
|
||||
|
|
@ -318,9 +365,7 @@ impl graphics::Compositor for Compositor {
|
|||
viewport: &Viewport,
|
||||
background_color: Color,
|
||||
) -> Vec<u8> {
|
||||
renderer.with_primitives(|backend, primitives| {
|
||||
screenshot(self, backend, primitives, viewport, background_color)
|
||||
})
|
||||
screenshot(self, renderer, viewport, background_color)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -328,18 +373,11 @@ impl graphics::Compositor for Compositor {
|
|||
///
|
||||
/// Returns RGBA bytes of the texture data.
|
||||
pub fn screenshot(
|
||||
compositor: &Compositor,
|
||||
backend: &mut Backend,
|
||||
primitives: &[Primitive],
|
||||
compositor: &mut Compositor,
|
||||
renderer: &mut Renderer,
|
||||
viewport: &Viewport,
|
||||
background_color: Color,
|
||||
) -> 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 {
|
||||
|
|
@ -363,14 +401,20 @@ pub fn screenshot(
|
|||
|
||||
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,
|
||||
);
|
||||
|
||||
|
|
@ -407,7 +451,7 @@ pub fn screenshot(
|
|||
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, |_| {});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue