Merge branch 'master' into beacon
This commit is contained in:
commit
e060129951
30 changed files with 915 additions and 633 deletions
1
.github/workflows/test.yml
vendored
1
.github/workflows/test.yml
vendored
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
examples/todos/snapshots/creates_a_new_task-wgpu.sha256
Normal file
1
examples/todos/snapshots/creates_a_new_task-wgpu.sha256
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
804a1bb6d49e3b3158463202960447d9e7820b967280f41dd0c34c00d3edf2c3
|
||||||
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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")))]
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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> {
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
356
wgpu/src/lib.rs
356
wgpu/src/lib.rs
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
153
wgpu/src/quad.rs
153
wgpu/src/quad.rs
|
|
@ -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)]
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ impl Layer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Pipeline {
|
pub struct Pipeline {
|
||||||
pipeline: wgpu::RenderPipeline,
|
pipeline: wgpu::RenderPipeline,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue