Merge branch 'master' into beacon

This commit is contained in:
Héctor Ramón Jiménez 2025-04-01 02:18:20 +02:00
commit e060129951
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
30 changed files with 915 additions and 633 deletions

View file

@ -5,6 +5,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
env: env:
RUSTFLAGS: --deny warnings RUSTFLAGS: --deny warnings
ICED_TEST_BACKEND: tiny-skia
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, windows-latest, macOS-latest] os: [ubuntu-latest, windows-latest, macOS-latest]

View file

@ -80,16 +80,15 @@ fn benchmark<'a>(
let format = wgpu::TextureFormat::Bgra8UnormSrgb; let format = wgpu::TextureFormat::Bgra8UnormSrgb;
let mut engine = iced_wgpu::Engine::new( let engine = iced_wgpu::Engine::new(
adapter, adapter,
device, device.clone(),
queue, queue.clone(),
format, format,
Some(Antialiasing::MSAAx4), Some(Antialiasing::MSAAx4),
); );
let mut renderer = let mut renderer = Renderer::new(engine, Font::DEFAULT, Pixels::from(16));
Renderer::new(device, &engine, Font::DEFAULT, Pixels::from(16));
let viewport = let viewport =
graphics::Viewport::with_physical_size(Size::new(3840, 2160), 2.0); graphics::Viewport::with_physical_size(Size::new(3840, 2160), 2.0);
@ -134,23 +133,13 @@ fn benchmark<'a>(
cache = Some(user_interface.into_cache()); cache = Some(user_interface.into_cache());
let mut encoder = let submission = renderer.present(
device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: None,
});
renderer.present(
&mut engine,
device,
queue,
&mut encoder,
Some(Color::BLACK), Some(Color::BLACK),
format, format,
&texture_view, &texture_view,
&viewport, &viewport,
); );
let submission = engine.submit(queue, encoder);
let _ = device.poll(wgpu::Maintain::WaitForSubmissionIndex(submission)); let _ = device.poll(wgpu::Maintain::WaitForSubmissionIndex(submission));
i += 1; i += 1;

View file

@ -106,7 +106,19 @@ impl Default for Style {
/// a window nor a compositor. /// a window nor a compositor.
pub trait Headless { pub trait Headless {
/// Creates a new [`Headless`] renderer; /// Creates a new [`Headless`] renderer;
fn new(default_font: Font, default_text_size: Pixels) -> Self; fn new(
default_font: Font,
default_text_size: Pixels,
backend: Option<&str>,
) -> impl Future<Output = Option<Self>>
where
Self: Sized;
/// Returns the unique name of the renderer.
///
/// This name may be used by testing libraries to uniquely identify
/// snapshots.
fn name(&self) -> String;
/// Draws offscreen into a screenshot, returning a collection of /// Draws offscreen into a screenshot, returning a collection of
/// bytes representing the rendered pixels in RGBA order. /// bytes representing the rendered pixels in RGBA order.

View file

@ -36,11 +36,9 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
Loading, Loading,
Ready { Ready {
window: Arc<winit::window::Window>, window: Arc<winit::window::Window>,
device: wgpu::Device,
queue: wgpu::Queue,
surface: wgpu::Surface<'static>, surface: wgpu::Surface<'static>,
format: wgpu::TextureFormat, format: wgpu::TextureFormat,
engine: Engine, device: wgpu::Device,
renderer: Renderer, renderer: Renderer,
scene: Scene, scene: Scene,
controls: Controls, controls: Controls,
@ -146,25 +144,26 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
let controls = Controls::new(); let controls = Controls::new();
// Initialize iced // Initialize iced
let engine = let renderer = {
Engine::new(&adapter, &device, &queue, format, None); let engine = Engine::new(
let renderer = Renderer::new( &adapter,
&device, device.clone(),
&engine, queue,
Font::default(), format,
Pixels::from(16), None,
); );
Renderer::new(engine, Font::default(), Pixels::from(16))
};
// You should change this if you want to render continuously // You should change this if you want to render continuously
event_loop.set_control_flow(ControlFlow::Wait); event_loop.set_control_flow(ControlFlow::Wait);
*self = Self::Ready { *self = Self::Ready {
window, window,
device,
queue,
surface, surface,
format, format,
engine, device,
renderer, renderer,
scene, scene,
controls, controls,
@ -188,10 +187,8 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
let Self::Ready { let Self::Ready {
window, window,
device, device,
queue,
surface, surface,
format, format,
engine,
renderer, renderer,
scene, scene,
controls, controls,
@ -285,10 +282,6 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
*cache = interface.into_cache(); *cache = interface.into_cache();
renderer.present( renderer.present(
engine,
device,
queue,
&mut encoder,
None, None,
frame.texture.format(), frame.texture.format(),
&view, &view,
@ -296,7 +289,6 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
); );
// Then we submit the work // Then we submit the work
engine.submit(queue, encoder);
frame.present(); frame.present();
// Update the mouse cursor // Update the mouse cursor

View file

@ -1,6 +1,6 @@
use iced::widget::{ use iced::widget::{
button, center, center_x, column, horizontal_space, scrollable, text, button, center, center_x, column, container, horizontal_space, scrollable,
text_input, text, text_input,
}; };
use iced::window; use iced::window;
use iced::{ use iced::{
@ -189,13 +189,12 @@ impl Window {
let new_window_button = let new_window_button =
button(text("New Window")).on_press(Message::OpenWindow); button(text("New Window")).on_press(Message::OpenWindow);
let content = scrollable( let content = column![scale_input, title_input, new_window_button]
column![scale_input, title_input, new_window_button]
.spacing(50) .spacing(50)
.width(Fill) .width(Fill)
.align_x(Center), .align_x(Center)
); .width(200);
center_x(content).width(200).into() container(scrollable(center_x(content))).padding(10).into()
} }
} }

View file

@ -0,0 +1 @@
804a1bb6d49e3b3158463202960447d9e7820b967280f41dd0c34c00d3edf2c3

View file

@ -79,6 +79,7 @@ pub trait Compositor: Sized {
surface: &mut Self::Surface, surface: &mut Self::Surface,
viewport: &Viewport, viewport: &Viewport,
background_color: Color, background_color: Color,
on_pre_present: impl FnOnce(),
) -> Result<(), SurfaceError>; ) -> Result<(), SurfaceError>;
/// Screenshots the current [`Renderer`] primitives to an offscreen texture, and returns the bytes of /// Screenshots the current [`Renderer`] primitives to an offscreen texture, and returns the bytes of
@ -190,6 +191,7 @@ impl Compositor for () {
_surface: &mut Self::Surface, _surface: &mut Self::Surface,
_viewport: &Viewport, _viewport: &Viewport,
_background_color: Color, _background_color: Color,
_on_pre_present: impl FnOnce(),
) -> Result<(), SurfaceError> { ) -> Result<(), SurfaceError> {
Ok(()) Ok(())
} }

View file

@ -3,7 +3,8 @@ use crate::core::image;
use crate::core::renderer; use crate::core::renderer;
use crate::core::svg; use crate::core::svg;
use crate::core::{ use crate::core::{
self, Background, Color, Image, Point, Rectangle, Size, Svg, Transformation, self, Background, Color, Font, Image, Pixels, Point, Rectangle, Size, Svg,
Transformation,
}; };
use crate::graphics; use crate::graphics;
use crate::graphics::compositor; use crate::graphics::compositor;
@ -321,6 +322,7 @@ where
surface: &mut Self::Surface, surface: &mut Self::Surface,
viewport: &graphics::Viewport, viewport: &graphics::Viewport,
background_color: Color, background_color: Color,
on_pre_present: impl FnOnce(),
) -> Result<(), compositor::SurfaceError> { ) -> Result<(), compositor::SurfaceError> {
match (self, renderer, surface) { match (self, renderer, surface) {
( (
@ -332,6 +334,7 @@ where
surface, surface,
viewport, viewport,
background_color, background_color,
on_pre_present,
), ),
( (
Self::Secondary(compositor), Self::Secondary(compositor),
@ -342,6 +345,7 @@ where
surface, surface,
viewport, viewport,
background_color, background_color,
on_pre_present,
), ),
_ => unreachable!(), _ => unreachable!(),
} }
@ -600,6 +604,48 @@ mod geometry {
} }
} }
impl<A, B> renderer::Headless for Renderer<A, B>
where
A: renderer::Headless,
B: renderer::Headless,
{
async fn new(
default_font: Font,
default_text_size: Pixels,
backend: Option<&str>,
) -> Option<Self> {
if let Some(renderer) =
A::new(default_font, default_text_size, backend).await
{
return Some(Self::Primary(renderer));
}
B::new(default_font, default_text_size, backend)
.await
.map(Self::Secondary)
}
fn name(&self) -> String {
delegate!(self, renderer, renderer.name())
}
fn screenshot(
&mut self,
size: Size<u32>,
scale_factor: f32,
background_color: Color,
) -> Vec<u8> {
match self {
crate::fallback::Renderer::Primary(renderer) => {
renderer.screenshot(size, scale_factor, background_color)
}
crate::fallback::Renderer::Secondary(renderer) => {
renderer.screenshot(size, scale_factor, background_color)
}
}
}
}
impl<A, B> compositor::Default for Renderer<A, B> impl<A, B> compositor::Default for Renderer<A, B>
where where
A: compositor::Default, A: compositor::Default,

View file

@ -23,9 +23,6 @@ pub type Compositor = renderer::Compositor;
#[cfg(all(feature = "wgpu", feature = "tiny-skia"))] #[cfg(all(feature = "wgpu", feature = "tiny-skia"))]
mod renderer { mod renderer {
use crate::core::renderer;
use crate::core::{Color, Font, Pixels, Size};
pub type Renderer = crate::fallback::Renderer< pub type Renderer = crate::fallback::Renderer<
iced_wgpu::Renderer, iced_wgpu::Renderer,
iced_tiny_skia::Renderer, iced_tiny_skia::Renderer,
@ -35,31 +32,6 @@ mod renderer {
iced_wgpu::window::Compositor, iced_wgpu::window::Compositor,
iced_tiny_skia::window::Compositor, iced_tiny_skia::window::Compositor,
>; >;
impl renderer::Headless for Renderer {
fn new(default_font: Font, default_text_size: Pixels) -> Self {
Self::Secondary(iced_tiny_skia::Renderer::new(
default_font,
default_text_size,
))
}
fn screenshot(
&mut self,
size: Size<u32>,
scale_factor: f32,
background_color: Color,
) -> Vec<u8> {
match self {
crate::fallback::Renderer::Primary(_) => unreachable!(
"iced_wgpu does not support headless mode yet!"
),
crate::fallback::Renderer::Secondary(renderer) => {
renderer.screenshot(size, scale_factor, background_color)
}
}
}
}
} }
#[cfg(all(feature = "wgpu", not(feature = "tiny-skia")))] #[cfg(all(feature = "wgpu", not(feature = "tiny-skia")))]

View file

@ -109,6 +109,7 @@ use crate::runtime::UserInterface;
use crate::runtime::user_interface; use crate::runtime::user_interface;
use std::borrow::Cow; use std::borrow::Cow;
use std::env;
use std::fs; use std::fs;
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -186,8 +187,16 @@ where
load_font(font).expect("Font must be valid"); load_font(font).expect("Font must be valid");
} }
let mut renderer = let mut renderer = {
Renderer::new(default_font, settings.default_text_size); let backend = env::var("ICED_TEST_BACKEND").ok();
iced_runtime::futures::futures::executor::block_on(Renderer::new(
default_font,
settings.default_text_size,
backend.as_deref(),
))
.expect("Create new headless renderer")
};
let raw = UserInterface::build( let raw = UserInterface::build(
element, element,
@ -455,6 +464,7 @@ where
physical_size, physical_size,
f64::from(scale_factor), f64::from(scale_factor),
), ),
renderer: self.renderer.name(),
}) })
} }
@ -470,6 +480,7 @@ where
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Snapshot { pub struct Snapshot {
screenshot: window::Screenshot, screenshot: window::Screenshot,
renderer: String,
} }
impl Snapshot { impl Snapshot {
@ -479,7 +490,7 @@ impl Snapshot {
/// If the PNG image does not exist, it will be created by the [`Snapshot`] for future /// If the PNG image does not exist, it will be created by the [`Snapshot`] for future
/// testing and `true` will be returned. /// testing and `true` will be returned.
pub fn matches_image(&self, path: impl AsRef<Path>) -> Result<bool, Error> { pub fn matches_image(&self, path: impl AsRef<Path>) -> Result<bool, Error> {
let path = snapshot_path(path, "png"); let path = self.path(path, "png");
if path.exists() { if path.exists() {
let file = fs::File::open(&path)?; let file = fs::File::open(&path)?;
@ -520,7 +531,7 @@ impl Snapshot {
pub fn matches_hash(&self, path: impl AsRef<Path>) -> Result<bool, Error> { pub fn matches_hash(&self, path: impl AsRef<Path>) -> Result<bool, Error> {
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
let path = snapshot_path(path, "sha256"); let path = self.path(path, "sha256");
let hash = { let hash = {
let mut hasher = Sha256::new(); let mut hasher = Sha256::new();
@ -541,6 +552,20 @@ impl Snapshot {
Ok(true) Ok(true)
} }
} }
fn path(&self, path: impl AsRef<Path>, extension: &str) -> PathBuf {
let path = path.as_ref();
path.with_file_name(format!(
"{name}-{renderer}",
name = path
.file_stem()
.map(std::ffi::OsStr::to_string_lossy)
.unwrap_or_default(),
renderer = self.renderer
))
.with_extension(extension)
}
} }
/// Returns the sequence of events of a click. /// Returns the sequence of events of a click.
@ -633,7 +658,3 @@ fn load_font(font: impl Into<Cow<'static, [u8]>>) -> Result<(), Error> {
Ok(()) Ok(())
} }
fn snapshot_path(path: impl AsRef<Path>, extension: &str) -> PathBuf {
path.as_ref().with_extension(extension)
}

View file

@ -7,7 +7,7 @@ use crate::graphics::geometry::stroke::{self, Stroke};
use crate::graphics::geometry::{self, Path, Style}; use crate::graphics::geometry::{self, Path, Style};
use crate::graphics::{self, Gradient, Image, Text}; use crate::graphics::{self, Gradient, Image, Text};
use std::rc::Rc; use std::sync::Arc;
#[derive(Debug)] #[derive(Debug)]
pub enum Geometry { pub enum Geometry {
@ -22,9 +22,9 @@ pub enum Geometry {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Cache { pub struct Cache {
pub text: Rc<[Text]>, pub text: Arc<[Text]>,
pub images: Rc<[graphics::Image]>, pub images: Arc<[graphics::Image]>,
pub primitives: Rc<[Primitive]>, pub primitives: Arc<[Primitive]>,
pub clip_bounds: Rectangle, pub clip_bounds: Rectangle,
} }
@ -43,9 +43,9 @@ impl Cached for Geometry {
text, text,
clip_bounds, clip_bounds,
} => Cache { } => Cache {
primitives: Rc::from(primitives), primitives: Arc::from(primitives),
images: Rc::from(images), images: Arc::from(images),
text: Rc::from(text), text: Arc::from(text),
clip_bounds, clip_bounds,
}, },
Self::Cache(cache) => cache, Self::Cache(cache) => cache,

View file

@ -8,7 +8,7 @@ use crate::graphics::layer;
use crate::graphics::text::{Editor, Paragraph, Text}; use crate::graphics::text::{Editor, Paragraph, Text};
use crate::graphics::{self, Image}; use crate::graphics::{self, Image};
use std::rc::Rc; use std::sync::Arc;
pub type Stack = layer::Stack<Layer>; pub type Stack = layer::Stack<Layer>;
@ -107,7 +107,7 @@ impl Layer {
pub fn draw_text_cache( pub fn draw_text_cache(
&mut self, &mut self,
text: Rc<[Text]>, text: Arc<[Text]>,
clip_bounds: Rectangle, clip_bounds: Rectangle,
transformation: Transformation, transformation: Transformation,
) { ) {
@ -163,7 +163,7 @@ impl Layer {
pub fn draw_primitive_cache( pub fn draw_primitive_cache(
&mut self, &mut self,
primitives: Rc<[Primitive]>, primitives: Arc<[Primitive]>,
clip_bounds: Rectangle, clip_bounds: Rectangle,
transformation: Transformation, transformation: Transformation,
) { ) {
@ -242,7 +242,7 @@ impl Layer {
Item::Cached(cache_a, bounds_a, transformation_a), Item::Cached(cache_a, bounds_a, transformation_a),
Item::Cached(cache_b, bounds_b, transformation_b), Item::Cached(cache_b, bounds_b, transformation_b),
) => { ) => {
Rc::ptr_eq(cache_a, cache_b) Arc::ptr_eq(cache_a, cache_b)
&& bounds_a == bounds_b && bounds_a == bounds_b
&& transformation_a == transformation_b && transformation_a == transformation_b
} }
@ -304,7 +304,7 @@ impl graphics::Layer for Layer {
pub enum Item<T> { pub enum Item<T> {
Live(T), Live(T),
Group(Vec<T>, Rectangle, Transformation), Group(Vec<T>, Rectangle, Transformation),
Cached(Rc<[T]>, Rectangle, Transformation), Cached(Arc<[T]>, Rectangle, Transformation),
} }
impl<T> Item<T> { impl<T> Item<T> {

View file

@ -357,8 +357,22 @@ impl compositor::Default for Renderer {
} }
impl renderer::Headless for Renderer { impl renderer::Headless for Renderer {
fn new(default_font: Font, default_text_size: Pixels) -> Self { async fn new(
Self::new(default_font, default_text_size) default_font: Font,
default_text_size: Pixels,
backend: Option<&str>,
) -> Option<Self> {
if backend.is_some_and(|backend| {
!["tiny-skia", "tiny_skia"].contains(&backend)
}) {
return None;
}
Some(Self::new(default_font, default_text_size))
}
fn name(&self) -> String {
"tiny-skia".to_owned()
} }
fn screenshot( fn screenshot(

View file

@ -113,8 +113,15 @@ impl crate::graphics::Compositor for Compositor {
surface: &mut Self::Surface, surface: &mut Self::Surface,
viewport: &Viewport, viewport: &Viewport,
background_color: Color, background_color: Color,
on_pre_present: impl FnOnce(),
) -> Result<(), compositor::SurfaceError> { ) -> Result<(), compositor::SurfaceError> {
present(renderer, surface, viewport, background_color) present(
renderer,
surface,
viewport,
background_color,
on_pre_present,
)
} }
fn screenshot( fn screenshot(
@ -143,6 +150,7 @@ pub fn present(
surface: &mut Surface, surface: &mut Surface,
viewport: &Viewport, viewport: &Viewport,
background_color: Color, background_color: Color,
on_pre_present: impl FnOnce(),
) -> Result<(), compositor::SurfaceError> { ) -> Result<(), compositor::SurfaceError> {
let physical_size = viewport.physical_size(); let physical_size = viewport.physical_size();
@ -202,6 +210,7 @@ pub fn present(
background_color, background_color,
); );
on_pre_present();
buffer.present().map_err(|_| compositor::SurfaceError::Lost) buffer.present().map_err(|_| compositor::SurfaceError::Lost)
} }

View file

@ -1,13 +1,16 @@
use crate::buffer;
use crate::graphics::Antialiasing; use crate::graphics::Antialiasing;
use crate::primitive; use crate::primitive;
use crate::quad; use crate::quad;
use crate::text; use crate::text;
use crate::triangle; use crate::triangle;
use std::sync::{Arc, RwLock};
#[derive(Clone)]
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Engine { pub struct Engine {
pub(crate) staging_belt: wgpu::util::StagingBelt, pub(crate) device: wgpu::Device,
pub(crate) queue: wgpu::Queue,
pub(crate) format: wgpu::TextureFormat, pub(crate) format: wgpu::TextureFormat,
pub(crate) quad_pipeline: quad::Pipeline, pub(crate) quad_pipeline: quad::Pipeline,
@ -15,46 +18,41 @@ pub struct Engine {
pub(crate) triangle_pipeline: triangle::Pipeline, pub(crate) triangle_pipeline: triangle::Pipeline,
#[cfg(any(feature = "image", feature = "svg"))] #[cfg(any(feature = "image", feature = "svg"))]
pub(crate) image_pipeline: crate::image::Pipeline, pub(crate) image_pipeline: crate::image::Pipeline,
pub(crate) primitive_storage: primitive::Storage, pub(crate) primitive_storage: Arc<RwLock<primitive::Storage>>,
} }
impl Engine { impl Engine {
pub fn new( pub fn new(
_adapter: &wgpu::Adapter, _adapter: &wgpu::Adapter,
device: &wgpu::Device, device: wgpu::Device,
queue: &wgpu::Queue, queue: wgpu::Queue,
format: wgpu::TextureFormat, format: wgpu::TextureFormat,
antialiasing: Option<Antialiasing>, // TODO: Initialize AA pipelines lazily antialiasing: Option<Antialiasing>, // TODO: Initialize AA pipelines lazily
) -> Self { ) -> 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 { 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, format,
quad_pipeline, quad_pipeline: quad::Pipeline::new(&device, format),
text_pipeline, text_pipeline: text::Pipeline::new(&device, &queue, format),
triangle_pipeline, triangle_pipeline: triangle::Pipeline::new(
&device,
format,
antialiasing,
),
#[cfg(any(feature = "image", feature = "svg"))] #[cfg(any(feature = "image", feature = "svg"))]
image_pipeline, image_pipeline: {
let backend = _adapter.get_info().backend;
primitive_storage: primitive::Storage::default(), crate::image::Pipeline::new(&device, format, backend)
},
primitive_storage: Arc::new(RwLock::new(
primitive::Storage::default(),
)),
device,
queue,
} }
} }
@ -65,23 +63,4 @@ impl Engine {
) -> crate::image::Cache { ) -> crate::image::Cache {
self.image_pipeline.create_cache(device) 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
}
} }

View file

@ -21,16 +21,14 @@ pub use crate::graphics::Image;
pub type Batch = Vec<Image>; pub type Batch = Vec<Image>;
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Pipeline { pub struct Pipeline {
pipeline: wgpu::RenderPipeline, raw: wgpu::RenderPipeline,
backend: wgpu::Backend, backend: wgpu::Backend,
nearest_sampler: wgpu::Sampler, nearest_sampler: wgpu::Sampler,
linear_sampler: wgpu::Sampler, linear_sampler: wgpu::Sampler,
texture_layout: Arc<wgpu::BindGroupLayout>, texture_layout: Arc<wgpu::BindGroupLayout>,
constant_layout: wgpu::BindGroupLayout, constant_layout: wgpu::BindGroupLayout,
layers: Vec<Layer>,
prepare_layer: usize,
} }
impl Pipeline { impl Pipeline {
@ -194,26 +192,37 @@ impl Pipeline {
}); });
Pipeline { Pipeline {
pipeline, raw: pipeline,
backend, backend,
nearest_sampler, nearest_sampler,
linear_sampler, linear_sampler,
texture_layout: Arc::new(texture_layout), texture_layout: Arc::new(texture_layout),
constant_layout, constant_layout,
layers: Vec::new(),
prepare_layer: 0,
} }
} }
pub fn create_cache(&self, device: &wgpu::Device) -> Cache { pub fn create_cache(&self, device: &wgpu::Device) -> Cache {
Cache::new(device, self.backend, self.texture_layout.clone()) Cache::new(device, self.backend, self.texture_layout.clone())
} }
}
#[derive(Default)]
pub struct State {
layers: Vec<Layer>,
prepare_layer: usize,
}
impl State {
pub fn new() -> Self {
Self::default()
}
pub fn prepare( pub fn prepare(
&mut self, &mut self,
pipeline: &Pipeline,
device: &wgpu::Device, device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt, belt: &mut wgpu::util::StagingBelt,
encoder: &mut wgpu::CommandEncoder,
cache: &mut Cache, cache: &mut Cache,
images: &Batch, images: &Batch,
transformation: Transformation, transformation: Transformation,
@ -285,9 +294,9 @@ impl Pipeline {
if self.layers.len() <= self.prepare_layer { if self.layers.len() <= self.prepare_layer {
self.layers.push(Layer::new( self.layers.push(Layer::new(
device, device,
&self.constant_layout, &pipeline.constant_layout,
&self.nearest_sampler, &pipeline.nearest_sampler,
&self.linear_sampler, &pipeline.linear_sampler,
)); ));
} }
@ -308,13 +317,14 @@ impl Pipeline {
pub fn render<'a>( pub fn render<'a>(
&'a self, &'a self,
pipeline: &'a Pipeline,
cache: &'a Cache, cache: &'a Cache,
layer: usize, layer: usize,
bounds: Rectangle<u32>, bounds: Rectangle<u32>,
render_pass: &mut wgpu::RenderPass<'a>, render_pass: &mut wgpu::RenderPass<'a>,
) { ) {
if let Some(layer) = self.layers.get(layer) { if let Some(layer) = self.layers.get(layer) {
render_pass.set_pipeline(&self.pipeline); render_pass.set_pipeline(&pipeline.raw);
render_pass.set_scissor_rect( render_pass.set_scissor_rect(
bounds.x, bounds.x,
@ -329,7 +339,7 @@ impl Pipeline {
} }
} }
pub fn end_frame(&mut self) { pub fn trim(&mut self) {
self.prepare_layer = 0; self.prepare_layer = 0;
} }
} }

View file

@ -60,8 +60,9 @@ pub use settings::Settings;
#[cfg(feature = "geometry")] #[cfg(feature = "geometry")]
pub use geometry::Geometry; pub use geometry::Geometry;
use crate::core::renderer;
use crate::core::{ use crate::core::{
Background, Color, Font, Pixels, Point, Rectangle, Transformation, Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
}; };
use crate::graphics::Viewport; use crate::graphics::Viewport;
use crate::graphics::text::{Editor, Paragraph}; use crate::graphics::text::{Editor, Paragraph};
@ -72,23 +73,30 @@ use crate::graphics::text::{Editor, Paragraph};
/// [`iced`]: https://github.com/iced-rs/iced /// [`iced`]: https://github.com/iced-rs/iced
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Renderer { pub struct Renderer {
engine: Engine,
default_font: Font, default_font: Font,
default_text_size: Pixels, default_text_size: Pixels,
layers: layer::Stack, layers: layer::Stack,
triangle_storage: triangle::Storage, quad: quad::State,
text_storage: text::Storage, triangle: triangle::State,
text: text::State,
text_viewport: text::Viewport, text_viewport: text::Viewport,
#[cfg(any(feature = "svg", feature = "image"))]
image: image::State,
// TODO: Centralize all the image feature handling // TODO: Centralize all the image feature handling
#[cfg(any(feature = "svg", feature = "image"))] #[cfg(any(feature = "svg", feature = "image"))]
image_cache: std::cell::RefCell<image::Cache>, image_cache: std::cell::RefCell<image::Cache>,
staging_belt: wgpu::util::StagingBelt,
} }
impl Renderer { impl Renderer {
pub fn new( pub fn new(
device: &wgpu::Device, engine: Engine,
engine: &Engine,
default_font: Font, default_font: Font,
default_text_size: Pixels, default_text_size: Pixels,
) -> Self { ) -> Self {
@ -97,50 +105,206 @@ impl Renderer {
default_text_size, default_text_size,
layers: layer::Stack::new(), layers: layer::Stack::new(),
triangle_storage: triangle::Storage::new(), quad: quad::State::new(),
text_storage: text::Storage::new(), triangle: triangle::State::new(
text_viewport: engine.text_pipeline.create_viewport(device), &engine.device,
&engine.triangle_pipeline,
),
text: text::State::new(),
text_viewport: engine.text_pipeline.create_viewport(&engine.device),
#[cfg(any(feature = "svg", feature = "image"))]
image: image::State::new(),
#[cfg(any(feature = "svg", feature = "image"))] #[cfg(any(feature = "svg", feature = "image"))]
image_cache: std::cell::RefCell::new( image_cache: std::cell::RefCell::new(
engine.create_image_cache(device), engine.create_image_cache(&engine.device),
), ),
// TODO: Resize belt smartly (?)
// It would be great if the `StagingBelt` API exposed methods
// for introspection to detect when a resize may be worth it.
staging_belt: wgpu::util::StagingBelt::new(
buffer::MAX_WRITE_SIZE as u64,
),
engine,
} }
} }
fn draw(
&mut self,
clear_color: Option<Color>,
target: &wgpu::TextureView,
viewport: &Viewport,
) -> wgpu::CommandEncoder {
let mut encoder = self.engine.device.create_command_encoder(
&wgpu::CommandEncoderDescriptor {
label: Some("iced_wgpu encoder"),
},
);
self.prepare(&mut encoder, viewport);
self.render(&mut encoder, target, clear_color, viewport);
self.quad.trim();
self.triangle.trim();
self.text.trim();
// TODO: Move to runtime!
self.engine.text_pipeline.trim();
#[cfg(any(feature = "svg", feature = "image"))]
{
self.image.trim();
self.image_cache.borrow_mut().trim();
}
encoder
}
pub fn present( pub fn present(
&mut self, &mut self,
engine: &mut Engine,
device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
clear_color: Option<Color>, clear_color: Option<Color>,
format: wgpu::TextureFormat, _format: wgpu::TextureFormat,
frame: &wgpu::TextureView, frame: &wgpu::TextureView,
viewport: &Viewport, viewport: &Viewport,
) { ) -> wgpu::SubmissionIndex {
self.prepare(engine, device, queue, format, encoder, viewport); let encoder = self.draw(clear_color, frame, viewport);
self.render(engine, encoder, frame, clear_color, viewport);
self.triangle_storage.trim(); self.staging_belt.finish();
self.text_storage.trim(); let submission = self.engine.queue.submit([encoder.finish()]);
self.staging_belt.recall();
submission
}
#[cfg(any(feature = "svg", feature = "image"))] /// Renders the current surface to an offscreen buffer.
self.image_cache.borrow_mut().trim(); ///
/// Returns RGBA bytes of the texture data.
pub fn screenshot(
&mut self,
viewport: &Viewport,
background_color: Color,
) -> Vec<u8> {
#[derive(Clone, Copy, Debug)]
struct BufferDimensions {
width: u32,
height: u32,
unpadded_bytes_per_row: usize,
padded_bytes_per_row: usize,
}
impl BufferDimensions {
fn new(size: Size<u32>) -> Self {
let unpadded_bytes_per_row = size.width as usize * 4; //slice of buffer per row; always RGBA
let alignment = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize; //256
let padded_bytes_per_row_padding = (alignment
- unpadded_bytes_per_row % alignment)
% alignment;
let padded_bytes_per_row =
unpadded_bytes_per_row + padded_bytes_per_row_padding;
Self {
width: size.width,
height: size.height,
unpadded_bytes_per_row,
padded_bytes_per_row,
}
}
}
let dimensions = BufferDimensions::new(viewport.physical_size());
let texture_extent = wgpu::Extent3d {
width: dimensions.width,
height: dimensions.height,
depth_or_array_layers: 1,
};
let texture =
self.engine.device.create_texture(&wgpu::TextureDescriptor {
label: Some("iced_wgpu.offscreen.source_texture"),
size: texture_extent,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: self.engine.format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::COPY_SRC
| wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = self.draw(Some(background_color), &view, viewport);
let texture = crate::color::convert(
&self.engine.device,
&mut encoder,
texture,
if graphics::color::GAMMA_CORRECTION {
wgpu::TextureFormat::Rgba8UnormSrgb
} else {
wgpu::TextureFormat::Rgba8Unorm
},
);
let output_buffer =
self.engine.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("iced_wgpu.offscreen.output_texture_buffer"),
size: (dimensions.padded_bytes_per_row
* dimensions.height as usize) as u64,
usage: wgpu::BufferUsages::MAP_READ
| wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
encoder.copy_texture_to_buffer(
texture.as_image_copy(),
wgpu::TexelCopyBufferInfo {
buffer: &output_buffer,
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(dimensions.padded_bytes_per_row as u32),
rows_per_image: None,
},
},
texture_extent,
);
self.staging_belt.finish();
let index = self.engine.queue.submit([encoder.finish()]);
self.staging_belt.recall();
let slice = output_buffer.slice(..);
slice.map_async(wgpu::MapMode::Read, |_| {});
let _ = self
.engine
.device
.poll(wgpu::Maintain::WaitForSubmissionIndex(index));
let mapped_buffer = slice.get_mapped_range();
mapped_buffer.chunks(dimensions.padded_bytes_per_row).fold(
vec![],
|mut acc, row| {
acc.extend(&row[..dimensions.unpadded_bytes_per_row]);
acc
},
)
} }
fn prepare( fn prepare(
&mut self, &mut self,
engine: &mut Engine,
device: &wgpu::Device,
queue: &wgpu::Queue,
_format: wgpu::TextureFormat,
encoder: &mut wgpu::CommandEncoder, encoder: &mut wgpu::CommandEncoder,
viewport: &Viewport, viewport: &Viewport,
) { ) {
let scale_factor = viewport.scale_factor() as f32; let scale_factor = viewport.scale_factor() as f32;
self.text_viewport.update(queue, viewport.physical_size()); self.text_viewport
.update(&self.engine.queue, viewport.physical_size());
let physical_bounds = Rectangle::<f32>::from(Rectangle::with_size( let physical_bounds = Rectangle::<f32>::from(Rectangle::with_size(
viewport.physical_size(), viewport.physical_size(),
@ -156,10 +320,11 @@ impl Renderer {
} }
if !layer.quads.is_empty() { if !layer.quads.is_empty() {
engine.quad_pipeline.prepare( self.quad.prepare(
device, &self.engine.quad_pipeline,
&self.engine.device,
&mut self.staging_belt,
encoder, encoder,
&mut engine.staging_belt,
&layer.quads, &layer.quads,
viewport.projection(), viewport.projection(),
scale_factor, scale_factor,
@ -167,11 +332,11 @@ impl Renderer {
} }
if !layer.triangles.is_empty() { if !layer.triangles.is_empty() {
engine.triangle_pipeline.prepare( self.triangle.prepare(
device, &self.engine.triangle_pipeline,
&self.engine.device,
&mut self.staging_belt,
encoder, encoder,
&mut engine.staging_belt,
&mut self.triangle_storage,
&layer.triangles, &layer.triangles,
Transformation::scale(scale_factor), Transformation::scale(scale_factor),
viewport.physical_size(), viewport.physical_size(),
@ -179,12 +344,18 @@ impl Renderer {
} }
if !layer.primitives.is_empty() { if !layer.primitives.is_empty() {
let mut primitive_storage = self
.engine
.primitive_storage
.write()
.expect("Write primitive storage");
for instance in &layer.primitives { for instance in &layer.primitives {
instance.primitive.prepare( instance.primitive.prepare(
device, &self.engine.device,
queue, &self.engine.queue,
engine.format, self.engine.format,
&mut engine.primitive_storage, &mut primitive_storage,
&instance.bounds, &instance.bounds,
viewport, viewport,
); );
@ -193,10 +364,11 @@ impl Renderer {
#[cfg(any(feature = "svg", feature = "image"))] #[cfg(any(feature = "svg", feature = "image"))]
if !layer.images.is_empty() { if !layer.images.is_empty() {
engine.image_pipeline.prepare( self.image.prepare(
device, &self.engine.image_pipeline,
&self.engine.device,
&mut self.staging_belt,
encoder, encoder,
&mut engine.staging_belt,
&mut self.image_cache.borrow_mut(), &mut self.image_cache.borrow_mut(),
&layer.images, &layer.images,
viewport.projection(), viewport.projection(),
@ -205,12 +377,12 @@ impl Renderer {
} }
if !layer.text.is_empty() { if !layer.text.is_empty() {
engine.text_pipeline.prepare( self.text.prepare(
device, &self.engine.text_pipeline,
queue, &self.engine.device,
&self.engine.queue,
&self.text_viewport, &self.text_viewport,
encoder, encoder,
&mut self.text_storage,
&layer.text, &layer.text,
layer.bounds, layer.bounds,
Transformation::scale(scale_factor), Transformation::scale(scale_factor),
@ -221,7 +393,6 @@ impl Renderer {
fn render( fn render(
&mut self, &mut self,
engine: &mut Engine,
encoder: &mut wgpu::CommandEncoder, encoder: &mut wgpu::CommandEncoder,
frame: &wgpu::TextureView, frame: &wgpu::TextureView,
clear_color: Option<Color>, clear_color: Option<Color>,
@ -288,7 +459,8 @@ impl Renderer {
}; };
if !layer.quads.is_empty() { if !layer.quads.is_empty() {
engine.quad_pipeline.render( self.quad.render(
&self.engine.quad_pipeline,
quad_layer, quad_layer,
scissor_rect, scissor_rect,
&layer.quads, &layer.quads,
@ -301,10 +473,10 @@ impl Renderer {
if !layer.triangles.is_empty() { if !layer.triangles.is_empty() {
let _ = ManuallyDrop::into_inner(render_pass); let _ = ManuallyDrop::into_inner(render_pass);
mesh_layer += engine.triangle_pipeline.render( mesh_layer += self.triangle.render(
&self.engine.triangle_pipeline,
encoder, encoder,
frame, frame,
&self.triangle_storage,
mesh_layer, mesh_layer,
&layer.triangles, &layer.triangles,
physical_bounds, physical_bounds,
@ -334,6 +506,12 @@ impl Renderer {
if !layer.primitives.is_empty() { if !layer.primitives.is_empty() {
let _ = ManuallyDrop::into_inner(render_pass); let _ = ManuallyDrop::into_inner(render_pass);
let primitive_storage = self
.engine
.primitive_storage
.read()
.expect("Read primitive storage");
for instance in &layer.primitives { for instance in &layer.primitives {
if let Some(clip_bounds) = (instance.bounds * scale) if let Some(clip_bounds) = (instance.bounds * scale)
.intersection(&physical_bounds) .intersection(&physical_bounds)
@ -341,7 +519,7 @@ impl Renderer {
{ {
instance.primitive.render( instance.primitive.render(
encoder, encoder,
&engine.primitive_storage, &primitive_storage,
frame, frame,
&clip_bounds, &clip_bounds,
); );
@ -370,7 +548,8 @@ impl Renderer {
#[cfg(any(feature = "svg", feature = "image"))] #[cfg(any(feature = "svg", feature = "image"))]
if !layer.images.is_empty() { if !layer.images.is_empty() {
engine.image_pipeline.render( self.image.render(
&self.engine.image_pipeline,
&image_cache, &image_cache,
image_layer, image_layer,
scissor_rect, scissor_rect,
@ -381,9 +560,9 @@ impl Renderer {
} }
if !layer.text.is_empty() { if !layer.text.is_empty() {
text_layer += engine.text_pipeline.render( text_layer += self.text.render(
&self.engine.text_pipeline,
&self.text_viewport, &self.text_viewport,
&self.text_storage,
text_layer, text_layer,
&layer.text, &layer.text,
scissor_rect, scissor_rect,
@ -583,3 +762,76 @@ impl primitive::Renderer for Renderer {
impl graphics::compositor::Default for crate::Renderer { impl graphics::compositor::Default for crate::Renderer {
type Compositor = window::Compositor; type Compositor = window::Compositor;
} }
impl renderer::Headless for Renderer {
async fn new(
default_font: Font,
default_text_size: Pixels,
backend: Option<&str>,
) -> Option<Self> {
if backend.is_some_and(|backend| backend != "wgpu") {
return None;
}
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
backends: wgpu::Backends::from_env()
.unwrap_or(wgpu::Backends::PRIMARY),
flags: wgpu::InstanceFlags::empty(),
..wgpu::InstanceDescriptor::default()
});
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
force_fallback_adapter: false,
compatible_surface: None,
})
.await?;
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: Some("iced_wgpu [headless]"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits {
max_bind_groups: 2,
..wgpu::Limits::default()
},
memory_hints: wgpu::MemoryHints::MemoryUsage,
},
None,
)
.await
.ok()?;
let engine = Engine::new(
&adapter,
device,
queue,
if graphics::color::GAMMA_CORRECTION {
wgpu::TextureFormat::Rgba8UnormSrgb
} else {
wgpu::TextureFormat::Rgba8Unorm
},
Some(graphics::Antialiasing::MSAAx4),
);
Some(Self::new(engine, default_font, default_text_size))
}
fn name(&self) -> String {
"wgpu".to_owned()
}
fn screenshot(
&mut self,
size: Size<u32>,
scale_factor: f32,
background_color: Color,
) -> Vec<u8> {
self.screenshot(
&Viewport::with_physical_size(size, f64::from(scale_factor)),
background_color,
)
}
}

View file

@ -61,7 +61,7 @@ pub trait Renderer: core::Renderer {
/// Stores custom, user-provided types. /// Stores custom, user-provided types.
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Storage { pub struct Storage {
pipelines: FxHashMap<TypeId, Box<dyn Any + Send>>, pipelines: FxHashMap<TypeId, Box<dyn Any + Send + Sync>>,
} }
impl Storage { impl Storage {
@ -71,7 +71,7 @@ impl Storage {
} }
/// Inserts the data `T` in to [`Storage`]. /// Inserts the data `T` in to [`Storage`].
pub fn store<T: 'static + Send>(&mut self, data: T) { pub fn store<T: 'static + Send + Sync>(&mut self, data: T) {
let _ = self.pipelines.insert(TypeId::of::<T>(), Box::new(data)); let _ = self.pipelines.insert(TypeId::of::<T>(), Box::new(data));
} }

View file

@ -43,15 +43,96 @@ pub struct Quad {
pub shadow_blur_radius: f32, pub shadow_blur_radius: f32,
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Pipeline { pub struct Pipeline {
solid: solid::Pipeline, solid: solid::Pipeline,
gradient: gradient::Pipeline, gradient: gradient::Pipeline,
constant_layout: wgpu::BindGroupLayout, constant_layout: wgpu::BindGroupLayout,
}
#[derive(Default)]
pub struct State {
layers: Vec<Layer>, layers: Vec<Layer>,
prepare_layer: usize, prepare_layer: usize,
} }
impl State {
pub fn new() -> Self {
Self::default()
}
pub fn prepare(
&mut self,
pipeline: &Pipeline,
device: &wgpu::Device,
belt: &mut wgpu::util::StagingBelt,
encoder: &mut wgpu::CommandEncoder,
quads: &Batch,
transformation: Transformation,
scale: f32,
) {
if self.layers.len() <= self.prepare_layer {
self.layers
.push(Layer::new(device, &pipeline.constant_layout));
}
let layer = &mut self.layers[self.prepare_layer];
layer.prepare(device, encoder, belt, quads, transformation, scale);
self.prepare_layer += 1;
}
pub fn render<'a>(
&'a self,
pipeline: &'a Pipeline,
layer: usize,
bounds: Rectangle<u32>,
quads: &Batch,
render_pass: &mut wgpu::RenderPass<'a>,
) {
if let Some(layer) = self.layers.get(layer) {
render_pass.set_scissor_rect(
bounds.x,
bounds.y,
bounds.width,
bounds.height,
);
let mut solid_offset = 0;
let mut gradient_offset = 0;
for (kind, count) in &quads.order {
match kind {
Kind::Solid => {
pipeline.solid.render(
render_pass,
&layer.constants,
&layer.solid,
solid_offset..(solid_offset + count),
);
solid_offset += count;
}
Kind::Gradient => {
pipeline.gradient.render(
render_pass,
&layer.constants,
&layer.gradient,
gradient_offset..(gradient_offset + count),
);
gradient_offset += count;
}
}
}
}
}
pub fn trim(&mut self) {
self.prepare_layer = 0;
}
}
impl Pipeline { impl Pipeline {
pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Pipeline { pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Pipeline {
let constant_layout = let constant_layout =
@ -74,79 +155,9 @@ impl Pipeline {
Self { Self {
solid: solid::Pipeline::new(device, format, &constant_layout), solid: solid::Pipeline::new(device, format, &constant_layout),
gradient: gradient::Pipeline::new(device, format, &constant_layout), gradient: gradient::Pipeline::new(device, format, &constant_layout),
layers: Vec::new(),
prepare_layer: 0,
constant_layout, constant_layout,
} }
} }
pub fn prepare(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
quads: &Batch,
transformation: Transformation,
scale: f32,
) {
if self.layers.len() <= self.prepare_layer {
self.layers.push(Layer::new(device, &self.constant_layout));
}
let layer = &mut self.layers[self.prepare_layer];
layer.prepare(device, encoder, belt, quads, transformation, scale);
self.prepare_layer += 1;
}
pub fn render<'a>(
&'a self,
layer: usize,
bounds: Rectangle<u32>,
quads: &Batch,
render_pass: &mut wgpu::RenderPass<'a>,
) {
if let Some(layer) = self.layers.get(layer) {
render_pass.set_scissor_rect(
bounds.x,
bounds.y,
bounds.width,
bounds.height,
);
let mut solid_offset = 0;
let mut gradient_offset = 0;
for (kind, count) in &quads.order {
match kind {
Kind::Solid => {
self.solid.render(
render_pass,
&layer.constants,
&layer.solid,
solid_offset..(solid_offset + count),
);
solid_offset += count;
}
Kind::Gradient => {
self.gradient.render(
render_pass,
&layer.constants,
&layer.gradient,
gradient_offset..(gradient_offset + count),
);
gradient_offset += count;
}
}
}
}
}
pub fn end_frame(&mut self) {
self.prepare_layer = 0;
}
} }
#[derive(Debug)] #[derive(Debug)]

View file

@ -57,7 +57,7 @@ impl Layer {
} }
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Pipeline { pub struct Pipeline {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pipeline: wgpu::RenderPipeline, pipeline: wgpu::RenderPipeline,

View file

@ -51,7 +51,7 @@ impl Layer {
} }
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Pipeline { pub struct Pipeline {
pipeline: wgpu::RenderPipeline, pipeline: wgpu::RenderPipeline,
} }

View file

@ -9,7 +9,7 @@ use crate::graphics::text::{Editor, Paragraph, font_system, to_color};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::collections::hash_map; use std::collections::hash_map;
use std::sync::atomic::{self, AtomicU64}; use std::sync::atomic::{self, AtomicU64};
use std::sync::{self, Arc}; use std::sync::{self, Arc, RwLock};
pub use crate::graphics::Text; pub use crate::graphics::Text;
@ -94,10 +94,6 @@ struct Group {
} }
impl Storage { impl Storage {
pub fn new() -> Self {
Self::default()
}
fn get(&self, cache: &Cache) -> Option<(&cryoglyph::TextAtlas, &Upload)> { fn get(&self, cache: &Cache) -> Option<(&cryoglyph::TextAtlas, &Upload)> {
if cache.text.is_empty() { if cache.text.is_empty() {
return None; return None;
@ -272,14 +268,12 @@ impl Viewport {
} }
} }
#[derive(Clone)]
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Pipeline { pub struct Pipeline {
state: cryoglyph::Cache,
format: wgpu::TextureFormat, format: wgpu::TextureFormat,
atlas: cryoglyph::TextAtlas, cache: cryoglyph::Cache,
renderers: Vec<cryoglyph::TextRenderer>, atlas: Arc<RwLock<cryoglyph::TextAtlas>>,
prepare_layer: usize,
cache: BufferCache,
} }
impl Pipeline { impl Pipeline {
@ -288,32 +282,53 @@ impl Pipeline {
queue: &wgpu::Queue, queue: &wgpu::Queue,
format: wgpu::TextureFormat, format: wgpu::TextureFormat,
) -> Self { ) -> Self {
let state = cryoglyph::Cache::new(device); let cache = cryoglyph::Cache::new(device);
let atlas = cryoglyph::TextAtlas::with_color_mode( let atlas = cryoglyph::TextAtlas::with_color_mode(
device, queue, &state, format, COLOR_MODE, device, queue, &cache, format, COLOR_MODE,
); );
Pipeline { Pipeline {
state,
format, format,
renderers: Vec::new(), cache,
atlas, atlas: Arc::new(RwLock::new(atlas)),
prepare_layer: 0,
cache: BufferCache::new(),
} }
} }
pub fn create_viewport(&self, device: &wgpu::Device) -> Viewport {
Viewport(cryoglyph::Viewport::new(device, &self.cache))
}
pub fn trim(&self) {
self.atlas.write().expect("Write text atlas").trim();
}
}
#[derive(Default)]
pub struct State {
renderers: Vec<cryoglyph::TextRenderer>,
prepare_layer: usize,
cache: BufferCache,
storage: Storage,
}
impl State {
pub fn new() -> Self {
Self::default()
}
pub fn prepare( pub fn prepare(
&mut self, &mut self,
pipeline: &Pipeline,
device: &wgpu::Device, device: &wgpu::Device,
queue: &wgpu::Queue, queue: &wgpu::Queue,
viewport: &Viewport, viewport: &Viewport,
encoder: &mut wgpu::CommandEncoder, encoder: &mut wgpu::CommandEncoder,
storage: &mut Storage,
batch: &Batch, batch: &Batch,
layer_bounds: Rectangle, layer_bounds: Rectangle,
layer_transformation: Transformation, layer_transformation: Transformation,
) { ) {
let mut atlas = pipeline.atlas.write().expect("Write to text atlas");
for item in batch { for item in batch {
match item { match item {
Item::Group { Item::Group {
@ -322,7 +337,7 @@ impl Pipeline {
} => { } => {
if self.renderers.len() <= self.prepare_layer { if self.renderers.len() <= self.prepare_layer {
self.renderers.push(cryoglyph::TextRenderer::new( self.renderers.push(cryoglyph::TextRenderer::new(
&mut self.atlas, &mut atlas,
device, device,
wgpu::MultisampleState::default(), wgpu::MultisampleState::default(),
None, None,
@ -336,7 +351,7 @@ impl Pipeline {
&viewport.0, &viewport.0,
encoder, encoder,
renderer, renderer,
&mut self.atlas, &mut atlas,
&mut self.cache, &mut self.cache,
text, text,
layer_bounds * layer_transformation, layer_bounds * layer_transformation,
@ -358,13 +373,13 @@ impl Pipeline {
transformation, transformation,
cache, cache,
} => { } => {
storage.prepare( self.storage.prepare(
device, device,
queue, queue,
&viewport.0, &viewport.0,
encoder, encoder,
self.format, pipeline.format,
&self.state, &pipeline.cache,
cache, cache,
layer_transformation * *transformation, layer_transformation * *transformation,
layer_bounds * layer_transformation, layer_bounds * layer_transformation,
@ -376,13 +391,14 @@ impl Pipeline {
pub fn render<'a>( pub fn render<'a>(
&'a self, &'a self,
pipeline: &'a Pipeline,
viewport: &'a Viewport, viewport: &'a Viewport,
storage: &'a Storage,
start: usize, start: usize,
batch: &'a Batch, batch: &'a Batch,
bounds: Rectangle<u32>, bounds: Rectangle<u32>,
render_pass: &mut wgpu::RenderPass<'a>, render_pass: &mut wgpu::RenderPass<'a>,
) -> usize { ) -> usize {
let atlas = pipeline.atlas.read().expect("Read text atlas");
let mut layer_count = 0; let mut layer_count = 0;
render_pass.set_scissor_rect( render_pass.set_scissor_rect(
@ -398,13 +414,13 @@ impl Pipeline {
let renderer = &self.renderers[start + layer_count]; let renderer = &self.renderers[start + layer_count];
renderer renderer
.render(&self.atlas, &viewport.0, render_pass) .render(&atlas, &viewport.0, render_pass)
.expect("Render text"); .expect("Render text");
layer_count += 1; layer_count += 1;
} }
Item::Cached { cache, .. } => { Item::Cached { cache, .. } => {
if let Some((atlas, upload)) = storage.get(cache) { if let Some((atlas, upload)) = self.storage.get(cache) {
upload upload
.renderer .renderer
.render(atlas, &viewport.0, render_pass) .render(atlas, &viewport.0, render_pass)
@ -417,13 +433,9 @@ impl Pipeline {
layer_count layer_count
} }
pub fn create_viewport(&self, device: &wgpu::Device) -> Viewport { pub fn trim(&mut self) {
Viewport(cryoglyph::Viewport::new(device, &self.state))
}
pub fn end_frame(&mut self) {
self.atlas.trim();
self.cache.trim(); self.cache.trim();
self.storage.trim();
self.prepare_layer = 0; self.prepare_layer = 0;
} }

View file

@ -153,42 +153,47 @@ impl Storage {
} }
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Pipeline { pub struct Pipeline {
blit: Option<msaa::Blit>, msaa: Option<msaa::Pipeline>,
solid: solid::Pipeline, solid: solid::Pipeline,
gradient: gradient::Pipeline, gradient: gradient::Pipeline,
layers: Vec<Layer>,
prepare_layer: usize,
} }
impl Pipeline { pub struct State {
pub fn new( msaa: Option<msaa::State>,
device: &wgpu::Device, layers: Vec<Layer>,
format: wgpu::TextureFormat, prepare_layer: usize,
antialiasing: Option<Antialiasing>, storage: Storage,
) -> Pipeline { }
Pipeline {
blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)), impl State {
solid: solid::Pipeline::new(device, format, antialiasing), pub fn new(device: &wgpu::Device, pipeline: &Pipeline) -> Self {
gradient: gradient::Pipeline::new(device, format, antialiasing), Self {
msaa: pipeline
.msaa
.as_ref()
.map(|pipeline| msaa::State::new(device, pipeline)),
layers: Vec::new(), layers: Vec::new(),
prepare_layer: 0, prepare_layer: 0,
storage: Storage::new(),
} }
} }
pub fn prepare( pub fn prepare(
&mut self, &mut self,
pipeline: &Pipeline,
device: &wgpu::Device, device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt, belt: &mut wgpu::util::StagingBelt,
storage: &mut Storage, encoder: &mut wgpu::CommandEncoder,
items: &[Item], items: &[Item],
scale: Transformation, scale: Transformation,
target_size: Size<u32>, target_size: Size<u32>,
) { ) {
let projection = if let Some(blit) = &mut self.blit { let projection = if let Some((state, pipeline)) =
blit.prepare(device, encoder, belt, target_size) * scale self.msaa.as_mut().zip(pipeline.msaa.as_ref())
{
state.prepare(device, encoder, belt, pipeline, target_size) * scale
} else { } else {
Transformation::orthographic(target_size.width, target_size.height) Transformation::orthographic(target_size.width, target_size.height)
* scale * scale
@ -203,8 +208,8 @@ impl Pipeline {
if self.layers.len() <= self.prepare_layer { if self.layers.len() <= self.prepare_layer {
self.layers.push(Layer::new( self.layers.push(Layer::new(
device, device,
&self.solid, &pipeline.solid,
&self.gradient, &pipeline.gradient,
)); ));
} }
@ -213,8 +218,8 @@ impl Pipeline {
device, device,
encoder, encoder,
belt, belt,
&self.solid, &pipeline.solid,
&self.gradient, &pipeline.gradient,
meshes, meshes,
projection * *transformation, projection * *transformation,
); );
@ -225,12 +230,12 @@ impl Pipeline {
transformation, transformation,
cache, cache,
} => { } => {
storage.prepare( self.storage.prepare(
device, device,
encoder, encoder,
belt, belt,
&self.solid, &pipeline.solid,
&self.gradient, &pipeline.gradient,
cache, cache,
projection * *transformation, projection * *transformation,
); );
@ -241,9 +246,9 @@ impl Pipeline {
pub fn render( pub fn render(
&mut self, &mut self,
pipeline: &Pipeline,
encoder: &mut wgpu::CommandEncoder, encoder: &mut wgpu::CommandEncoder,
target: &wgpu::TextureView, target: &wgpu::TextureView,
storage: &Storage,
start: usize, start: usize,
batch: &Batch, batch: &Batch,
bounds: Rectangle, bounds: Rectangle,
@ -269,7 +274,7 @@ impl Pipeline {
transformation, transformation,
cache, cache,
} => { } => {
let upload = storage.get(cache)?; let upload = self.storage.get(cache)?;
Some(( Some((
&upload.layer, &upload.layer,
@ -282,9 +287,9 @@ impl Pipeline {
render( render(
encoder, encoder,
target, target,
self.blit.as_mut(), self.msaa.as_ref().zip(pipeline.msaa.as_ref()),
&self.solid, &pipeline.solid,
&self.gradient, &pipeline.gradient,
bounds, bounds,
items, items,
); );
@ -292,48 +297,55 @@ impl Pipeline {
layer_count layer_count
} }
pub fn end_frame(&mut self) { pub fn trim(&mut self) {
self.storage.trim();
self.prepare_layer = 0; self.prepare_layer = 0;
} }
} }
impl Pipeline {
pub fn new(
device: &wgpu::Device,
format: wgpu::TextureFormat,
antialiasing: Option<Antialiasing>,
) -> Pipeline {
Pipeline {
msaa: antialiasing.map(|a| msaa::Pipeline::new(device, format, a)),
solid: solid::Pipeline::new(device, format, antialiasing),
gradient: gradient::Pipeline::new(device, format, antialiasing),
}
}
}
fn render<'a>( fn render<'a>(
encoder: &mut wgpu::CommandEncoder, encoder: &mut wgpu::CommandEncoder,
target: &wgpu::TextureView, target: &wgpu::TextureView,
mut blit: Option<&mut msaa::Blit>, mut msaa: Option<(&msaa::State, &msaa::Pipeline)>,
solid: &solid::Pipeline, solid: &solid::Pipeline,
gradient: &gradient::Pipeline, gradient: &gradient::Pipeline,
bounds: Rectangle, bounds: Rectangle,
group: impl Iterator<Item = (&'a Layer, &'a [Mesh], Transformation)>, group: impl Iterator<Item = (&'a Layer, &'a [Mesh], Transformation)>,
) { ) {
{ {
let (attachment, resolve_target, load) = if let Some(blit) = &mut blit { let mut render_pass = if let Some((_state, pipeline)) = &mut msaa {
let (attachment, resolve_target) = blit.targets(); pipeline.render_pass(encoder)
(
attachment,
Some(resolve_target),
wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
)
} else { } else {
(target, None, wgpu::LoadOp::Load)
};
let mut render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor { encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu.triangle.render_pass"), label: Some("iced_wgpu.triangle.render_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment { color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: attachment, view: target,
resolve_target, resolve_target: None,
ops: wgpu::Operations { ops: wgpu::Operations {
load, load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store, store: wgpu::StoreOp::Store,
}, },
})], })],
depth_stencil_attachment: None, depth_stencil_attachment: None,
timestamp_writes: None, timestamp_writes: None,
occlusion_query_set: None, occlusion_query_set: None,
}); })
};
for (layer, meshes, transformation) in group { for (layer, meshes, transformation) in group {
layer.render( layer.render(
@ -347,8 +359,8 @@ fn render<'a>(
} }
} }
if let Some(blit) = blit { if let Some((state, pipeline)) = msaa {
blit.draw(encoder, target); state.render(pipeline, encoder, target);
} }
} }
@ -649,7 +661,7 @@ mod solid {
use crate::graphics::mesh; use crate::graphics::mesh;
use crate::triangle; use crate::triangle;
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Pipeline { pub struct Pipeline {
pub pipeline: wgpu::RenderPipeline, pub pipeline: wgpu::RenderPipeline,
pub constants_layout: wgpu::BindGroupLayout, pub constants_layout: wgpu::BindGroupLayout,
@ -801,7 +813,7 @@ mod gradient {
use crate::graphics::mesh; use crate::graphics::mesh;
use crate::triangle; use crate::triangle;
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Pipeline { pub struct Pipeline {
pub pipeline: wgpu::RenderPipeline, pub pipeline: wgpu::RenderPipeline,
pub constants_layout: wgpu::BindGroupLayout, pub constants_layout: wgpu::BindGroupLayout,

View file

@ -2,35 +2,28 @@ use crate::core::{Size, Transformation};
use crate::graphics; use crate::graphics;
use std::num::NonZeroU64; use std::num::NonZeroU64;
use std::sync::{Arc, RwLock};
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Blit { pub struct Pipeline {
format: wgpu::TextureFormat, format: wgpu::TextureFormat,
pipeline: wgpu::RenderPipeline, sampler: wgpu::Sampler,
constants: wgpu::BindGroup, raw: wgpu::RenderPipeline,
ratio: wgpu::Buffer, constant_layout: wgpu::BindGroupLayout,
texture_layout: wgpu::BindGroupLayout, texture_layout: wgpu::BindGroupLayout,
sample_count: u32, sample_count: u32,
targets: Option<Targets>, targets: Arc<RwLock<Option<Targets>>>,
last_region: Option<Size<u32>>,
} }
impl Blit { impl Pipeline {
pub fn new( pub fn new(
device: &wgpu::Device, device: &wgpu::Device,
format: wgpu::TextureFormat, format: wgpu::TextureFormat,
antialiasing: graphics::Antialiasing, antialiasing: graphics::Antialiasing,
) -> Blit { ) -> Pipeline {
let sampler = let sampler =
device.create_sampler(&wgpu::SamplerDescriptor::default()); 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 = let constant_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("iced_wgpu::triangle:msaa uniforms layout"), label: Some("iced_wgpu::triangle:msaa uniforms layout"),
@ -56,22 +49,6 @@ impl Blit {
], ],
}); });
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),
},
wgpu::BindGroupEntry {
binding: 1,
resource: ratio.as_entire_binding(),
},
],
});
let texture_layout = let texture_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("iced_wgpu::triangle::msaa texture layout"), label: Some("iced_wgpu::triangle::msaa texture layout"),
@ -143,31 +120,30 @@ impl Blit {
cache: None, cache: None,
}); });
Blit { Self {
format, format,
pipeline, sampler,
constants: constant_bind_group, raw: pipeline,
ratio, constant_layout,
texture_layout, texture_layout,
sample_count: antialiasing.sample_count(), sample_count: antialiasing.sample_count(),
targets: None, targets: Arc::new(RwLock::new(None)),
last_region: None,
} }
} }
pub fn prepare( fn targets(
&mut self, &self,
device: &wgpu::Device, device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
region_size: Size<u32>, region_size: Size<u32>,
) -> Transformation { ) -> Targets {
match &mut self.targets { let mut targets = self.targets.write().expect("Write MSAA targets");
match targets.as_mut() {
Some(targets) Some(targets)
if region_size.width <= targets.size.width if region_size.width <= targets.size.width
&& region_size.height <= targets.size.height => {} && region_size.height <= targets.size.height => {}
_ => { _ => {
self.targets = Some(Targets::new( *targets = Some(Targets::new(
device, device,
self.format, self.format,
&self.texture_layout, &self.texture_layout,
@ -177,69 +153,34 @@ impl Blit {
} }
} }
let targets = self.targets.as_mut().unwrap(); targets.as_ref().unwrap().clone()
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 render_pass<'a>(
}
pub fn targets(&self) -> (&wgpu::TextureView, &wgpu::TextureView) {
let targets = self.targets.as_ref().unwrap();
(&targets.attachment, &targets.resolve)
}
pub fn draw(
&self, &self,
encoder: &mut wgpu::CommandEncoder, encoder: &'a mut wgpu::CommandEncoder,
target: &wgpu::TextureView, ) -> wgpu::RenderPass<'a> {
) { let targets = self.targets.read().expect("Read MSAA targets");
let mut render_pass = let targets = targets.as_ref().unwrap();
encoder.begin_render_pass(&wgpu::RenderPassDescriptor { encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu::triangle::msaa render pass"), label: Some("iced_wgpu.triangle.render_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment { color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: target, view: &targets.attachment,
resolve_target: None, resolve_target: Some(&targets.resolve),
ops: wgpu::Operations { ops: wgpu::Operations {
load: wgpu::LoadOp::Load, load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: wgpu::StoreOp::Store, store: wgpu::StoreOp::Store,
}, },
})], })],
depth_stencil_attachment: None, depth_stencil_attachment: None,
timestamp_writes: None, timestamp_writes: None,
occlusion_query_set: None, occlusion_query_set: None,
}); })
render_pass.set_pipeline(&self.pipeline);
render_pass.set_bind_group(0, &self.constants, &[]);
render_pass.set_bind_group(
1,
&self.targets.as_ref().unwrap().bind_group,
&[],
);
render_pass.draw(0..6, 0..1);
} }
} }
#[derive(Debug)] #[derive(Debug, Clone)]
struct Targets { struct Targets {
attachment: wgpu::TextureView, attachment: wgpu::TextureView,
resolve: wgpu::TextureView, resolve: wgpu::TextureView,
@ -308,9 +249,117 @@ impl Targets {
} }
} }
#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] #[derive(Debug, Clone, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
#[repr(C)] #[repr(C)]
struct Ratio { struct Ratio {
u: f32, u: f32,
v: f32, v: f32,
} }
pub struct State {
ratio: wgpu::Buffer,
constants: wgpu::BindGroup,
last_ratio: Option<Ratio>,
}
impl State {
pub fn new(device: &wgpu::Device, pipeline: &Pipeline) -> Self {
let ratio = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("iced_wgpu::triangle::msaa ratio"),
size: std::mem::size_of::<Ratio>() as u64,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM,
mapped_at_creation: false,
});
let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("iced_wgpu::triangle::msaa uniforms bind group"),
layout: &pipeline.constant_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Sampler(&pipeline.sampler),
},
wgpu::BindGroupEntry {
binding: 1,
resource: ratio.as_entire_binding(),
},
],
});
Self {
ratio,
constants,
last_ratio: None,
}
}
pub fn prepare(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
pipeline: &Pipeline,
region_size: Size<u32>,
) -> Transformation {
let targets = pipeline.targets(device, region_size);
let ratio = Ratio {
u: region_size.width as f32 / targets.size.width as f32,
v: region_size.height as f32 / targets.size.height as f32,
};
if Some(ratio) != self.last_ratio {
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_ratio = Some(ratio);
}
Transformation::orthographic(targets.size.width, targets.size.height)
}
pub fn render(
&self,
pipeline: &Pipeline,
encoder: &mut wgpu::CommandEncoder,
target: &wgpu::TextureView,
) {
let mut render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu::triangle::msaa render pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: target,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
render_pass.set_pipeline(&pipeline.raw);
render_pass.set_bind_group(0, &self.constants, &[]);
render_pass.set_bind_group(
1,
&pipeline
.targets
.read()
.expect("Read MSAA targets")
.as_ref()
.unwrap()
.bind_group,
&[],
);
render_pass.draw(0..6, 0..1);
}
}

View file

@ -1,5 +1,5 @@
//! Connect a window with a renderer. //! Connect a window with a renderer.
use crate::core::{Color, Size}; use crate::core::Color;
use crate::graphics::color; use crate::graphics::color;
use crate::graphics::compositor; use crate::graphics::compositor;
use crate::graphics::error; use crate::graphics::error;
@ -12,8 +12,6 @@ use crate::{Engine, Renderer};
pub struct Compositor { pub struct Compositor {
instance: wgpu::Instance, instance: wgpu::Instance,
adapter: wgpu::Adapter, adapter: wgpu::Adapter,
device: wgpu::Device,
queue: wgpu::Queue,
format: wgpu::TextureFormat, format: wgpu::TextureFormat,
alpha_mode: wgpu::CompositeAlphaMode, alpha_mode: wgpu::CompositeAlphaMode,
engine: Engine, engine: Engine,
@ -178,8 +176,8 @@ impl Compositor {
Ok((device, queue)) => { Ok((device, queue)) => {
let engine = Engine::new( let engine = Engine::new(
&adapter, &adapter,
&device, device,
&queue, queue,
format, format,
settings.antialiasing, settings.antialiasing,
); );
@ -187,8 +185,6 @@ impl Compositor {
return Ok(Compositor { return Ok(Compositor {
instance, instance,
adapter, adapter,
device,
queue,
format, format,
alpha_mode, alpha_mode,
engine, engine,
@ -215,38 +211,27 @@ pub async fn new<W: compositor::Window>(
/// Presents the given primitives with the given [`Compositor`]. /// Presents the given primitives with the given [`Compositor`].
pub fn present( pub fn present(
compositor: &mut Compositor,
renderer: &mut Renderer, renderer: &mut Renderer,
surface: &mut wgpu::Surface<'static>, surface: &mut wgpu::Surface<'static>,
viewport: &Viewport, viewport: &Viewport,
background_color: Color, background_color: Color,
on_pre_present: impl FnOnce(),
) -> Result<(), compositor::SurfaceError> { ) -> Result<(), compositor::SurfaceError> {
match surface.get_current_texture() { match surface.get_current_texture() {
Ok(frame) => { Ok(frame) => {
let mut encoder = compositor.device.create_command_encoder(
&wgpu::CommandEncoderDescriptor {
label: Some("iced_wgpu encoder"),
},
);
let view = &frame let view = &frame
.texture .texture
.create_view(&wgpu::TextureViewDescriptor::default()); .create_view(&wgpu::TextureViewDescriptor::default());
renderer.present( let _submission = renderer.present(
&mut compositor.engine,
&compositor.device,
&compositor.queue,
&mut encoder,
Some(background_color), Some(background_color),
frame.texture.format(), frame.texture.format(),
view, view,
viewport, viewport,
); );
let _ = compositor.engine.submit(&compositor.queue, encoder);
// Present the frame // Present the frame
on_pre_present();
frame.present(); frame.present();
Ok(()) Ok(())
@ -301,8 +286,7 @@ impl graphics::Compositor for Compositor {
fn create_renderer(&self) -> Self::Renderer { fn create_renderer(&self) -> Self::Renderer {
Renderer::new( Renderer::new(
&self.device, self.engine.clone(),
&self.engine,
self.settings.default_font, self.settings.default_font,
self.settings.default_text_size, self.settings.default_text_size,
) )
@ -333,7 +317,7 @@ impl graphics::Compositor for Compositor {
height: u32, height: u32,
) { ) {
surface.configure( surface.configure(
&self.device, &self.engine.device,
&wgpu::SurfaceConfiguration { &wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT, usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: self.format, format: self.format,
@ -362,8 +346,15 @@ impl graphics::Compositor for Compositor {
surface: &mut Self::Surface, surface: &mut Self::Surface,
viewport: &Viewport, viewport: &Viewport,
background_color: Color, background_color: Color,
on_pre_present: impl FnOnce(),
) -> Result<(), compositor::SurfaceError> { ) -> Result<(), compositor::SurfaceError> {
present(self, renderer, surface, viewport, background_color) present(
renderer,
surface,
viewport,
background_color,
on_pre_present,
)
} }
fn screenshot( fn screenshot(
@ -372,134 +363,6 @@ impl graphics::Compositor for Compositor {
viewport: &Viewport, viewport: &Viewport,
background_color: Color, background_color: Color,
) -> Vec<u8> { ) -> Vec<u8> {
screenshot(self, renderer, viewport, background_color) renderer.screenshot(viewport, background_color)
}
}
/// Renders the current surface to an offscreen buffer.
///
/// Returns RGBA bytes of the texture data.
pub fn screenshot(
compositor: &mut Compositor,
renderer: &mut Renderer,
viewport: &Viewport,
background_color: Color,
) -> Vec<u8> {
let dimensions = BufferDimensions::new(viewport.physical_size());
let texture_extent = wgpu::Extent3d {
width: dimensions.width,
height: dimensions.height,
depth_or_array_layers: 1,
};
let texture = compositor.device.create_texture(&wgpu::TextureDescriptor {
label: Some("iced_wgpu.offscreen.source_texture"),
size: texture_extent,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: compositor.format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::COPY_SRC
| wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = 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,
viewport,
);
let texture = crate::color::convert(
&compositor.device,
&mut encoder,
texture,
if color::GAMMA_CORRECTION {
wgpu::TextureFormat::Rgba8UnormSrgb
} else {
wgpu::TextureFormat::Rgba8Unorm
},
);
let output_buffer =
compositor.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("iced_wgpu.offscreen.output_texture_buffer"),
size: (dimensions.padded_bytes_per_row * dimensions.height as usize)
as u64,
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
encoder.copy_texture_to_buffer(
texture.as_image_copy(),
wgpu::TexelCopyBufferInfo {
buffer: &output_buffer,
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(dimensions.padded_bytes_per_row as u32),
rows_per_image: None,
},
},
texture_extent,
);
let index = compositor.engine.submit(&compositor.queue, encoder);
let slice = output_buffer.slice(..);
slice.map_async(wgpu::MapMode::Read, |_| {});
let _ = compositor
.device
.poll(wgpu::Maintain::WaitForSubmissionIndex(index));
let mapped_buffer = slice.get_mapped_range();
mapped_buffer.chunks(dimensions.padded_bytes_per_row).fold(
vec![],
|mut acc, row| {
acc.extend(&row[..dimensions.unpadded_bytes_per_row]);
acc
},
)
}
#[derive(Clone, Copy, Debug)]
struct BufferDimensions {
width: u32,
height: u32,
unpadded_bytes_per_row: usize,
padded_bytes_per_row: usize,
}
impl BufferDimensions {
fn new(size: Size<u32>) -> Self {
let unpadded_bytes_per_row = size.width as usize * 4; //slice of buffer per row; always RGBA
let alignment = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize; //256
let padded_bytes_per_row_padding =
(alignment - unpadded_bytes_per_row % alignment) % alignment;
let padded_bytes_per_row =
unpadded_bytes_per_row + padded_bytes_per_row_padding;
Self {
width: size.width,
height: size.height,
unpadded_bytes_per_row,
padded_bytes_per_row,
}
} }
} }

View file

@ -474,6 +474,7 @@ fn parse_with<'a>(
let mut strikethrough = false; let mut strikethrough = false;
let mut metadata = false; let mut metadata = false;
let mut table = false; let mut table = false;
let mut code_block = false;
let mut link = None; let mut link = None;
let mut image = None; let mut image = None;
let mut stack = Vec::new(); let mut stack = Vec::new();
@ -627,6 +628,7 @@ fn parse_with<'a>(
}); });
} }
code_block = true;
code_language = code_language =
(!language.is_empty()).then(|| language.into_string()); (!language.is_empty()).then(|| language.into_string());
@ -732,6 +734,8 @@ fn parse_with<'a>(
) )
} }
pulldown_cmark::TagEnd::CodeBlock if !metadata && !table => { pulldown_cmark::TagEnd::CodeBlock if !metadata && !table => {
code_block = false;
#[cfg(feature = "highlighter")] #[cfg(feature = "highlighter")]
{ {
state.borrow_mut().highlighter = highlighter.take(); state.borrow_mut().highlighter = highlighter.take();
@ -759,15 +763,29 @@ fn parse_with<'a>(
_ => None, _ => None,
}, },
pulldown_cmark::Event::Text(text) if !metadata && !table => { pulldown_cmark::Event::Text(text) if !metadata && !table => {
#[cfg(feature = "highlighter")] if code_block {
if let Some(highlighter) = &mut highlighter {
code.push_str(&text); code.push_str(&text);
#[cfg(feature = "highlighter")]
if let Some(highlighter) = &mut highlighter {
for line in text.lines() { for line in text.lines() {
code_lines.push(Text::new( code_lines.push(Text::new(
highlighter.highlight_line(line).to_vec(), highlighter.highlight_line(line).to_vec(),
)); ));
} }
}
#[cfg(not(feature = "highlighter"))]
for line in text.lines() {
code_lines.push(Text::new(vec![Span::Standard {
text: line.to_owned(),
strong,
emphasis,
strikethrough,
link: link.clone(),
code: false,
}]));
}
return None; return None;
} }

View file

@ -343,6 +343,10 @@ fn update<Message: Clone, Theme, Renderer>(
state.cursor_position = cursor_position; state.cursor_position = cursor_position;
state.bounds = bounds; state.bounds = bounds;
if widget.interaction.is_some() && state.is_hovered != was_hovered {
shell.request_redraw();
}
match ( match (
widget.on_enter.as_ref(), widget.on_enter.as_ref(),
widget.on_move.as_ref(), widget.on_move.as_ref(),

View file

@ -167,7 +167,10 @@ where
/// ///
/// The original alignment of the [`Row`] is preserved per row wrapped. /// The original alignment of the [`Row`] is preserved per row wrapped.
pub fn wrap(self) -> Wrapping<'a, Message, Theme, Renderer> { pub fn wrap(self) -> Wrapping<'a, Message, Theme, Renderer> {
Wrapping { row: self } Wrapping {
row: self,
vertical_spacing: None,
}
} }
} }
@ -372,6 +375,15 @@ pub struct Wrapping<
Renderer = crate::Renderer, Renderer = crate::Renderer,
> { > {
row: Row<'a, Message, Theme, Renderer>, row: Row<'a, Message, Theme, Renderer>,
vertical_spacing: Option<f32>,
}
impl<Message, Theme, Renderer> Wrapping<'_, Message, Theme, Renderer> {
/// Sets the vertical spacing _between_ lines.
pub fn vertical_spacing(mut self, amount: impl Into<Pixels>) -> Self {
self.vertical_spacing = Some(amount.into().0);
self
}
} }
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
@ -403,6 +415,7 @@ where
.shrink(self.row.padding); .shrink(self.row.padding);
let spacing = self.row.spacing; let spacing = self.row.spacing;
let vertical_spacing = self.vertical_spacing.unwrap_or(spacing);
let max_width = limits.max().width; let max_width = limits.max().width;
let mut children: Vec<layout::Node> = Vec::new(); let mut children: Vec<layout::Node> = Vec::new();
@ -447,7 +460,7 @@ where
align(row_start..i, row_height, &mut children); align(row_start..i, row_height, &mut children);
y += row_height + spacing; y += row_height + vertical_spacing;
x = 0.0; x = 0.0;
row_start = i; row_start = i;
row_height = 0.0; row_height = 0.0;

View file

@ -819,6 +819,7 @@ async fn run_instance<P>(
&mut window.surface, &mut window.surface,
window.state.viewport(), window.state.viewport(),
window.state.background_color(), window.state.background_color(),
|| window.raw.pre_present_notify(),
) { ) {
Ok(()) => { Ok(()) => {
present_span.finish(); present_span.finish();