Merge pull request #2382 from iced-rs/wgpu/better-architecture
Improved architecture for `iced_wgpu` and `iced_tiny_skia`
This commit is contained in:
commit
105b8bd5ad
67 changed files with 4991 additions and 4301 deletions
|
|
@ -1,2 +1,2 @@
|
||||||
[alias]
|
[alias]
|
||||||
lint = "clippy --workspace --no-deps -- -D warnings"
|
lint = "clippy --workspace --benches --all-features --no-deps -- -D warnings"
|
||||||
|
|
|
||||||
16
.github/workflows/check.yml
vendored
16
.github/workflows/check.yml
vendored
|
|
@ -1,14 +1,6 @@
|
||||||
name: Check
|
name: Check
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
jobs:
|
jobs:
|
||||||
widget:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: hecrj/setup-rust-action@v2
|
|
||||||
- uses: actions/checkout@master
|
|
||||||
- name: Check standalone `iced_widget` crate
|
|
||||||
run: cargo check --package iced_widget --features image,svg,canvas
|
|
||||||
|
|
||||||
wasm:
|
wasm:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
|
|
@ -27,3 +19,11 @@ jobs:
|
||||||
run: cargo build --package todos --target wasm32-unknown-unknown
|
run: cargo build --package todos --target wasm32-unknown-unknown
|
||||||
- name: Check compilation of `integration` example
|
- name: Check compilation of `integration` example
|
||||||
run: cargo build --package integration --target wasm32-unknown-unknown
|
run: cargo build --package integration --target wasm32-unknown-unknown
|
||||||
|
|
||||||
|
widget:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: hecrj/setup-rust-action@v2
|
||||||
|
- uses: actions/checkout@master
|
||||||
|
- name: Check standalone `iced_widget` crate
|
||||||
|
run: cargo check --package iced_widget --features image,svg,canvas
|
||||||
|
|
|
||||||
|
|
@ -12,18 +12,15 @@ use iced_wgpu::Renderer;
|
||||||
criterion_main!(benches);
|
criterion_main!(benches);
|
||||||
criterion_group!(benches, wgpu_benchmark);
|
criterion_group!(benches, wgpu_benchmark);
|
||||||
|
|
||||||
|
#[allow(unused_results)]
|
||||||
pub fn wgpu_benchmark(c: &mut Criterion) {
|
pub fn wgpu_benchmark(c: &mut Criterion) {
|
||||||
let _ = c
|
c.bench_function("wgpu — canvas (light)", |b| benchmark(b, scene(10)));
|
||||||
.bench_function("wgpu — canvas (light)", |b| benchmark(b, scene(10)));
|
c.bench_function("wgpu — canvas (heavy)", |b| benchmark(b, scene(1_000)));
|
||||||
|
|
||||||
let _ = c.bench_function("wgpu — canvas (heavy)", |b| {
|
|
||||||
benchmark(b, scene(1_000))
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn benchmark<'a>(
|
fn benchmark(
|
||||||
bencher: &mut Bencher<'_>,
|
bencher: &mut Bencher<'_>,
|
||||||
widget: Element<'a, (), Theme, Renderer>,
|
widget: Element<'_, (), Theme, Renderer>,
|
||||||
) {
|
) {
|
||||||
use iced_futures::futures::executor;
|
use iced_futures::futures::executor;
|
||||||
use iced_wgpu::graphics;
|
use iced_wgpu::graphics;
|
||||||
|
|
@ -58,21 +55,15 @@ fn benchmark<'a>(
|
||||||
|
|
||||||
let format = wgpu::TextureFormat::Bgra8UnormSrgb;
|
let format = wgpu::TextureFormat::Bgra8UnormSrgb;
|
||||||
|
|
||||||
let backend = iced_wgpu::Backend::new(
|
let mut engine = iced_wgpu::Engine::new(
|
||||||
&adapter,
|
&adapter,
|
||||||
&device,
|
&device,
|
||||||
&queue,
|
&queue,
|
||||||
iced_wgpu::Settings {
|
|
||||||
present_mode: wgpu::PresentMode::Immediate,
|
|
||||||
internal_backend: wgpu::Backends::all(),
|
|
||||||
default_font: Font::DEFAULT,
|
|
||||||
default_text_size: Pixels::from(16),
|
|
||||||
antialiasing: Some(Antialiasing::MSAAx4),
|
|
||||||
},
|
|
||||||
format,
|
format,
|
||||||
|
Some(Antialiasing::MSAAx4),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut renderer = Renderer::new(backend, Font::DEFAULT, Pixels::from(16));
|
let mut renderer = Renderer::new(&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);
|
||||||
|
|
@ -117,25 +108,20 @@ fn benchmark<'a>(
|
||||||
label: None,
|
label: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
renderer.with_primitives(|backend, primitives| {
|
renderer.present::<&str>(
|
||||||
backend.present::<&str>(
|
&mut engine,
|
||||||
&device,
|
&device,
|
||||||
&queue,
|
&queue,
|
||||||
&mut encoder,
|
&mut encoder,
|
||||||
Some(Color::BLACK),
|
Some(Color::BLACK),
|
||||||
format,
|
format,
|
||||||
&texture_view,
|
&texture_view,
|
||||||
primitives,
|
&viewport,
|
||||||
&viewport,
|
&[],
|
||||||
&[],
|
);
|
||||||
);
|
|
||||||
|
|
||||||
let submission = queue.submit(Some(encoder.finish()));
|
let submission = engine.submit(&queue, encoder);
|
||||||
backend.recall();
|
let _ = device.poll(wgpu::Maintain::WaitForSubmissionIndex(submission));
|
||||||
|
|
||||||
let _ =
|
|
||||||
device.poll(wgpu::Maintain::WaitForSubmissionIndex(submission));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,24 +16,32 @@ pub struct Rectangle<T = f32> {
|
||||||
pub height: T,
|
pub height: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Rectangle<f32> {
|
impl<T> Rectangle<T>
|
||||||
/// Creates a new [`Rectangle`] with its top-left corner in the given
|
where
|
||||||
/// [`Point`] and with the provided [`Size`].
|
T: Default,
|
||||||
pub fn new(top_left: Point, size: Size) -> Self {
|
{
|
||||||
|
/// Creates a new [`Rectangle`] with its top-left corner at the origin
|
||||||
|
/// and with the provided [`Size`].
|
||||||
|
pub fn with_size(size: Size<T>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
x: top_left.x,
|
x: T::default(),
|
||||||
y: top_left.y,
|
y: T::default(),
|
||||||
width: size.width,
|
width: size.width,
|
||||||
height: size.height,
|
height: size.height,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a new [`Rectangle`] with its top-left corner at the origin
|
impl Rectangle<f32> {
|
||||||
/// and with the provided [`Size`].
|
/// A rectangle starting at [`Point::ORIGIN`] with infinite width and height.
|
||||||
pub fn with_size(size: Size) -> Self {
|
pub const INFINITE: Self = Self::new(Point::ORIGIN, Size::INFINITY);
|
||||||
|
|
||||||
|
/// Creates a new [`Rectangle`] with its top-left corner in the given
|
||||||
|
/// [`Point`] and with the provided [`Size`].
|
||||||
|
pub const fn new(top_left: Point, size: Size) -> Self {
|
||||||
Self {
|
Self {
|
||||||
x: 0.0,
|
x: top_left.x,
|
||||||
y: 0.0,
|
y: top_left.y,
|
||||||
width: size.width,
|
width: size.width,
|
||||||
height: size.height,
|
height: size.height,
|
||||||
}
|
}
|
||||||
|
|
@ -139,13 +147,20 @@ impl Rectangle<f32> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Snaps the [`Rectangle`] to __unsigned__ integer coordinates.
|
/// Snaps the [`Rectangle`] to __unsigned__ integer coordinates.
|
||||||
pub fn snap(self) -> Rectangle<u32> {
|
pub fn snap(self) -> Option<Rectangle<u32>> {
|
||||||
Rectangle {
|
let width = self.width as u32;
|
||||||
|
let height = self.height as u32;
|
||||||
|
|
||||||
|
if width < 1 || height < 1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Rectangle {
|
||||||
x: self.x as u32,
|
x: self.x as u32,
|
||||||
y: self.y as u32,
|
y: self.y as u32,
|
||||||
width: self.width as u32,
|
width,
|
||||||
height: self.height as u32,
|
height,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expands the [`Rectangle`] a given amount.
|
/// Expands the [`Rectangle`] a given amount.
|
||||||
|
|
|
||||||
|
|
@ -9,29 +9,29 @@ use crate::{
|
||||||
/// A component that can be used by widgets to draw themselves on a screen.
|
/// A component that can be used by widgets to draw themselves on a screen.
|
||||||
pub trait Renderer {
|
pub trait Renderer {
|
||||||
/// Starts recording a new layer.
|
/// Starts recording a new layer.
|
||||||
fn start_layer(&mut self);
|
fn start_layer(&mut self, bounds: Rectangle);
|
||||||
|
|
||||||
/// Ends recording a new layer.
|
/// Ends recording a new layer.
|
||||||
///
|
///
|
||||||
/// The new layer will clip its contents to the provided `bounds`.
|
/// The new layer will clip its contents to the provided `bounds`.
|
||||||
fn end_layer(&mut self, bounds: Rectangle);
|
fn end_layer(&mut self);
|
||||||
|
|
||||||
/// Draws the primitives recorded in the given closure in a new layer.
|
/// Draws the primitives recorded in the given closure in a new layer.
|
||||||
///
|
///
|
||||||
/// The layer will clip its contents to the provided `bounds`.
|
/// The layer will clip its contents to the provided `bounds`.
|
||||||
fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
|
fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
|
||||||
self.start_layer();
|
self.start_layer(bounds);
|
||||||
f(self);
|
f(self);
|
||||||
self.end_layer(bounds);
|
self.end_layer();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts recording with a new [`Transformation`].
|
/// Starts recording with a new [`Transformation`].
|
||||||
fn start_transformation(&mut self);
|
fn start_transformation(&mut self, transformation: Transformation);
|
||||||
|
|
||||||
/// Ends recording a new layer.
|
/// Ends recording a new layer.
|
||||||
///
|
///
|
||||||
/// The new layer will clip its contents to the provided `bounds`.
|
/// The new layer will clip its contents to the provided `bounds`.
|
||||||
fn end_transformation(&mut self, transformation: Transformation);
|
fn end_transformation(&mut self);
|
||||||
|
|
||||||
/// Applies a [`Transformation`] to the primitives recorded in the given closure.
|
/// Applies a [`Transformation`] to the primitives recorded in the given closure.
|
||||||
fn with_transformation(
|
fn with_transformation(
|
||||||
|
|
@ -39,9 +39,9 @@ pub trait Renderer {
|
||||||
transformation: Transformation,
|
transformation: Transformation,
|
||||||
f: impl FnOnce(&mut Self),
|
f: impl FnOnce(&mut Self),
|
||||||
) {
|
) {
|
||||||
self.start_transformation();
|
self.start_transformation(transformation);
|
||||||
f(self);
|
f(self);
|
||||||
self.end_transformation(transformation);
|
self.end_transformation();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies a translation to the primitives recorded in the given closure.
|
/// Applies a translation to the primitives recorded in the given closure.
|
||||||
|
|
|
||||||
|
|
@ -7,16 +7,14 @@ use crate::{
|
||||||
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
|
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
impl Renderer for () {
|
impl Renderer for () {
|
||||||
fn start_layer(&mut self) {}
|
fn start_layer(&mut self, _bounds: Rectangle) {}
|
||||||
|
|
||||||
fn end_layer(&mut self, _bounds: Rectangle) {}
|
fn end_layer(&mut self) {}
|
||||||
|
|
||||||
fn start_transformation(&mut self) {}
|
fn start_transformation(&mut self, _transformation: Transformation) {}
|
||||||
|
|
||||||
fn end_transformation(&mut self, _transformation: Transformation) {}
|
fn end_transformation(&mut self) {}
|
||||||
|
|
||||||
fn clear(&mut self) {}
|
fn clear(&mut self) {}
|
||||||
|
|
||||||
|
|
@ -45,8 +43,6 @@ impl text::Renderer for () {
|
||||||
Pixels(16.0)
|
Pixels(16.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_font(&mut self, _font: Cow<'static, [u8]>) {}
|
|
||||||
|
|
||||||
fn fill_paragraph(
|
fn fill_paragraph(
|
||||||
&mut self,
|
&mut self,
|
||||||
_paragraph: &Self::Paragraph,
|
_paragraph: &Self::Paragraph,
|
||||||
|
|
|
||||||
|
|
@ -99,3 +99,17 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> std::ops::Mul<T> for Size<T>
|
||||||
|
where
|
||||||
|
T: std::ops::Mul<Output = T> + Copy,
|
||||||
|
{
|
||||||
|
type Output = Size<T>;
|
||||||
|
|
||||||
|
fn mul(self, rhs: T) -> Self::Output {
|
||||||
|
Size {
|
||||||
|
width: self.width * rhs,
|
||||||
|
height: self.height * rhs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ pub use paragraph::Paragraph;
|
||||||
use crate::alignment;
|
use crate::alignment;
|
||||||
use crate::{Color, Pixels, Point, Rectangle, Size};
|
use crate::{Color, Pixels, Point, Rectangle, Size};
|
||||||
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
/// A paragraph.
|
/// A paragraph.
|
||||||
|
|
@ -192,9 +191,6 @@ pub trait Renderer: crate::Renderer {
|
||||||
/// Returns the default size of [`Text`].
|
/// Returns the default size of [`Text`].
|
||||||
fn default_size(&self) -> Pixels;
|
fn default_size(&self) -> Pixels;
|
||||||
|
|
||||||
/// Loads a [`Self::Font`] from its bytes.
|
|
||||||
fn load_font(&mut self, font: Cow<'static, [u8]>);
|
|
||||||
|
|
||||||
/// Draws the given [`Paragraph`] at the given position and with the given
|
/// Draws the given [`Paragraph`] at the given position and with the given
|
||||||
/// [`Color`].
|
/// [`Color`].
|
||||||
fn fill_paragraph(
|
fn fill_paragraph(
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,12 @@ impl Transformation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Transformation {
|
||||||
|
fn default() -> Self {
|
||||||
|
Transformation::IDENTITY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Mul for Transformation {
|
impl Mul for Transformation {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ use pipeline::cube::{self, Cube};
|
||||||
|
|
||||||
use iced::mouse;
|
use iced::mouse;
|
||||||
use iced::time::Duration;
|
use iced::time::Duration;
|
||||||
use iced::widget::shader;
|
use iced::widget::shader::{self, Viewport};
|
||||||
use iced::{Color, Rectangle, Size};
|
use iced::{Color, Rectangle};
|
||||||
|
|
||||||
use glam::Vec3;
|
use glam::Vec3;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
|
@ -130,25 +130,29 @@ impl Primitive {
|
||||||
impl shader::Primitive for Primitive {
|
impl shader::Primitive for Primitive {
|
||||||
fn prepare(
|
fn prepare(
|
||||||
&self,
|
&self,
|
||||||
format: wgpu::TextureFormat,
|
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
queue: &wgpu::Queue,
|
queue: &wgpu::Queue,
|
||||||
_bounds: Rectangle,
|
format: wgpu::TextureFormat,
|
||||||
target_size: Size<u32>,
|
|
||||||
_scale_factor: f32,
|
|
||||||
storage: &mut shader::Storage,
|
storage: &mut shader::Storage,
|
||||||
|
_bounds: &Rectangle,
|
||||||
|
viewport: &Viewport,
|
||||||
) {
|
) {
|
||||||
if !storage.has::<Pipeline>() {
|
if !storage.has::<Pipeline>() {
|
||||||
storage.store(Pipeline::new(device, queue, format, target_size));
|
storage.store(Pipeline::new(
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
format,
|
||||||
|
viewport.physical_size(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let pipeline = storage.get_mut::<Pipeline>().unwrap();
|
let pipeline = storage.get_mut::<Pipeline>().unwrap();
|
||||||
|
|
||||||
//upload data to GPU
|
// Upload data to GPU
|
||||||
pipeline.update(
|
pipeline.update(
|
||||||
device,
|
device,
|
||||||
queue,
|
queue,
|
||||||
target_size,
|
viewport.physical_size(),
|
||||||
&self.uniforms,
|
&self.uniforms,
|
||||||
self.cubes.len(),
|
self.cubes.len(),
|
||||||
&self.cubes,
|
&self.cubes,
|
||||||
|
|
@ -157,20 +161,19 @@ impl shader::Primitive for Primitive {
|
||||||
|
|
||||||
fn render(
|
fn render(
|
||||||
&self,
|
&self,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
storage: &shader::Storage,
|
storage: &shader::Storage,
|
||||||
target: &wgpu::TextureView,
|
target: &wgpu::TextureView,
|
||||||
_target_size: Size<u32>,
|
clip_bounds: &Rectangle<u32>,
|
||||||
viewport: Rectangle<u32>,
|
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
|
||||||
) {
|
) {
|
||||||
//at this point our pipeline should always be initialized
|
// At this point our pipeline should always be initialized
|
||||||
let pipeline = storage.get::<Pipeline>().unwrap();
|
let pipeline = storage.get::<Pipeline>().unwrap();
|
||||||
|
|
||||||
//render primitive
|
// Render primitive
|
||||||
pipeline.render(
|
pipeline.render(
|
||||||
target,
|
target,
|
||||||
encoder,
|
encoder,
|
||||||
viewport,
|
*clip_bounds,
|
||||||
self.cubes.len() as u32,
|
self.cubes.len() as u32,
|
||||||
self.show_depth_buffer,
|
self.show_depth_buffer,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,10 @@ mod rainbow {
|
||||||
use iced::advanced::renderer;
|
use iced::advanced::renderer;
|
||||||
use iced::advanced::widget::{self, Widget};
|
use iced::advanced::widget::{self, Widget};
|
||||||
use iced::mouse;
|
use iced::mouse;
|
||||||
use iced::{Element, Length, Rectangle, Renderer, Size, Theme, Vector};
|
use iced::{
|
||||||
|
Element, Length, Rectangle, Renderer, Size, Theme, Transformation,
|
||||||
|
Vector,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub struct Rainbow;
|
pub struct Rainbow;
|
||||||
|
|
@ -79,7 +82,6 @@ mod rainbow {
|
||||||
let posn_l = [0.0, bounds.height / 2.0];
|
let posn_l = [0.0, bounds.height / 2.0];
|
||||||
|
|
||||||
let mesh = Mesh::Solid {
|
let mesh = Mesh::Solid {
|
||||||
size: bounds.size(),
|
|
||||||
buffers: mesh::Indexed {
|
buffers: mesh::Indexed {
|
||||||
vertices: vec![
|
vertices: vec![
|
||||||
SolidVertex2D {
|
SolidVertex2D {
|
||||||
|
|
@ -130,6 +132,8 @@ mod rainbow {
|
||||||
0, 8, 1, // L
|
0, 8, 1, // L
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
transformation: Transformation::IDENTITY,
|
||||||
|
clip_bounds: Rectangle::INFINITE,
|
||||||
};
|
};
|
||||||
|
|
||||||
renderer.with_translation(
|
renderer.with_translation(
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use controls::Controls;
|
||||||
use scene::Scene;
|
use scene::Scene;
|
||||||
|
|
||||||
use iced_wgpu::graphics::Viewport;
|
use iced_wgpu::graphics::Viewport;
|
||||||
use iced_wgpu::{wgpu, Backend, Renderer, Settings};
|
use iced_wgpu::{wgpu, Engine, Renderer};
|
||||||
use iced_winit::conversion;
|
use iced_winit::conversion;
|
||||||
use iced_winit::core::mouse;
|
use iced_winit::core::mouse;
|
||||||
use iced_winit::core::renderer;
|
use iced_winit::core::renderer;
|
||||||
|
|
@ -155,11 +155,9 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
||||||
// Initialize iced
|
// Initialize iced
|
||||||
let mut debug = Debug::new();
|
let mut debug = Debug::new();
|
||||||
let mut renderer = Renderer::new(
|
let mut engine = Engine::new(&adapter, &device, &queue, format, None);
|
||||||
Backend::new(&adapter, &device, &queue, Settings::default(), format),
|
let mut renderer =
|
||||||
Font::default(),
|
Renderer::new(&engine, Font::default(), Pixels::from(16));
|
||||||
Pixels(16.0),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut state = program::State::new(
|
let mut state = program::State::new(
|
||||||
controls,
|
controls,
|
||||||
|
|
@ -228,19 +226,17 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// And then iced on top
|
// And then iced on top
|
||||||
renderer.with_primitives(|backend, primitive| {
|
renderer.present(
|
||||||
backend.present(
|
&mut engine,
|
||||||
&device,
|
&device,
|
||||||
&queue,
|
&queue,
|
||||||
&mut encoder,
|
&mut encoder,
|
||||||
None,
|
None,
|
||||||
frame.texture.format(),
|
frame.texture.format(),
|
||||||
&view,
|
&view,
|
||||||
primitive,
|
&viewport,
|
||||||
&viewport,
|
&debug.overlay(),
|
||||||
&debug.overlay(),
|
);
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Then we submit the work
|
// Then we submit the work
|
||||||
queue.submit(Some(encoder.finish()));
|
queue.submit(Some(encoder.finish()));
|
||||||
|
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
//! Write a graphics backend.
|
|
||||||
use crate::core::image;
|
|
||||||
use crate::core::svg;
|
|
||||||
use crate::core::Size;
|
|
||||||
use crate::{Compositor, Mesh, Renderer};
|
|
||||||
|
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
/// The graphics backend of a [`Renderer`].
|
|
||||||
///
|
|
||||||
/// [`Renderer`]: crate::Renderer
|
|
||||||
pub trait Backend: Sized {
|
|
||||||
/// The custom kind of primitives this [`Backend`] supports.
|
|
||||||
type Primitive: TryFrom<Mesh, Error = &'static str>;
|
|
||||||
|
|
||||||
/// The default compositor of this [`Backend`].
|
|
||||||
type Compositor: Compositor<Renderer = Renderer<Self>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A graphics backend that supports text rendering.
|
|
||||||
pub trait Text {
|
|
||||||
/// Loads a font from its bytes.
|
|
||||||
fn load_font(&mut self, font: Cow<'static, [u8]>);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A graphics backend that supports image rendering.
|
|
||||||
pub trait Image {
|
|
||||||
/// Returns the dimensions of the provided image.
|
|
||||||
fn dimensions(&self, handle: &image::Handle) -> Size<u32>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A graphics backend that supports SVG rendering.
|
|
||||||
pub trait Svg {
|
|
||||||
/// Returns the viewport dimensions of the provided SVG.
|
|
||||||
fn viewport_dimensions(&self, handle: &svg::Handle) -> Size<u32>;
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
use crate::Primitive;
|
|
||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
/// A piece of data that can be cached.
|
/// A piece of data that can be cached.
|
||||||
pub trait Cached: Sized {
|
pub trait Cached: Sized {
|
||||||
/// The type of cache produced.
|
/// The type of cache produced.
|
||||||
type Cache;
|
type Cache: Clone;
|
||||||
|
|
||||||
/// Loads the [`Cache`] into a proper instance.
|
/// Loads the [`Cache`] into a proper instance.
|
||||||
///
|
///
|
||||||
|
|
@ -15,21 +11,7 @@ pub trait Cached: Sized {
|
||||||
/// Caches this value, producing its corresponding [`Cache`].
|
/// Caches this value, producing its corresponding [`Cache`].
|
||||||
///
|
///
|
||||||
/// [`Cache`]: Self::Cache
|
/// [`Cache`]: Self::Cache
|
||||||
fn cache(self) -> Self::Cache;
|
fn cache(self, previous: Option<Self::Cache>) -> Self::Cache;
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Cached for Primitive<T> {
|
|
||||||
type Cache = Arc<Self>;
|
|
||||||
|
|
||||||
fn load(cache: &Arc<Self>) -> Self {
|
|
||||||
Self::Cache {
|
|
||||||
content: cache.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cache(self) -> Arc<Self> {
|
|
||||||
Arc::new(self)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
|
|
@ -38,5 +20,5 @@ impl Cached for () {
|
||||||
|
|
||||||
fn load(_cache: &Self::Cache) -> Self {}
|
fn load(_cache: &Self::Cache) -> Self {}
|
||||||
|
|
||||||
fn cache(self) -> Self::Cache {}
|
fn cache(self, _previous: Option<Self::Cache>) -> Self::Cache {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,11 @@ use crate::futures::{MaybeSend, MaybeSync};
|
||||||
use crate::{Error, Settings, Viewport};
|
use crate::{Error, Settings, Viewport};
|
||||||
|
|
||||||
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
|
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
|
||||||
use std::future::Future;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::future::Future;
|
||||||
|
|
||||||
/// A graphics compositor that can draw to windows.
|
/// A graphics compositor that can draw to windows.
|
||||||
pub trait Compositor: Sized {
|
pub trait Compositor: Sized {
|
||||||
/// The iced renderer of the backend.
|
/// The iced renderer of the backend.
|
||||||
|
|
@ -60,6 +62,14 @@ pub trait Compositor: Sized {
|
||||||
/// Returns [`Information`] used by this [`Compositor`].
|
/// Returns [`Information`] used by this [`Compositor`].
|
||||||
fn fetch_information(&self) -> Information;
|
fn fetch_information(&self) -> Information;
|
||||||
|
|
||||||
|
/// Loads a font from its bytes.
|
||||||
|
fn load_font(&mut self, font: Cow<'static, [u8]>) {
|
||||||
|
crate::text::font_system()
|
||||||
|
.write()
|
||||||
|
.expect("Write to font system")
|
||||||
|
.load_font(font);
|
||||||
|
}
|
||||||
|
|
||||||
/// Presents the [`Renderer`] primitives to the next frame of the given [`Surface`].
|
/// Presents the [`Renderer`] primitives to the next frame of the given [`Surface`].
|
||||||
///
|
///
|
||||||
/// [`Renderer`]: Self::Renderer
|
/// [`Renderer`]: Self::Renderer
|
||||||
|
|
@ -168,6 +178,8 @@ impl Compositor for () {
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_font(&mut self, _font: Cow<'static, [u8]>) {}
|
||||||
|
|
||||||
fn fetch_information(&self) -> Information {
|
fn fetch_information(&self) -> Information {
|
||||||
Information {
|
Information {
|
||||||
adapter: String::from("Null Renderer"),
|
adapter: String::from("Null Renderer"),
|
||||||
|
|
|
||||||
|
|
@ -1,196 +1,14 @@
|
||||||
//! Track and compute the damage of graphical primitives.
|
//! Compute the damage between frames.
|
||||||
use crate::core::alignment;
|
use crate::core::{Point, Rectangle};
|
||||||
use crate::core::{Rectangle, Size};
|
|
||||||
use crate::Primitive;
|
|
||||||
|
|
||||||
use std::sync::Arc;
|
/// Diffs the damage regions given some previous and current primitives.
|
||||||
|
pub fn diff<T>(
|
||||||
/// A type that has some damage bounds.
|
previous: &[T],
|
||||||
pub trait Damage: PartialEq {
|
current: &[T],
|
||||||
/// Returns the bounds of the [`Damage`].
|
bounds: impl Fn(&T) -> Vec<Rectangle>,
|
||||||
fn bounds(&self) -> Rectangle;
|
diff: impl Fn(&T, &T) -> Vec<Rectangle>,
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Damage> Damage for Primitive<T> {
|
|
||||||
fn bounds(&self) -> Rectangle {
|
|
||||||
match self {
|
|
||||||
Self::Text {
|
|
||||||
bounds,
|
|
||||||
horizontal_alignment,
|
|
||||||
vertical_alignment,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let mut bounds = *bounds;
|
|
||||||
|
|
||||||
bounds.x = match horizontal_alignment {
|
|
||||||
alignment::Horizontal::Left => bounds.x,
|
|
||||||
alignment::Horizontal::Center => {
|
|
||||||
bounds.x - bounds.width / 2.0
|
|
||||||
}
|
|
||||||
alignment::Horizontal::Right => bounds.x - bounds.width,
|
|
||||||
};
|
|
||||||
|
|
||||||
bounds.y = match vertical_alignment {
|
|
||||||
alignment::Vertical::Top => bounds.y,
|
|
||||||
alignment::Vertical::Center => {
|
|
||||||
bounds.y - bounds.height / 2.0
|
|
||||||
}
|
|
||||||
alignment::Vertical::Bottom => bounds.y - bounds.height,
|
|
||||||
};
|
|
||||||
|
|
||||||
bounds.expand(1.5)
|
|
||||||
}
|
|
||||||
Self::Paragraph {
|
|
||||||
paragraph,
|
|
||||||
position,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let mut bounds =
|
|
||||||
Rectangle::new(*position, paragraph.min_bounds);
|
|
||||||
|
|
||||||
bounds.x = match paragraph.horizontal_alignment {
|
|
||||||
alignment::Horizontal::Left => bounds.x,
|
|
||||||
alignment::Horizontal::Center => {
|
|
||||||
bounds.x - bounds.width / 2.0
|
|
||||||
}
|
|
||||||
alignment::Horizontal::Right => bounds.x - bounds.width,
|
|
||||||
};
|
|
||||||
|
|
||||||
bounds.y = match paragraph.vertical_alignment {
|
|
||||||
alignment::Vertical::Top => bounds.y,
|
|
||||||
alignment::Vertical::Center => {
|
|
||||||
bounds.y - bounds.height / 2.0
|
|
||||||
}
|
|
||||||
alignment::Vertical::Bottom => bounds.y - bounds.height,
|
|
||||||
};
|
|
||||||
|
|
||||||
bounds.expand(1.5)
|
|
||||||
}
|
|
||||||
Self::Editor {
|
|
||||||
editor, position, ..
|
|
||||||
} => {
|
|
||||||
let bounds = Rectangle::new(*position, editor.bounds);
|
|
||||||
|
|
||||||
bounds.expand(1.5)
|
|
||||||
}
|
|
||||||
Self::RawText(raw) => {
|
|
||||||
// TODO: Add `size` field to `raw` to compute more accurate
|
|
||||||
// damage bounds (?)
|
|
||||||
raw.clip_bounds.expand(1.5)
|
|
||||||
}
|
|
||||||
Self::Quad { bounds, shadow, .. } if shadow.color.a > 0.0 => {
|
|
||||||
let bounds_with_shadow = Rectangle {
|
|
||||||
x: bounds.x + shadow.offset.x.min(0.0) - shadow.blur_radius,
|
|
||||||
y: bounds.y + shadow.offset.y.min(0.0) - shadow.blur_radius,
|
|
||||||
width: bounds.width
|
|
||||||
+ shadow.offset.x.abs()
|
|
||||||
+ shadow.blur_radius * 2.0,
|
|
||||||
height: bounds.height
|
|
||||||
+ shadow.offset.y.abs()
|
|
||||||
+ shadow.blur_radius * 2.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
bounds_with_shadow.expand(1.0)
|
|
||||||
}
|
|
||||||
Self::Quad { bounds, .. }
|
|
||||||
| Self::Image { bounds, .. }
|
|
||||||
| Self::Svg { bounds, .. } => bounds.expand(1.0),
|
|
||||||
Self::Clip { bounds, .. } => bounds.expand(1.0),
|
|
||||||
Self::Group { primitives } => primitives
|
|
||||||
.iter()
|
|
||||||
.map(Self::bounds)
|
|
||||||
.fold(Rectangle::with_size(Size::ZERO), |a, b| {
|
|
||||||
Rectangle::union(&a, &b)
|
|
||||||
}),
|
|
||||||
Self::Transform {
|
|
||||||
transformation,
|
|
||||||
content,
|
|
||||||
} => content.bounds() * *transformation,
|
|
||||||
Self::Cache { content } => content.bounds(),
|
|
||||||
Self::Custom(custom) => custom.bounds(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn regions<T: Damage>(a: &Primitive<T>, b: &Primitive<T>) -> Vec<Rectangle> {
|
|
||||||
match (a, b) {
|
|
||||||
(
|
|
||||||
Primitive::Group {
|
|
||||||
primitives: primitives_a,
|
|
||||||
},
|
|
||||||
Primitive::Group {
|
|
||||||
primitives: primitives_b,
|
|
||||||
},
|
|
||||||
) => return list(primitives_a, primitives_b),
|
|
||||||
(
|
|
||||||
Primitive::Clip {
|
|
||||||
bounds: bounds_a,
|
|
||||||
content: content_a,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
Primitive::Clip {
|
|
||||||
bounds: bounds_b,
|
|
||||||
content: content_b,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
if bounds_a == bounds_b {
|
|
||||||
return regions(content_a, content_b)
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|r| r.intersection(&bounds_a.expand(1.0)))
|
|
||||||
.collect();
|
|
||||||
} else {
|
|
||||||
return vec![bounds_a.expand(1.0), bounds_b.expand(1.0)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(
|
|
||||||
Primitive::Transform {
|
|
||||||
transformation: transformation_a,
|
|
||||||
content: content_a,
|
|
||||||
},
|
|
||||||
Primitive::Transform {
|
|
||||||
transformation: transformation_b,
|
|
||||||
content: content_b,
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
if transformation_a == transformation_b {
|
|
||||||
return regions(content_a, content_b)
|
|
||||||
.into_iter()
|
|
||||||
.map(|r| r * *transformation_a)
|
|
||||||
.collect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(
|
|
||||||
Primitive::Cache { content: content_a },
|
|
||||||
Primitive::Cache { content: content_b },
|
|
||||||
) => {
|
|
||||||
if Arc::ptr_eq(content_a, content_b) {
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ if a == b => return vec![],
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let bounds_a = a.bounds();
|
|
||||||
let bounds_b = b.bounds();
|
|
||||||
|
|
||||||
if bounds_a == bounds_b {
|
|
||||||
vec![bounds_a]
|
|
||||||
} else {
|
|
||||||
vec![bounds_a, bounds_b]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Computes the damage regions between the two given lists of primitives.
|
|
||||||
pub fn list<T: Damage>(
|
|
||||||
previous: &[Primitive<T>],
|
|
||||||
current: &[Primitive<T>],
|
|
||||||
) -> Vec<Rectangle> {
|
) -> Vec<Rectangle> {
|
||||||
let damage = previous
|
let damage = previous.iter().zip(current).flat_map(|(a, b)| diff(a, b));
|
||||||
.iter()
|
|
||||||
.zip(current)
|
|
||||||
.flat_map(|(a, b)| regions(a, b));
|
|
||||||
|
|
||||||
if previous.len() == current.len() {
|
if previous.len() == current.len() {
|
||||||
damage.collect()
|
damage.collect()
|
||||||
|
|
@ -203,39 +21,45 @@ pub fn list<T: Damage>(
|
||||||
|
|
||||||
// Extend damage by the added/removed primitives
|
// Extend damage by the added/removed primitives
|
||||||
damage
|
damage
|
||||||
.chain(bigger[smaller.len()..].iter().map(Damage::bounds))
|
.chain(bigger[smaller.len()..].iter().flat_map(bounds))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Computes the damage regions given some previous and current primitives.
|
||||||
|
pub fn list<T>(
|
||||||
|
previous: &[T],
|
||||||
|
current: &[T],
|
||||||
|
bounds: impl Fn(&T) -> Vec<Rectangle>,
|
||||||
|
are_equal: impl Fn(&T, &T) -> bool,
|
||||||
|
) -> Vec<Rectangle> {
|
||||||
|
diff(previous, current, &bounds, |a, b| {
|
||||||
|
if are_equal(a, b) {
|
||||||
|
vec![]
|
||||||
|
} else {
|
||||||
|
bounds(a).into_iter().chain(bounds(b)).collect()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Groups the given damage regions that are close together inside the given
|
/// Groups the given damage regions that are close together inside the given
|
||||||
/// bounds.
|
/// bounds.
|
||||||
pub fn group(
|
pub fn group(mut damage: Vec<Rectangle>, bounds: Rectangle) -> Vec<Rectangle> {
|
||||||
mut damage: Vec<Rectangle>,
|
|
||||||
scale_factor: f32,
|
|
||||||
bounds: Size<u32>,
|
|
||||||
) -> Vec<Rectangle> {
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
const AREA_THRESHOLD: f32 = 20_000.0;
|
const AREA_THRESHOLD: f32 = 20_000.0;
|
||||||
|
|
||||||
let bounds = Rectangle {
|
|
||||||
x: 0.0,
|
|
||||||
y: 0.0,
|
|
||||||
width: bounds.width as f32,
|
|
||||||
height: bounds.height as f32,
|
|
||||||
};
|
|
||||||
|
|
||||||
damage.sort_by(|a, b| {
|
damage.sort_by(|a, b| {
|
||||||
a.x.partial_cmp(&b.x)
|
a.center()
|
||||||
|
.distance(Point::ORIGIN)
|
||||||
|
.partial_cmp(&b.center().distance(Point::ORIGIN))
|
||||||
.unwrap_or(Ordering::Equal)
|
.unwrap_or(Ordering::Equal)
|
||||||
.then_with(|| a.y.partial_cmp(&b.y).unwrap_or(Ordering::Equal))
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut output = Vec::new();
|
let mut output = Vec::new();
|
||||||
let mut scaled = damage
|
let mut scaled = damage
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|region| (region * scale_factor).intersection(&bounds))
|
.filter_map(|region| region.intersection(&bounds))
|
||||||
.filter(|region| region.width >= 1.0 && region.height >= 1.0);
|
.filter(|region| region.width >= 1.0 && region.height >= 1.0);
|
||||||
|
|
||||||
if let Some(mut current) = scaled.next() {
|
if let Some(mut current) = scaled.next() {
|
||||||
|
|
|
||||||
|
|
@ -36,15 +36,6 @@ pub trait Renderer: core::Renderer {
|
||||||
fn draw_geometry(&mut self, geometry: Self::Geometry);
|
fn draw_geometry(&mut self, geometry: Self::Geometry);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The graphics backend of a geometry renderer.
|
|
||||||
pub trait Backend {
|
|
||||||
/// The kind of [`Frame`] this backend supports.
|
|
||||||
type Frame: frame::Backend;
|
|
||||||
|
|
||||||
/// Creates a new [`Self::Frame`].
|
|
||||||
fn new_frame(&self, size: Size) -> Self::Frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
impl Renderer for () {
|
impl Renderer for () {
|
||||||
type Geometry = ();
|
type Geometry = ();
|
||||||
|
|
|
||||||
|
|
@ -22,13 +22,20 @@ where
|
||||||
/// Creates a new empty [`Cache`].
|
/// Creates a new empty [`Cache`].
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Cache {
|
Cache {
|
||||||
state: RefCell::new(State::Empty),
|
state: RefCell::new(State::Empty { previous: None }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears the [`Cache`], forcing a redraw the next time it is used.
|
/// Clears the [`Cache`], forcing a redraw the next time it is used.
|
||||||
pub fn clear(&self) {
|
pub fn clear(&self) {
|
||||||
*self.state.borrow_mut() = State::Empty;
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
let previous = match self.state.borrow().deref() {
|
||||||
|
State::Empty { previous } => previous.clone(),
|
||||||
|
State::Filled { geometry, .. } => Some(geometry.clone()),
|
||||||
|
};
|
||||||
|
|
||||||
|
*self.state.borrow_mut() = State::Empty { previous };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws geometry using the provided closure and stores it in the
|
/// Draws geometry using the provided closure and stores it in the
|
||||||
|
|
@ -49,20 +56,24 @@ where
|
||||||
) -> Renderer::Geometry {
|
) -> Renderer::Geometry {
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
if let State::Filled {
|
let previous = match self.state.borrow().deref() {
|
||||||
bounds: cached_bounds,
|
State::Empty { previous } => previous.clone(),
|
||||||
geometry,
|
State::Filled {
|
||||||
} = self.state.borrow().deref()
|
bounds: cached_bounds,
|
||||||
{
|
geometry,
|
||||||
if *cached_bounds == bounds {
|
} => {
|
||||||
return Cached::load(geometry);
|
if *cached_bounds == bounds {
|
||||||
|
return Cached::load(geometry);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(geometry.clone())
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
let mut frame = Frame::new(renderer, bounds);
|
let mut frame = Frame::new(renderer, bounds);
|
||||||
draw_fn(&mut frame);
|
draw_fn(&mut frame);
|
||||||
|
|
||||||
let geometry = frame.into_geometry().cache();
|
let geometry = frame.into_geometry().cache(previous);
|
||||||
let result = Cached::load(&geometry);
|
let result = Cached::load(&geometry);
|
||||||
|
|
||||||
*self.state.borrow_mut() = State::Filled { bounds, geometry };
|
*self.state.borrow_mut() = State::Filled { bounds, geometry };
|
||||||
|
|
@ -79,7 +90,7 @@ where
|
||||||
let state = self.state.borrow();
|
let state = self.state.borrow();
|
||||||
|
|
||||||
match *state {
|
match *state {
|
||||||
State::Empty => write!(f, "Cache::Empty"),
|
State::Empty { .. } => write!(f, "Cache::Empty"),
|
||||||
State::Filled { bounds, .. } => {
|
State::Filled { bounds, .. } => {
|
||||||
write!(f, "Cache::Filled {{ bounds: {bounds:?} }}")
|
write!(f, "Cache::Filled {{ bounds: {bounds:?} }}")
|
||||||
}
|
}
|
||||||
|
|
@ -100,7 +111,9 @@ enum State<Geometry>
|
||||||
where
|
where
|
||||||
Geometry: Cached,
|
Geometry: Cached,
|
||||||
{
|
{
|
||||||
Empty,
|
Empty {
|
||||||
|
previous: Option<Geometry::Cache>,
|
||||||
|
},
|
||||||
Filled {
|
Filled {
|
||||||
bounds: Size,
|
bounds: Size,
|
||||||
geometry: Geometry::Cache,
|
geometry: Geometry::Cache,
|
||||||
|
|
|
||||||
|
|
@ -113,13 +113,11 @@ where
|
||||||
region: Rectangle,
|
region: Rectangle,
|
||||||
f: impl FnOnce(&mut Self) -> R,
|
f: impl FnOnce(&mut Self) -> R,
|
||||||
) -> R {
|
) -> R {
|
||||||
let mut frame = self.draft(region.size());
|
let mut frame = self.draft(region);
|
||||||
|
|
||||||
let result = f(&mut frame);
|
let result = f(&mut frame);
|
||||||
|
|
||||||
let origin = Point::new(region.x, region.y);
|
self.paste(frame, Point::new(region.x, region.y));
|
||||||
|
|
||||||
self.paste(frame, origin);
|
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
@ -129,14 +127,14 @@ where
|
||||||
/// Draw its contents back to this [`Frame`] with [`paste`].
|
/// Draw its contents back to this [`Frame`] with [`paste`].
|
||||||
///
|
///
|
||||||
/// [`paste`]: Self::paste
|
/// [`paste`]: Self::paste
|
||||||
pub fn draft(&mut self, size: Size) -> Self {
|
fn draft(&mut self, clip_bounds: Rectangle) -> Self {
|
||||||
Self {
|
Self {
|
||||||
raw: self.raw.draft(size),
|
raw: self.raw.draft(clip_bounds),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws the contents of the given [`Frame`] with origin at the given [`Point`].
|
/// Draws the contents of the given [`Frame`] with origin at the given [`Point`].
|
||||||
pub fn paste(&mut self, frame: Self, at: Point) {
|
fn paste(&mut self, frame: Self, at: Point) {
|
||||||
self.raw.paste(frame.raw, at);
|
self.raw.paste(frame.raw, at);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -187,7 +185,7 @@ pub trait Backend: Sized {
|
||||||
fn scale(&mut self, scale: impl Into<f32>);
|
fn scale(&mut self, scale: impl Into<f32>);
|
||||||
fn scale_nonuniform(&mut self, scale: impl Into<Vector>);
|
fn scale_nonuniform(&mut self, scale: impl Into<Vector>);
|
||||||
|
|
||||||
fn draft(&mut self, size: Size) -> Self;
|
fn draft(&mut self, clip_bounds: Rectangle) -> Self;
|
||||||
fn paste(&mut self, frame: Self, at: Point);
|
fn paste(&mut self, frame: Self, at: Point);
|
||||||
|
|
||||||
fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>);
|
fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>);
|
||||||
|
|
@ -232,7 +230,7 @@ impl Backend for () {
|
||||||
fn scale(&mut self, _scale: impl Into<f32>) {}
|
fn scale(&mut self, _scale: impl Into<f32>) {}
|
||||||
fn scale_nonuniform(&mut self, _scale: impl Into<Vector>) {}
|
fn scale_nonuniform(&mut self, _scale: impl Into<Vector>) {}
|
||||||
|
|
||||||
fn draft(&mut self, _size: Size) -> Self {}
|
fn draft(&mut self, _clip_bounds: Rectangle) -> Self {}
|
||||||
fn paste(&mut self, _frame: Self, _at: Point) {}
|
fn paste(&mut self, _frame: Self, _at: Point) {}
|
||||||
|
|
||||||
fn stroke<'a>(&mut self, _path: &Path, _stroke: impl Into<Stroke<'a>>) {}
|
fn stroke<'a>(&mut self, _path: &Path, _stroke: impl Into<Stroke<'a>>) {}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,107 @@
|
||||||
//! Load and operate on images.
|
//! Load and operate on images.
|
||||||
use crate::core::image::{Data, Handle};
|
#[cfg(feature = "image")]
|
||||||
|
|
||||||
use bitflags::bitflags;
|
|
||||||
|
|
||||||
pub use ::image as image_rs;
|
pub use ::image as image_rs;
|
||||||
|
|
||||||
|
use crate::core::image;
|
||||||
|
use crate::core::svg;
|
||||||
|
use crate::core::{Color, Rectangle};
|
||||||
|
|
||||||
|
/// A raster or vector image.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum Image {
|
||||||
|
/// A raster image.
|
||||||
|
Raster {
|
||||||
|
/// The handle of a raster image.
|
||||||
|
handle: image::Handle,
|
||||||
|
|
||||||
|
/// The filter method of a raster image.
|
||||||
|
filter_method: image::FilterMethod,
|
||||||
|
|
||||||
|
/// The bounds of the image.
|
||||||
|
bounds: Rectangle,
|
||||||
|
},
|
||||||
|
/// A vector image.
|
||||||
|
Vector {
|
||||||
|
/// The handle of a vector image.
|
||||||
|
handle: svg::Handle,
|
||||||
|
|
||||||
|
/// The [`Color`] filter
|
||||||
|
color: Option<Color>,
|
||||||
|
|
||||||
|
/// The bounds of the image.
|
||||||
|
bounds: Rectangle,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Image {
|
||||||
|
/// Returns the bounds of the [`Image`].
|
||||||
|
pub fn bounds(&self) -> Rectangle {
|
||||||
|
match self {
|
||||||
|
Image::Raster { bounds, .. } | Image::Vector { bounds, .. } => {
|
||||||
|
*bounds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
/// Tries to load an image by its [`Handle`].
|
/// Tries to load an image by its [`Handle`].
|
||||||
pub fn load(handle: &Handle) -> image_rs::ImageResult<image_rs::DynamicImage> {
|
///
|
||||||
|
/// [`Handle`]: image::Handle
|
||||||
|
pub fn load(
|
||||||
|
handle: &image::Handle,
|
||||||
|
) -> ::image::ImageResult<::image::DynamicImage> {
|
||||||
|
use bitflags::bitflags;
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
struct Operation: u8 {
|
||||||
|
const FLIP_HORIZONTALLY = 0b001;
|
||||||
|
const ROTATE_180 = 0b010;
|
||||||
|
const FLIP_DIAGONALLY = 0b100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Operation {
|
||||||
|
// Meaning of the returned value is described e.g. at:
|
||||||
|
// https://magnushoff.com/articles/jpeg-orientation/
|
||||||
|
fn from_exif<R>(reader: &mut R) -> Result<Self, exif::Error>
|
||||||
|
where
|
||||||
|
R: std::io::BufRead + std::io::Seek,
|
||||||
|
{
|
||||||
|
let exif = exif::Reader::new().read_from_container(reader)?;
|
||||||
|
|
||||||
|
Ok(exif
|
||||||
|
.get_field(exif::Tag::Orientation, exif::In::PRIMARY)
|
||||||
|
.and_then(|field| field.value.get_uint(0))
|
||||||
|
.and_then(|value| u8::try_from(value).ok())
|
||||||
|
.and_then(|value| Self::from_bits(value.saturating_sub(1)))
|
||||||
|
.unwrap_or_else(Self::empty))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perform(
|
||||||
|
self,
|
||||||
|
mut image: ::image::DynamicImage,
|
||||||
|
) -> ::image::DynamicImage {
|
||||||
|
use ::image::imageops;
|
||||||
|
|
||||||
|
if self.contains(Self::FLIP_DIAGONALLY) {
|
||||||
|
imageops::flip_vertical_in_place(&mut image);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.contains(Self::ROTATE_180) {
|
||||||
|
imageops::rotate180_in_place(&mut image);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.contains(Self::FLIP_HORIZONTALLY) {
|
||||||
|
imageops::flip_horizontal_in_place(&mut image);
|
||||||
|
}
|
||||||
|
|
||||||
|
image
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match handle.data() {
|
match handle.data() {
|
||||||
Data::Path(path) => {
|
image::Data::Path(path) => {
|
||||||
let image = ::image::open(path)?;
|
let image = ::image::open(path)?;
|
||||||
|
|
||||||
let operation = std::fs::File::open(path)
|
let operation = std::fs::File::open(path)
|
||||||
|
|
@ -19,7 +112,7 @@ pub fn load(handle: &Handle) -> image_rs::ImageResult<image_rs::DynamicImage> {
|
||||||
|
|
||||||
Ok(operation.perform(image))
|
Ok(operation.perform(image))
|
||||||
}
|
}
|
||||||
Data::Bytes(bytes) => {
|
image::Data::Bytes(bytes) => {
|
||||||
let image = ::image::load_from_memory(bytes)?;
|
let image = ::image::load_from_memory(bytes)?;
|
||||||
let operation =
|
let operation =
|
||||||
Operation::from_exif(&mut std::io::Cursor::new(bytes))
|
Operation::from_exif(&mut std::io::Cursor::new(bytes))
|
||||||
|
|
@ -28,68 +121,22 @@ pub fn load(handle: &Handle) -> image_rs::ImageResult<image_rs::DynamicImage> {
|
||||||
|
|
||||||
Ok(operation.perform(image))
|
Ok(operation.perform(image))
|
||||||
}
|
}
|
||||||
Data::Rgba {
|
image::Data::Rgba {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
pixels,
|
pixels,
|
||||||
} => {
|
} => {
|
||||||
if let Some(image) = image_rs::ImageBuffer::from_vec(
|
if let Some(image) =
|
||||||
*width,
|
::image::ImageBuffer::from_vec(*width, *height, pixels.to_vec())
|
||||||
*height,
|
{
|
||||||
pixels.to_vec(),
|
Ok(::image::DynamicImage::ImageRgba8(image))
|
||||||
) {
|
|
||||||
Ok(image_rs::DynamicImage::ImageRgba8(image))
|
|
||||||
} else {
|
} else {
|
||||||
Err(image_rs::error::ImageError::Limits(
|
Err(::image::error::ImageError::Limits(
|
||||||
image_rs::error::LimitError::from_kind(
|
::image::error::LimitError::from_kind(
|
||||||
image_rs::error::LimitErrorKind::DimensionError,
|
::image::error::LimitErrorKind::DimensionError,
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
|
||||||
struct Operation: u8 {
|
|
||||||
const FLIP_HORIZONTALLY = 0b001;
|
|
||||||
const ROTATE_180 = 0b010;
|
|
||||||
const FLIP_DIAGONALLY = 0b100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Operation {
|
|
||||||
// Meaning of the returned value is described e.g. at:
|
|
||||||
// https://magnushoff.com/articles/jpeg-orientation/
|
|
||||||
fn from_exif<R>(reader: &mut R) -> Result<Self, exif::Error>
|
|
||||||
where
|
|
||||||
R: std::io::BufRead + std::io::Seek,
|
|
||||||
{
|
|
||||||
let exif = exif::Reader::new().read_from_container(reader)?;
|
|
||||||
|
|
||||||
Ok(exif
|
|
||||||
.get_field(exif::Tag::Orientation, exif::In::PRIMARY)
|
|
||||||
.and_then(|field| field.value.get_uint(0))
|
|
||||||
.and_then(|value| u8::try_from(value).ok())
|
|
||||||
.and_then(|value| Self::from_bits(value.saturating_sub(1)))
|
|
||||||
.unwrap_or_else(Self::empty))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn perform(self, mut image: image::DynamicImage) -> image::DynamicImage {
|
|
||||||
use image::imageops;
|
|
||||||
|
|
||||||
if self.contains(Self::FLIP_DIAGONALLY) {
|
|
||||||
imageops::flip_vertical_in_place(&mut image);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.contains(Self::ROTATE_180) {
|
|
||||||
imageops::rotate180_in_place(&mut image);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.contains(Self::FLIP_HORIZONTALLY) {
|
|
||||||
imageops::flip_horizontal_in_place(&mut image);
|
|
||||||
}
|
|
||||||
|
|
||||||
image
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
144
graphics/src/layer.rs
Normal file
144
graphics/src/layer.rs
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
//! Draw and stack layers of graphical primitives.
|
||||||
|
use crate::core::{Rectangle, Transformation};
|
||||||
|
|
||||||
|
/// A layer of graphical primitives.
|
||||||
|
///
|
||||||
|
/// Layers normally dictate a set of primitives that are
|
||||||
|
/// rendered in a specific order.
|
||||||
|
pub trait Layer: Default {
|
||||||
|
/// Creates a new [`Layer`] with the given bounds.
|
||||||
|
fn with_bounds(bounds: Rectangle) -> Self;
|
||||||
|
|
||||||
|
/// Flushes and settles any pending group of primitives in the [`Layer`].
|
||||||
|
///
|
||||||
|
/// This will be called when a [`Layer`] is finished. It allows layers to efficiently
|
||||||
|
/// record primitives together and defer grouping until the end.
|
||||||
|
fn flush(&mut self);
|
||||||
|
|
||||||
|
/// Resizes the [`Layer`] to the given bounds.
|
||||||
|
fn resize(&mut self, bounds: Rectangle);
|
||||||
|
|
||||||
|
/// Clears all the layers contents and resets its bounds.
|
||||||
|
fn reset(&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A stack of layers used for drawing.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Stack<T: Layer> {
|
||||||
|
layers: Vec<T>,
|
||||||
|
transformations: Vec<Transformation>,
|
||||||
|
previous: Vec<usize>,
|
||||||
|
current: usize,
|
||||||
|
active_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Layer> Stack<T> {
|
||||||
|
/// Creates a new empty [`Stack`].
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
layers: vec![T::default()],
|
||||||
|
transformations: vec![Transformation::IDENTITY],
|
||||||
|
previous: vec![],
|
||||||
|
current: 0,
|
||||||
|
active_count: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable reference to the current [`Layer`] of the [`Stack`], together with
|
||||||
|
/// the current [`Transformation`].
|
||||||
|
#[inline]
|
||||||
|
pub fn current_mut(&mut self) -> (&mut T, Transformation) {
|
||||||
|
let transformation = self.transformation();
|
||||||
|
|
||||||
|
(&mut self.layers[self.current], transformation)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current [`Transformation`] of the [`Stack`].
|
||||||
|
#[inline]
|
||||||
|
pub fn transformation(&self) -> Transformation {
|
||||||
|
self.transformations.last().copied().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pushes a new clipping region in the [`Stack`]; creating a new layer in the
|
||||||
|
/// process.
|
||||||
|
pub fn push_clip(&mut self, bounds: Rectangle) {
|
||||||
|
self.previous.push(self.current);
|
||||||
|
|
||||||
|
self.current = self.active_count;
|
||||||
|
self.active_count += 1;
|
||||||
|
|
||||||
|
let bounds = bounds * self.transformation();
|
||||||
|
|
||||||
|
if self.current == self.layers.len() {
|
||||||
|
self.layers.push(T::with_bounds(bounds));
|
||||||
|
} else {
|
||||||
|
self.layers[self.current].resize(bounds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pops the current clipping region from the [`Stack`] and restores the previous one.
|
||||||
|
///
|
||||||
|
/// The current layer will be recorded for drawing.
|
||||||
|
pub fn pop_clip(&mut self) {
|
||||||
|
self.flush();
|
||||||
|
|
||||||
|
self.current = self.previous.pop().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pushes a new [`Transformation`] in the [`Stack`].
|
||||||
|
///
|
||||||
|
/// Future drawing operations will be affected by this new [`Transformation`] until
|
||||||
|
/// it is popped using [`pop_transformation`].
|
||||||
|
///
|
||||||
|
/// [`pop_transformation`]: Self::pop_transformation
|
||||||
|
pub fn push_transformation(&mut self, transformation: Transformation) {
|
||||||
|
self.transformations
|
||||||
|
.push(self.transformation() * transformation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pops the current [`Transformation`] in the [`Stack`].
|
||||||
|
pub fn pop_transformation(&mut self) {
|
||||||
|
let _ = self.transformations.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over mutable references to the layers in the [`Stack`].
|
||||||
|
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
|
||||||
|
self.flush();
|
||||||
|
|
||||||
|
self.layers[..self.active_count].iter_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over immutable references to the layers in the [`Stack`].
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
||||||
|
self.layers[..self.active_count].iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the slice of layers in the [`Stack`].
|
||||||
|
pub fn as_slice(&self) -> &[T] {
|
||||||
|
&self.layers[..self.active_count]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Flushes and settles any primitives in the current layer of the [`Stack`].
|
||||||
|
pub fn flush(&mut self) {
|
||||||
|
self.layers[self.current].flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears the layers of the [`Stack`], allowing reuse.
|
||||||
|
///
|
||||||
|
/// This will normally keep layer allocations for future drawing operations.
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
for layer in self.layers[..self.active_count].iter_mut() {
|
||||||
|
layer.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.current = 0;
|
||||||
|
self.active_count = 1;
|
||||||
|
self.previous.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Layer> Default for Stack<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,37 +10,32 @@
|
||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
mod antialiasing;
|
mod antialiasing;
|
||||||
mod cached;
|
mod cached;
|
||||||
mod primitive;
|
|
||||||
mod settings;
|
mod settings;
|
||||||
mod viewport;
|
mod viewport;
|
||||||
|
|
||||||
pub mod backend;
|
|
||||||
pub mod color;
|
pub mod color;
|
||||||
pub mod compositor;
|
pub mod compositor;
|
||||||
pub mod damage;
|
pub mod damage;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod gradient;
|
pub mod gradient;
|
||||||
|
pub mod image;
|
||||||
|
pub mod layer;
|
||||||
pub mod mesh;
|
pub mod mesh;
|
||||||
pub mod renderer;
|
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
|
||||||
#[cfg(feature = "geometry")]
|
#[cfg(feature = "geometry")]
|
||||||
pub mod geometry;
|
pub mod geometry;
|
||||||
|
|
||||||
#[cfg(feature = "image")]
|
|
||||||
pub mod image;
|
|
||||||
|
|
||||||
pub use antialiasing::Antialiasing;
|
pub use antialiasing::Antialiasing;
|
||||||
pub use backend::Backend;
|
|
||||||
pub use cached::Cached;
|
pub use cached::Cached;
|
||||||
pub use compositor::Compositor;
|
pub use compositor::Compositor;
|
||||||
pub use damage::Damage;
|
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
pub use gradient::Gradient;
|
pub use gradient::Gradient;
|
||||||
|
pub use image::Image;
|
||||||
|
pub use layer::Layer;
|
||||||
pub use mesh::Mesh;
|
pub use mesh::Mesh;
|
||||||
pub use primitive::Primitive;
|
|
||||||
pub use renderer::Renderer;
|
|
||||||
pub use settings::Settings;
|
pub use settings::Settings;
|
||||||
|
pub use text::Text;
|
||||||
pub use viewport::Viewport;
|
pub use viewport::Viewport;
|
||||||
|
|
||||||
pub use iced_core as core;
|
pub use iced_core as core;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
//! Draw triangles!
|
//! Draw triangles!
|
||||||
use crate::color;
|
use crate::color;
|
||||||
use crate::core::{Rectangle, Size};
|
use crate::core::{Rectangle, Transformation};
|
||||||
use crate::gradient;
|
use crate::gradient;
|
||||||
use crate::Damage;
|
|
||||||
|
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
|
||||||
|
|
@ -14,29 +13,55 @@ pub enum Mesh {
|
||||||
/// The vertices and indices of the mesh.
|
/// The vertices and indices of the mesh.
|
||||||
buffers: Indexed<SolidVertex2D>,
|
buffers: Indexed<SolidVertex2D>,
|
||||||
|
|
||||||
/// The size of the drawable region of the mesh.
|
/// The [`Transformation`] for the vertices of the [`Mesh`].
|
||||||
///
|
transformation: Transformation,
|
||||||
/// Any geometry that falls out of this region will be clipped.
|
|
||||||
size: Size,
|
/// The clip bounds of the [`Mesh`].
|
||||||
|
clip_bounds: Rectangle,
|
||||||
},
|
},
|
||||||
/// A mesh with a gradient.
|
/// A mesh with a gradient.
|
||||||
Gradient {
|
Gradient {
|
||||||
/// The vertices and indices of the mesh.
|
/// The vertices and indices of the mesh.
|
||||||
buffers: Indexed<GradientVertex2D>,
|
buffers: Indexed<GradientVertex2D>,
|
||||||
|
|
||||||
/// The size of the drawable region of the mesh.
|
/// The [`Transformation`] for the vertices of the [`Mesh`].
|
||||||
///
|
transformation: Transformation,
|
||||||
/// Any geometry that falls out of this region will be clipped.
|
|
||||||
size: Size,
|
/// The clip bounds of the [`Mesh`].
|
||||||
|
clip_bounds: Rectangle,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Damage for Mesh {
|
impl Mesh {
|
||||||
fn bounds(&self) -> Rectangle {
|
/// Returns the indices of the [`Mesh`].
|
||||||
|
pub fn indices(&self) -> &[u32] {
|
||||||
match self {
|
match self {
|
||||||
Self::Solid { size, .. } | Self::Gradient { size, .. } => {
|
Self::Solid { buffers, .. } => &buffers.indices,
|
||||||
Rectangle::with_size(*size)
|
Self::Gradient { buffers, .. } => &buffers.indices,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the [`Transformation`] of the [`Mesh`].
|
||||||
|
pub fn transformation(&self) -> Transformation {
|
||||||
|
match self {
|
||||||
|
Self::Solid { transformation, .. }
|
||||||
|
| Self::Gradient { transformation, .. } => *transformation,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the clip bounds of the [`Mesh`].
|
||||||
|
pub fn clip_bounds(&self) -> Rectangle {
|
||||||
|
match self {
|
||||||
|
Self::Solid {
|
||||||
|
clip_bounds,
|
||||||
|
transformation,
|
||||||
|
..
|
||||||
}
|
}
|
||||||
|
| Self::Gradient {
|
||||||
|
clip_bounds,
|
||||||
|
transformation,
|
||||||
|
..
|
||||||
|
} => *clip_bounds * *transformation,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -75,6 +100,47 @@ pub struct GradientVertex2D {
|
||||||
pub gradient: gradient::Packed,
|
pub gradient: gradient::Packed,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The result of counting the attributes of a set of meshes.
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
pub struct AttributeCount {
|
||||||
|
/// The total amount of solid vertices.
|
||||||
|
pub solid_vertices: usize,
|
||||||
|
|
||||||
|
/// The total amount of solid meshes.
|
||||||
|
pub solids: usize,
|
||||||
|
|
||||||
|
/// The total amount of gradient vertices.
|
||||||
|
pub gradient_vertices: usize,
|
||||||
|
|
||||||
|
/// The total amount of gradient meshes.
|
||||||
|
pub gradients: usize,
|
||||||
|
|
||||||
|
/// The total amount of indices.
|
||||||
|
pub indices: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of total vertices & total indices of all [`Mesh`]es.
|
||||||
|
pub fn attribute_count_of(meshes: &[Mesh]) -> AttributeCount {
|
||||||
|
meshes
|
||||||
|
.iter()
|
||||||
|
.fold(AttributeCount::default(), |mut count, mesh| {
|
||||||
|
match mesh {
|
||||||
|
Mesh::Solid { buffers, .. } => {
|
||||||
|
count.solids += 1;
|
||||||
|
count.solid_vertices += buffers.vertices.len();
|
||||||
|
count.indices += buffers.indices.len();
|
||||||
|
}
|
||||||
|
Mesh::Gradient { buffers, .. } => {
|
||||||
|
count.gradients += 1;
|
||||||
|
count.gradient_vertices += buffers.vertices.len();
|
||||||
|
count.indices += buffers.indices.len();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
count
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// A renderer capable of drawing a [`Mesh`].
|
/// A renderer capable of drawing a [`Mesh`].
|
||||||
pub trait Renderer {
|
pub trait Renderer {
|
||||||
/// Draws the given [`Mesh`].
|
/// Draws the given [`Mesh`].
|
||||||
|
|
|
||||||
|
|
@ -1,160 +0,0 @@
|
||||||
//! Draw using different graphical primitives.
|
|
||||||
use crate::core::alignment;
|
|
||||||
use crate::core::image;
|
|
||||||
use crate::core::svg;
|
|
||||||
use crate::core::text;
|
|
||||||
use crate::core::{
|
|
||||||
Background, Border, Color, Font, Pixels, Point, Rectangle, Shadow,
|
|
||||||
Transformation, Vector,
|
|
||||||
};
|
|
||||||
use crate::text::editor;
|
|
||||||
use crate::text::paragraph;
|
|
||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
/// A rendering primitive.
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub enum Primitive<T> {
|
|
||||||
/// A text primitive
|
|
||||||
Text {
|
|
||||||
/// The contents of the text.
|
|
||||||
content: String,
|
|
||||||
/// The bounds of the text.
|
|
||||||
bounds: Rectangle,
|
|
||||||
/// The color of the text.
|
|
||||||
color: Color,
|
|
||||||
/// The size of the text in logical pixels.
|
|
||||||
size: Pixels,
|
|
||||||
/// The line height of the text.
|
|
||||||
line_height: text::LineHeight,
|
|
||||||
/// The font of the text.
|
|
||||||
font: Font,
|
|
||||||
/// The horizontal alignment of the text.
|
|
||||||
horizontal_alignment: alignment::Horizontal,
|
|
||||||
/// The vertical alignment of the text.
|
|
||||||
vertical_alignment: alignment::Vertical,
|
|
||||||
/// The shaping strategy of the text.
|
|
||||||
shaping: text::Shaping,
|
|
||||||
/// The clip bounds of the text.
|
|
||||||
clip_bounds: Rectangle,
|
|
||||||
},
|
|
||||||
/// A paragraph primitive
|
|
||||||
Paragraph {
|
|
||||||
/// The [`paragraph::Weak`] reference.
|
|
||||||
paragraph: paragraph::Weak,
|
|
||||||
/// The position of the paragraph.
|
|
||||||
position: Point,
|
|
||||||
/// The color of the paragraph.
|
|
||||||
color: Color,
|
|
||||||
/// The clip bounds of the paragraph.
|
|
||||||
clip_bounds: Rectangle,
|
|
||||||
},
|
|
||||||
/// An editor primitive
|
|
||||||
Editor {
|
|
||||||
/// The [`editor::Weak`] reference.
|
|
||||||
editor: editor::Weak,
|
|
||||||
/// The position of the editor.
|
|
||||||
position: Point,
|
|
||||||
/// The color of the editor.
|
|
||||||
color: Color,
|
|
||||||
/// The clip bounds of the editor.
|
|
||||||
clip_bounds: Rectangle,
|
|
||||||
},
|
|
||||||
/// A raw `cosmic-text` primitive
|
|
||||||
RawText(crate::text::Raw),
|
|
||||||
/// A quad primitive
|
|
||||||
Quad {
|
|
||||||
/// The bounds of the quad
|
|
||||||
bounds: Rectangle,
|
|
||||||
/// The background of the quad
|
|
||||||
background: Background,
|
|
||||||
/// The [`Border`] of the quad
|
|
||||||
border: Border,
|
|
||||||
/// The [`Shadow`] of the quad
|
|
||||||
shadow: Shadow,
|
|
||||||
},
|
|
||||||
/// An image primitive
|
|
||||||
Image {
|
|
||||||
/// The handle of the image
|
|
||||||
handle: image::Handle,
|
|
||||||
/// The filter method of the image
|
|
||||||
filter_method: image::FilterMethod,
|
|
||||||
/// The bounds of the image
|
|
||||||
bounds: Rectangle,
|
|
||||||
},
|
|
||||||
/// An SVG primitive
|
|
||||||
Svg {
|
|
||||||
/// The path of the SVG file
|
|
||||||
handle: svg::Handle,
|
|
||||||
|
|
||||||
/// The [`Color`] filter
|
|
||||||
color: Option<Color>,
|
|
||||||
|
|
||||||
/// The bounds of the viewport
|
|
||||||
bounds: Rectangle,
|
|
||||||
},
|
|
||||||
/// A group of primitives
|
|
||||||
Group {
|
|
||||||
/// The primitives of the group
|
|
||||||
primitives: Vec<Primitive<T>>,
|
|
||||||
},
|
|
||||||
/// A clip primitive
|
|
||||||
Clip {
|
|
||||||
/// The bounds of the clip
|
|
||||||
bounds: Rectangle,
|
|
||||||
/// The content of the clip
|
|
||||||
content: Box<Primitive<T>>,
|
|
||||||
},
|
|
||||||
/// A primitive that applies a [`Transformation`]
|
|
||||||
Transform {
|
|
||||||
/// The [`Transformation`]
|
|
||||||
transformation: Transformation,
|
|
||||||
|
|
||||||
/// The primitive to transform
|
|
||||||
content: Box<Primitive<T>>,
|
|
||||||
},
|
|
||||||
/// A cached primitive.
|
|
||||||
///
|
|
||||||
/// This can be useful if you are implementing a widget where primitive
|
|
||||||
/// generation is expensive.
|
|
||||||
Cache {
|
|
||||||
/// The cached primitive
|
|
||||||
content: Arc<Primitive<T>>,
|
|
||||||
},
|
|
||||||
/// A backend-specific primitive.
|
|
||||||
Custom(T),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Primitive<T> {
|
|
||||||
/// Groups the current [`Primitive`].
|
|
||||||
pub fn group(primitives: Vec<Self>) -> Self {
|
|
||||||
Self::Group { primitives }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clips the current [`Primitive`].
|
|
||||||
pub fn clip(self, bounds: Rectangle) -> Self {
|
|
||||||
Self::Clip {
|
|
||||||
bounds,
|
|
||||||
content: Box::new(self),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Translates the current [`Primitive`].
|
|
||||||
pub fn translate(self, translation: Vector) -> Self {
|
|
||||||
Self::Transform {
|
|
||||||
transformation: Transformation::translate(
|
|
||||||
translation.x,
|
|
||||||
translation.y,
|
|
||||||
),
|
|
||||||
content: Box::new(self),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Transforms the current [`Primitive`].
|
|
||||||
pub fn transform(self, transformation: Transformation) -> Self {
|
|
||||||
Self::Transform {
|
|
||||||
transformation,
|
|
||||||
content: Box::new(self),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,269 +0,0 @@
|
||||||
//! Create a renderer from a [`Backend`].
|
|
||||||
use crate::backend::{self, Backend};
|
|
||||||
use crate::compositor;
|
|
||||||
use crate::core;
|
|
||||||
use crate::core::image;
|
|
||||||
use crate::core::renderer;
|
|
||||||
use crate::core::svg;
|
|
||||||
use crate::core::text::Text;
|
|
||||||
use crate::core::{
|
|
||||||
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
|
|
||||||
};
|
|
||||||
use crate::mesh;
|
|
||||||
use crate::text;
|
|
||||||
use crate::{Mesh, Primitive};
|
|
||||||
|
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
/// A backend-agnostic renderer that supports all the built-in widgets.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Renderer<B: Backend> {
|
|
||||||
backend: B,
|
|
||||||
default_font: Font,
|
|
||||||
default_text_size: Pixels,
|
|
||||||
primitives: Vec<Primitive<B::Primitive>>,
|
|
||||||
stack: Vec<Vec<Primitive<B::Primitive>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B: Backend> Renderer<B> {
|
|
||||||
/// Creates a new [`Renderer`] from the given [`Backend`].
|
|
||||||
pub fn new(
|
|
||||||
backend: B,
|
|
||||||
default_font: Font,
|
|
||||||
default_text_size: Pixels,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
backend,
|
|
||||||
default_font,
|
|
||||||
default_text_size,
|
|
||||||
primitives: Vec::new(),
|
|
||||||
stack: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a reference to the [`Backend`] of the [`Renderer`].
|
|
||||||
pub fn backend(&self) -> &B {
|
|
||||||
&self.backend
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enqueues the given [`Primitive`] in the [`Renderer`] for drawing.
|
|
||||||
pub fn draw_primitive(&mut self, primitive: Primitive<B::Primitive>) {
|
|
||||||
self.primitives.push(primitive);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs the given closure with the [`Backend`] and the recorded primitives
|
|
||||||
/// of the [`Renderer`].
|
|
||||||
pub fn with_primitives<O>(
|
|
||||||
&mut self,
|
|
||||||
f: impl FnOnce(&mut B, &[Primitive<B::Primitive>]) -> O,
|
|
||||||
) -> O {
|
|
||||||
f(&mut self.backend, &self.primitives)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B: Backend> iced_core::Renderer for Renderer<B> {
|
|
||||||
fn start_layer(&mut self) {
|
|
||||||
self.stack.push(std::mem::take(&mut self.primitives));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn end_layer(&mut self, bounds: Rectangle) {
|
|
||||||
let layer = std::mem::replace(
|
|
||||||
&mut self.primitives,
|
|
||||||
self.stack.pop().expect("a layer should be recording"),
|
|
||||||
);
|
|
||||||
|
|
||||||
self.primitives.push(Primitive::group(layer).clip(bounds));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_transformation(&mut self) {
|
|
||||||
self.stack.push(std::mem::take(&mut self.primitives));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn end_transformation(&mut self, transformation: Transformation) {
|
|
||||||
let layer = std::mem::replace(
|
|
||||||
&mut self.primitives,
|
|
||||||
self.stack.pop().expect("a layer should be recording"),
|
|
||||||
);
|
|
||||||
|
|
||||||
self.primitives
|
|
||||||
.push(Primitive::group(layer).transform(transformation));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fill_quad(
|
|
||||||
&mut self,
|
|
||||||
quad: renderer::Quad,
|
|
||||||
background: impl Into<Background>,
|
|
||||||
) {
|
|
||||||
self.primitives.push(Primitive::Quad {
|
|
||||||
bounds: quad.bounds,
|
|
||||||
background: background.into(),
|
|
||||||
border: quad.border,
|
|
||||||
shadow: quad.shadow,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear(&mut self) {
|
|
||||||
self.primitives.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> core::text::Renderer for Renderer<B>
|
|
||||||
where
|
|
||||||
B: Backend + backend::Text,
|
|
||||||
{
|
|
||||||
type Font = Font;
|
|
||||||
type Paragraph = text::Paragraph;
|
|
||||||
type Editor = text::Editor;
|
|
||||||
|
|
||||||
const ICON_FONT: Font = Font::with_name("Iced-Icons");
|
|
||||||
const CHECKMARK_ICON: char = '\u{f00c}';
|
|
||||||
const ARROW_DOWN_ICON: char = '\u{e800}';
|
|
||||||
|
|
||||||
fn default_font(&self) -> Self::Font {
|
|
||||||
self.default_font
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default_size(&self) -> Pixels {
|
|
||||||
self.default_text_size
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
|
|
||||||
self.backend.load_font(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fill_paragraph(
|
|
||||||
&mut self,
|
|
||||||
paragraph: &Self::Paragraph,
|
|
||||||
position: Point,
|
|
||||||
color: Color,
|
|
||||||
clip_bounds: Rectangle,
|
|
||||||
) {
|
|
||||||
self.primitives.push(Primitive::Paragraph {
|
|
||||||
paragraph: paragraph.downgrade(),
|
|
||||||
position,
|
|
||||||
color,
|
|
||||||
clip_bounds,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fill_editor(
|
|
||||||
&mut self,
|
|
||||||
editor: &Self::Editor,
|
|
||||||
position: Point,
|
|
||||||
color: Color,
|
|
||||||
clip_bounds: Rectangle,
|
|
||||||
) {
|
|
||||||
self.primitives.push(Primitive::Editor {
|
|
||||||
editor: editor.downgrade(),
|
|
||||||
position,
|
|
||||||
color,
|
|
||||||
clip_bounds,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fill_text(
|
|
||||||
&mut self,
|
|
||||||
text: Text,
|
|
||||||
position: Point,
|
|
||||||
color: Color,
|
|
||||||
clip_bounds: Rectangle,
|
|
||||||
) {
|
|
||||||
self.primitives.push(Primitive::Text {
|
|
||||||
content: text.content,
|
|
||||||
bounds: Rectangle::new(position, text.bounds),
|
|
||||||
size: text.size,
|
|
||||||
line_height: text.line_height,
|
|
||||||
color,
|
|
||||||
font: text.font,
|
|
||||||
horizontal_alignment: text.horizontal_alignment,
|
|
||||||
vertical_alignment: text.vertical_alignment,
|
|
||||||
shaping: text.shaping,
|
|
||||||
clip_bounds,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> image::Renderer for Renderer<B>
|
|
||||||
where
|
|
||||||
B: Backend + backend::Image,
|
|
||||||
{
|
|
||||||
type Handle = image::Handle;
|
|
||||||
|
|
||||||
fn measure_image(&self, handle: &image::Handle) -> Size<u32> {
|
|
||||||
self.backend().dimensions(handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_image(
|
|
||||||
&mut self,
|
|
||||||
handle: image::Handle,
|
|
||||||
filter_method: image::FilterMethod,
|
|
||||||
bounds: Rectangle,
|
|
||||||
) {
|
|
||||||
self.primitives.push(Primitive::Image {
|
|
||||||
handle,
|
|
||||||
filter_method,
|
|
||||||
bounds,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> svg::Renderer for Renderer<B>
|
|
||||||
where
|
|
||||||
B: Backend + backend::Svg,
|
|
||||||
{
|
|
||||||
fn measure_svg(&self, handle: &svg::Handle) -> Size<u32> {
|
|
||||||
self.backend().viewport_dimensions(handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_svg(
|
|
||||||
&mut self,
|
|
||||||
handle: svg::Handle,
|
|
||||||
color: Option<Color>,
|
|
||||||
bounds: Rectangle,
|
|
||||||
) {
|
|
||||||
self.primitives.push(Primitive::Svg {
|
|
||||||
handle,
|
|
||||||
color,
|
|
||||||
bounds,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B: Backend> mesh::Renderer for Renderer<B> {
|
|
||||||
fn draw_mesh(&mut self, mesh: Mesh) {
|
|
||||||
match B::Primitive::try_from(mesh) {
|
|
||||||
Ok(primitive) => {
|
|
||||||
self.draw_primitive(Primitive::Custom(primitive));
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
log::warn!("mesh primitive could not be drawn: {error:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "geometry")]
|
|
||||||
impl<B> crate::geometry::Renderer for Renderer<B>
|
|
||||||
where
|
|
||||||
B: Backend + crate::geometry::Backend,
|
|
||||||
B::Frame:
|
|
||||||
crate::geometry::frame::Backend<Geometry = Primitive<B::Primitive>>,
|
|
||||||
{
|
|
||||||
type Frame = B::Frame;
|
|
||||||
type Geometry = Primitive<B::Primitive>;
|
|
||||||
|
|
||||||
fn new_frame(&self, size: Size) -> Self::Frame {
|
|
||||||
self.backend.new_frame(size)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_geometry(&mut self, geometry: Self::Geometry) {
|
|
||||||
self.draw_primitive(geometry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B> compositor::Default for Renderer<B>
|
|
||||||
where
|
|
||||||
B: Backend,
|
|
||||||
{
|
|
||||||
type Compositor = B::Compositor;
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::core::{Font, Pixels};
|
use crate::core::{Font, Pixels};
|
||||||
use crate::Antialiasing;
|
use crate::Antialiasing;
|
||||||
|
|
||||||
/// The settings of a Backend.
|
/// The settings of a renderer.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
/// The default [`Font`] to use.
|
/// The default [`Font`] to use.
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,141 @@ pub use paragraph::Paragraph;
|
||||||
|
|
||||||
pub use cosmic_text;
|
pub use cosmic_text;
|
||||||
|
|
||||||
|
use crate::core::alignment;
|
||||||
use crate::core::font::{self, Font};
|
use crate::core::font::{self, Font};
|
||||||
use crate::core::text::Shaping;
|
use crate::core::text::Shaping;
|
||||||
use crate::core::{Color, Point, Rectangle, Size};
|
use crate::core::{Color, Pixels, Point, Rectangle, Size, Transformation};
|
||||||
|
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::sync::{Arc, RwLock, Weak};
|
use std::sync::{Arc, RwLock, Weak};
|
||||||
|
|
||||||
|
/// A text primitive.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum Text {
|
||||||
|
/// A paragraph.
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
Paragraph {
|
||||||
|
paragraph: paragraph::Weak,
|
||||||
|
position: Point,
|
||||||
|
color: Color,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
transformation: Transformation,
|
||||||
|
},
|
||||||
|
/// An editor.
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
Editor {
|
||||||
|
editor: editor::Weak,
|
||||||
|
position: Point,
|
||||||
|
color: Color,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
transformation: Transformation,
|
||||||
|
},
|
||||||
|
/// Some cached text.
|
||||||
|
Cached {
|
||||||
|
/// The contents of the text.
|
||||||
|
content: String,
|
||||||
|
/// The bounds of the text.
|
||||||
|
bounds: Rectangle,
|
||||||
|
/// The color of the text.
|
||||||
|
color: Color,
|
||||||
|
/// The size of the text in logical pixels.
|
||||||
|
size: Pixels,
|
||||||
|
/// The line height of the text.
|
||||||
|
line_height: Pixels,
|
||||||
|
/// The font of the text.
|
||||||
|
font: Font,
|
||||||
|
/// The horizontal alignment of the text.
|
||||||
|
horizontal_alignment: alignment::Horizontal,
|
||||||
|
/// The vertical alignment of the text.
|
||||||
|
vertical_alignment: alignment::Vertical,
|
||||||
|
/// The shaping strategy of the text.
|
||||||
|
shaping: Shaping,
|
||||||
|
/// The clip bounds of the text.
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
},
|
||||||
|
/// Some raw text.
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
Raw {
|
||||||
|
raw: Raw,
|
||||||
|
transformation: Transformation,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Text {
|
||||||
|
/// Returns the visible bounds of the [`Text`].
|
||||||
|
pub fn visible_bounds(&self) -> Option<Rectangle> {
|
||||||
|
let (bounds, horizontal_alignment, vertical_alignment) = match self {
|
||||||
|
Text::Paragraph {
|
||||||
|
position,
|
||||||
|
paragraph,
|
||||||
|
clip_bounds,
|
||||||
|
transformation,
|
||||||
|
..
|
||||||
|
} => (
|
||||||
|
Rectangle::new(*position, paragraph.min_bounds)
|
||||||
|
.intersection(clip_bounds)
|
||||||
|
.map(|bounds| bounds * *transformation),
|
||||||
|
Some(paragraph.horizontal_alignment),
|
||||||
|
Some(paragraph.vertical_alignment),
|
||||||
|
),
|
||||||
|
Text::Editor {
|
||||||
|
editor,
|
||||||
|
position,
|
||||||
|
clip_bounds,
|
||||||
|
transformation,
|
||||||
|
..
|
||||||
|
} => (
|
||||||
|
Rectangle::new(*position, editor.bounds)
|
||||||
|
.intersection(clip_bounds)
|
||||||
|
.map(|bounds| bounds * *transformation),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
Text::Cached {
|
||||||
|
bounds,
|
||||||
|
clip_bounds,
|
||||||
|
horizontal_alignment,
|
||||||
|
vertical_alignment,
|
||||||
|
..
|
||||||
|
} => (
|
||||||
|
bounds.intersection(clip_bounds),
|
||||||
|
Some(*horizontal_alignment),
|
||||||
|
Some(*vertical_alignment),
|
||||||
|
),
|
||||||
|
Text::Raw { raw, .. } => (Some(raw.clip_bounds), None, None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut bounds = bounds?;
|
||||||
|
|
||||||
|
if let Some(alignment) = horizontal_alignment {
|
||||||
|
match alignment {
|
||||||
|
alignment::Horizontal::Left => {}
|
||||||
|
alignment::Horizontal::Center => {
|
||||||
|
bounds.x -= bounds.width / 2.0;
|
||||||
|
}
|
||||||
|
alignment::Horizontal::Right => {
|
||||||
|
bounds.x -= bounds.width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(alignment) = vertical_alignment {
|
||||||
|
match alignment {
|
||||||
|
alignment::Vertical::Top => {}
|
||||||
|
alignment::Vertical::Center => {
|
||||||
|
bounds.y -= bounds.height / 2.0;
|
||||||
|
}
|
||||||
|
alignment::Vertical::Bottom => {
|
||||||
|
bounds.y -= bounds.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(bounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The regular variant of the [Fira Sans] font.
|
/// The regular variant of the [Fira Sans] font.
|
||||||
///
|
///
|
||||||
/// It is loaded as part of the default fonts in Wasm builds.
|
/// It is loaded as part of the default fonts in Wasm builds.
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ tiny-skia = ["iced_tiny_skia"]
|
||||||
image = ["iced_tiny_skia?/image", "iced_wgpu?/image"]
|
image = ["iced_tiny_skia?/image", "iced_wgpu?/image"]
|
||||||
svg = ["iced_tiny_skia?/svg", "iced_wgpu?/svg"]
|
svg = ["iced_tiny_skia?/svg", "iced_wgpu?/svg"]
|
||||||
geometry = ["iced_graphics/geometry", "iced_tiny_skia?/geometry", "iced_wgpu?/geometry"]
|
geometry = ["iced_graphics/geometry", "iced_tiny_skia?/geometry", "iced_wgpu?/geometry"]
|
||||||
tracing = ["iced_wgpu?/tracing"]
|
|
||||||
web-colors = ["iced_wgpu?/web-colors"]
|
web-colors = ["iced_wgpu?/web-colors"]
|
||||||
webgl = ["iced_wgpu?/webgl"]
|
webgl = ["iced_wgpu?/webgl"]
|
||||||
fira-sans = ["iced_graphics/fira-sans"]
|
fira-sans = ["iced_graphics/fira-sans"]
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ use crate::graphics;
|
||||||
use crate::graphics::compositor;
|
use crate::graphics::compositor;
|
||||||
use crate::graphics::mesh;
|
use crate::graphics::mesh;
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// A renderer `A` with a fallback strategy `B`.
|
/// A renderer `A` with a fallback strategy `B`.
|
||||||
///
|
///
|
||||||
/// This type can be used to easily compose existing renderers and
|
/// This type can be used to easily compose existing renderers and
|
||||||
|
|
@ -47,20 +49,24 @@ where
|
||||||
delegate!(self, renderer, renderer.clear());
|
delegate!(self, renderer, renderer.clear());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_layer(&mut self) {
|
fn start_layer(&mut self, bounds: Rectangle) {
|
||||||
delegate!(self, renderer, renderer.start_layer());
|
delegate!(self, renderer, renderer.start_layer(bounds));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn end_layer(&mut self, bounds: Rectangle) {
|
fn end_layer(&mut self) {
|
||||||
delegate!(self, renderer, renderer.end_layer(bounds));
|
delegate!(self, renderer, renderer.end_layer());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_transformation(&mut self) {
|
fn start_transformation(&mut self, transformation: Transformation) {
|
||||||
delegate!(self, renderer, renderer.start_transformation());
|
delegate!(
|
||||||
|
self,
|
||||||
|
renderer,
|
||||||
|
renderer.start_transformation(transformation)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn end_transformation(&mut self, transformation: Transformation) {
|
fn end_transformation(&mut self) {
|
||||||
delegate!(self, renderer, renderer.end_transformation(transformation));
|
delegate!(self, renderer, renderer.end_transformation());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,10 +95,6 @@ where
|
||||||
delegate!(self, renderer, renderer.default_size())
|
delegate!(self, renderer, renderer.default_size())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_font(&mut self, font: std::borrow::Cow<'static, [u8]>) {
|
|
||||||
delegate!(self, renderer, renderer.load_font(font));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fill_paragraph(
|
fn fill_paragraph(
|
||||||
&mut self,
|
&mut self,
|
||||||
text: &Self::Paragraph,
|
text: &Self::Paragraph,
|
||||||
|
|
@ -319,6 +321,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_font(&mut self, font: Cow<'static, [u8]>) {
|
||||||
|
delegate!(self, compositor, compositor.load_font(font));
|
||||||
|
}
|
||||||
|
|
||||||
fn fetch_information(&self) -> compositor::Information {
|
fn fetch_information(&self) -> compositor::Information {
|
||||||
delegate!(self, compositor, compositor.fetch_information())
|
delegate!(self, compositor, compositor.fetch_information())
|
||||||
}
|
}
|
||||||
|
|
@ -395,19 +401,19 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wgpu")]
|
#[cfg(feature = "wgpu")]
|
||||||
impl<A, B> iced_wgpu::primitive::pipeline::Renderer for Renderer<A, B>
|
impl<A, B> iced_wgpu::primitive::Renderer for Renderer<A, B>
|
||||||
where
|
where
|
||||||
A: iced_wgpu::primitive::pipeline::Renderer,
|
A: iced_wgpu::primitive::Renderer,
|
||||||
B: core::Renderer,
|
B: core::Renderer,
|
||||||
{
|
{
|
||||||
fn draw_pipeline_primitive(
|
fn draw_primitive(
|
||||||
&mut self,
|
&mut self,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
primitive: impl iced_wgpu::primitive::pipeline::Primitive,
|
primitive: impl iced_wgpu::Primitive,
|
||||||
) {
|
) {
|
||||||
match self {
|
match self {
|
||||||
Self::Primary(renderer) => {
|
Self::Primary(renderer) => {
|
||||||
renderer.draw_pipeline_primitive(bounds, primitive);
|
renderer.draw_primitive(bounds, primitive);
|
||||||
}
|
}
|
||||||
Self::Secondary(_) => {
|
Self::Secondary(_) => {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
|
|
@ -421,7 +427,7 @@ where
|
||||||
#[cfg(feature = "geometry")]
|
#[cfg(feature = "geometry")]
|
||||||
mod geometry {
|
mod geometry {
|
||||||
use super::Renderer;
|
use super::Renderer;
|
||||||
use crate::core::{Point, Radians, Size, Vector};
|
use crate::core::{Point, Radians, Rectangle, Size, Vector};
|
||||||
use crate::graphics::geometry::{self, Fill, Path, Stroke, Text};
|
use crate::graphics::geometry::{self, Fill, Path, Stroke, Text};
|
||||||
use crate::graphics::Cached;
|
use crate::graphics::Cached;
|
||||||
|
|
||||||
|
|
@ -457,7 +463,7 @@ mod geometry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Geometry<A, B> {
|
pub enum Geometry<A, B> {
|
||||||
Primary(A),
|
Primary(A),
|
||||||
Secondary(B),
|
Secondary(B),
|
||||||
|
|
@ -477,12 +483,23 @@ mod geometry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cache(self) -> Self::Cache {
|
fn cache(self, previous: Option<Self::Cache>) -> Self::Cache {
|
||||||
match self {
|
match (self, previous) {
|
||||||
Self::Primary(geometry) => Geometry::Primary(geometry.cache()),
|
(
|
||||||
Self::Secondary(geometry) => {
|
Self::Primary(geometry),
|
||||||
Geometry::Secondary(geometry.cache())
|
Some(Geometry::Primary(previous)),
|
||||||
|
) => Geometry::Primary(geometry.cache(Some(previous))),
|
||||||
|
(Self::Primary(geometry), None) => {
|
||||||
|
Geometry::Primary(geometry.cache(None))
|
||||||
}
|
}
|
||||||
|
(
|
||||||
|
Self::Secondary(geometry),
|
||||||
|
Some(Geometry::Secondary(previous)),
|
||||||
|
) => Geometry::Secondary(geometry.cache(Some(previous))),
|
||||||
|
(Self::Secondary(geometry), None) => {
|
||||||
|
Geometry::Secondary(geometry.cache(None))
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -545,10 +562,10 @@ mod geometry {
|
||||||
delegate!(self, frame, frame.pop_transform());
|
delegate!(self, frame, frame.pop_transform());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draft(&mut self, size: Size) -> Self {
|
fn draft(&mut self, bounds: Rectangle) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Self::Primary(frame) => Self::Primary(frame.draft(size)),
|
Self::Primary(frame) => Self::Primary(frame.draft(bounds)),
|
||||||
Self::Secondary(frame) => Self::Secondary(frame.draft(size)),
|
Self::Secondary(frame) => Self::Secondary(frame.draft(bounds)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
831
tiny_skia/src/engine.rs
Normal file
831
tiny_skia/src/engine.rs
Normal file
|
|
@ -0,0 +1,831 @@
|
||||||
|
use crate::core::renderer::Quad;
|
||||||
|
use crate::core::{
|
||||||
|
Background, Color, Gradient, Rectangle, Size, Transformation, Vector,
|
||||||
|
};
|
||||||
|
use crate::graphics::{Image, Text};
|
||||||
|
use crate::text;
|
||||||
|
use crate::Primitive;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Engine {
|
||||||
|
text_pipeline: text::Pipeline,
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
pub(crate) raster_pipeline: crate::raster::Pipeline,
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
pub(crate) vector_pipeline: crate::vector::Pipeline,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Engine {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
text_pipeline: text::Pipeline::new(),
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
raster_pipeline: crate::raster::Pipeline::new(),
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
vector_pipeline: crate::vector::Pipeline::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_quad(
|
||||||
|
&mut self,
|
||||||
|
quad: &Quad,
|
||||||
|
background: &Background,
|
||||||
|
transformation: Transformation,
|
||||||
|
pixels: &mut tiny_skia::PixmapMut<'_>,
|
||||||
|
clip_mask: &mut tiny_skia::Mask,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
) {
|
||||||
|
debug_assert!(
|
||||||
|
quad.bounds.width.is_normal(),
|
||||||
|
"Quad with non-normal width!"
|
||||||
|
);
|
||||||
|
debug_assert!(
|
||||||
|
quad.bounds.height.is_normal(),
|
||||||
|
"Quad with non-normal height!"
|
||||||
|
);
|
||||||
|
|
||||||
|
let physical_bounds = quad.bounds * transformation;
|
||||||
|
|
||||||
|
if !clip_bounds.intersects(&physical_bounds) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
|
||||||
|
.then_some(clip_mask as &_);
|
||||||
|
|
||||||
|
let transform = into_transform(transformation);
|
||||||
|
|
||||||
|
// Make sure the border radius is not larger than the bounds
|
||||||
|
let border_width = quad
|
||||||
|
.border
|
||||||
|
.width
|
||||||
|
.min(quad.bounds.width / 2.0)
|
||||||
|
.min(quad.bounds.height / 2.0);
|
||||||
|
|
||||||
|
let mut fill_border_radius = <[f32; 4]>::from(quad.border.radius);
|
||||||
|
|
||||||
|
for radius in &mut fill_border_radius {
|
||||||
|
*radius = (*radius)
|
||||||
|
.min(quad.bounds.width / 2.0)
|
||||||
|
.min(quad.bounds.height / 2.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = rounded_rectangle(quad.bounds, fill_border_radius);
|
||||||
|
|
||||||
|
let shadow = quad.shadow;
|
||||||
|
|
||||||
|
if shadow.color.a > 0.0 {
|
||||||
|
let shadow_bounds = Rectangle {
|
||||||
|
x: quad.bounds.x + shadow.offset.x - shadow.blur_radius,
|
||||||
|
y: quad.bounds.y + shadow.offset.y - shadow.blur_radius,
|
||||||
|
width: quad.bounds.width + shadow.blur_radius * 2.0,
|
||||||
|
height: quad.bounds.height + shadow.blur_radius * 2.0,
|
||||||
|
} * transformation;
|
||||||
|
|
||||||
|
let radii = fill_border_radius
|
||||||
|
.into_iter()
|
||||||
|
.map(|radius| radius * transformation.scale_factor())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let (x, y, width, height) = (
|
||||||
|
shadow_bounds.x as u32,
|
||||||
|
shadow_bounds.y as u32,
|
||||||
|
shadow_bounds.width as u32,
|
||||||
|
shadow_bounds.height as u32,
|
||||||
|
);
|
||||||
|
let half_width = physical_bounds.width / 2.0;
|
||||||
|
let half_height = physical_bounds.height / 2.0;
|
||||||
|
|
||||||
|
let colors = (y..y + height)
|
||||||
|
.flat_map(|y| (x..x + width).map(move |x| (x as f32, y as f32)))
|
||||||
|
.filter_map(|(x, y)| {
|
||||||
|
tiny_skia::Size::from_wh(half_width, half_height).map(
|
||||||
|
|size| {
|
||||||
|
let shadow_distance = rounded_box_sdf(
|
||||||
|
Vector::new(
|
||||||
|
x - physical_bounds.position().x
|
||||||
|
- (shadow.offset.x
|
||||||
|
* transformation.scale_factor())
|
||||||
|
- half_width,
|
||||||
|
y - physical_bounds.position().y
|
||||||
|
- (shadow.offset.y
|
||||||
|
* transformation.scale_factor())
|
||||||
|
- half_height,
|
||||||
|
),
|
||||||
|
size,
|
||||||
|
&radii,
|
||||||
|
)
|
||||||
|
.max(0.0);
|
||||||
|
let shadow_alpha = 1.0
|
||||||
|
- smoothstep(
|
||||||
|
-shadow.blur_radius
|
||||||
|
* transformation.scale_factor(),
|
||||||
|
shadow.blur_radius
|
||||||
|
* transformation.scale_factor(),
|
||||||
|
shadow_distance,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut color = into_color(shadow.color);
|
||||||
|
color.apply_opacity(shadow_alpha);
|
||||||
|
|
||||||
|
color.to_color_u8().premultiply()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if let Some(pixmap) = tiny_skia::IntSize::from_wh(width, height)
|
||||||
|
.and_then(|size| {
|
||||||
|
tiny_skia::Pixmap::from_vec(
|
||||||
|
bytemuck::cast_vec(colors),
|
||||||
|
size,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
pixels.draw_pixmap(
|
||||||
|
x as i32,
|
||||||
|
y as i32,
|
||||||
|
pixmap.as_ref(),
|
||||||
|
&tiny_skia::PixmapPaint::default(),
|
||||||
|
tiny_skia::Transform::default(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pixels.fill_path(
|
||||||
|
&path,
|
||||||
|
&tiny_skia::Paint {
|
||||||
|
shader: match background {
|
||||||
|
Background::Color(color) => {
|
||||||
|
tiny_skia::Shader::SolidColor(into_color(*color))
|
||||||
|
}
|
||||||
|
Background::Gradient(Gradient::Linear(linear)) => {
|
||||||
|
let (start, end) =
|
||||||
|
linear.angle.to_distance(&quad.bounds);
|
||||||
|
|
||||||
|
let stops: Vec<tiny_skia::GradientStop> = linear
|
||||||
|
.stops
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.map(|stop| {
|
||||||
|
tiny_skia::GradientStop::new(
|
||||||
|
stop.offset,
|
||||||
|
tiny_skia::Color::from_rgba(
|
||||||
|
stop.color.b,
|
||||||
|
stop.color.g,
|
||||||
|
stop.color.r,
|
||||||
|
stop.color.a,
|
||||||
|
)
|
||||||
|
.expect("Create color"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
tiny_skia::LinearGradient::new(
|
||||||
|
tiny_skia::Point {
|
||||||
|
x: start.x,
|
||||||
|
y: start.y,
|
||||||
|
},
|
||||||
|
tiny_skia::Point { x: end.x, y: end.y },
|
||||||
|
if stops.is_empty() {
|
||||||
|
vec![tiny_skia::GradientStop::new(
|
||||||
|
0.0,
|
||||||
|
tiny_skia::Color::BLACK,
|
||||||
|
)]
|
||||||
|
} else {
|
||||||
|
stops
|
||||||
|
},
|
||||||
|
tiny_skia::SpreadMode::Pad,
|
||||||
|
tiny_skia::Transform::identity(),
|
||||||
|
)
|
||||||
|
.expect("Create linear gradient")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
anti_alias: true,
|
||||||
|
..tiny_skia::Paint::default()
|
||||||
|
},
|
||||||
|
tiny_skia::FillRule::EvenOdd,
|
||||||
|
transform,
|
||||||
|
clip_mask,
|
||||||
|
);
|
||||||
|
|
||||||
|
if border_width > 0.0 {
|
||||||
|
// Border path is offset by half the border width
|
||||||
|
let border_bounds = Rectangle {
|
||||||
|
x: quad.bounds.x + border_width / 2.0,
|
||||||
|
y: quad.bounds.y + border_width / 2.0,
|
||||||
|
width: quad.bounds.width - border_width,
|
||||||
|
height: quad.bounds.height - border_width,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make sure the border radius is correct
|
||||||
|
let mut border_radius = <[f32; 4]>::from(quad.border.radius);
|
||||||
|
let mut is_simple_border = true;
|
||||||
|
|
||||||
|
for radius in &mut border_radius {
|
||||||
|
*radius = if *radius == 0.0 {
|
||||||
|
// Path should handle this fine
|
||||||
|
0.0
|
||||||
|
} else if *radius > border_width / 2.0 {
|
||||||
|
*radius - border_width / 2.0
|
||||||
|
} else {
|
||||||
|
is_simple_border = false;
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
.min(border_bounds.width / 2.0)
|
||||||
|
.min(border_bounds.height / 2.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stroking a path works well in this case
|
||||||
|
if is_simple_border {
|
||||||
|
let border_path =
|
||||||
|
rounded_rectangle(border_bounds, border_radius);
|
||||||
|
|
||||||
|
pixels.stroke_path(
|
||||||
|
&border_path,
|
||||||
|
&tiny_skia::Paint {
|
||||||
|
shader: tiny_skia::Shader::SolidColor(into_color(
|
||||||
|
quad.border.color,
|
||||||
|
)),
|
||||||
|
anti_alias: true,
|
||||||
|
..tiny_skia::Paint::default()
|
||||||
|
},
|
||||||
|
&tiny_skia::Stroke {
|
||||||
|
width: border_width,
|
||||||
|
..tiny_skia::Stroke::default()
|
||||||
|
},
|
||||||
|
transform,
|
||||||
|
clip_mask,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Draw corners that have too small border radii as having no border radius,
|
||||||
|
// but mask them with the rounded rectangle with the correct border radius.
|
||||||
|
let mut temp_pixmap = tiny_skia::Pixmap::new(
|
||||||
|
quad.bounds.width as u32,
|
||||||
|
quad.bounds.height as u32,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut quad_mask = tiny_skia::Mask::new(
|
||||||
|
quad.bounds.width as u32,
|
||||||
|
quad.bounds.height as u32,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let zero_bounds = Rectangle {
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
width: quad.bounds.width,
|
||||||
|
height: quad.bounds.height,
|
||||||
|
};
|
||||||
|
let path = rounded_rectangle(zero_bounds, fill_border_radius);
|
||||||
|
|
||||||
|
quad_mask.fill_path(
|
||||||
|
&path,
|
||||||
|
tiny_skia::FillRule::EvenOdd,
|
||||||
|
true,
|
||||||
|
transform,
|
||||||
|
);
|
||||||
|
let path_bounds = Rectangle {
|
||||||
|
x: border_width / 2.0,
|
||||||
|
y: border_width / 2.0,
|
||||||
|
width: quad.bounds.width - border_width,
|
||||||
|
height: quad.bounds.height - border_width,
|
||||||
|
};
|
||||||
|
|
||||||
|
let border_radius_path =
|
||||||
|
rounded_rectangle(path_bounds, border_radius);
|
||||||
|
|
||||||
|
temp_pixmap.stroke_path(
|
||||||
|
&border_radius_path,
|
||||||
|
&tiny_skia::Paint {
|
||||||
|
shader: tiny_skia::Shader::SolidColor(into_color(
|
||||||
|
quad.border.color,
|
||||||
|
)),
|
||||||
|
anti_alias: true,
|
||||||
|
..tiny_skia::Paint::default()
|
||||||
|
},
|
||||||
|
&tiny_skia::Stroke {
|
||||||
|
width: border_width,
|
||||||
|
..tiny_skia::Stroke::default()
|
||||||
|
},
|
||||||
|
transform,
|
||||||
|
Some(&quad_mask),
|
||||||
|
);
|
||||||
|
|
||||||
|
pixels.draw_pixmap(
|
||||||
|
quad.bounds.x as i32,
|
||||||
|
quad.bounds.y as i32,
|
||||||
|
temp_pixmap.as_ref(),
|
||||||
|
&tiny_skia::PixmapPaint::default(),
|
||||||
|
transform,
|
||||||
|
clip_mask,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_text(
|
||||||
|
&mut self,
|
||||||
|
text: &Text,
|
||||||
|
transformation: Transformation,
|
||||||
|
pixels: &mut tiny_skia::PixmapMut<'_>,
|
||||||
|
clip_mask: &mut tiny_skia::Mask,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
) {
|
||||||
|
match text {
|
||||||
|
Text::Paragraph {
|
||||||
|
paragraph,
|
||||||
|
position,
|
||||||
|
color,
|
||||||
|
clip_bounds: _, // TODO
|
||||||
|
transformation: local_transformation,
|
||||||
|
} => {
|
||||||
|
let transformation = transformation * *local_transformation;
|
||||||
|
|
||||||
|
let physical_bounds =
|
||||||
|
Rectangle::new(*position, paragraph.min_bounds)
|
||||||
|
* transformation;
|
||||||
|
|
||||||
|
if !clip_bounds.intersects(&physical_bounds) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
|
||||||
|
.then_some(clip_mask as &_);
|
||||||
|
|
||||||
|
self.text_pipeline.draw_paragraph(
|
||||||
|
paragraph,
|
||||||
|
*position,
|
||||||
|
*color,
|
||||||
|
pixels,
|
||||||
|
clip_mask,
|
||||||
|
transformation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Text::Editor {
|
||||||
|
editor,
|
||||||
|
position,
|
||||||
|
color,
|
||||||
|
clip_bounds: _, // TODO
|
||||||
|
transformation: local_transformation,
|
||||||
|
} => {
|
||||||
|
let transformation = transformation * *local_transformation;
|
||||||
|
|
||||||
|
let physical_bounds =
|
||||||
|
Rectangle::new(*position, editor.bounds) * transformation;
|
||||||
|
|
||||||
|
if !clip_bounds.intersects(&physical_bounds) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
|
||||||
|
.then_some(clip_mask as &_);
|
||||||
|
|
||||||
|
self.text_pipeline.draw_editor(
|
||||||
|
editor,
|
||||||
|
*position,
|
||||||
|
*color,
|
||||||
|
pixels,
|
||||||
|
clip_mask,
|
||||||
|
transformation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Text::Cached {
|
||||||
|
content,
|
||||||
|
bounds,
|
||||||
|
color,
|
||||||
|
size,
|
||||||
|
line_height,
|
||||||
|
font,
|
||||||
|
horizontal_alignment,
|
||||||
|
vertical_alignment,
|
||||||
|
shaping,
|
||||||
|
clip_bounds: text_bounds, // TODO
|
||||||
|
} => {
|
||||||
|
let physical_bounds = *text_bounds * transformation;
|
||||||
|
|
||||||
|
if !clip_bounds.intersects(&physical_bounds) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
|
||||||
|
.then_some(clip_mask as &_);
|
||||||
|
|
||||||
|
self.text_pipeline.draw_cached(
|
||||||
|
content,
|
||||||
|
*bounds,
|
||||||
|
*color,
|
||||||
|
*size,
|
||||||
|
*line_height,
|
||||||
|
*font,
|
||||||
|
*horizontal_alignment,
|
||||||
|
*vertical_alignment,
|
||||||
|
*shaping,
|
||||||
|
pixels,
|
||||||
|
clip_mask,
|
||||||
|
transformation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Text::Raw {
|
||||||
|
raw,
|
||||||
|
transformation: local_transformation,
|
||||||
|
} => {
|
||||||
|
let Some(buffer) = raw.buffer.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let transformation = transformation * *local_transformation;
|
||||||
|
let (width, height) = buffer.size();
|
||||||
|
|
||||||
|
let physical_bounds =
|
||||||
|
Rectangle::new(raw.position, Size::new(width, height))
|
||||||
|
* transformation;
|
||||||
|
|
||||||
|
if !clip_bounds.intersects(&physical_bounds) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
|
||||||
|
.then_some(clip_mask as &_);
|
||||||
|
|
||||||
|
self.text_pipeline.draw_raw(
|
||||||
|
&buffer,
|
||||||
|
raw.position,
|
||||||
|
raw.color,
|
||||||
|
pixels,
|
||||||
|
clip_mask,
|
||||||
|
transformation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_primitive(
|
||||||
|
&mut self,
|
||||||
|
primitive: &Primitive,
|
||||||
|
transformation: Transformation,
|
||||||
|
pixels: &mut tiny_skia::PixmapMut<'_>,
|
||||||
|
clip_mask: &mut tiny_skia::Mask,
|
||||||
|
layer_bounds: Rectangle,
|
||||||
|
) {
|
||||||
|
match primitive {
|
||||||
|
Primitive::Fill { path, paint, rule } => {
|
||||||
|
let physical_bounds = {
|
||||||
|
let bounds = path.bounds();
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
x: bounds.x(),
|
||||||
|
y: bounds.y(),
|
||||||
|
width: bounds.width(),
|
||||||
|
height: bounds.height(),
|
||||||
|
} * transformation
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(clip_bounds) =
|
||||||
|
layer_bounds.intersection(&physical_bounds)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let clip_mask =
|
||||||
|
(physical_bounds != clip_bounds).then_some(clip_mask as &_);
|
||||||
|
|
||||||
|
pixels.fill_path(
|
||||||
|
path,
|
||||||
|
paint,
|
||||||
|
*rule,
|
||||||
|
into_transform(transformation),
|
||||||
|
clip_mask,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Primitive::Stroke {
|
||||||
|
path,
|
||||||
|
paint,
|
||||||
|
stroke,
|
||||||
|
} => {
|
||||||
|
let physical_bounds = {
|
||||||
|
let bounds = path.bounds();
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
x: bounds.x(),
|
||||||
|
y: bounds.y(),
|
||||||
|
width: bounds.width(),
|
||||||
|
height: bounds.height(),
|
||||||
|
} * transformation
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(clip_bounds) =
|
||||||
|
layer_bounds.intersection(&physical_bounds)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let clip_mask =
|
||||||
|
(physical_bounds != clip_bounds).then_some(clip_mask as &_);
|
||||||
|
|
||||||
|
pixels.stroke_path(
|
||||||
|
path,
|
||||||
|
paint,
|
||||||
|
stroke,
|
||||||
|
into_transform(transformation),
|
||||||
|
clip_mask,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_image(
|
||||||
|
&mut self,
|
||||||
|
image: &Image,
|
||||||
|
_transformation: Transformation,
|
||||||
|
_pixels: &mut tiny_skia::PixmapMut<'_>,
|
||||||
|
_clip_mask: &mut tiny_skia::Mask,
|
||||||
|
_clip_bounds: Rectangle,
|
||||||
|
) {
|
||||||
|
match image {
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
Image::Raster {
|
||||||
|
handle,
|
||||||
|
filter_method,
|
||||||
|
bounds,
|
||||||
|
} => {
|
||||||
|
let physical_bounds = *bounds * _transformation;
|
||||||
|
|
||||||
|
if !_clip_bounds.intersects(&physical_bounds) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let clip_mask = (!physical_bounds.is_within(&_clip_bounds))
|
||||||
|
.then_some(_clip_mask as &_);
|
||||||
|
|
||||||
|
self.raster_pipeline.draw(
|
||||||
|
handle,
|
||||||
|
*filter_method,
|
||||||
|
*bounds,
|
||||||
|
_pixels,
|
||||||
|
into_transform(_transformation),
|
||||||
|
clip_mask,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
Image::Vector {
|
||||||
|
handle,
|
||||||
|
color,
|
||||||
|
bounds,
|
||||||
|
} => {
|
||||||
|
let physical_bounds = *bounds * _transformation;
|
||||||
|
|
||||||
|
if !_clip_bounds.intersects(&physical_bounds) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let clip_mask = (!physical_bounds.is_within(&_clip_bounds))
|
||||||
|
.then_some(_clip_mask as &_);
|
||||||
|
|
||||||
|
self.vector_pipeline.draw(
|
||||||
|
handle,
|
||||||
|
*color,
|
||||||
|
physical_bounds,
|
||||||
|
_pixels,
|
||||||
|
clip_mask,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "image"))]
|
||||||
|
Image::Raster { .. } => {
|
||||||
|
log::warn!(
|
||||||
|
"Unsupported primitive in `iced_tiny_skia`: {image:?}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "svg"))]
|
||||||
|
Image::Vector { .. } => {
|
||||||
|
log::warn!(
|
||||||
|
"Unsupported primitive in `iced_tiny_skia`: {image:?}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trim(&mut self) {
|
||||||
|
self.text_pipeline.trim_cache();
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
self.raster_pipeline.trim_cache();
|
||||||
|
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
self.vector_pipeline.trim_cache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_color(color: Color) -> tiny_skia::Color {
|
||||||
|
tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a)
|
||||||
|
.expect("Convert color from iced to tiny_skia")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_transform(transformation: Transformation) -> tiny_skia::Transform {
|
||||||
|
let translation = transformation.translation();
|
||||||
|
|
||||||
|
tiny_skia::Transform {
|
||||||
|
sx: transformation.scale_factor(),
|
||||||
|
kx: 0.0,
|
||||||
|
ky: 0.0,
|
||||||
|
sy: transformation.scale_factor(),
|
||||||
|
tx: translation.x,
|
||||||
|
ty: translation.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rounded_rectangle(
|
||||||
|
bounds: Rectangle,
|
||||||
|
border_radius: [f32; 4],
|
||||||
|
) -> tiny_skia::Path {
|
||||||
|
let [top_left, top_right, bottom_right, bottom_left] = border_radius;
|
||||||
|
|
||||||
|
if top_left == 0.0
|
||||||
|
&& top_right == 0.0
|
||||||
|
&& bottom_right == 0.0
|
||||||
|
&& bottom_left == 0.0
|
||||||
|
{
|
||||||
|
return tiny_skia::PathBuilder::from_rect(
|
||||||
|
tiny_skia::Rect::from_xywh(
|
||||||
|
bounds.x,
|
||||||
|
bounds.y,
|
||||||
|
bounds.width,
|
||||||
|
bounds.height,
|
||||||
|
)
|
||||||
|
.expect("Build quad rectangle"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if top_left == top_right
|
||||||
|
&& top_left == bottom_right
|
||||||
|
&& top_left == bottom_left
|
||||||
|
&& top_left == bounds.width / 2.0
|
||||||
|
&& top_left == bounds.height / 2.0
|
||||||
|
{
|
||||||
|
return tiny_skia::PathBuilder::from_circle(
|
||||||
|
bounds.x + bounds.width / 2.0,
|
||||||
|
bounds.y + bounds.height / 2.0,
|
||||||
|
top_left,
|
||||||
|
)
|
||||||
|
.expect("Build circle path");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut builder = tiny_skia::PathBuilder::new();
|
||||||
|
|
||||||
|
builder.move_to(bounds.x + top_left, bounds.y);
|
||||||
|
builder.line_to(bounds.x + bounds.width - top_right, bounds.y);
|
||||||
|
|
||||||
|
if top_right > 0.0 {
|
||||||
|
arc_to(
|
||||||
|
&mut builder,
|
||||||
|
bounds.x + bounds.width - top_right,
|
||||||
|
bounds.y,
|
||||||
|
bounds.x + bounds.width,
|
||||||
|
bounds.y + top_right,
|
||||||
|
top_right,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
maybe_line_to(
|
||||||
|
&mut builder,
|
||||||
|
bounds.x + bounds.width,
|
||||||
|
bounds.y + bounds.height - bottom_right,
|
||||||
|
);
|
||||||
|
|
||||||
|
if bottom_right > 0.0 {
|
||||||
|
arc_to(
|
||||||
|
&mut builder,
|
||||||
|
bounds.x + bounds.width,
|
||||||
|
bounds.y + bounds.height - bottom_right,
|
||||||
|
bounds.x + bounds.width - bottom_right,
|
||||||
|
bounds.y + bounds.height,
|
||||||
|
bottom_right,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
maybe_line_to(
|
||||||
|
&mut builder,
|
||||||
|
bounds.x + bottom_left,
|
||||||
|
bounds.y + bounds.height,
|
||||||
|
);
|
||||||
|
|
||||||
|
if bottom_left > 0.0 {
|
||||||
|
arc_to(
|
||||||
|
&mut builder,
|
||||||
|
bounds.x + bottom_left,
|
||||||
|
bounds.y + bounds.height,
|
||||||
|
bounds.x,
|
||||||
|
bounds.y + bounds.height - bottom_left,
|
||||||
|
bottom_left,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
maybe_line_to(&mut builder, bounds.x, bounds.y + top_left);
|
||||||
|
|
||||||
|
if top_left > 0.0 {
|
||||||
|
arc_to(
|
||||||
|
&mut builder,
|
||||||
|
bounds.x,
|
||||||
|
bounds.y + top_left,
|
||||||
|
bounds.x + top_left,
|
||||||
|
bounds.y,
|
||||||
|
top_left,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.finish().expect("Build rounded rectangle path")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybe_line_to(path: &mut tiny_skia::PathBuilder, x: f32, y: f32) {
|
||||||
|
if path.last_point() != Some(tiny_skia::Point { x, y }) {
|
||||||
|
path.line_to(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arc_to(
|
||||||
|
path: &mut tiny_skia::PathBuilder,
|
||||||
|
x_from: f32,
|
||||||
|
y_from: f32,
|
||||||
|
x_to: f32,
|
||||||
|
y_to: f32,
|
||||||
|
radius: f32,
|
||||||
|
) {
|
||||||
|
let svg_arc = kurbo::SvgArc {
|
||||||
|
from: kurbo::Point::new(f64::from(x_from), f64::from(y_from)),
|
||||||
|
to: kurbo::Point::new(f64::from(x_to), f64::from(y_to)),
|
||||||
|
radii: kurbo::Vec2::new(f64::from(radius), f64::from(radius)),
|
||||||
|
x_rotation: 0.0,
|
||||||
|
large_arc: false,
|
||||||
|
sweep: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
match kurbo::Arc::from_svg_arc(&svg_arc) {
|
||||||
|
Some(arc) => {
|
||||||
|
arc.to_cubic_beziers(0.1, |p1, p2, p| {
|
||||||
|
path.cubic_to(
|
||||||
|
p1.x as f32,
|
||||||
|
p1.y as f32,
|
||||||
|
p2.x as f32,
|
||||||
|
p2.y as f32,
|
||||||
|
p.x as f32,
|
||||||
|
p.y as f32,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
path.line_to(x_to, y_to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn smoothstep(a: f32, b: f32, x: f32) -> f32 {
|
||||||
|
let x = ((x - a) / (b - a)).clamp(0.0, 1.0);
|
||||||
|
|
||||||
|
x * x * (3.0 - 2.0 * x)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rounded_box_sdf(
|
||||||
|
to_center: Vector,
|
||||||
|
size: tiny_skia::Size,
|
||||||
|
radii: &[f32],
|
||||||
|
) -> f32 {
|
||||||
|
let radius = match (to_center.x > 0.0, to_center.y > 0.0) {
|
||||||
|
(true, true) => radii[2],
|
||||||
|
(true, false) => radii[1],
|
||||||
|
(false, true) => radii[3],
|
||||||
|
(false, false) => radii[0],
|
||||||
|
};
|
||||||
|
|
||||||
|
let x = (to_center.x.abs() - size.width() + radius).max(0.0);
|
||||||
|
let y = (to_center.y.abs() - size.height() + radius).max(0.0);
|
||||||
|
|
||||||
|
(x.powf(2.0) + y.powf(2.0)).sqrt() - radius
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn adjust_clip_mask(clip_mask: &mut tiny_skia::Mask, bounds: Rectangle) {
|
||||||
|
clip_mask.clear();
|
||||||
|
|
||||||
|
let path = {
|
||||||
|
let mut builder = tiny_skia::PathBuilder::new();
|
||||||
|
builder.push_rect(
|
||||||
|
tiny_skia::Rect::from_xywh(
|
||||||
|
bounds.x,
|
||||||
|
bounds.y,
|
||||||
|
bounds.width,
|
||||||
|
bounds.height,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
builder.finish().unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
clip_mask.fill_path(
|
||||||
|
&path,
|
||||||
|
tiny_skia::FillRule::EvenOdd,
|
||||||
|
false,
|
||||||
|
tiny_skia::Transform::default(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,58 +1,98 @@
|
||||||
use crate::core::text::LineHeight;
|
use crate::core::text::LineHeight;
|
||||||
use crate::core::{
|
use crate::core::{Pixels, Point, Radians, Rectangle, Size, Vector};
|
||||||
Pixels, Point, Radians, Rectangle, Size, Transformation, Vector,
|
|
||||||
};
|
|
||||||
use crate::graphics::geometry::fill::{self, Fill};
|
use crate::graphics::geometry::fill::{self, Fill};
|
||||||
use crate::graphics::geometry::stroke::{self, Stroke};
|
use crate::graphics::geometry::stroke::{self, Stroke};
|
||||||
use crate::graphics::geometry::{self, Path, Style, Text};
|
use crate::graphics::geometry::{self, Path, Style};
|
||||||
use crate::graphics::Gradient;
|
use crate::graphics::{Cached, Gradient, Text};
|
||||||
use crate::primitive::{self, Primitive};
|
use crate::Primitive;
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Geometry {
|
||||||
|
Live {
|
||||||
|
text: Vec<Text>,
|
||||||
|
primitives: Vec<Primitive>,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
},
|
||||||
|
Cache(Cache),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Cache {
|
||||||
|
pub text: Rc<[Text]>,
|
||||||
|
pub primitives: Rc<[Primitive]>,
|
||||||
|
pub clip_bounds: Rectangle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cached for Geometry {
|
||||||
|
type Cache = Cache;
|
||||||
|
|
||||||
|
fn load(cache: &Cache) -> Self {
|
||||||
|
Self::Cache(cache.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cache(self, _previous: Option<Cache>) -> Cache {
|
||||||
|
match self {
|
||||||
|
Self::Live {
|
||||||
|
primitives,
|
||||||
|
text,
|
||||||
|
clip_bounds,
|
||||||
|
} => Cache {
|
||||||
|
primitives: Rc::from(primitives),
|
||||||
|
text: Rc::from(text),
|
||||||
|
clip_bounds,
|
||||||
|
},
|
||||||
|
Self::Cache(cache) => cache,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Frame {
|
pub struct Frame {
|
||||||
size: Size,
|
clip_bounds: Rectangle,
|
||||||
transform: tiny_skia::Transform,
|
transform: tiny_skia::Transform,
|
||||||
stack: Vec<tiny_skia::Transform>,
|
stack: Vec<tiny_skia::Transform>,
|
||||||
primitives: Vec<Primitive>,
|
primitives: Vec<Primitive>,
|
||||||
|
text: Vec<Text>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Frame {
|
impl Frame {
|
||||||
pub fn new(size: Size) -> Self {
|
pub fn new(size: Size) -> Self {
|
||||||
Self {
|
Self::with_clip(Rectangle::with_size(size))
|
||||||
size,
|
|
||||||
transform: tiny_skia::Transform::identity(),
|
|
||||||
stack: Vec::new(),
|
|
||||||
primitives: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_primitive(self) -> Primitive {
|
pub fn with_clip(clip_bounds: Rectangle) -> Self {
|
||||||
Primitive::Clip {
|
Self {
|
||||||
bounds: Rectangle::new(Point::ORIGIN, self.size),
|
clip_bounds,
|
||||||
content: Box::new(Primitive::Group {
|
stack: Vec::new(),
|
||||||
primitives: self.primitives,
|
primitives: Vec::new(),
|
||||||
}),
|
text: Vec::new(),
|
||||||
|
transform: tiny_skia::Transform::from_translate(
|
||||||
|
clip_bounds.x,
|
||||||
|
clip_bounds.y,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl geometry::frame::Backend for Frame {
|
impl geometry::frame::Backend for Frame {
|
||||||
type Geometry = Primitive;
|
type Geometry = Geometry;
|
||||||
|
|
||||||
fn width(&self) -> f32 {
|
fn width(&self) -> f32 {
|
||||||
self.size.width
|
self.clip_bounds.width
|
||||||
}
|
}
|
||||||
|
|
||||||
fn height(&self) -> f32 {
|
fn height(&self) -> f32 {
|
||||||
self.size.height
|
self.clip_bounds.height
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size(&self) -> Size {
|
fn size(&self) -> Size {
|
||||||
self.size
|
self.clip_bounds.size()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn center(&self) -> Point {
|
fn center(&self) -> Point {
|
||||||
Point::new(self.size.width / 2.0, self.size.height / 2.0)
|
Point::new(self.clip_bounds.width / 2.0, self.clip_bounds.height / 2.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
|
fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
|
||||||
|
|
@ -67,12 +107,11 @@ impl geometry::frame::Backend for Frame {
|
||||||
let mut paint = into_paint(fill.style);
|
let mut paint = into_paint(fill.style);
|
||||||
paint.shader.transform(self.transform);
|
paint.shader.transform(self.transform);
|
||||||
|
|
||||||
self.primitives
|
self.primitives.push(Primitive::Fill {
|
||||||
.push(Primitive::Custom(primitive::Custom::Fill {
|
path,
|
||||||
path,
|
paint,
|
||||||
paint,
|
rule: into_fill_rule(fill.rule),
|
||||||
rule: into_fill_rule(fill.rule),
|
});
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fill_rectangle(
|
fn fill_rectangle(
|
||||||
|
|
@ -95,12 +134,11 @@ impl geometry::frame::Backend for Frame {
|
||||||
};
|
};
|
||||||
paint.shader.transform(self.transform);
|
paint.shader.transform(self.transform);
|
||||||
|
|
||||||
self.primitives
|
self.primitives.push(Primitive::Fill {
|
||||||
.push(Primitive::Custom(primitive::Custom::Fill {
|
path,
|
||||||
path,
|
paint,
|
||||||
paint,
|
rule: into_fill_rule(fill.rule),
|
||||||
rule: into_fill_rule(fill.rule),
|
});
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
|
fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
|
||||||
|
|
@ -116,20 +154,19 @@ impl geometry::frame::Backend for Frame {
|
||||||
let mut paint = into_paint(stroke.style);
|
let mut paint = into_paint(stroke.style);
|
||||||
paint.shader.transform(self.transform);
|
paint.shader.transform(self.transform);
|
||||||
|
|
||||||
self.primitives
|
self.primitives.push(Primitive::Stroke {
|
||||||
.push(Primitive::Custom(primitive::Custom::Stroke {
|
path,
|
||||||
path,
|
paint,
|
||||||
paint,
|
stroke: skia_stroke,
|
||||||
stroke: skia_stroke,
|
});
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fill_text(&mut self, text: impl Into<Text>) {
|
fn fill_text(&mut self, text: impl Into<geometry::Text>) {
|
||||||
let text = text.into();
|
let text = text.into();
|
||||||
|
|
||||||
let (scale_x, scale_y) = self.transform.get_scale();
|
let (scale_x, scale_y) = self.transform.get_scale();
|
||||||
|
|
||||||
if self.transform.is_scale_translate()
|
if !self.transform.has_skew()
|
||||||
&& scale_x == scale_y
|
&& scale_x == scale_y
|
||||||
&& scale_x > 0.0
|
&& scale_x > 0.0
|
||||||
&& scale_y > 0.0
|
&& scale_y > 0.0
|
||||||
|
|
@ -171,12 +208,12 @@ impl geometry::frame::Backend for Frame {
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Honor layering!
|
// TODO: Honor layering!
|
||||||
self.primitives.push(Primitive::Text {
|
self.text.push(Text::Cached {
|
||||||
content: text.content,
|
content: text.content,
|
||||||
bounds,
|
bounds,
|
||||||
color: text.color,
|
color: text.color,
|
||||||
size,
|
size,
|
||||||
line_height,
|
line_height: line_height.to_absolute(size),
|
||||||
font: text.font,
|
font: text.font,
|
||||||
horizontal_alignment: text.horizontal_alignment,
|
horizontal_alignment: text.horizontal_alignment,
|
||||||
vertical_alignment: text.vertical_alignment,
|
vertical_alignment: text.vertical_alignment,
|
||||||
|
|
@ -196,15 +233,13 @@ impl geometry::frame::Backend for Frame {
|
||||||
self.transform = self.stack.pop().expect("Pop transform");
|
self.transform = self.stack.pop().expect("Pop transform");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draft(&mut self, size: Size) -> Self {
|
fn draft(&mut self, clip_bounds: Rectangle) -> Self {
|
||||||
Self::new(size)
|
Self::with_clip(clip_bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paste(&mut self, frame: Self, at: Point) {
|
fn paste(&mut self, frame: Self, _at: Point) {
|
||||||
self.primitives.push(Primitive::Transform {
|
self.primitives.extend(frame.primitives);
|
||||||
transformation: Transformation::translate(at.x, at.y),
|
self.text.extend(frame.text);
|
||||||
content: Box::new(frame.into_primitive()),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn translate(&mut self, translation: Vector) {
|
fn translate(&mut self, translation: Vector) {
|
||||||
|
|
@ -230,8 +265,12 @@ impl geometry::frame::Backend for Frame {
|
||||||
self.transform = self.transform.pre_scale(scale.x, scale.y);
|
self.transform = self.transform.pre_scale(scale.x, scale.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_geometry(self) -> Self::Geometry {
|
fn into_geometry(self) -> Geometry {
|
||||||
self.into_primitive()
|
Geometry::Live {
|
||||||
|
primitives: self.primitives,
|
||||||
|
text: self.text,
|
||||||
|
clip_bounds: self.clip_bounds,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
333
tiny_skia/src/layer.rs
Normal file
333
tiny_skia/src/layer.rs
Normal file
|
|
@ -0,0 +1,333 @@
|
||||||
|
use crate::core::image;
|
||||||
|
use crate::core::renderer::Quad;
|
||||||
|
use crate::core::svg;
|
||||||
|
use crate::core::{Background, Color, Point, Rectangle, Transformation};
|
||||||
|
use crate::graphics::damage;
|
||||||
|
use crate::graphics::layer;
|
||||||
|
use crate::graphics::text::{Editor, Paragraph, Text};
|
||||||
|
use crate::graphics::{self, Image};
|
||||||
|
use crate::Primitive;
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
pub type Stack = layer::Stack<Layer>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Layer {
|
||||||
|
pub bounds: Rectangle,
|
||||||
|
pub quads: Vec<(Quad, Background)>,
|
||||||
|
pub primitives: Vec<Item<Primitive>>,
|
||||||
|
pub text: Vec<Item<Text>>,
|
||||||
|
pub images: Vec<Image>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Layer {
|
||||||
|
pub fn draw_quad(
|
||||||
|
&mut self,
|
||||||
|
mut quad: Quad,
|
||||||
|
background: Background,
|
||||||
|
transformation: Transformation,
|
||||||
|
) {
|
||||||
|
quad.bounds = quad.bounds * transformation;
|
||||||
|
self.quads.push((quad, background));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_paragraph(
|
||||||
|
&mut self,
|
||||||
|
paragraph: &Paragraph,
|
||||||
|
position: Point,
|
||||||
|
color: Color,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
transformation: Transformation,
|
||||||
|
) {
|
||||||
|
let paragraph = Text::Paragraph {
|
||||||
|
paragraph: paragraph.downgrade(),
|
||||||
|
position,
|
||||||
|
color,
|
||||||
|
clip_bounds,
|
||||||
|
transformation,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.text.push(Item::Live(paragraph));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_editor(
|
||||||
|
&mut self,
|
||||||
|
editor: &Editor,
|
||||||
|
position: Point,
|
||||||
|
color: Color,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
transformation: Transformation,
|
||||||
|
) {
|
||||||
|
let editor = Text::Editor {
|
||||||
|
editor: editor.downgrade(),
|
||||||
|
position,
|
||||||
|
color,
|
||||||
|
clip_bounds,
|
||||||
|
transformation,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.text.push(Item::Live(editor));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_text(
|
||||||
|
&mut self,
|
||||||
|
text: crate::core::Text,
|
||||||
|
position: Point,
|
||||||
|
color: Color,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
transformation: Transformation,
|
||||||
|
) {
|
||||||
|
let text = Text::Cached {
|
||||||
|
content: text.content,
|
||||||
|
bounds: Rectangle::new(position, text.bounds) * transformation,
|
||||||
|
color,
|
||||||
|
size: text.size * transformation.scale_factor(),
|
||||||
|
line_height: text.line_height.to_absolute(text.size)
|
||||||
|
* transformation.scale_factor(),
|
||||||
|
font: text.font,
|
||||||
|
horizontal_alignment: text.horizontal_alignment,
|
||||||
|
vertical_alignment: text.vertical_alignment,
|
||||||
|
shaping: text.shaping,
|
||||||
|
clip_bounds: clip_bounds * transformation,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.text.push(Item::Live(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_text_group(
|
||||||
|
&mut self,
|
||||||
|
text: Vec<Text>,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
transformation: Transformation,
|
||||||
|
) {
|
||||||
|
self.text
|
||||||
|
.push(Item::Group(text, clip_bounds, transformation));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_text_cache(
|
||||||
|
&mut self,
|
||||||
|
text: Rc<[Text]>,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
transformation: Transformation,
|
||||||
|
) {
|
||||||
|
self.text
|
||||||
|
.push(Item::Cached(text, clip_bounds, transformation));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_image(
|
||||||
|
&mut self,
|
||||||
|
handle: image::Handle,
|
||||||
|
filter_method: image::FilterMethod,
|
||||||
|
bounds: Rectangle,
|
||||||
|
transformation: Transformation,
|
||||||
|
) {
|
||||||
|
let image = Image::Raster {
|
||||||
|
handle,
|
||||||
|
filter_method,
|
||||||
|
bounds: bounds * transformation,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.images.push(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_svg(
|
||||||
|
&mut self,
|
||||||
|
handle: svg::Handle,
|
||||||
|
color: Option<Color>,
|
||||||
|
bounds: Rectangle,
|
||||||
|
transformation: Transformation,
|
||||||
|
) {
|
||||||
|
let svg = Image::Vector {
|
||||||
|
handle,
|
||||||
|
color,
|
||||||
|
bounds: bounds * transformation,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.images.push(svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_primitive_group(
|
||||||
|
&mut self,
|
||||||
|
primitives: Vec<Primitive>,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
transformation: Transformation,
|
||||||
|
) {
|
||||||
|
self.primitives.push(Item::Group(
|
||||||
|
primitives,
|
||||||
|
clip_bounds,
|
||||||
|
transformation,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_primitive_cache(
|
||||||
|
&mut self,
|
||||||
|
primitives: Rc<[Primitive]>,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
transformation: Transformation,
|
||||||
|
) {
|
||||||
|
self.primitives.push(Item::Cached(
|
||||||
|
primitives,
|
||||||
|
clip_bounds,
|
||||||
|
transformation,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn damage(previous: &Self, current: &Self) -> Vec<Rectangle> {
|
||||||
|
if previous.bounds != current.bounds {
|
||||||
|
return vec![previous.bounds, current.bounds];
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut damage = damage::list(
|
||||||
|
&previous.quads,
|
||||||
|
¤t.quads,
|
||||||
|
|(quad, _)| {
|
||||||
|
quad.bounds
|
||||||
|
.expand(1.0)
|
||||||
|
.intersection(¤t.bounds)
|
||||||
|
.into_iter()
|
||||||
|
.collect()
|
||||||
|
},
|
||||||
|
|(quad_a, background_a), (quad_b, background_b)| {
|
||||||
|
quad_a == quad_b && background_a == background_b
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let text = damage::diff(
|
||||||
|
&previous.text,
|
||||||
|
¤t.text,
|
||||||
|
|item| {
|
||||||
|
item.as_slice()
|
||||||
|
.iter()
|
||||||
|
.filter_map(Text::visible_bounds)
|
||||||
|
.map(|bounds| bounds * item.transformation())
|
||||||
|
.collect()
|
||||||
|
},
|
||||||
|
|text_a, text_b| {
|
||||||
|
damage::list(
|
||||||
|
text_a.as_slice(),
|
||||||
|
text_b.as_slice(),
|
||||||
|
|text| {
|
||||||
|
text.visible_bounds()
|
||||||
|
.into_iter()
|
||||||
|
.map(|bounds| bounds * text_a.transformation())
|
||||||
|
.collect()
|
||||||
|
},
|
||||||
|
|text_a, text_b| text_a == text_b,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let primitives = damage::list(
|
||||||
|
&previous.primitives,
|
||||||
|
¤t.primitives,
|
||||||
|
|item| match item {
|
||||||
|
Item::Live(primitive) => vec![primitive.visible_bounds()],
|
||||||
|
Item::Group(primitives, group_bounds, transformation) => {
|
||||||
|
primitives
|
||||||
|
.as_slice()
|
||||||
|
.iter()
|
||||||
|
.map(Primitive::visible_bounds)
|
||||||
|
.map(|bounds| bounds * *transformation)
|
||||||
|
.filter_map(|bounds| bounds.intersection(group_bounds))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
Item::Cached(_, bounds, _) => {
|
||||||
|
vec![*bounds]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|primitive_a, primitive_b| match (primitive_a, primitive_b) {
|
||||||
|
(
|
||||||
|
Item::Cached(cache_a, bounds_a, transformation_a),
|
||||||
|
Item::Cached(cache_b, bounds_b, transformation_b),
|
||||||
|
) => {
|
||||||
|
Rc::ptr_eq(cache_a, cache_b)
|
||||||
|
&& bounds_a == bounds_b
|
||||||
|
&& transformation_a == transformation_b
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let images = damage::list(
|
||||||
|
&previous.images,
|
||||||
|
¤t.images,
|
||||||
|
|image| vec![image.bounds().expand(1.0)],
|
||||||
|
Image::eq,
|
||||||
|
);
|
||||||
|
|
||||||
|
damage.extend(text);
|
||||||
|
damage.extend(primitives);
|
||||||
|
damage.extend(images);
|
||||||
|
damage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Layer {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
bounds: Rectangle::INFINITE,
|
||||||
|
quads: Vec::new(),
|
||||||
|
primitives: Vec::new(),
|
||||||
|
text: Vec::new(),
|
||||||
|
images: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl graphics::Layer for Layer {
|
||||||
|
fn with_bounds(bounds: Rectangle) -> Self {
|
||||||
|
Self {
|
||||||
|
bounds,
|
||||||
|
..Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) {}
|
||||||
|
|
||||||
|
fn resize(&mut self, bounds: graphics::core::Rectangle) {
|
||||||
|
self.bounds = bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.bounds = Rectangle::INFINITE;
|
||||||
|
|
||||||
|
self.quads.clear();
|
||||||
|
self.primitives.clear();
|
||||||
|
self.text.clear();
|
||||||
|
self.images.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Item<T> {
|
||||||
|
Live(T),
|
||||||
|
Group(Vec<T>, Rectangle, Transformation),
|
||||||
|
Cached(Rc<[T]>, Rectangle, Transformation),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Item<T> {
|
||||||
|
pub fn transformation(&self) -> Transformation {
|
||||||
|
match self {
|
||||||
|
Item::Live(_) => Transformation::IDENTITY,
|
||||||
|
Item::Group(_, _, transformation)
|
||||||
|
| Item::Cached(_, _, transformation) => *transformation,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clip_bounds(&self) -> Rectangle {
|
||||||
|
match self {
|
||||||
|
Item::Live(_) => Rectangle::INFINITE,
|
||||||
|
Item::Group(_, clip_bounds, _)
|
||||||
|
| Item::Cached(_, clip_bounds, _) => *clip_bounds,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_slice(&self) -> &[T] {
|
||||||
|
match self {
|
||||||
|
Item::Live(item) => std::slice::from_ref(item),
|
||||||
|
Item::Group(group, _, _) => group.as_slice(),
|
||||||
|
Item::Cached(cache, _, _) => cache,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,8 @@
|
||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
pub mod window;
|
pub mod window;
|
||||||
|
|
||||||
mod backend;
|
mod engine;
|
||||||
|
mod layer;
|
||||||
mod primitive;
|
mod primitive;
|
||||||
mod settings;
|
mod settings;
|
||||||
mod text;
|
mod text;
|
||||||
|
|
@ -19,12 +20,389 @@ pub mod geometry;
|
||||||
pub use iced_graphics as graphics;
|
pub use iced_graphics as graphics;
|
||||||
pub use iced_graphics::core;
|
pub use iced_graphics::core;
|
||||||
|
|
||||||
pub use backend::Backend;
|
pub use layer::Layer;
|
||||||
pub use primitive::Primitive;
|
pub use primitive::Primitive;
|
||||||
pub use settings::Settings;
|
pub use settings::Settings;
|
||||||
|
|
||||||
|
#[cfg(feature = "geometry")]
|
||||||
|
pub use geometry::Geometry;
|
||||||
|
|
||||||
|
use crate::core::renderer;
|
||||||
|
use crate::core::{
|
||||||
|
Background, Color, Font, Pixels, Point, Rectangle, Transformation,
|
||||||
|
};
|
||||||
|
use crate::engine::Engine;
|
||||||
|
use crate::graphics::compositor;
|
||||||
|
use crate::graphics::text::{Editor, Paragraph};
|
||||||
|
use crate::graphics::Viewport;
|
||||||
|
|
||||||
/// A [`tiny-skia`] graphics renderer for [`iced`].
|
/// A [`tiny-skia`] graphics renderer for [`iced`].
|
||||||
///
|
///
|
||||||
/// [`tiny-skia`]: https://github.com/RazrFalcon/tiny-skia
|
/// [`tiny-skia`]: https://github.com/RazrFalcon/tiny-skia
|
||||||
/// [`iced`]: https://github.com/iced-rs/iced
|
/// [`iced`]: https://github.com/iced-rs/iced
|
||||||
pub type Renderer = iced_graphics::Renderer<Backend>;
|
#[derive(Debug)]
|
||||||
|
pub struct Renderer {
|
||||||
|
default_font: Font,
|
||||||
|
default_text_size: Pixels,
|
||||||
|
layers: layer::Stack,
|
||||||
|
engine: Engine, // TODO: Shared engine
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Renderer {
|
||||||
|
pub fn new(default_font: Font, default_text_size: Pixels) -> Self {
|
||||||
|
Self {
|
||||||
|
default_font,
|
||||||
|
default_text_size,
|
||||||
|
layers: layer::Stack::new(),
|
||||||
|
engine: Engine::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn layers(&mut self) -> &[Layer] {
|
||||||
|
self.layers.flush();
|
||||||
|
self.layers.as_slice()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw<T: AsRef<str>>(
|
||||||
|
&mut self,
|
||||||
|
pixels: &mut tiny_skia::PixmapMut<'_>,
|
||||||
|
clip_mask: &mut tiny_skia::Mask,
|
||||||
|
viewport: &Viewport,
|
||||||
|
damage: &[Rectangle],
|
||||||
|
background_color: Color,
|
||||||
|
overlay: &[T],
|
||||||
|
) {
|
||||||
|
let physical_size = viewport.physical_size();
|
||||||
|
let scale_factor = viewport.scale_factor() as f32;
|
||||||
|
|
||||||
|
if !overlay.is_empty() {
|
||||||
|
let path = tiny_skia::PathBuilder::from_rect(
|
||||||
|
tiny_skia::Rect::from_xywh(
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
physical_size.width as f32,
|
||||||
|
physical_size.height as f32,
|
||||||
|
)
|
||||||
|
.expect("Create damage rectangle"),
|
||||||
|
);
|
||||||
|
|
||||||
|
pixels.fill_path(
|
||||||
|
&path,
|
||||||
|
&tiny_skia::Paint {
|
||||||
|
shader: tiny_skia::Shader::SolidColor(engine::into_color(
|
||||||
|
Color {
|
||||||
|
a: 0.1,
|
||||||
|
..background_color
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
anti_alias: false,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
tiny_skia::FillRule::default(),
|
||||||
|
tiny_skia::Transform::identity(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.layers.flush();
|
||||||
|
|
||||||
|
for ®ion in damage {
|
||||||
|
let region = region * scale_factor;
|
||||||
|
|
||||||
|
let path = tiny_skia::PathBuilder::from_rect(
|
||||||
|
tiny_skia::Rect::from_xywh(
|
||||||
|
region.x,
|
||||||
|
region.y,
|
||||||
|
region.width,
|
||||||
|
region.height,
|
||||||
|
)
|
||||||
|
.expect("Create damage rectangle"),
|
||||||
|
);
|
||||||
|
|
||||||
|
pixels.fill_path(
|
||||||
|
&path,
|
||||||
|
&tiny_skia::Paint {
|
||||||
|
shader: tiny_skia::Shader::SolidColor(engine::into_color(
|
||||||
|
background_color,
|
||||||
|
)),
|
||||||
|
anti_alias: false,
|
||||||
|
blend_mode: tiny_skia::BlendMode::Source,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
tiny_skia::FillRule::default(),
|
||||||
|
tiny_skia::Transform::identity(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
for layer in self.layers.iter() {
|
||||||
|
let Some(clip_bounds) =
|
||||||
|
region.intersection(&(layer.bounds * scale_factor))
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
engine::adjust_clip_mask(clip_mask, clip_bounds);
|
||||||
|
|
||||||
|
for (quad, background) in &layer.quads {
|
||||||
|
self.engine.draw_quad(
|
||||||
|
quad,
|
||||||
|
background,
|
||||||
|
Transformation::scale(scale_factor),
|
||||||
|
pixels,
|
||||||
|
clip_mask,
|
||||||
|
clip_bounds,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for group in &layer.primitives {
|
||||||
|
let Some(new_clip_bounds) = (group.clip_bounds()
|
||||||
|
* scale_factor)
|
||||||
|
.intersection(&clip_bounds)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
engine::adjust_clip_mask(clip_mask, new_clip_bounds);
|
||||||
|
|
||||||
|
for primitive in group.as_slice() {
|
||||||
|
self.engine.draw_primitive(
|
||||||
|
primitive,
|
||||||
|
group.transformation()
|
||||||
|
* Transformation::scale(scale_factor),
|
||||||
|
pixels,
|
||||||
|
clip_mask,
|
||||||
|
clip_bounds,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
engine::adjust_clip_mask(clip_mask, clip_bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
for group in &layer.text {
|
||||||
|
for text in group.as_slice() {
|
||||||
|
self.engine.draw_text(
|
||||||
|
text,
|
||||||
|
group.transformation()
|
||||||
|
* Transformation::scale(scale_factor),
|
||||||
|
pixels,
|
||||||
|
clip_mask,
|
||||||
|
clip_bounds,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for image in &layer.images {
|
||||||
|
self.engine.draw_image(
|
||||||
|
image,
|
||||||
|
Transformation::scale(scale_factor),
|
||||||
|
pixels,
|
||||||
|
clip_mask,
|
||||||
|
clip_bounds,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !overlay.is_empty() {
|
||||||
|
pixels.stroke_path(
|
||||||
|
&path,
|
||||||
|
&tiny_skia::Paint {
|
||||||
|
shader: tiny_skia::Shader::SolidColor(
|
||||||
|
engine::into_color(Color::from_rgb(1.0, 0.0, 0.0)),
|
||||||
|
),
|
||||||
|
anti_alias: false,
|
||||||
|
..tiny_skia::Paint::default()
|
||||||
|
},
|
||||||
|
&tiny_skia::Stroke {
|
||||||
|
width: 1.0,
|
||||||
|
..tiny_skia::Stroke::default()
|
||||||
|
},
|
||||||
|
tiny_skia::Transform::identity(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.engine.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::Renderer for Renderer {
|
||||||
|
fn start_layer(&mut self, bounds: Rectangle) {
|
||||||
|
self.layers.push_clip(bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end_layer(&mut self) {
|
||||||
|
self.layers.pop_clip();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_transformation(&mut self, transformation: Transformation) {
|
||||||
|
self.layers.push_transformation(transformation);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end_transformation(&mut self) {
|
||||||
|
self.layers.pop_transformation();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_quad(
|
||||||
|
&mut self,
|
||||||
|
quad: renderer::Quad,
|
||||||
|
background: impl Into<Background>,
|
||||||
|
) {
|
||||||
|
let (layer, transformation) = self.layers.current_mut();
|
||||||
|
layer.draw_quad(quad, background.into(), transformation);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&mut self) {
|
||||||
|
self.layers.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::text::Renderer for Renderer {
|
||||||
|
type Font = Font;
|
||||||
|
type Paragraph = Paragraph;
|
||||||
|
type Editor = Editor;
|
||||||
|
|
||||||
|
const ICON_FONT: Font = Font::with_name("Iced-Icons");
|
||||||
|
const CHECKMARK_ICON: char = '\u{f00c}';
|
||||||
|
const ARROW_DOWN_ICON: char = '\u{e800}';
|
||||||
|
|
||||||
|
fn default_font(&self) -> Self::Font {
|
||||||
|
self.default_font
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_size(&self) -> Pixels {
|
||||||
|
self.default_text_size
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_paragraph(
|
||||||
|
&mut self,
|
||||||
|
text: &Self::Paragraph,
|
||||||
|
position: Point,
|
||||||
|
color: Color,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
) {
|
||||||
|
let (layer, transformation) = self.layers.current_mut();
|
||||||
|
|
||||||
|
layer.draw_paragraph(
|
||||||
|
text,
|
||||||
|
position,
|
||||||
|
color,
|
||||||
|
clip_bounds,
|
||||||
|
transformation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_editor(
|
||||||
|
&mut self,
|
||||||
|
editor: &Self::Editor,
|
||||||
|
position: Point,
|
||||||
|
color: Color,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
) {
|
||||||
|
let (layer, transformation) = self.layers.current_mut();
|
||||||
|
layer.draw_editor(editor, position, color, clip_bounds, transformation);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_text(
|
||||||
|
&mut self,
|
||||||
|
text: core::Text,
|
||||||
|
position: Point,
|
||||||
|
color: Color,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
) {
|
||||||
|
let (layer, transformation) = self.layers.current_mut();
|
||||||
|
layer.draw_text(text, position, color, clip_bounds, transformation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "geometry")]
|
||||||
|
impl graphics::geometry::Renderer for Renderer {
|
||||||
|
type Geometry = Geometry;
|
||||||
|
type Frame = geometry::Frame;
|
||||||
|
|
||||||
|
fn new_frame(&self, size: core::Size) -> Self::Frame {
|
||||||
|
geometry::Frame::new(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_geometry(&mut self, geometry: Self::Geometry) {
|
||||||
|
let (layer, transformation) = self.layers.current_mut();
|
||||||
|
|
||||||
|
match geometry {
|
||||||
|
Geometry::Live {
|
||||||
|
primitives,
|
||||||
|
text,
|
||||||
|
clip_bounds,
|
||||||
|
} => {
|
||||||
|
layer.draw_primitive_group(
|
||||||
|
primitives,
|
||||||
|
clip_bounds,
|
||||||
|
transformation,
|
||||||
|
);
|
||||||
|
|
||||||
|
layer.draw_text_group(text, clip_bounds, transformation);
|
||||||
|
}
|
||||||
|
Geometry::Cache(cache) => {
|
||||||
|
layer.draw_primitive_cache(
|
||||||
|
cache.primitives,
|
||||||
|
cache.clip_bounds,
|
||||||
|
transformation,
|
||||||
|
);
|
||||||
|
|
||||||
|
layer.draw_text_cache(
|
||||||
|
cache.text,
|
||||||
|
cache.clip_bounds,
|
||||||
|
transformation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl graphics::mesh::Renderer for Renderer {
|
||||||
|
fn draw_mesh(&mut self, _mesh: graphics::Mesh) {
|
||||||
|
log::warn!("iced_tiny_skia does not support drawing meshes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
impl core::image::Renderer for Renderer {
|
||||||
|
type Handle = core::image::Handle;
|
||||||
|
|
||||||
|
fn measure_image(&self, handle: &Self::Handle) -> crate::core::Size<u32> {
|
||||||
|
self.engine.raster_pipeline.dimensions(handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_image(
|
||||||
|
&mut self,
|
||||||
|
handle: Self::Handle,
|
||||||
|
filter_method: core::image::FilterMethod,
|
||||||
|
bounds: Rectangle,
|
||||||
|
) {
|
||||||
|
let (layer, transformation) = self.layers.current_mut();
|
||||||
|
layer.draw_image(handle, filter_method, bounds, transformation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
impl core::svg::Renderer for Renderer {
|
||||||
|
fn measure_svg(
|
||||||
|
&self,
|
||||||
|
handle: &core::svg::Handle,
|
||||||
|
) -> crate::core::Size<u32> {
|
||||||
|
self.engine.vector_pipeline.viewport_dimensions(handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_svg(
|
||||||
|
&mut self,
|
||||||
|
handle: core::svg::Handle,
|
||||||
|
color: Option<Color>,
|
||||||
|
bounds: Rectangle,
|
||||||
|
) {
|
||||||
|
let (layer, transformation) = self.layers.current_mut();
|
||||||
|
layer.draw_svg(handle, color, bounds, transformation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl compositor::Default for Renderer {
|
||||||
|
type Compositor = window::Compositor;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
use crate::core::Rectangle;
|
use crate::core::Rectangle;
|
||||||
use crate::graphics::{Damage, Mesh};
|
|
||||||
|
|
||||||
pub type Primitive = crate::graphics::Primitive<Custom>;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Custom {
|
pub enum Primitive {
|
||||||
/// A path filled with some paint.
|
/// A path filled with some paint.
|
||||||
Fill {
|
Fill {
|
||||||
/// The path to fill.
|
/// The path to fill.
|
||||||
|
|
@ -25,28 +22,19 @@ pub enum Custom {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Damage for Custom {
|
impl Primitive {
|
||||||
fn bounds(&self) -> Rectangle {
|
/// Returns the visible bounds of the [`Primitive`].
|
||||||
match self {
|
pub fn visible_bounds(&self) -> Rectangle {
|
||||||
Self::Fill { path, .. } | Self::Stroke { path, .. } => {
|
let bounds = match self {
|
||||||
let bounds = path.bounds();
|
Primitive::Fill { path, .. } => path.bounds(),
|
||||||
|
Primitive::Stroke { path, .. } => path.bounds(),
|
||||||
|
};
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
x: bounds.x(),
|
x: bounds.x(),
|
||||||
y: bounds.y(),
|
y: bounds.y(),
|
||||||
width: bounds.width(),
|
width: bounds.width(),
|
||||||
height: bounds.height(),
|
height: bounds.height(),
|
||||||
}
|
|
||||||
.expand(1.0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<Mesh> for Custom {
|
|
||||||
type Error = &'static str;
|
|
||||||
|
|
||||||
fn try_from(_mesh: Mesh) -> Result<Self, Self::Error> {
|
|
||||||
Err("unsupported")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::core::{Font, Pixels};
|
use crate::core::{Font, Pixels};
|
||||||
use crate::graphics;
|
use crate::graphics;
|
||||||
|
|
||||||
/// The settings of a [`Backend`].
|
/// The settings of a [`Compositor`].
|
||||||
///
|
///
|
||||||
/// [`Backend`]: crate::Backend
|
/// [`Compositor`]: crate::window::Compositor
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
/// The default [`Font`] to use.
|
/// The default [`Font`] to use.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::core::alignment;
|
use crate::core::alignment;
|
||||||
use crate::core::text::{LineHeight, Shaping};
|
use crate::core::text::Shaping;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Color, Font, Pixels, Point, Rectangle, Size, Transformation,
|
Color, Font, Pixels, Point, Rectangle, Size, Transformation,
|
||||||
};
|
};
|
||||||
|
|
@ -27,6 +27,8 @@ impl Pipeline {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Shared engine
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
|
pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
|
||||||
font_system()
|
font_system()
|
||||||
.write()
|
.write()
|
||||||
|
|
@ -41,7 +43,6 @@ impl Pipeline {
|
||||||
paragraph: ¶graph::Weak,
|
paragraph: ¶graph::Weak,
|
||||||
position: Point,
|
position: Point,
|
||||||
color: Color,
|
color: Color,
|
||||||
scale_factor: f32,
|
|
||||||
pixels: &mut tiny_skia::PixmapMut<'_>,
|
pixels: &mut tiny_skia::PixmapMut<'_>,
|
||||||
clip_mask: Option<&tiny_skia::Mask>,
|
clip_mask: Option<&tiny_skia::Mask>,
|
||||||
transformation: Transformation,
|
transformation: Transformation,
|
||||||
|
|
@ -62,7 +63,6 @@ impl Pipeline {
|
||||||
color,
|
color,
|
||||||
paragraph.horizontal_alignment(),
|
paragraph.horizontal_alignment(),
|
||||||
paragraph.vertical_alignment(),
|
paragraph.vertical_alignment(),
|
||||||
scale_factor,
|
|
||||||
pixels,
|
pixels,
|
||||||
clip_mask,
|
clip_mask,
|
||||||
transformation,
|
transformation,
|
||||||
|
|
@ -74,7 +74,6 @@ impl Pipeline {
|
||||||
editor: &editor::Weak,
|
editor: &editor::Weak,
|
||||||
position: Point,
|
position: Point,
|
||||||
color: Color,
|
color: Color,
|
||||||
scale_factor: f32,
|
|
||||||
pixels: &mut tiny_skia::PixmapMut<'_>,
|
pixels: &mut tiny_skia::PixmapMut<'_>,
|
||||||
clip_mask: Option<&tiny_skia::Mask>,
|
clip_mask: Option<&tiny_skia::Mask>,
|
||||||
transformation: Transformation,
|
transformation: Transformation,
|
||||||
|
|
@ -95,7 +94,6 @@ impl Pipeline {
|
||||||
color,
|
color,
|
||||||
alignment::Horizontal::Left,
|
alignment::Horizontal::Left,
|
||||||
alignment::Vertical::Top,
|
alignment::Vertical::Top,
|
||||||
scale_factor,
|
|
||||||
pixels,
|
pixels,
|
||||||
clip_mask,
|
clip_mask,
|
||||||
transformation,
|
transformation,
|
||||||
|
|
@ -108,17 +106,16 @@ impl Pipeline {
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
color: Color,
|
color: Color,
|
||||||
size: Pixels,
|
size: Pixels,
|
||||||
line_height: LineHeight,
|
line_height: Pixels,
|
||||||
font: Font,
|
font: Font,
|
||||||
horizontal_alignment: alignment::Horizontal,
|
horizontal_alignment: alignment::Horizontal,
|
||||||
vertical_alignment: alignment::Vertical,
|
vertical_alignment: alignment::Vertical,
|
||||||
shaping: Shaping,
|
shaping: Shaping,
|
||||||
scale_factor: f32,
|
|
||||||
pixels: &mut tiny_skia::PixmapMut<'_>,
|
pixels: &mut tiny_skia::PixmapMut<'_>,
|
||||||
clip_mask: Option<&tiny_skia::Mask>,
|
clip_mask: Option<&tiny_skia::Mask>,
|
||||||
transformation: Transformation,
|
transformation: Transformation,
|
||||||
) {
|
) {
|
||||||
let line_height = f32::from(line_height.to_absolute(size));
|
let line_height = f32::from(line_height);
|
||||||
|
|
||||||
let mut font_system = font_system().write().expect("Write font system");
|
let mut font_system = font_system().write().expect("Write font system");
|
||||||
let font_system = font_system.raw();
|
let font_system = font_system.raw();
|
||||||
|
|
@ -149,7 +146,6 @@ impl Pipeline {
|
||||||
color,
|
color,
|
||||||
horizontal_alignment,
|
horizontal_alignment,
|
||||||
vertical_alignment,
|
vertical_alignment,
|
||||||
scale_factor,
|
|
||||||
pixels,
|
pixels,
|
||||||
clip_mask,
|
clip_mask,
|
||||||
transformation,
|
transformation,
|
||||||
|
|
@ -161,7 +157,6 @@ impl Pipeline {
|
||||||
buffer: &cosmic_text::Buffer,
|
buffer: &cosmic_text::Buffer,
|
||||||
position: Point,
|
position: Point,
|
||||||
color: Color,
|
color: Color,
|
||||||
scale_factor: f32,
|
|
||||||
pixels: &mut tiny_skia::PixmapMut<'_>,
|
pixels: &mut tiny_skia::PixmapMut<'_>,
|
||||||
clip_mask: Option<&tiny_skia::Mask>,
|
clip_mask: Option<&tiny_skia::Mask>,
|
||||||
transformation: Transformation,
|
transformation: Transformation,
|
||||||
|
|
@ -178,7 +173,6 @@ impl Pipeline {
|
||||||
color,
|
color,
|
||||||
alignment::Horizontal::Left,
|
alignment::Horizontal::Left,
|
||||||
alignment::Vertical::Top,
|
alignment::Vertical::Top,
|
||||||
scale_factor,
|
|
||||||
pixels,
|
pixels,
|
||||||
clip_mask,
|
clip_mask,
|
||||||
transformation,
|
transformation,
|
||||||
|
|
@ -199,12 +193,11 @@ fn draw(
|
||||||
color: Color,
|
color: Color,
|
||||||
horizontal_alignment: alignment::Horizontal,
|
horizontal_alignment: alignment::Horizontal,
|
||||||
vertical_alignment: alignment::Vertical,
|
vertical_alignment: alignment::Vertical,
|
||||||
scale_factor: f32,
|
|
||||||
pixels: &mut tiny_skia::PixmapMut<'_>,
|
pixels: &mut tiny_skia::PixmapMut<'_>,
|
||||||
clip_mask: Option<&tiny_skia::Mask>,
|
clip_mask: Option<&tiny_skia::Mask>,
|
||||||
transformation: Transformation,
|
transformation: Transformation,
|
||||||
) {
|
) {
|
||||||
let bounds = bounds * transformation * scale_factor;
|
let bounds = bounds * transformation;
|
||||||
|
|
||||||
let x = match horizontal_alignment {
|
let x = match horizontal_alignment {
|
||||||
alignment::Horizontal::Left => bounds.x,
|
alignment::Horizontal::Left => bounds.x,
|
||||||
|
|
@ -222,8 +215,8 @@ fn draw(
|
||||||
|
|
||||||
for run in buffer.layout_runs() {
|
for run in buffer.layout_runs() {
|
||||||
for glyph in run.glyphs {
|
for glyph in run.glyphs {
|
||||||
let physical_glyph = glyph
|
let physical_glyph =
|
||||||
.physical((x, y), scale_factor * transformation.scale_factor());
|
glyph.physical((x, y), transformation.scale_factor());
|
||||||
|
|
||||||
if let Some((buffer, placement)) = glyph_cache.allocate(
|
if let Some((buffer, placement)) = glyph_cache.allocate(
|
||||||
physical_glyph.cache_key,
|
physical_glyph.cache_key,
|
||||||
|
|
@ -247,10 +240,8 @@ fn draw(
|
||||||
pixels.draw_pixmap(
|
pixels.draw_pixmap(
|
||||||
physical_glyph.x + placement.left,
|
physical_glyph.x + placement.left,
|
||||||
physical_glyph.y - placement.top
|
physical_glyph.y - placement.top
|
||||||
+ (run.line_y
|
+ (run.line_y * transformation.scale_factor()).round()
|
||||||
* scale_factor
|
as i32,
|
||||||
* transformation.scale_factor())
|
|
||||||
.round() as i32,
|
|
||||||
pixmap,
|
pixmap,
|
||||||
&tiny_skia::PixmapPaint {
|
&tiny_skia::PixmapPaint {
|
||||||
opacity,
|
opacity,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use crate::graphics::compositor::{self, Information};
|
||||||
use crate::graphics::damage;
|
use crate::graphics::damage;
|
||||||
use crate::graphics::error::{self, Error};
|
use crate::graphics::error::{self, Error};
|
||||||
use crate::graphics::{self, Viewport};
|
use crate::graphics::{self, Viewport};
|
||||||
use crate::{Backend, Primitive, Renderer, Settings};
|
use crate::{Layer, Renderer, Settings};
|
||||||
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
|
|
@ -21,7 +21,7 @@ pub struct Surface {
|
||||||
Box<dyn compositor::Window>,
|
Box<dyn compositor::Window>,
|
||||||
>,
|
>,
|
||||||
clip_mask: tiny_skia::Mask,
|
clip_mask: tiny_skia::Mask,
|
||||||
primitive_stack: VecDeque<Vec<Primitive>>,
|
layer_stack: VecDeque<Vec<Layer>>,
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
max_age: u8,
|
max_age: u8,
|
||||||
}
|
}
|
||||||
|
|
@ -50,7 +50,6 @@ impl crate::graphics::Compositor for Compositor {
|
||||||
|
|
||||||
fn create_renderer(&self) -> Self::Renderer {
|
fn create_renderer(&self) -> Self::Renderer {
|
||||||
Renderer::new(
|
Renderer::new(
|
||||||
Backend::new(),
|
|
||||||
self.settings.default_font,
|
self.settings.default_font,
|
||||||
self.settings.default_text_size,
|
self.settings.default_text_size,
|
||||||
)
|
)
|
||||||
|
|
@ -72,7 +71,7 @@ impl crate::graphics::Compositor for Compositor {
|
||||||
window,
|
window,
|
||||||
clip_mask: tiny_skia::Mask::new(width, height)
|
clip_mask: tiny_skia::Mask::new(width, height)
|
||||||
.expect("Create clip mask"),
|
.expect("Create clip mask"),
|
||||||
primitive_stack: VecDeque::new(),
|
layer_stack: VecDeque::new(),
|
||||||
background_color: Color::BLACK,
|
background_color: Color::BLACK,
|
||||||
max_age: 0,
|
max_age: 0,
|
||||||
};
|
};
|
||||||
|
|
@ -98,7 +97,7 @@ impl crate::graphics::Compositor for Compositor {
|
||||||
|
|
||||||
surface.clip_mask =
|
surface.clip_mask =
|
||||||
tiny_skia::Mask::new(width, height).expect("Create clip mask");
|
tiny_skia::Mask::new(width, height).expect("Create clip mask");
|
||||||
surface.primitive_stack.clear();
|
surface.layer_stack.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_information(&self) -> Information {
|
fn fetch_information(&self) -> Information {
|
||||||
|
|
@ -116,16 +115,7 @@ impl crate::graphics::Compositor for Compositor {
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
overlay: &[T],
|
overlay: &[T],
|
||||||
) -> Result<(), compositor::SurfaceError> {
|
) -> Result<(), compositor::SurfaceError> {
|
||||||
renderer.with_primitives(|backend, primitives| {
|
present(renderer, surface, viewport, background_color, overlay)
|
||||||
present(
|
|
||||||
backend,
|
|
||||||
surface,
|
|
||||||
primitives,
|
|
||||||
viewport,
|
|
||||||
background_color,
|
|
||||||
overlay,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn screenshot<T: AsRef<str>>(
|
fn screenshot<T: AsRef<str>>(
|
||||||
|
|
@ -136,16 +126,7 @@ impl crate::graphics::Compositor for Compositor {
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
overlay: &[T],
|
overlay: &[T],
|
||||||
) -> Vec<u8> {
|
) -> Vec<u8> {
|
||||||
renderer.with_primitives(|backend, primitives| {
|
screenshot(renderer, surface, viewport, background_color, overlay)
|
||||||
screenshot(
|
|
||||||
surface,
|
|
||||||
backend,
|
|
||||||
primitives,
|
|
||||||
viewport,
|
|
||||||
background_color,
|
|
||||||
overlay,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,38 +142,42 @@ pub fn new<W: compositor::Window>(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn present<T: AsRef<str>>(
|
pub fn present<T: AsRef<str>>(
|
||||||
backend: &mut Backend,
|
renderer: &mut Renderer,
|
||||||
surface: &mut Surface,
|
surface: &mut Surface,
|
||||||
primitives: &[Primitive],
|
|
||||||
viewport: &Viewport,
|
viewport: &Viewport,
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
overlay: &[T],
|
overlay: &[T],
|
||||||
) -> Result<(), compositor::SurfaceError> {
|
) -> Result<(), compositor::SurfaceError> {
|
||||||
let physical_size = viewport.physical_size();
|
let physical_size = viewport.physical_size();
|
||||||
let scale_factor = viewport.scale_factor() as f32;
|
|
||||||
|
|
||||||
let mut buffer = surface
|
let mut buffer = surface
|
||||||
.window
|
.window
|
||||||
.buffer_mut()
|
.buffer_mut()
|
||||||
.map_err(|_| compositor::SurfaceError::Lost)?;
|
.map_err(|_| compositor::SurfaceError::Lost)?;
|
||||||
|
|
||||||
let last_primitives = {
|
let last_layers = {
|
||||||
let age = buffer.age();
|
let age = buffer.age();
|
||||||
|
|
||||||
surface.max_age = surface.max_age.max(age);
|
surface.max_age = surface.max_age.max(age);
|
||||||
surface.primitive_stack.truncate(surface.max_age as usize);
|
surface.layer_stack.truncate(surface.max_age as usize);
|
||||||
|
|
||||||
if age > 0 {
|
if age > 0 {
|
||||||
surface.primitive_stack.get(age as usize - 1)
|
surface.layer_stack.get(age as usize - 1)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let damage = last_primitives
|
let damage = last_layers
|
||||||
.and_then(|last_primitives| {
|
.and_then(|last_layers| {
|
||||||
(surface.background_color == background_color)
|
(surface.background_color == background_color).then(|| {
|
||||||
.then(|| damage::list(last_primitives, primitives))
|
damage::diff(
|
||||||
|
last_layers,
|
||||||
|
renderer.layers(),
|
||||||
|
|layer| vec![layer.bounds],
|
||||||
|
Layer::damage,
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| vec![Rectangle::with_size(viewport.logical_size())]);
|
.unwrap_or_else(|| vec![Rectangle::with_size(viewport.logical_size())]);
|
||||||
|
|
||||||
|
|
@ -200,10 +185,11 @@ pub fn present<T: AsRef<str>>(
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
surface.primitive_stack.push_front(primitives.to_vec());
|
surface.layer_stack.push_front(renderer.layers().to_vec());
|
||||||
surface.background_color = background_color;
|
surface.background_color = background_color;
|
||||||
|
|
||||||
let damage = damage::group(damage, scale_factor, physical_size);
|
let damage =
|
||||||
|
damage::group(damage, Rectangle::with_size(viewport.logical_size()));
|
||||||
|
|
||||||
let mut pixels = tiny_skia::PixmapMut::from_bytes(
|
let mut pixels = tiny_skia::PixmapMut::from_bytes(
|
||||||
bytemuck::cast_slice_mut(&mut buffer),
|
bytemuck::cast_slice_mut(&mut buffer),
|
||||||
|
|
@ -212,10 +198,9 @@ pub fn present<T: AsRef<str>>(
|
||||||
)
|
)
|
||||||
.expect("Create pixel map");
|
.expect("Create pixel map");
|
||||||
|
|
||||||
backend.draw(
|
renderer.draw(
|
||||||
&mut pixels,
|
&mut pixels,
|
||||||
&mut surface.clip_mask,
|
&mut surface.clip_mask,
|
||||||
primitives,
|
|
||||||
viewport,
|
viewport,
|
||||||
&damage,
|
&damage,
|
||||||
background_color,
|
background_color,
|
||||||
|
|
@ -226,9 +211,8 @@ pub fn present<T: AsRef<str>>(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn screenshot<T: AsRef<str>>(
|
pub fn screenshot<T: AsRef<str>>(
|
||||||
|
renderer: &mut Renderer,
|
||||||
surface: &mut Surface,
|
surface: &mut Surface,
|
||||||
backend: &mut Backend,
|
|
||||||
primitives: &[Primitive],
|
|
||||||
viewport: &Viewport,
|
viewport: &Viewport,
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
overlay: &[T],
|
overlay: &[T],
|
||||||
|
|
@ -238,7 +222,7 @@ pub fn screenshot<T: AsRef<str>>(
|
||||||
let mut offscreen_buffer: Vec<u32> =
|
let mut offscreen_buffer: Vec<u32> =
|
||||||
vec![0; size.width as usize * size.height as usize];
|
vec![0; size.width as usize * size.height as usize];
|
||||||
|
|
||||||
backend.draw(
|
renderer.draw(
|
||||||
&mut tiny_skia::PixmapMut::from_bytes(
|
&mut tiny_skia::PixmapMut::from_bytes(
|
||||||
bytemuck::cast_slice_mut(&mut offscreen_buffer),
|
bytemuck::cast_slice_mut(&mut offscreen_buffer),
|
||||||
size.width,
|
size.width,
|
||||||
|
|
@ -246,7 +230,6 @@ pub fn screenshot<T: AsRef<str>>(
|
||||||
)
|
)
|
||||||
.expect("Create offscreen pixel map"),
|
.expect("Create offscreen pixel map"),
|
||||||
&mut surface.clip_mask,
|
&mut surface.clip_mask,
|
||||||
primitives,
|
|
||||||
viewport,
|
viewport,
|
||||||
&[Rectangle::with_size(Size::new(
|
&[Rectangle::with_size(Size::new(
|
||||||
size.width as f32,
|
size.width as f32,
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,3 @@ lyon.optional = true
|
||||||
|
|
||||||
resvg.workspace = true
|
resvg.workspace = true
|
||||||
resvg.optional = true
|
resvg.optional = true
|
||||||
|
|
||||||
tracing.workspace = true
|
|
||||||
tracing.optional = true
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,432 +0,0 @@
|
||||||
use crate::buffer;
|
|
||||||
use crate::core::{Color, Size, Transformation};
|
|
||||||
use crate::graphics::backend;
|
|
||||||
use crate::graphics::color;
|
|
||||||
use crate::graphics::Viewport;
|
|
||||||
use crate::primitive::pipeline;
|
|
||||||
use crate::primitive::{self, Primitive};
|
|
||||||
use crate::quad;
|
|
||||||
use crate::text;
|
|
||||||
use crate::triangle;
|
|
||||||
use crate::window;
|
|
||||||
use crate::{Layer, Settings};
|
|
||||||
|
|
||||||
#[cfg(feature = "tracing")]
|
|
||||||
use tracing::info_span;
|
|
||||||
|
|
||||||
#[cfg(any(feature = "image", feature = "svg"))]
|
|
||||||
use crate::image;
|
|
||||||
|
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
/// A [`wgpu`] graphics backend for [`iced`].
|
|
||||||
///
|
|
||||||
/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
|
|
||||||
/// [`iced`]: https://github.com/iced-rs/iced
|
|
||||||
#[allow(missing_debug_implementations)]
|
|
||||||
pub struct Backend {
|
|
||||||
quad_pipeline: quad::Pipeline,
|
|
||||||
text_pipeline: text::Pipeline,
|
|
||||||
triangle_pipeline: triangle::Pipeline,
|
|
||||||
pipeline_storage: pipeline::Storage,
|
|
||||||
#[cfg(any(feature = "image", feature = "svg"))]
|
|
||||||
image_pipeline: image::Pipeline,
|
|
||||||
staging_belt: wgpu::util::StagingBelt,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Backend {
|
|
||||||
/// Creates a new [`Backend`].
|
|
||||||
pub fn new(
|
|
||||||
_adapter: &wgpu::Adapter,
|
|
||||||
device: &wgpu::Device,
|
|
||||||
queue: &wgpu::Queue,
|
|
||||||
settings: Settings,
|
|
||||||
format: wgpu::TextureFormat,
|
|
||||||
) -> Self {
|
|
||||||
let text_pipeline = text::Pipeline::new(device, queue, format);
|
|
||||||
let quad_pipeline = quad::Pipeline::new(device, format);
|
|
||||||
let triangle_pipeline =
|
|
||||||
triangle::Pipeline::new(device, format, settings.antialiasing);
|
|
||||||
|
|
||||||
#[cfg(any(feature = "image", feature = "svg"))]
|
|
||||||
let image_pipeline = {
|
|
||||||
let backend = _adapter.get_info().backend;
|
|
||||||
|
|
||||||
image::Pipeline::new(device, format, backend)
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
|
||||||
quad_pipeline,
|
|
||||||
text_pipeline,
|
|
||||||
triangle_pipeline,
|
|
||||||
pipeline_storage: pipeline::Storage::default(),
|
|
||||||
|
|
||||||
#[cfg(any(feature = "image", feature = "svg"))]
|
|
||||||
image_pipeline,
|
|
||||||
|
|
||||||
// TODO: Resize belt smartly (?)
|
|
||||||
// It would be great if the `StagingBelt` API exposed methods
|
|
||||||
// for introspection to detect when a resize may be worth it.
|
|
||||||
staging_belt: wgpu::util::StagingBelt::new(
|
|
||||||
buffer::MAX_WRITE_SIZE as u64,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draws the provided primitives in the given `TextureView`.
|
|
||||||
///
|
|
||||||
/// The text provided as overlay will be rendered on top of the primitives.
|
|
||||||
/// This is useful for rendering debug information.
|
|
||||||
pub fn present<T: AsRef<str>>(
|
|
||||||
&mut self,
|
|
||||||
device: &wgpu::Device,
|
|
||||||
queue: &wgpu::Queue,
|
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
|
||||||
clear_color: Option<Color>,
|
|
||||||
format: wgpu::TextureFormat,
|
|
||||||
frame: &wgpu::TextureView,
|
|
||||||
primitives: &[Primitive],
|
|
||||||
viewport: &Viewport,
|
|
||||||
overlay_text: &[T],
|
|
||||||
) {
|
|
||||||
log::debug!("Drawing");
|
|
||||||
#[cfg(feature = "tracing")]
|
|
||||||
let _ = info_span!("Wgpu::Backend", "PRESENT").entered();
|
|
||||||
|
|
||||||
let target_size = viewport.physical_size();
|
|
||||||
let scale_factor = viewport.scale_factor() as f32;
|
|
||||||
let transformation = viewport.projection();
|
|
||||||
|
|
||||||
let mut layers = Layer::generate(primitives, viewport);
|
|
||||||
|
|
||||||
if !overlay_text.is_empty() {
|
|
||||||
layers.push(Layer::overlay(overlay_text, viewport));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.prepare(
|
|
||||||
device,
|
|
||||||
queue,
|
|
||||||
format,
|
|
||||||
encoder,
|
|
||||||
scale_factor,
|
|
||||||
target_size,
|
|
||||||
transformation,
|
|
||||||
&layers,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.staging_belt.finish();
|
|
||||||
|
|
||||||
self.render(
|
|
||||||
device,
|
|
||||||
encoder,
|
|
||||||
frame,
|
|
||||||
clear_color,
|
|
||||||
scale_factor,
|
|
||||||
target_size,
|
|
||||||
&layers,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.quad_pipeline.end_frame();
|
|
||||||
self.text_pipeline.end_frame();
|
|
||||||
self.triangle_pipeline.end_frame();
|
|
||||||
|
|
||||||
#[cfg(any(feature = "image", feature = "svg"))]
|
|
||||||
self.image_pipeline.end_frame();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Recalls staging memory for future uploads.
|
|
||||||
///
|
|
||||||
/// This method should be called after the command encoder
|
|
||||||
/// has been submitted.
|
|
||||||
pub fn recall(&mut self) {
|
|
||||||
self.staging_belt.recall();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare(
|
|
||||||
&mut self,
|
|
||||||
device: &wgpu::Device,
|
|
||||||
queue: &wgpu::Queue,
|
|
||||||
format: wgpu::TextureFormat,
|
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
|
||||||
scale_factor: f32,
|
|
||||||
target_size: Size<u32>,
|
|
||||||
transformation: Transformation,
|
|
||||||
layers: &[Layer<'_>],
|
|
||||||
) {
|
|
||||||
for layer in layers {
|
|
||||||
let bounds = (layer.bounds * scale_factor).snap();
|
|
||||||
|
|
||||||
if bounds.width < 1 || bounds.height < 1 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !layer.quads.is_empty() {
|
|
||||||
self.quad_pipeline.prepare(
|
|
||||||
device,
|
|
||||||
encoder,
|
|
||||||
&mut self.staging_belt,
|
|
||||||
&layer.quads,
|
|
||||||
transformation,
|
|
||||||
scale_factor,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !layer.meshes.is_empty() {
|
|
||||||
let scaled =
|
|
||||||
transformation * Transformation::scale(scale_factor);
|
|
||||||
|
|
||||||
self.triangle_pipeline.prepare(
|
|
||||||
device,
|
|
||||||
encoder,
|
|
||||||
&mut self.staging_belt,
|
|
||||||
&layer.meshes,
|
|
||||||
scaled,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(feature = "image", feature = "svg"))]
|
|
||||||
{
|
|
||||||
if !layer.images.is_empty() {
|
|
||||||
let scaled =
|
|
||||||
transformation * Transformation::scale(scale_factor);
|
|
||||||
|
|
||||||
self.image_pipeline.prepare(
|
|
||||||
device,
|
|
||||||
encoder,
|
|
||||||
&mut self.staging_belt,
|
|
||||||
&layer.images,
|
|
||||||
scaled,
|
|
||||||
scale_factor,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !layer.text.is_empty() {
|
|
||||||
self.text_pipeline.prepare(
|
|
||||||
device,
|
|
||||||
queue,
|
|
||||||
encoder,
|
|
||||||
&layer.text,
|
|
||||||
layer.bounds,
|
|
||||||
scale_factor,
|
|
||||||
target_size,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !layer.pipelines.is_empty() {
|
|
||||||
for pipeline in &layer.pipelines {
|
|
||||||
pipeline.primitive.prepare(
|
|
||||||
format,
|
|
||||||
device,
|
|
||||||
queue,
|
|
||||||
pipeline.bounds,
|
|
||||||
target_size,
|
|
||||||
scale_factor,
|
|
||||||
&mut self.pipeline_storage,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(
|
|
||||||
&mut self,
|
|
||||||
device: &wgpu::Device,
|
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
|
||||||
target: &wgpu::TextureView,
|
|
||||||
clear_color: Option<Color>,
|
|
||||||
scale_factor: f32,
|
|
||||||
target_size: Size<u32>,
|
|
||||||
layers: &[Layer<'_>],
|
|
||||||
) {
|
|
||||||
use std::mem::ManuallyDrop;
|
|
||||||
|
|
||||||
let mut quad_layer = 0;
|
|
||||||
let mut triangle_layer = 0;
|
|
||||||
#[cfg(any(feature = "image", feature = "svg"))]
|
|
||||||
let mut image_layer = 0;
|
|
||||||
let mut text_layer = 0;
|
|
||||||
|
|
||||||
let mut render_pass = ManuallyDrop::new(encoder.begin_render_pass(
|
|
||||||
&wgpu::RenderPassDescriptor {
|
|
||||||
label: Some("iced_wgpu render pass"),
|
|
||||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
||||||
view: target,
|
|
||||||
resolve_target: None,
|
|
||||||
ops: wgpu::Operations {
|
|
||||||
load: match clear_color {
|
|
||||||
Some(background_color) => wgpu::LoadOp::Clear({
|
|
||||||
let [r, g, b, a] =
|
|
||||||
color::pack(background_color).components();
|
|
||||||
|
|
||||||
wgpu::Color {
|
|
||||||
r: f64::from(r),
|
|
||||||
g: f64::from(g),
|
|
||||||
b: f64::from(b),
|
|
||||||
a: f64::from(a),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
None => wgpu::LoadOp::Load,
|
|
||||||
},
|
|
||||||
store: wgpu::StoreOp::Store,
|
|
||||||
},
|
|
||||||
})],
|
|
||||||
depth_stencil_attachment: None,
|
|
||||||
timestamp_writes: None,
|
|
||||||
occlusion_query_set: None,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
|
|
||||||
for layer in layers {
|
|
||||||
let bounds = (layer.bounds * scale_factor).snap();
|
|
||||||
|
|
||||||
if bounds.width < 1 || bounds.height < 1 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !layer.quads.is_empty() {
|
|
||||||
self.quad_pipeline.render(
|
|
||||||
quad_layer,
|
|
||||||
bounds,
|
|
||||||
&layer.quads,
|
|
||||||
&mut render_pass,
|
|
||||||
);
|
|
||||||
|
|
||||||
quad_layer += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !layer.meshes.is_empty() {
|
|
||||||
let _ = ManuallyDrop::into_inner(render_pass);
|
|
||||||
|
|
||||||
self.triangle_pipeline.render(
|
|
||||||
device,
|
|
||||||
encoder,
|
|
||||||
target,
|
|
||||||
triangle_layer,
|
|
||||||
target_size,
|
|
||||||
&layer.meshes,
|
|
||||||
scale_factor,
|
|
||||||
);
|
|
||||||
|
|
||||||
triangle_layer += 1;
|
|
||||||
|
|
||||||
render_pass = ManuallyDrop::new(encoder.begin_render_pass(
|
|
||||||
&wgpu::RenderPassDescriptor {
|
|
||||||
label: Some("iced_wgpu render pass"),
|
|
||||||
color_attachments: &[Some(
|
|
||||||
wgpu::RenderPassColorAttachment {
|
|
||||||
view: target,
|
|
||||||
resolve_target: None,
|
|
||||||
ops: wgpu::Operations {
|
|
||||||
load: wgpu::LoadOp::Load,
|
|
||||||
store: wgpu::StoreOp::Store,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)],
|
|
||||||
depth_stencil_attachment: None,
|
|
||||||
timestamp_writes: None,
|
|
||||||
occlusion_query_set: None,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(feature = "image", feature = "svg"))]
|
|
||||||
{
|
|
||||||
if !layer.images.is_empty() {
|
|
||||||
self.image_pipeline.render(
|
|
||||||
image_layer,
|
|
||||||
bounds,
|
|
||||||
&mut render_pass,
|
|
||||||
);
|
|
||||||
|
|
||||||
image_layer += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !layer.text.is_empty() {
|
|
||||||
self.text_pipeline
|
|
||||||
.render(text_layer, bounds, &mut render_pass);
|
|
||||||
|
|
||||||
text_layer += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !layer.pipelines.is_empty() {
|
|
||||||
let _ = ManuallyDrop::into_inner(render_pass);
|
|
||||||
|
|
||||||
for pipeline in &layer.pipelines {
|
|
||||||
let viewport = (pipeline.viewport * scale_factor).snap();
|
|
||||||
|
|
||||||
if viewport.width < 1 || viewport.height < 1 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
pipeline.primitive.render(
|
|
||||||
&self.pipeline_storage,
|
|
||||||
target,
|
|
||||||
target_size,
|
|
||||||
viewport,
|
|
||||||
encoder,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render_pass = ManuallyDrop::new(encoder.begin_render_pass(
|
|
||||||
&wgpu::RenderPassDescriptor {
|
|
||||||
label: Some("iced_wgpu render pass"),
|
|
||||||
color_attachments: &[Some(
|
|
||||||
wgpu::RenderPassColorAttachment {
|
|
||||||
view: target,
|
|
||||||
resolve_target: None,
|
|
||||||
ops: wgpu::Operations {
|
|
||||||
load: wgpu::LoadOp::Load,
|
|
||||||
store: wgpu::StoreOp::Store,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)],
|
|
||||||
depth_stencil_attachment: None,
|
|
||||||
timestamp_writes: None,
|
|
||||||
occlusion_query_set: None,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = ManuallyDrop::into_inner(render_pass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl backend::Backend for Backend {
|
|
||||||
type Primitive = primitive::Custom;
|
|
||||||
type Compositor = window::Compositor;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl backend::Text for Backend {
|
|
||||||
fn load_font(&mut self, font: Cow<'static, [u8]>) {
|
|
||||||
self.text_pipeline.load_font(font);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "image")]
|
|
||||||
impl backend::Image for Backend {
|
|
||||||
fn dimensions(&self, handle: &crate::core::image::Handle) -> Size<u32> {
|
|
||||||
self.image_pipeline.dimensions(handle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "svg")]
|
|
||||||
impl backend::Svg for Backend {
|
|
||||||
fn viewport_dimensions(
|
|
||||||
&self,
|
|
||||||
handle: &crate::core::svg::Handle,
|
|
||||||
) -> Size<u32> {
|
|
||||||
self.image_pipeline.viewport_dimensions(handle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "geometry")]
|
|
||||||
impl crate::graphics::geometry::Backend for Backend {
|
|
||||||
type Frame = crate::geometry::Frame;
|
|
||||||
|
|
||||||
fn new_frame(&self, size: Size) -> Self::Frame {
|
|
||||||
crate::geometry::Frame::new(size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
84
wgpu/src/engine.rs
Normal file
84
wgpu/src/engine.rs
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
use crate::buffer;
|
||||||
|
use crate::graphics::Antialiasing;
|
||||||
|
use crate::primitive;
|
||||||
|
use crate::quad;
|
||||||
|
use crate::text;
|
||||||
|
use crate::triangle;
|
||||||
|
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub struct Engine {
|
||||||
|
pub(crate) staging_belt: wgpu::util::StagingBelt,
|
||||||
|
pub(crate) format: wgpu::TextureFormat,
|
||||||
|
|
||||||
|
pub(crate) quad_pipeline: quad::Pipeline,
|
||||||
|
pub(crate) text_pipeline: text::Pipeline,
|
||||||
|
pub(crate) triangle_pipeline: triangle::Pipeline,
|
||||||
|
#[cfg(any(feature = "image", feature = "svg"))]
|
||||||
|
pub(crate) image_pipeline: crate::image::Pipeline,
|
||||||
|
pub(crate) primitive_storage: primitive::Storage,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Engine {
|
||||||
|
pub fn new(
|
||||||
|
_adapter: &wgpu::Adapter,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
format: wgpu::TextureFormat,
|
||||||
|
antialiasing: Option<Antialiasing>, // TODO: Initialize AA pipelines lazily
|
||||||
|
) -> Self {
|
||||||
|
let text_pipeline = text::Pipeline::new(device, queue, format);
|
||||||
|
let quad_pipeline = quad::Pipeline::new(device, format);
|
||||||
|
let triangle_pipeline =
|
||||||
|
triangle::Pipeline::new(device, format, antialiasing);
|
||||||
|
|
||||||
|
#[cfg(any(feature = "image", feature = "svg"))]
|
||||||
|
let image_pipeline = {
|
||||||
|
let backend = _adapter.get_info().backend;
|
||||||
|
|
||||||
|
crate::image::Pipeline::new(device, format, backend)
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
// TODO: Resize belt smartly (?)
|
||||||
|
// It would be great if the `StagingBelt` API exposed methods
|
||||||
|
// for introspection to detect when a resize may be worth it.
|
||||||
|
staging_belt: wgpu::util::StagingBelt::new(
|
||||||
|
buffer::MAX_WRITE_SIZE as u64,
|
||||||
|
),
|
||||||
|
format,
|
||||||
|
|
||||||
|
quad_pipeline,
|
||||||
|
text_pipeline,
|
||||||
|
triangle_pipeline,
|
||||||
|
|
||||||
|
#[cfg(any(feature = "image", feature = "svg"))]
|
||||||
|
image_pipeline,
|
||||||
|
|
||||||
|
primitive_storage: primitive::Storage::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "image", feature = "svg"))]
|
||||||
|
pub fn image_cache(&self) -> &crate::image::cache::Shared {
|
||||||
|
self.image_pipeline.cache()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn submit(
|
||||||
|
&mut self,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
encoder: wgpu::CommandEncoder,
|
||||||
|
) -> wgpu::SubmissionIndex {
|
||||||
|
self.staging_belt.finish();
|
||||||
|
let index = queue.submit(Some(encoder.finish()));
|
||||||
|
self.staging_belt.recall();
|
||||||
|
|
||||||
|
self.quad_pipeline.end_frame();
|
||||||
|
self.text_pipeline.end_frame();
|
||||||
|
self.triangle_pipeline.end_frame();
|
||||||
|
|
||||||
|
#[cfg(any(feature = "image", feature = "svg"))]
|
||||||
|
self.image_pipeline.end_frame();
|
||||||
|
|
||||||
|
index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,23 +6,74 @@ use crate::core::{
|
||||||
use crate::graphics::color;
|
use crate::graphics::color;
|
||||||
use crate::graphics::geometry::fill::{self, Fill};
|
use crate::graphics::geometry::fill::{self, Fill};
|
||||||
use crate::graphics::geometry::{
|
use crate::graphics::geometry::{
|
||||||
self, LineCap, LineDash, LineJoin, Path, Stroke, Style, Text,
|
self, LineCap, LineDash, LineJoin, Path, Stroke, Style,
|
||||||
};
|
};
|
||||||
use crate::graphics::gradient::{self, Gradient};
|
use crate::graphics::gradient::{self, Gradient};
|
||||||
use crate::graphics::mesh::{self, Mesh};
|
use crate::graphics::mesh::{self, Mesh};
|
||||||
use crate::primitive::{self, Primitive};
|
use crate::graphics::{self, Cached, Text};
|
||||||
|
use crate::text;
|
||||||
|
use crate::triangle;
|
||||||
|
|
||||||
use lyon::geom::euclid;
|
use lyon::geom::euclid;
|
||||||
use lyon::tessellation;
|
use lyon::tessellation;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Geometry {
|
||||||
|
Live { meshes: Vec<Mesh>, text: Vec<Text> },
|
||||||
|
Cached(Cache),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Cache {
|
||||||
|
pub meshes: Option<triangle::Cache>,
|
||||||
|
pub text: Option<text::Cache>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cached for Geometry {
|
||||||
|
type Cache = Cache;
|
||||||
|
|
||||||
|
fn load(cache: &Self::Cache) -> Self {
|
||||||
|
Geometry::Cached(cache.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cache(self, previous: Option<Self::Cache>) -> Self::Cache {
|
||||||
|
match self {
|
||||||
|
Self::Live { meshes, text } => {
|
||||||
|
if let Some(mut previous) = previous {
|
||||||
|
if let Some(cache) = &mut previous.meshes {
|
||||||
|
cache.update(meshes);
|
||||||
|
} else {
|
||||||
|
previous.meshes = triangle::Cache::new(meshes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(cache) = &mut previous.text {
|
||||||
|
cache.update(text);
|
||||||
|
} else {
|
||||||
|
previous.text = text::Cache::new(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
previous
|
||||||
|
} else {
|
||||||
|
Cache {
|
||||||
|
meshes: triangle::Cache::new(meshes),
|
||||||
|
text: text::Cache::new(text),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Cached(cache) => cache,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A frame for drawing some geometry.
|
/// A frame for drawing some geometry.
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Frame {
|
pub struct Frame {
|
||||||
size: Size,
|
clip_bounds: Rectangle,
|
||||||
buffers: BufferStack,
|
buffers: BufferStack,
|
||||||
primitives: Vec<Primitive>,
|
meshes: Vec<Mesh>,
|
||||||
|
text: Vec<Text>,
|
||||||
transforms: Transforms,
|
transforms: Transforms,
|
||||||
fill_tessellator: tessellation::FillTessellator,
|
fill_tessellator: tessellation::FillTessellator,
|
||||||
stroke_tessellator: tessellation::StrokeTessellator,
|
stroke_tessellator: tessellation::StrokeTessellator,
|
||||||
|
|
@ -31,81 +82,49 @@ pub struct Frame {
|
||||||
impl Frame {
|
impl Frame {
|
||||||
/// Creates a new [`Frame`] with the given [`Size`].
|
/// Creates a new [`Frame`] with the given [`Size`].
|
||||||
pub fn new(size: Size) -> Frame {
|
pub fn new(size: Size) -> Frame {
|
||||||
|
Self::with_clip(Rectangle::with_size(size))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new [`Frame`] with the given clip bounds.
|
||||||
|
pub fn with_clip(bounds: Rectangle) -> Frame {
|
||||||
Frame {
|
Frame {
|
||||||
size,
|
clip_bounds: bounds,
|
||||||
buffers: BufferStack::new(),
|
buffers: BufferStack::new(),
|
||||||
primitives: Vec::new(),
|
meshes: Vec::new(),
|
||||||
|
text: Vec::new(),
|
||||||
transforms: Transforms {
|
transforms: Transforms {
|
||||||
previous: Vec::new(),
|
previous: Vec::new(),
|
||||||
current: Transform(lyon::math::Transform::identity()),
|
current: Transform(lyon::math::Transform::translation(
|
||||||
|
bounds.x, bounds.y,
|
||||||
|
)),
|
||||||
},
|
},
|
||||||
fill_tessellator: tessellation::FillTessellator::new(),
|
fill_tessellator: tessellation::FillTessellator::new(),
|
||||||
stroke_tessellator: tessellation::StrokeTessellator::new(),
|
stroke_tessellator: tessellation::StrokeTessellator::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_primitives(mut self) -> Vec<Primitive> {
|
|
||||||
for buffer in self.buffers.stack {
|
|
||||||
match buffer {
|
|
||||||
Buffer::Solid(buffer) => {
|
|
||||||
if !buffer.indices.is_empty() {
|
|
||||||
self.primitives.push(Primitive::Custom(
|
|
||||||
primitive::Custom::Mesh(Mesh::Solid {
|
|
||||||
buffers: mesh::Indexed {
|
|
||||||
vertices: buffer.vertices,
|
|
||||||
indices: buffer.indices,
|
|
||||||
},
|
|
||||||
size: self.size,
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Buffer::Gradient(buffer) => {
|
|
||||||
if !buffer.indices.is_empty() {
|
|
||||||
self.primitives.push(Primitive::Custom(
|
|
||||||
primitive::Custom::Mesh(Mesh::Gradient {
|
|
||||||
buffers: mesh::Indexed {
|
|
||||||
vertices: buffer.vertices,
|
|
||||||
indices: buffer.indices,
|
|
||||||
},
|
|
||||||
size: self.size,
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.primitives
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl geometry::frame::Backend for Frame {
|
impl geometry::frame::Backend for Frame {
|
||||||
type Geometry = Primitive;
|
type Geometry = Geometry;
|
||||||
|
|
||||||
/// Creates a new empty [`Frame`] with the given dimensions.
|
|
||||||
///
|
|
||||||
/// The default coordinate system of a [`Frame`] has its origin at the
|
|
||||||
/// top-left corner of its bounds.
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn width(&self) -> f32 {
|
fn width(&self) -> f32 {
|
||||||
self.size.width
|
self.clip_bounds.width
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn height(&self) -> f32 {
|
fn height(&self) -> f32 {
|
||||||
self.size.height
|
self.clip_bounds.height
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn size(&self) -> Size {
|
fn size(&self) -> Size {
|
||||||
self.size
|
self.clip_bounds.size()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn center(&self) -> Point {
|
fn center(&self) -> Point {
|
||||||
Point::new(self.size.width / 2.0, self.size.height / 2.0)
|
Point::new(self.clip_bounds.width / 2.0, self.clip_bounds.height / 2.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
|
fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
|
||||||
|
|
@ -208,7 +227,7 @@ impl geometry::frame::Backend for Frame {
|
||||||
.expect("Stroke path");
|
.expect("Stroke path");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fill_text(&mut self, text: impl Into<Text>) {
|
fn fill_text(&mut self, text: impl Into<geometry::Text>) {
|
||||||
let text = text.into();
|
let text = text.into();
|
||||||
|
|
||||||
let (scale_x, scale_y) = self.transforms.current.scale();
|
let (scale_x, scale_y) = self.transforms.current.scale();
|
||||||
|
|
@ -246,18 +265,17 @@ impl geometry::frame::Backend for Frame {
|
||||||
height: f32::INFINITY,
|
height: f32::INFINITY,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Honor layering!
|
self.text.push(graphics::Text::Cached {
|
||||||
self.primitives.push(Primitive::Text {
|
|
||||||
content: text.content,
|
content: text.content,
|
||||||
bounds,
|
bounds,
|
||||||
color: text.color,
|
color: text.color,
|
||||||
size,
|
size,
|
||||||
line_height,
|
line_height: line_height.to_absolute(size),
|
||||||
font: text.font,
|
font: text.font,
|
||||||
horizontal_alignment: text.horizontal_alignment,
|
horizontal_alignment: text.horizontal_alignment,
|
||||||
vertical_alignment: text.vertical_alignment,
|
vertical_alignment: text.vertical_alignment,
|
||||||
shaping: text.shaping,
|
shaping: text.shaping,
|
||||||
clip_bounds: Rectangle::with_size(Size::INFINITY),
|
clip_bounds: self.clip_bounds,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
text.draw_with(|path, color| self.fill(&path, color));
|
text.draw_with(|path, color| self.fill(&path, color));
|
||||||
|
|
@ -308,41 +326,24 @@ impl geometry::frame::Backend for Frame {
|
||||||
self.transforms.current = self.transforms.previous.pop().unwrap();
|
self.transforms.current = self.transforms.previous.pop().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draft(&mut self, size: Size) -> Frame {
|
fn draft(&mut self, clip_bounds: Rectangle) -> Frame {
|
||||||
Frame::new(size)
|
Frame::with_clip(clip_bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paste(&mut self, frame: Frame, at: Point) {
|
fn paste(&mut self, frame: Frame, _at: Point) {
|
||||||
let size = frame.size();
|
self.meshes
|
||||||
let primitives = frame.into_primitives();
|
.extend(frame.buffers.into_meshes(frame.clip_bounds));
|
||||||
let transformation = Transformation::translate(at.x, at.y);
|
|
||||||
|
|
||||||
let (text, meshes) = primitives
|
self.text.extend(frame.text);
|
||||||
.into_iter()
|
|
||||||
.partition(|primitive| matches!(primitive, Primitive::Text { .. }));
|
|
||||||
|
|
||||||
self.primitives.push(Primitive::Group {
|
|
||||||
primitives: vec![
|
|
||||||
Primitive::Transform {
|
|
||||||
transformation,
|
|
||||||
content: Box::new(Primitive::Group { primitives: meshes }),
|
|
||||||
},
|
|
||||||
Primitive::Transform {
|
|
||||||
transformation,
|
|
||||||
content: Box::new(Primitive::Clip {
|
|
||||||
bounds: Rectangle::with_size(size),
|
|
||||||
content: Box::new(Primitive::Group {
|
|
||||||
primitives: text,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_geometry(self) -> Self::Geometry {
|
fn into_geometry(mut self) -> Self::Geometry {
|
||||||
Primitive::Group {
|
self.meshes
|
||||||
primitives: self.into_primitives(),
|
.extend(self.buffers.into_meshes(self.clip_bounds));
|
||||||
|
|
||||||
|
Geometry::Live {
|
||||||
|
meshes: self.meshes,
|
||||||
|
text: self.text,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -429,6 +430,34 @@ impl BufferStack {
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn into_meshes(self, clip_bounds: Rectangle) -> impl Iterator<Item = Mesh> {
|
||||||
|
self.stack
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(move |buffer| match buffer {
|
||||||
|
Buffer::Solid(buffer) if !buffer.indices.is_empty() => {
|
||||||
|
Some(Mesh::Solid {
|
||||||
|
buffers: mesh::Indexed {
|
||||||
|
vertices: buffer.vertices,
|
||||||
|
indices: buffer.indices,
|
||||||
|
},
|
||||||
|
clip_bounds,
|
||||||
|
transformation: Transformation::IDENTITY,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Buffer::Gradient(buffer) if !buffer.indices.is_empty() => {
|
||||||
|
Some(Mesh::Gradient {
|
||||||
|
buffers: mesh::Indexed {
|
||||||
|
vertices: buffer.vertices,
|
||||||
|
indices: buffer.indices,
|
||||||
|
},
|
||||||
|
clip_bounds,
|
||||||
|
transformation: Transformation::IDENTITY,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -591,7 +620,9 @@ pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path {
|
||||||
let mut draw_line = false;
|
let mut draw_line = false;
|
||||||
|
|
||||||
walk_along_path(
|
walk_along_path(
|
||||||
path.raw().iter().flattened(0.01),
|
path.raw().iter().flattened(
|
||||||
|
lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE,
|
||||||
|
),
|
||||||
0.0,
|
0.0,
|
||||||
lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE,
|
lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE,
|
||||||
&mut RepeatedPattern {
|
&mut RepeatedPattern {
|
||||||
|
|
|
||||||
107
wgpu/src/image/cache.rs
Normal file
107
wgpu/src/image/cache.rs
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
use crate::core::{self, Size};
|
||||||
|
use crate::image::atlas::{self, Atlas};
|
||||||
|
|
||||||
|
use std::cell::{RefCell, RefMut};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Cache {
|
||||||
|
atlas: Atlas,
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
raster: crate::image::raster::Cache,
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
vector: crate::image::vector::Cache,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cache {
|
||||||
|
pub fn new(device: &wgpu::Device, backend: wgpu::Backend) -> Self {
|
||||||
|
Self {
|
||||||
|
atlas: Atlas::new(device, backend),
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
raster: crate::image::raster::Cache::default(),
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
vector: crate::image::vector::Cache::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn layer_count(&self) -> usize {
|
||||||
|
self.atlas.layer_count()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
pub fn measure_image(&mut self, handle: &core::image::Handle) -> Size<u32> {
|
||||||
|
self.raster.load(handle).dimensions()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
pub fn measure_svg(&mut self, handle: &core::svg::Handle) -> Size<u32> {
|
||||||
|
self.vector.load(handle).viewport_dimensions()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
pub fn upload_raster(
|
||||||
|
&mut self,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
handle: &core::image::Handle,
|
||||||
|
) -> Option<&atlas::Entry> {
|
||||||
|
self.raster.upload(device, encoder, handle, &mut self.atlas)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
pub fn upload_vector(
|
||||||
|
&mut self,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
handle: &core::svg::Handle,
|
||||||
|
color: Option<core::Color>,
|
||||||
|
size: [f32; 2],
|
||||||
|
scale: f32,
|
||||||
|
) -> Option<&atlas::Entry> {
|
||||||
|
self.vector.upload(
|
||||||
|
device,
|
||||||
|
encoder,
|
||||||
|
handle,
|
||||||
|
color,
|
||||||
|
size,
|
||||||
|
scale,
|
||||||
|
&mut self.atlas,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_bind_group(
|
||||||
|
&self,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
layout: &wgpu::BindGroupLayout,
|
||||||
|
) -> wgpu::BindGroup {
|
||||||
|
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
label: Some("iced_wgpu::image texture atlas bind group"),
|
||||||
|
layout,
|
||||||
|
entries: &[wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: wgpu::BindingResource::TextureView(self.atlas.view()),
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trim(&mut self) {
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
self.raster.trim(&mut self.atlas);
|
||||||
|
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
self.vector.trim(&mut self.atlas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Shared(Rc<RefCell<Cache>>);
|
||||||
|
|
||||||
|
impl Shared {
|
||||||
|
pub fn new(cache: Cache) -> Self {
|
||||||
|
Self(Rc::new(RefCell::new(cache)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lock(&self) -> RefMut<'_, Cache> {
|
||||||
|
self.0.borrow_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
pub(crate) mod cache;
|
||||||
|
pub(crate) use cache::Cache;
|
||||||
|
|
||||||
mod atlas;
|
mod atlas;
|
||||||
|
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
|
|
@ -6,46 +9,332 @@ mod raster;
|
||||||
#[cfg(feature = "svg")]
|
#[cfg(feature = "svg")]
|
||||||
mod vector;
|
mod vector;
|
||||||
|
|
||||||
use atlas::Atlas;
|
|
||||||
|
|
||||||
use crate::core::{Rectangle, Size, Transformation};
|
use crate::core::{Rectangle, Size, Transformation};
|
||||||
use crate::layer;
|
|
||||||
use crate::Buffer;
|
use crate::Buffer;
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use bytemuck::{Pod, Zeroable};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
use bytemuck::{Pod, Zeroable};
|
pub use crate::graphics::Image;
|
||||||
|
|
||||||
#[cfg(feature = "image")]
|
pub type Batch = Vec<Image>;
|
||||||
use crate::core::image;
|
|
||||||
|
|
||||||
#[cfg(feature = "svg")]
|
|
||||||
use crate::core::svg;
|
|
||||||
|
|
||||||
#[cfg(feature = "tracing")]
|
|
||||||
use tracing::info_span;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Pipeline {
|
pub struct Pipeline {
|
||||||
#[cfg(feature = "image")]
|
|
||||||
raster_cache: RefCell<raster::Cache>,
|
|
||||||
#[cfg(feature = "svg")]
|
|
||||||
vector_cache: RefCell<vector::Cache>,
|
|
||||||
|
|
||||||
pipeline: wgpu::RenderPipeline,
|
pipeline: wgpu::RenderPipeline,
|
||||||
nearest_sampler: wgpu::Sampler,
|
nearest_sampler: wgpu::Sampler,
|
||||||
linear_sampler: wgpu::Sampler,
|
linear_sampler: wgpu::Sampler,
|
||||||
texture: wgpu::BindGroup,
|
texture: wgpu::BindGroup,
|
||||||
texture_version: usize,
|
texture_version: usize,
|
||||||
texture_atlas: Atlas,
|
|
||||||
texture_layout: wgpu::BindGroupLayout,
|
texture_layout: wgpu::BindGroupLayout,
|
||||||
constant_layout: wgpu::BindGroupLayout,
|
constant_layout: wgpu::BindGroupLayout,
|
||||||
|
cache: cache::Shared,
|
||||||
layers: Vec<Layer>,
|
layers: Vec<Layer>,
|
||||||
prepare_layer: usize,
|
prepare_layer: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Pipeline {
|
||||||
|
pub fn new(
|
||||||
|
device: &wgpu::Device,
|
||||||
|
format: wgpu::TextureFormat,
|
||||||
|
backend: wgpu::Backend,
|
||||||
|
) -> Self {
|
||||||
|
let nearest_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
|
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||||
|
min_filter: wgpu::FilterMode::Nearest,
|
||||||
|
mag_filter: wgpu::FilterMode::Nearest,
|
||||||
|
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let linear_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
|
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||||
|
min_filter: wgpu::FilterMode::Linear,
|
||||||
|
mag_filter: wgpu::FilterMode::Linear,
|
||||||
|
mipmap_filter: wgpu::FilterMode::Linear,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let constant_layout =
|
||||||
|
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
label: Some("iced_wgpu::image constants layout"),
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: wgpu::ShaderStages::VERTEX,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: wgpu::BufferSize::new(
|
||||||
|
mem::size_of::<Uniforms>() as u64,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 1,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Sampler(
|
||||||
|
wgpu::SamplerBindingType::Filtering,
|
||||||
|
),
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
let texture_layout =
|
||||||
|
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
label: Some("iced_wgpu::image texture atlas layout"),
|
||||||
|
entries: &[wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Texture {
|
||||||
|
sample_type: wgpu::TextureSampleType::Float {
|
||||||
|
filterable: true,
|
||||||
|
},
|
||||||
|
view_dimension: wgpu::TextureViewDimension::D2Array,
|
||||||
|
multisampled: false,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
let layout =
|
||||||
|
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: Some("iced_wgpu::image pipeline layout"),
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
bind_group_layouts: &[&constant_layout, &texture_layout],
|
||||||
|
});
|
||||||
|
|
||||||
|
let shader =
|
||||||
|
device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||||
|
label: Some("iced_wgpu image shader"),
|
||||||
|
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
|
||||||
|
concat!(
|
||||||
|
include_str!("../shader/vertex.wgsl"),
|
||||||
|
"\n",
|
||||||
|
include_str!("../shader/image.wgsl"),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline =
|
||||||
|
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("iced_wgpu::image pipeline"),
|
||||||
|
layout: Some(&layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "vs_main",
|
||||||
|
buffers: &[wgpu::VertexBufferLayout {
|
||||||
|
array_stride: mem::size_of::<Instance>() as u64,
|
||||||
|
step_mode: wgpu::VertexStepMode::Instance,
|
||||||
|
attributes: &wgpu::vertex_attr_array!(
|
||||||
|
// Position
|
||||||
|
0 => Float32x2,
|
||||||
|
// Scale
|
||||||
|
1 => Float32x2,
|
||||||
|
// Atlas position
|
||||||
|
2 => Float32x2,
|
||||||
|
// Atlas scale
|
||||||
|
3 => Float32x2,
|
||||||
|
// Layer
|
||||||
|
4 => Sint32,
|
||||||
|
),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "fs_main",
|
||||||
|
targets: &[Some(wgpu::ColorTargetState {
|
||||||
|
format,
|
||||||
|
blend: Some(wgpu::BlendState {
|
||||||
|
color: wgpu::BlendComponent {
|
||||||
|
src_factor: wgpu::BlendFactor::SrcAlpha,
|
||||||
|
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
|
||||||
|
operation: wgpu::BlendOperation::Add,
|
||||||
|
},
|
||||||
|
alpha: wgpu::BlendComponent {
|
||||||
|
src_factor: wgpu::BlendFactor::One,
|
||||||
|
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
|
||||||
|
operation: wgpu::BlendOperation::Add,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
write_mask: wgpu::ColorWrites::ALL,
|
||||||
|
})],
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||||
|
front_face: wgpu::FrontFace::Cw,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState {
|
||||||
|
count: 1,
|
||||||
|
mask: !0,
|
||||||
|
alpha_to_coverage_enabled: false,
|
||||||
|
},
|
||||||
|
multiview: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let cache = Cache::new(device, backend);
|
||||||
|
let texture = cache.create_bind_group(device, &texture_layout);
|
||||||
|
|
||||||
|
Pipeline {
|
||||||
|
pipeline,
|
||||||
|
nearest_sampler,
|
||||||
|
linear_sampler,
|
||||||
|
texture,
|
||||||
|
texture_version: cache.layer_count(),
|
||||||
|
texture_layout,
|
||||||
|
constant_layout,
|
||||||
|
cache: cache::Shared::new(cache),
|
||||||
|
layers: Vec::new(),
|
||||||
|
prepare_layer: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cache(&self) -> &cache::Shared {
|
||||||
|
&self.cache
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prepare(
|
||||||
|
&mut self,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
belt: &mut wgpu::util::StagingBelt,
|
||||||
|
images: &Batch,
|
||||||
|
transformation: Transformation,
|
||||||
|
scale: f32,
|
||||||
|
) {
|
||||||
|
let transformation = transformation * Transformation::scale(scale);
|
||||||
|
|
||||||
|
let nearest_instances: &mut Vec<Instance> = &mut Vec::new();
|
||||||
|
let linear_instances: &mut Vec<Instance> = &mut Vec::new();
|
||||||
|
|
||||||
|
let mut cache = self.cache.lock();
|
||||||
|
|
||||||
|
for image in images {
|
||||||
|
match &image {
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
Image::Raster {
|
||||||
|
handle,
|
||||||
|
filter_method,
|
||||||
|
bounds,
|
||||||
|
} => {
|
||||||
|
if let Some(atlas_entry) =
|
||||||
|
cache.upload_raster(device, encoder, handle)
|
||||||
|
{
|
||||||
|
add_instances(
|
||||||
|
[bounds.x, bounds.y],
|
||||||
|
[bounds.width, bounds.height],
|
||||||
|
atlas_entry,
|
||||||
|
match filter_method {
|
||||||
|
crate::core::image::FilterMethod::Nearest => {
|
||||||
|
nearest_instances
|
||||||
|
}
|
||||||
|
crate::core::image::FilterMethod::Linear => {
|
||||||
|
linear_instances
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "image"))]
|
||||||
|
Image::Raster { .. } => {}
|
||||||
|
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
Image::Vector {
|
||||||
|
handle,
|
||||||
|
color,
|
||||||
|
bounds,
|
||||||
|
} => {
|
||||||
|
let size = [bounds.width, bounds.height];
|
||||||
|
|
||||||
|
if let Some(atlas_entry) = cache.upload_vector(
|
||||||
|
device, encoder, handle, *color, size, scale,
|
||||||
|
) {
|
||||||
|
add_instances(
|
||||||
|
[bounds.x, bounds.y],
|
||||||
|
size,
|
||||||
|
atlas_entry,
|
||||||
|
nearest_instances,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "svg"))]
|
||||||
|
Image::Vector { .. } => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if nearest_instances.is_empty() && linear_instances.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let texture_version = cache.layer_count();
|
||||||
|
|
||||||
|
if self.texture_version != texture_version {
|
||||||
|
log::info!("Atlas has grown. Recreating bind group...");
|
||||||
|
|
||||||
|
self.texture =
|
||||||
|
cache.create_bind_group(device, &self.texture_layout);
|
||||||
|
self.texture_version = texture_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.layers.len() <= self.prepare_layer {
|
||||||
|
self.layers.push(Layer::new(
|
||||||
|
device,
|
||||||
|
&self.constant_layout,
|
||||||
|
&self.nearest_sampler,
|
||||||
|
&self.linear_sampler,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let layer = &mut self.layers[self.prepare_layer];
|
||||||
|
|
||||||
|
layer.prepare(
|
||||||
|
device,
|
||||||
|
encoder,
|
||||||
|
belt,
|
||||||
|
nearest_instances,
|
||||||
|
linear_instances,
|
||||||
|
transformation,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.prepare_layer += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render<'a>(
|
||||||
|
&'a self,
|
||||||
|
layer: usize,
|
||||||
|
bounds: Rectangle<u32>,
|
||||||
|
render_pass: &mut wgpu::RenderPass<'a>,
|
||||||
|
) {
|
||||||
|
if let Some(layer) = self.layers.get(layer) {
|
||||||
|
render_pass.set_pipeline(&self.pipeline);
|
||||||
|
|
||||||
|
render_pass.set_scissor_rect(
|
||||||
|
bounds.x,
|
||||||
|
bounds.y,
|
||||||
|
bounds.width,
|
||||||
|
bounds.height,
|
||||||
|
);
|
||||||
|
|
||||||
|
render_pass.set_bind_group(1, &self.texture, &[]);
|
||||||
|
|
||||||
|
layer.render(render_pass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn end_frame(&mut self) {
|
||||||
|
self.cache.lock().trim();
|
||||||
|
self.prepare_layer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Layer {
|
struct Layer {
|
||||||
uniforms: wgpu::Buffer,
|
uniforms: wgpu::Buffer,
|
||||||
|
|
@ -194,367 +483,6 @@ impl Data {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pipeline {
|
|
||||||
pub fn new(
|
|
||||||
device: &wgpu::Device,
|
|
||||||
format: wgpu::TextureFormat,
|
|
||||||
backend: wgpu::Backend,
|
|
||||||
) -> Self {
|
|
||||||
let nearest_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
|
||||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
|
||||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
|
||||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
|
||||||
min_filter: wgpu::FilterMode::Nearest,
|
|
||||||
mag_filter: wgpu::FilterMode::Nearest,
|
|
||||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
|
|
||||||
let linear_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
|
||||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
|
||||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
|
||||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
|
||||||
min_filter: wgpu::FilterMode::Linear,
|
|
||||||
mag_filter: wgpu::FilterMode::Linear,
|
|
||||||
mipmap_filter: wgpu::FilterMode::Linear,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
|
|
||||||
let constant_layout =
|
|
||||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
|
||||||
label: Some("iced_wgpu::image constants layout"),
|
|
||||||
entries: &[
|
|
||||||
wgpu::BindGroupLayoutEntry {
|
|
||||||
binding: 0,
|
|
||||||
visibility: wgpu::ShaderStages::VERTEX,
|
|
||||||
ty: wgpu::BindingType::Buffer {
|
|
||||||
ty: wgpu::BufferBindingType::Uniform,
|
|
||||||
has_dynamic_offset: false,
|
|
||||||
min_binding_size: wgpu::BufferSize::new(
|
|
||||||
mem::size_of::<Uniforms>() as u64,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
count: None,
|
|
||||||
},
|
|
||||||
wgpu::BindGroupLayoutEntry {
|
|
||||||
binding: 1,
|
|
||||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
|
||||||
ty: wgpu::BindingType::Sampler(
|
|
||||||
wgpu::SamplerBindingType::Filtering,
|
|
||||||
),
|
|
||||||
count: None,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
let texture_layout =
|
|
||||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
|
||||||
label: Some("iced_wgpu::image texture atlas layout"),
|
|
||||||
entries: &[wgpu::BindGroupLayoutEntry {
|
|
||||||
binding: 0,
|
|
||||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
|
||||||
ty: wgpu::BindingType::Texture {
|
|
||||||
sample_type: wgpu::TextureSampleType::Float {
|
|
||||||
filterable: true,
|
|
||||||
},
|
|
||||||
view_dimension: wgpu::TextureViewDimension::D2Array,
|
|
||||||
multisampled: false,
|
|
||||||
},
|
|
||||||
count: None,
|
|
||||||
}],
|
|
||||||
});
|
|
||||||
|
|
||||||
let layout =
|
|
||||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
|
||||||
label: Some("iced_wgpu::image pipeline layout"),
|
|
||||||
push_constant_ranges: &[],
|
|
||||||
bind_group_layouts: &[&constant_layout, &texture_layout],
|
|
||||||
});
|
|
||||||
|
|
||||||
let shader =
|
|
||||||
device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
|
||||||
label: Some("iced_wgpu image shader"),
|
|
||||||
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
|
|
||||||
concat!(
|
|
||||||
include_str!("shader/vertex.wgsl"),
|
|
||||||
"\n",
|
|
||||||
include_str!("shader/image.wgsl"),
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
});
|
|
||||||
|
|
||||||
let pipeline =
|
|
||||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
|
||||||
label: Some("iced_wgpu::image pipeline"),
|
|
||||||
layout: Some(&layout),
|
|
||||||
vertex: wgpu::VertexState {
|
|
||||||
module: &shader,
|
|
||||||
entry_point: "vs_main",
|
|
||||||
buffers: &[wgpu::VertexBufferLayout {
|
|
||||||
array_stride: mem::size_of::<Instance>() as u64,
|
|
||||||
step_mode: wgpu::VertexStepMode::Instance,
|
|
||||||
attributes: &wgpu::vertex_attr_array!(
|
|
||||||
// Position
|
|
||||||
0 => Float32x2,
|
|
||||||
// Scale
|
|
||||||
1 => Float32x2,
|
|
||||||
// Atlas position
|
|
||||||
2 => Float32x2,
|
|
||||||
// Atlas scale
|
|
||||||
3 => Float32x2,
|
|
||||||
// Layer
|
|
||||||
4 => Sint32,
|
|
||||||
),
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
fragment: Some(wgpu::FragmentState {
|
|
||||||
module: &shader,
|
|
||||||
entry_point: "fs_main",
|
|
||||||
targets: &[Some(wgpu::ColorTargetState {
|
|
||||||
format,
|
|
||||||
blend: Some(wgpu::BlendState {
|
|
||||||
color: wgpu::BlendComponent {
|
|
||||||
src_factor: wgpu::BlendFactor::SrcAlpha,
|
|
||||||
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
|
|
||||||
operation: wgpu::BlendOperation::Add,
|
|
||||||
},
|
|
||||||
alpha: wgpu::BlendComponent {
|
|
||||||
src_factor: wgpu::BlendFactor::One,
|
|
||||||
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
|
|
||||||
operation: wgpu::BlendOperation::Add,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
write_mask: wgpu::ColorWrites::ALL,
|
|
||||||
})],
|
|
||||||
}),
|
|
||||||
primitive: wgpu::PrimitiveState {
|
|
||||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
|
||||||
front_face: wgpu::FrontFace::Cw,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
depth_stencil: None,
|
|
||||||
multisample: wgpu::MultisampleState {
|
|
||||||
count: 1,
|
|
||||||
mask: !0,
|
|
||||||
alpha_to_coverage_enabled: false,
|
|
||||||
},
|
|
||||||
multiview: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
let texture_atlas = Atlas::new(device, backend);
|
|
||||||
|
|
||||||
let texture = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
||||||
label: Some("iced_wgpu::image texture atlas bind group"),
|
|
||||||
layout: &texture_layout,
|
|
||||||
entries: &[wgpu::BindGroupEntry {
|
|
||||||
binding: 0,
|
|
||||||
resource: wgpu::BindingResource::TextureView(
|
|
||||||
texture_atlas.view(),
|
|
||||||
),
|
|
||||||
}],
|
|
||||||
});
|
|
||||||
|
|
||||||
Pipeline {
|
|
||||||
#[cfg(feature = "image")]
|
|
||||||
raster_cache: RefCell::new(raster::Cache::default()),
|
|
||||||
|
|
||||||
#[cfg(feature = "svg")]
|
|
||||||
vector_cache: RefCell::new(vector::Cache::default()),
|
|
||||||
|
|
||||||
pipeline,
|
|
||||||
nearest_sampler,
|
|
||||||
linear_sampler,
|
|
||||||
texture,
|
|
||||||
texture_version: texture_atlas.layer_count(),
|
|
||||||
texture_atlas,
|
|
||||||
texture_layout,
|
|
||||||
constant_layout,
|
|
||||||
|
|
||||||
layers: Vec::new(),
|
|
||||||
prepare_layer: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "image")]
|
|
||||||
pub fn dimensions(&self, handle: &image::Handle) -> Size<u32> {
|
|
||||||
let mut cache = self.raster_cache.borrow_mut();
|
|
||||||
let memory = cache.load(handle);
|
|
||||||
|
|
||||||
memory.dimensions()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "svg")]
|
|
||||||
pub fn viewport_dimensions(&self, handle: &svg::Handle) -> Size<u32> {
|
|
||||||
let mut cache = self.vector_cache.borrow_mut();
|
|
||||||
let svg = cache.load(handle);
|
|
||||||
|
|
||||||
svg.viewport_dimensions()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prepare(
|
|
||||||
&mut self,
|
|
||||||
device: &wgpu::Device,
|
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
|
||||||
belt: &mut wgpu::util::StagingBelt,
|
|
||||||
images: &[layer::Image],
|
|
||||||
transformation: Transformation,
|
|
||||||
_scale: f32,
|
|
||||||
) {
|
|
||||||
#[cfg(feature = "tracing")]
|
|
||||||
let _ = info_span!("Wgpu::Image", "PREPARE").entered();
|
|
||||||
|
|
||||||
#[cfg(feature = "tracing")]
|
|
||||||
let _ = info_span!("Wgpu::Image", "DRAW").entered();
|
|
||||||
|
|
||||||
let nearest_instances: &mut Vec<Instance> = &mut Vec::new();
|
|
||||||
let linear_instances: &mut Vec<Instance> = &mut Vec::new();
|
|
||||||
|
|
||||||
#[cfg(feature = "image")]
|
|
||||||
let mut raster_cache = self.raster_cache.borrow_mut();
|
|
||||||
|
|
||||||
#[cfg(feature = "svg")]
|
|
||||||
let mut vector_cache = self.vector_cache.borrow_mut();
|
|
||||||
|
|
||||||
for image in images {
|
|
||||||
match &image {
|
|
||||||
#[cfg(feature = "image")]
|
|
||||||
layer::Image::Raster {
|
|
||||||
handle,
|
|
||||||
filter_method,
|
|
||||||
bounds,
|
|
||||||
} => {
|
|
||||||
if let Some(atlas_entry) = raster_cache.upload(
|
|
||||||
device,
|
|
||||||
encoder,
|
|
||||||
handle,
|
|
||||||
&mut self.texture_atlas,
|
|
||||||
) {
|
|
||||||
add_instances(
|
|
||||||
[bounds.x, bounds.y],
|
|
||||||
[bounds.width, bounds.height],
|
|
||||||
atlas_entry,
|
|
||||||
match filter_method {
|
|
||||||
image::FilterMethod::Nearest => {
|
|
||||||
nearest_instances
|
|
||||||
}
|
|
||||||
image::FilterMethod::Linear => linear_instances,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "image"))]
|
|
||||||
layer::Image::Raster { .. } => {}
|
|
||||||
|
|
||||||
#[cfg(feature = "svg")]
|
|
||||||
layer::Image::Vector {
|
|
||||||
handle,
|
|
||||||
color,
|
|
||||||
bounds,
|
|
||||||
} => {
|
|
||||||
let size = [bounds.width, bounds.height];
|
|
||||||
|
|
||||||
if let Some(atlas_entry) = vector_cache.upload(
|
|
||||||
device,
|
|
||||||
encoder,
|
|
||||||
handle,
|
|
||||||
*color,
|
|
||||||
size,
|
|
||||||
_scale,
|
|
||||||
&mut self.texture_atlas,
|
|
||||||
) {
|
|
||||||
add_instances(
|
|
||||||
[bounds.x, bounds.y],
|
|
||||||
size,
|
|
||||||
atlas_entry,
|
|
||||||
nearest_instances,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "svg"))]
|
|
||||||
layer::Image::Vector { .. } => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if nearest_instances.is_empty() && linear_instances.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let texture_version = self.texture_atlas.layer_count();
|
|
||||||
|
|
||||||
if self.texture_version != texture_version {
|
|
||||||
log::info!("Atlas has grown. Recreating bind group...");
|
|
||||||
|
|
||||||
self.texture =
|
|
||||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
||||||
label: Some("iced_wgpu::image texture atlas bind group"),
|
|
||||||
layout: &self.texture_layout,
|
|
||||||
entries: &[wgpu::BindGroupEntry {
|
|
||||||
binding: 0,
|
|
||||||
resource: wgpu::BindingResource::TextureView(
|
|
||||||
self.texture_atlas.view(),
|
|
||||||
),
|
|
||||||
}],
|
|
||||||
});
|
|
||||||
|
|
||||||
self.texture_version = texture_version;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.layers.len() <= self.prepare_layer {
|
|
||||||
self.layers.push(Layer::new(
|
|
||||||
device,
|
|
||||||
&self.constant_layout,
|
|
||||||
&self.nearest_sampler,
|
|
||||||
&self.linear_sampler,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let layer = &mut self.layers[self.prepare_layer];
|
|
||||||
|
|
||||||
layer.prepare(
|
|
||||||
device,
|
|
||||||
encoder,
|
|
||||||
belt,
|
|
||||||
nearest_instances,
|
|
||||||
linear_instances,
|
|
||||||
transformation,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.prepare_layer += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render<'a>(
|
|
||||||
&'a self,
|
|
||||||
layer: usize,
|
|
||||||
bounds: Rectangle<u32>,
|
|
||||||
render_pass: &mut wgpu::RenderPass<'a>,
|
|
||||||
) {
|
|
||||||
if let Some(layer) = self.layers.get(layer) {
|
|
||||||
render_pass.set_pipeline(&self.pipeline);
|
|
||||||
|
|
||||||
render_pass.set_scissor_rect(
|
|
||||||
bounds.x,
|
|
||||||
bounds.y,
|
|
||||||
bounds.width,
|
|
||||||
bounds.height,
|
|
||||||
);
|
|
||||||
|
|
||||||
render_pass.set_bind_group(1, &self.texture, &[]);
|
|
||||||
|
|
||||||
layer.render(render_pass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn end_frame(&mut self) {
|
|
||||||
#[cfg(feature = "image")]
|
|
||||||
self.raster_cache.borrow_mut().trim(&mut self.texture_atlas);
|
|
||||||
|
|
||||||
#[cfg(feature = "svg")]
|
|
||||||
self.vector_cache.borrow_mut().trim(&mut self.texture_atlas);
|
|
||||||
|
|
||||||
self.prepare_layer = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||||
struct Instance {
|
struct Instance {
|
||||||
10
wgpu/src/image/null.rs
Normal file
10
wgpu/src/image/null.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
pub use crate::graphics::Image;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Batch;
|
||||||
|
|
||||||
|
impl Batch {
|
||||||
|
pub fn push(&mut self, _image: Image) {}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {}
|
||||||
|
}
|
||||||
|
|
@ -1,343 +1,293 @@
|
||||||
//! Organize rendering primitives into a flattened list of layers.
|
use crate::core::renderer;
|
||||||
mod image;
|
use crate::core::{Background, Color, Point, Rectangle, Transformation};
|
||||||
mod pipeline;
|
|
||||||
mod text;
|
|
||||||
|
|
||||||
pub mod mesh;
|
|
||||||
|
|
||||||
pub use image::Image;
|
|
||||||
pub use mesh::Mesh;
|
|
||||||
pub use pipeline::Pipeline;
|
|
||||||
pub use text::Text;
|
|
||||||
|
|
||||||
use crate::core;
|
|
||||||
use crate::core::alignment;
|
|
||||||
use crate::core::{
|
|
||||||
Color, Font, Pixels, Point, Rectangle, Size, Transformation, Vector,
|
|
||||||
};
|
|
||||||
use crate::graphics;
|
use crate::graphics;
|
||||||
use crate::graphics::color;
|
use crate::graphics::color;
|
||||||
use crate::graphics::Viewport;
|
use crate::graphics::layer;
|
||||||
|
use crate::graphics::text::{Editor, Paragraph};
|
||||||
|
use crate::graphics::Mesh;
|
||||||
|
use crate::image::{self, Image};
|
||||||
use crate::primitive::{self, Primitive};
|
use crate::primitive::{self, Primitive};
|
||||||
use crate::quad::{self, Quad};
|
use crate::quad::{self, Quad};
|
||||||
|
use crate::text::{self, Text};
|
||||||
|
use crate::triangle;
|
||||||
|
|
||||||
|
pub type Stack = layer::Stack<Layer>;
|
||||||
|
|
||||||
/// A group of primitives that should be clipped together.
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Layer<'a> {
|
pub struct Layer {
|
||||||
/// The clipping bounds of the [`Layer`].
|
|
||||||
pub bounds: Rectangle,
|
pub bounds: Rectangle,
|
||||||
|
|
||||||
/// The quads of the [`Layer`].
|
|
||||||
pub quads: quad::Batch,
|
pub quads: quad::Batch,
|
||||||
|
pub triangles: triangle::Batch,
|
||||||
/// The triangle meshes of the [`Layer`].
|
pub primitives: primitive::Batch,
|
||||||
pub meshes: Vec<Mesh<'a>>,
|
pub text: text::Batch,
|
||||||
|
pub images: image::Batch,
|
||||||
/// The text of the [`Layer`].
|
pending_meshes: Vec<Mesh>,
|
||||||
pub text: Vec<Text<'a>>,
|
pending_text: Vec<Text>,
|
||||||
|
|
||||||
/// The images of the [`Layer`].
|
|
||||||
pub images: Vec<Image>,
|
|
||||||
|
|
||||||
/// The custom pipelines of this [`Layer`].
|
|
||||||
pub pipelines: Vec<Pipeline>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Layer<'a> {
|
impl Layer {
|
||||||
/// Creates a new [`Layer`] with the given clipping bounds.
|
pub fn draw_quad(
|
||||||
pub fn new(bounds: Rectangle) -> Self {
|
&mut self,
|
||||||
|
quad: renderer::Quad,
|
||||||
|
background: Background,
|
||||||
|
transformation: Transformation,
|
||||||
|
) {
|
||||||
|
let bounds = quad.bounds * transformation;
|
||||||
|
|
||||||
|
let quad = Quad {
|
||||||
|
position: [bounds.x, bounds.y],
|
||||||
|
size: [bounds.width, bounds.height],
|
||||||
|
border_color: color::pack(quad.border.color),
|
||||||
|
border_radius: quad.border.radius.into(),
|
||||||
|
border_width: quad.border.width,
|
||||||
|
shadow_color: color::pack(quad.shadow.color),
|
||||||
|
shadow_offset: quad.shadow.offset.into(),
|
||||||
|
shadow_blur_radius: quad.shadow.blur_radius,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.quads.add(quad, &background);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_paragraph(
|
||||||
|
&mut self,
|
||||||
|
paragraph: &Paragraph,
|
||||||
|
position: Point,
|
||||||
|
color: Color,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
transformation: Transformation,
|
||||||
|
) {
|
||||||
|
let paragraph = Text::Paragraph {
|
||||||
|
paragraph: paragraph.downgrade(),
|
||||||
|
position,
|
||||||
|
color,
|
||||||
|
clip_bounds,
|
||||||
|
transformation,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.pending_text.push(paragraph);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_editor(
|
||||||
|
&mut self,
|
||||||
|
editor: &Editor,
|
||||||
|
position: Point,
|
||||||
|
color: Color,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
transformation: Transformation,
|
||||||
|
) {
|
||||||
|
let editor = Text::Editor {
|
||||||
|
editor: editor.downgrade(),
|
||||||
|
position,
|
||||||
|
color,
|
||||||
|
clip_bounds,
|
||||||
|
transformation,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.pending_text.push(editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_text(
|
||||||
|
&mut self,
|
||||||
|
text: crate::core::Text,
|
||||||
|
position: Point,
|
||||||
|
color: Color,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
transformation: Transformation,
|
||||||
|
) {
|
||||||
|
let text = Text::Cached {
|
||||||
|
content: text.content,
|
||||||
|
bounds: Rectangle::new(position, text.bounds) * transformation,
|
||||||
|
color,
|
||||||
|
size: text.size * transformation.scale_factor(),
|
||||||
|
line_height: text.line_height.to_absolute(text.size)
|
||||||
|
* transformation.scale_factor(),
|
||||||
|
font: text.font,
|
||||||
|
horizontal_alignment: text.horizontal_alignment,
|
||||||
|
vertical_alignment: text.vertical_alignment,
|
||||||
|
shaping: text.shaping,
|
||||||
|
clip_bounds: clip_bounds * transformation,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.pending_text.push(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_image(
|
||||||
|
&mut self,
|
||||||
|
handle: crate::core::image::Handle,
|
||||||
|
filter_method: crate::core::image::FilterMethod,
|
||||||
|
bounds: Rectangle,
|
||||||
|
transformation: Transformation,
|
||||||
|
) {
|
||||||
|
let image = Image::Raster {
|
||||||
|
handle,
|
||||||
|
filter_method,
|
||||||
|
bounds: bounds * transformation,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.images.push(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_svg(
|
||||||
|
&mut self,
|
||||||
|
handle: crate::core::svg::Handle,
|
||||||
|
color: Option<Color>,
|
||||||
|
bounds: Rectangle,
|
||||||
|
transformation: Transformation,
|
||||||
|
) {
|
||||||
|
let svg = Image::Vector {
|
||||||
|
handle,
|
||||||
|
color,
|
||||||
|
bounds: bounds * transformation,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.images.push(svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_mesh(
|
||||||
|
&mut self,
|
||||||
|
mut mesh: Mesh,
|
||||||
|
transformation: Transformation,
|
||||||
|
) {
|
||||||
|
match &mut mesh {
|
||||||
|
Mesh::Solid {
|
||||||
|
transformation: local_transformation,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| Mesh::Gradient {
|
||||||
|
transformation: local_transformation,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
*local_transformation = *local_transformation * transformation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.pending_meshes.push(mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_mesh_group(
|
||||||
|
&mut self,
|
||||||
|
meshes: Vec<Mesh>,
|
||||||
|
transformation: Transformation,
|
||||||
|
) {
|
||||||
|
self.flush_meshes();
|
||||||
|
|
||||||
|
self.triangles.push(triangle::Item::Group {
|
||||||
|
meshes,
|
||||||
|
transformation,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_mesh_cache(
|
||||||
|
&mut self,
|
||||||
|
cache: triangle::Cache,
|
||||||
|
transformation: Transformation,
|
||||||
|
) {
|
||||||
|
self.flush_meshes();
|
||||||
|
|
||||||
|
self.triangles.push(triangle::Item::Cached {
|
||||||
|
cache,
|
||||||
|
transformation,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_text_group(
|
||||||
|
&mut self,
|
||||||
|
text: Vec<Text>,
|
||||||
|
transformation: Transformation,
|
||||||
|
) {
|
||||||
|
self.flush_text();
|
||||||
|
|
||||||
|
self.text.push(text::Item::Group {
|
||||||
|
text,
|
||||||
|
transformation,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_text_cache(
|
||||||
|
&mut self,
|
||||||
|
cache: text::Cache,
|
||||||
|
transformation: Transformation,
|
||||||
|
) {
|
||||||
|
self.flush_text();
|
||||||
|
|
||||||
|
self.text.push(text::Item::Cached {
|
||||||
|
cache,
|
||||||
|
transformation,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_primitive(
|
||||||
|
&mut self,
|
||||||
|
bounds: Rectangle,
|
||||||
|
primitive: Box<dyn Primitive>,
|
||||||
|
transformation: Transformation,
|
||||||
|
) {
|
||||||
|
let bounds = bounds * transformation;
|
||||||
|
|
||||||
|
self.primitives
|
||||||
|
.push(primitive::Instance { bounds, primitive });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush_meshes(&mut self) {
|
||||||
|
if !self.pending_meshes.is_empty() {
|
||||||
|
self.triangles.push(triangle::Item::Group {
|
||||||
|
transformation: Transformation::IDENTITY,
|
||||||
|
meshes: self.pending_meshes.drain(..).collect(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush_text(&mut self) {
|
||||||
|
if !self.pending_text.is_empty() {
|
||||||
|
self.text.push(text::Item::Group {
|
||||||
|
transformation: Transformation::IDENTITY,
|
||||||
|
text: self.pending_text.drain(..).collect(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl graphics::Layer for Layer {
|
||||||
|
fn with_bounds(bounds: Rectangle) -> Self {
|
||||||
Self {
|
Self {
|
||||||
bounds,
|
bounds,
|
||||||
|
..Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) {
|
||||||
|
self.flush_meshes();
|
||||||
|
self.flush_text();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resize(&mut self, bounds: Rectangle) {
|
||||||
|
self.bounds = bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.bounds = Rectangle::INFINITE;
|
||||||
|
|
||||||
|
self.quads.clear();
|
||||||
|
self.triangles.clear();
|
||||||
|
self.primitives.clear();
|
||||||
|
self.text.clear();
|
||||||
|
self.images.clear();
|
||||||
|
self.pending_meshes.clear();
|
||||||
|
self.pending_text.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Layer {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
bounds: Rectangle::INFINITE,
|
||||||
quads: quad::Batch::default(),
|
quads: quad::Batch::default(),
|
||||||
meshes: Vec::new(),
|
triangles: triangle::Batch::default(),
|
||||||
text: Vec::new(),
|
primitives: primitive::Batch::default(),
|
||||||
images: Vec::new(),
|
text: text::Batch::default(),
|
||||||
pipelines: Vec::new(),
|
images: image::Batch::default(),
|
||||||
}
|
pending_meshes: Vec::new(),
|
||||||
}
|
pending_text: Vec::new(),
|
||||||
|
|
||||||
/// Creates a new [`Layer`] for the provided overlay text.
|
|
||||||
///
|
|
||||||
/// This can be useful for displaying debug information.
|
|
||||||
pub fn overlay(lines: &'a [impl AsRef<str>], viewport: &Viewport) -> Self {
|
|
||||||
let mut overlay =
|
|
||||||
Layer::new(Rectangle::with_size(viewport.logical_size()));
|
|
||||||
|
|
||||||
for (i, line) in lines.iter().enumerate() {
|
|
||||||
let text = text::Cached {
|
|
||||||
content: line.as_ref(),
|
|
||||||
bounds: Rectangle::new(
|
|
||||||
Point::new(11.0, 11.0 + 25.0 * i as f32),
|
|
||||||
Size::INFINITY,
|
|
||||||
),
|
|
||||||
color: Color::new(0.9, 0.9, 0.9, 1.0),
|
|
||||||
size: Pixels(20.0),
|
|
||||||
line_height: core::text::LineHeight::default(),
|
|
||||||
font: Font::MONOSPACE,
|
|
||||||
horizontal_alignment: alignment::Horizontal::Left,
|
|
||||||
vertical_alignment: alignment::Vertical::Top,
|
|
||||||
shaping: core::text::Shaping::Basic,
|
|
||||||
clip_bounds: Rectangle::with_size(Size::INFINITY),
|
|
||||||
};
|
|
||||||
|
|
||||||
overlay.text.push(Text::Cached(text.clone()));
|
|
||||||
|
|
||||||
overlay.text.push(Text::Cached(text::Cached {
|
|
||||||
bounds: text.bounds + Vector::new(-1.0, -1.0),
|
|
||||||
color: Color::BLACK,
|
|
||||||
..text
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
overlay
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Distributes the given [`Primitive`] and generates a list of layers based
|
|
||||||
/// on its contents.
|
|
||||||
pub fn generate(
|
|
||||||
primitives: &'a [Primitive],
|
|
||||||
viewport: &Viewport,
|
|
||||||
) -> Vec<Self> {
|
|
||||||
let first_layer =
|
|
||||||
Layer::new(Rectangle::with_size(viewport.logical_size()));
|
|
||||||
|
|
||||||
let mut layers = vec![first_layer];
|
|
||||||
|
|
||||||
for primitive in primitives {
|
|
||||||
Self::process_primitive(
|
|
||||||
&mut layers,
|
|
||||||
Transformation::IDENTITY,
|
|
||||||
primitive,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
layers
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_primitive(
|
|
||||||
layers: &mut Vec<Self>,
|
|
||||||
transformation: Transformation,
|
|
||||||
primitive: &'a Primitive,
|
|
||||||
current_layer: usize,
|
|
||||||
) {
|
|
||||||
match primitive {
|
|
||||||
Primitive::Paragraph {
|
|
||||||
paragraph,
|
|
||||||
position,
|
|
||||||
color,
|
|
||||||
clip_bounds,
|
|
||||||
} => {
|
|
||||||
let layer = &mut layers[current_layer];
|
|
||||||
|
|
||||||
layer.text.push(Text::Paragraph {
|
|
||||||
paragraph: paragraph.clone(),
|
|
||||||
position: *position,
|
|
||||||
color: *color,
|
|
||||||
clip_bounds: *clip_bounds,
|
|
||||||
transformation,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Primitive::Editor {
|
|
||||||
editor,
|
|
||||||
position,
|
|
||||||
color,
|
|
||||||
clip_bounds,
|
|
||||||
} => {
|
|
||||||
let layer = &mut layers[current_layer];
|
|
||||||
|
|
||||||
layer.text.push(Text::Editor {
|
|
||||||
editor: editor.clone(),
|
|
||||||
position: *position,
|
|
||||||
color: *color,
|
|
||||||
clip_bounds: *clip_bounds,
|
|
||||||
transformation,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Primitive::Text {
|
|
||||||
content,
|
|
||||||
bounds,
|
|
||||||
size,
|
|
||||||
line_height,
|
|
||||||
color,
|
|
||||||
font,
|
|
||||||
horizontal_alignment,
|
|
||||||
vertical_alignment,
|
|
||||||
shaping,
|
|
||||||
clip_bounds,
|
|
||||||
} => {
|
|
||||||
let layer = &mut layers[current_layer];
|
|
||||||
|
|
||||||
layer.text.push(Text::Cached(text::Cached {
|
|
||||||
content,
|
|
||||||
bounds: *bounds + transformation.translation(),
|
|
||||||
size: *size * transformation.scale_factor(),
|
|
||||||
line_height: *line_height,
|
|
||||||
color: *color,
|
|
||||||
font: *font,
|
|
||||||
horizontal_alignment: *horizontal_alignment,
|
|
||||||
vertical_alignment: *vertical_alignment,
|
|
||||||
shaping: *shaping,
|
|
||||||
clip_bounds: *clip_bounds * transformation,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
graphics::Primitive::RawText(raw) => {
|
|
||||||
let layer = &mut layers[current_layer];
|
|
||||||
|
|
||||||
layer.text.push(Text::Raw {
|
|
||||||
raw: raw.clone(),
|
|
||||||
transformation,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Primitive::Quad {
|
|
||||||
bounds,
|
|
||||||
background,
|
|
||||||
border,
|
|
||||||
shadow,
|
|
||||||
} => {
|
|
||||||
let layer = &mut layers[current_layer];
|
|
||||||
let bounds = *bounds * transformation;
|
|
||||||
|
|
||||||
let quad = Quad {
|
|
||||||
position: [bounds.x, bounds.y],
|
|
||||||
size: [bounds.width, bounds.height],
|
|
||||||
border_color: color::pack(border.color),
|
|
||||||
border_radius: border.radius.into(),
|
|
||||||
border_width: border.width,
|
|
||||||
shadow_color: shadow.color.into_linear(),
|
|
||||||
shadow_offset: shadow.offset.into(),
|
|
||||||
shadow_blur_radius: shadow.blur_radius,
|
|
||||||
};
|
|
||||||
|
|
||||||
layer.quads.add(quad, background);
|
|
||||||
}
|
|
||||||
Primitive::Image {
|
|
||||||
handle,
|
|
||||||
filter_method,
|
|
||||||
bounds,
|
|
||||||
} => {
|
|
||||||
let layer = &mut layers[current_layer];
|
|
||||||
|
|
||||||
layer.images.push(Image::Raster {
|
|
||||||
handle: handle.clone(),
|
|
||||||
filter_method: *filter_method,
|
|
||||||
bounds: *bounds * transformation,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Primitive::Svg {
|
|
||||||
handle,
|
|
||||||
color,
|
|
||||||
bounds,
|
|
||||||
} => {
|
|
||||||
let layer = &mut layers[current_layer];
|
|
||||||
|
|
||||||
layer.images.push(Image::Vector {
|
|
||||||
handle: handle.clone(),
|
|
||||||
color: *color,
|
|
||||||
bounds: *bounds * transformation,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Primitive::Group { primitives } => {
|
|
||||||
// TODO: Inspect a bit and regroup (?)
|
|
||||||
for primitive in primitives {
|
|
||||||
Self::process_primitive(
|
|
||||||
layers,
|
|
||||||
transformation,
|
|
||||||
primitive,
|
|
||||||
current_layer,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Primitive::Clip { bounds, content } => {
|
|
||||||
let layer = &mut layers[current_layer];
|
|
||||||
let translated_bounds = *bounds * transformation;
|
|
||||||
|
|
||||||
// Only draw visible content
|
|
||||||
if let Some(clip_bounds) =
|
|
||||||
layer.bounds.intersection(&translated_bounds)
|
|
||||||
{
|
|
||||||
let clip_layer = Layer::new(clip_bounds);
|
|
||||||
layers.push(clip_layer);
|
|
||||||
|
|
||||||
Self::process_primitive(
|
|
||||||
layers,
|
|
||||||
transformation,
|
|
||||||
content,
|
|
||||||
layers.len() - 1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Primitive::Transform {
|
|
||||||
transformation: new_transformation,
|
|
||||||
content,
|
|
||||||
} => {
|
|
||||||
Self::process_primitive(
|
|
||||||
layers,
|
|
||||||
transformation * *new_transformation,
|
|
||||||
content,
|
|
||||||
current_layer,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Primitive::Cache { content } => {
|
|
||||||
Self::process_primitive(
|
|
||||||
layers,
|
|
||||||
transformation,
|
|
||||||
content,
|
|
||||||
current_layer,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Primitive::Custom(custom) => match custom {
|
|
||||||
primitive::Custom::Mesh(mesh) => match mesh {
|
|
||||||
graphics::Mesh::Solid { buffers, size } => {
|
|
||||||
let layer = &mut layers[current_layer];
|
|
||||||
|
|
||||||
let bounds =
|
|
||||||
Rectangle::with_size(*size) * transformation;
|
|
||||||
|
|
||||||
// Only draw visible content
|
|
||||||
if let Some(clip_bounds) =
|
|
||||||
layer.bounds.intersection(&bounds)
|
|
||||||
{
|
|
||||||
layer.meshes.push(Mesh::Solid {
|
|
||||||
transformation,
|
|
||||||
buffers,
|
|
||||||
clip_bounds,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
graphics::Mesh::Gradient { buffers, size } => {
|
|
||||||
let layer = &mut layers[current_layer];
|
|
||||||
|
|
||||||
let bounds =
|
|
||||||
Rectangle::with_size(*size) * transformation;
|
|
||||||
|
|
||||||
// Only draw visible content
|
|
||||||
if let Some(clip_bounds) =
|
|
||||||
layer.bounds.intersection(&bounds)
|
|
||||||
{
|
|
||||||
layer.meshes.push(Mesh::Gradient {
|
|
||||||
transformation,
|
|
||||||
buffers,
|
|
||||||
clip_bounds,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
primitive::Custom::Pipeline(pipeline) => {
|
|
||||||
let layer = &mut layers[current_layer];
|
|
||||||
let bounds = pipeline.bounds * transformation;
|
|
||||||
|
|
||||||
if let Some(clip_bounds) =
|
|
||||||
layer.bounds.intersection(&bounds)
|
|
||||||
{
|
|
||||||
layer.pipelines.push(Pipeline {
|
|
||||||
bounds,
|
|
||||||
viewport: clip_bounds,
|
|
||||||
primitive: pipeline.primitive.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
use crate::core::image;
|
|
||||||
use crate::core::svg;
|
|
||||||
use crate::core::{Color, Rectangle};
|
|
||||||
|
|
||||||
/// A raster or vector image.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Image {
|
|
||||||
/// A raster image.
|
|
||||||
Raster {
|
|
||||||
/// The handle of a raster image.
|
|
||||||
handle: image::Handle,
|
|
||||||
|
|
||||||
/// The filter method of a raster image.
|
|
||||||
filter_method: image::FilterMethod,
|
|
||||||
|
|
||||||
/// The bounds of the image.
|
|
||||||
bounds: Rectangle,
|
|
||||||
},
|
|
||||||
/// A vector image.
|
|
||||||
Vector {
|
|
||||||
/// The handle of a vector image.
|
|
||||||
handle: svg::Handle,
|
|
||||||
|
|
||||||
/// The [`Color`] filter
|
|
||||||
color: Option<Color>,
|
|
||||||
|
|
||||||
/// The bounds of the image.
|
|
||||||
bounds: Rectangle,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
@ -1,97 +0,0 @@
|
||||||
//! A collection of triangle primitives.
|
|
||||||
use crate::core::{Rectangle, Transformation};
|
|
||||||
use crate::graphics::mesh;
|
|
||||||
|
|
||||||
/// A mesh of triangles.
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub enum Mesh<'a> {
|
|
||||||
/// A mesh of triangles with a solid color.
|
|
||||||
Solid {
|
|
||||||
/// The [`Transformation`] for the vertices of the [`Mesh`].
|
|
||||||
transformation: Transformation,
|
|
||||||
|
|
||||||
/// The vertex and index buffers of the [`Mesh`].
|
|
||||||
buffers: &'a mesh::Indexed<mesh::SolidVertex2D>,
|
|
||||||
|
|
||||||
/// The clipping bounds of the [`Mesh`].
|
|
||||||
clip_bounds: Rectangle<f32>,
|
|
||||||
},
|
|
||||||
/// A mesh of triangles with a gradient color.
|
|
||||||
Gradient {
|
|
||||||
/// The [`Transformation`] for the vertices of the [`Mesh`].
|
|
||||||
transformation: Transformation,
|
|
||||||
|
|
||||||
/// The vertex and index buffers of the [`Mesh`].
|
|
||||||
buffers: &'a mesh::Indexed<mesh::GradientVertex2D>,
|
|
||||||
|
|
||||||
/// The clipping bounds of the [`Mesh`].
|
|
||||||
clip_bounds: Rectangle<f32>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Mesh<'_> {
|
|
||||||
/// Returns the origin of the [`Mesh`].
|
|
||||||
pub fn transformation(&self) -> Transformation {
|
|
||||||
match self {
|
|
||||||
Self::Solid { transformation, .. }
|
|
||||||
| Self::Gradient { transformation, .. } => *transformation,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the indices of the [`Mesh`].
|
|
||||||
pub fn indices(&self) -> &[u32] {
|
|
||||||
match self {
|
|
||||||
Self::Solid { buffers, .. } => &buffers.indices,
|
|
||||||
Self::Gradient { buffers, .. } => &buffers.indices,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the clip bounds of the [`Mesh`].
|
|
||||||
pub fn clip_bounds(&self) -> Rectangle<f32> {
|
|
||||||
match self {
|
|
||||||
Self::Solid { clip_bounds, .. }
|
|
||||||
| Self::Gradient { clip_bounds, .. } => *clip_bounds,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The result of counting the attributes of a set of meshes.
|
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
|
||||||
pub struct AttributeCount {
|
|
||||||
/// The total amount of solid vertices.
|
|
||||||
pub solid_vertices: usize,
|
|
||||||
|
|
||||||
/// The total amount of solid meshes.
|
|
||||||
pub solids: usize,
|
|
||||||
|
|
||||||
/// The total amount of gradient vertices.
|
|
||||||
pub gradient_vertices: usize,
|
|
||||||
|
|
||||||
/// The total amount of gradient meshes.
|
|
||||||
pub gradients: usize,
|
|
||||||
|
|
||||||
/// The total amount of indices.
|
|
||||||
pub indices: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the number of total vertices & total indices of all [`Mesh`]es.
|
|
||||||
pub fn attribute_count_of<'a>(meshes: &'a [Mesh<'a>]) -> AttributeCount {
|
|
||||||
meshes
|
|
||||||
.iter()
|
|
||||||
.fold(AttributeCount::default(), |mut count, mesh| {
|
|
||||||
match mesh {
|
|
||||||
Mesh::Solid { buffers, .. } => {
|
|
||||||
count.solids += 1;
|
|
||||||
count.solid_vertices += buffers.vertices.len();
|
|
||||||
count.indices += buffers.indices.len();
|
|
||||||
}
|
|
||||||
Mesh::Gradient { buffers, .. } => {
|
|
||||||
count.gradients += 1;
|
|
||||||
count.gradient_vertices += buffers.vertices.len();
|
|
||||||
count.indices += buffers.indices.len();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
count
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
use crate::core::Rectangle;
|
|
||||||
use crate::primitive::pipeline::Primitive;
|
|
||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
/// A custom primitive which can be used to render primitives associated with a custom pipeline.
|
|
||||||
pub struct Pipeline {
|
|
||||||
/// The bounds of the [`Pipeline`].
|
|
||||||
pub bounds: Rectangle,
|
|
||||||
|
|
||||||
/// The viewport of the [`Pipeline`].
|
|
||||||
pub viewport: Rectangle,
|
|
||||||
|
|
||||||
/// The [`Primitive`] to render.
|
|
||||||
pub primitive: Arc<dyn Primitive>,
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
use crate::core::alignment;
|
|
||||||
use crate::core::text;
|
|
||||||
use crate::core::{Color, Font, Pixels, Point, Rectangle, Transformation};
|
|
||||||
use crate::graphics;
|
|
||||||
use crate::graphics::text::editor;
|
|
||||||
use crate::graphics::text::paragraph;
|
|
||||||
|
|
||||||
/// A text primitive.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Text<'a> {
|
|
||||||
/// A paragraph.
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
Paragraph {
|
|
||||||
paragraph: paragraph::Weak,
|
|
||||||
position: Point,
|
|
||||||
color: Color,
|
|
||||||
clip_bounds: Rectangle,
|
|
||||||
transformation: Transformation,
|
|
||||||
},
|
|
||||||
/// An editor.
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
Editor {
|
|
||||||
editor: editor::Weak,
|
|
||||||
position: Point,
|
|
||||||
color: Color,
|
|
||||||
clip_bounds: Rectangle,
|
|
||||||
transformation: Transformation,
|
|
||||||
},
|
|
||||||
/// Some cached text.
|
|
||||||
Cached(Cached<'a>),
|
|
||||||
/// Some raw text.
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
Raw {
|
|
||||||
raw: graphics::text::Raw,
|
|
||||||
transformation: Transformation,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Cached<'a> {
|
|
||||||
/// The content of the [`Text`].
|
|
||||||
pub content: &'a str,
|
|
||||||
|
|
||||||
/// The layout bounds of the [`Text`].
|
|
||||||
pub bounds: Rectangle,
|
|
||||||
|
|
||||||
/// The color of the [`Text`], in __linear RGB_.
|
|
||||||
pub color: Color,
|
|
||||||
|
|
||||||
/// The size of the [`Text`] in logical pixels.
|
|
||||||
pub size: Pixels,
|
|
||||||
|
|
||||||
/// The line height of the [`Text`].
|
|
||||||
pub line_height: text::LineHeight,
|
|
||||||
|
|
||||||
/// The font of the [`Text`].
|
|
||||||
pub font: Font,
|
|
||||||
|
|
||||||
/// The horizontal alignment of the [`Text`].
|
|
||||||
pub horizontal_alignment: alignment::Horizontal,
|
|
||||||
|
|
||||||
/// The vertical alignment of the [`Text`].
|
|
||||||
pub vertical_alignment: alignment::Vertical,
|
|
||||||
|
|
||||||
/// The shaping strategy of the text.
|
|
||||||
pub shaping: text::Shaping,
|
|
||||||
|
|
||||||
/// The clip bounds of the text.
|
|
||||||
pub clip_bounds: Rectangle,
|
|
||||||
}
|
|
||||||
541
wgpu/src/lib.rs
541
wgpu/src/lib.rs
|
|
@ -21,6 +21,7 @@
|
||||||
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
|
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
|
||||||
)]
|
)]
|
||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
|
#![allow(missing_docs)]
|
||||||
pub mod layer;
|
pub mod layer;
|
||||||
pub mod primitive;
|
pub mod primitive;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
|
|
@ -29,13 +30,21 @@ pub mod window;
|
||||||
#[cfg(feature = "geometry")]
|
#[cfg(feature = "geometry")]
|
||||||
pub mod geometry;
|
pub mod geometry;
|
||||||
|
|
||||||
mod backend;
|
|
||||||
mod buffer;
|
mod buffer;
|
||||||
mod color;
|
mod color;
|
||||||
|
mod engine;
|
||||||
mod quad;
|
mod quad;
|
||||||
mod text;
|
mod text;
|
||||||
mod triangle;
|
mod triangle;
|
||||||
|
|
||||||
|
#[cfg(any(feature = "image", feature = "svg"))]
|
||||||
|
#[path = "image/mod.rs"]
|
||||||
|
mod image;
|
||||||
|
|
||||||
|
#[cfg(not(any(feature = "image", feature = "svg")))]
|
||||||
|
#[path = "image/null.rs"]
|
||||||
|
mod image;
|
||||||
|
|
||||||
use buffer::Buffer;
|
use buffer::Buffer;
|
||||||
|
|
||||||
pub use iced_graphics as graphics;
|
pub use iced_graphics as graphics;
|
||||||
|
|
@ -43,16 +52,538 @@ pub use iced_graphics::core;
|
||||||
|
|
||||||
pub use wgpu;
|
pub use wgpu;
|
||||||
|
|
||||||
pub use backend::Backend;
|
pub use engine::Engine;
|
||||||
pub use layer::Layer;
|
pub use layer::Layer;
|
||||||
pub use primitive::Primitive;
|
pub use primitive::Primitive;
|
||||||
pub use settings::Settings;
|
pub use settings::Settings;
|
||||||
|
|
||||||
#[cfg(any(feature = "image", feature = "svg"))]
|
#[cfg(feature = "geometry")]
|
||||||
mod image;
|
pub use geometry::Geometry;
|
||||||
|
|
||||||
|
use crate::core::{
|
||||||
|
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
|
||||||
|
};
|
||||||
|
use crate::graphics::text::{Editor, Paragraph};
|
||||||
|
use crate::graphics::Viewport;
|
||||||
|
|
||||||
/// A [`wgpu`] graphics renderer for [`iced`].
|
/// A [`wgpu`] graphics renderer for [`iced`].
|
||||||
///
|
///
|
||||||
/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
|
/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
|
||||||
/// [`iced`]: https://github.com/iced-rs/iced
|
/// [`iced`]: https://github.com/iced-rs/iced
|
||||||
pub type Renderer = iced_graphics::Renderer<Backend>;
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub struct Renderer {
|
||||||
|
default_font: Font,
|
||||||
|
default_text_size: Pixels,
|
||||||
|
layers: layer::Stack,
|
||||||
|
|
||||||
|
triangle_storage: triangle::Storage,
|
||||||
|
text_storage: text::Storage,
|
||||||
|
|
||||||
|
// TODO: Centralize all the image feature handling
|
||||||
|
#[cfg(any(feature = "svg", feature = "image"))]
|
||||||
|
image_cache: image::cache::Shared,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Renderer {
|
||||||
|
pub fn new(
|
||||||
|
_engine: &Engine,
|
||||||
|
default_font: Font,
|
||||||
|
default_text_size: Pixels,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
default_font,
|
||||||
|
default_text_size,
|
||||||
|
layers: layer::Stack::new(),
|
||||||
|
|
||||||
|
triangle_storage: triangle::Storage::new(),
|
||||||
|
text_storage: text::Storage::new(),
|
||||||
|
|
||||||
|
#[cfg(any(feature = "svg", feature = "image"))]
|
||||||
|
image_cache: _engine.image_cache().clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn present<T: AsRef<str>>(
|
||||||
|
&mut self,
|
||||||
|
engine: &mut Engine,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
clear_color: Option<Color>,
|
||||||
|
format: wgpu::TextureFormat,
|
||||||
|
frame: &wgpu::TextureView,
|
||||||
|
viewport: &Viewport,
|
||||||
|
overlay: &[T],
|
||||||
|
) {
|
||||||
|
self.draw_overlay(overlay, viewport);
|
||||||
|
self.prepare(engine, device, queue, format, encoder, viewport);
|
||||||
|
self.render(engine, encoder, frame, clear_color, viewport);
|
||||||
|
|
||||||
|
self.triangle_storage.trim();
|
||||||
|
self.text_storage.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare(
|
||||||
|
&mut self,
|
||||||
|
engine: &mut Engine,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
_format: wgpu::TextureFormat,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
viewport: &Viewport,
|
||||||
|
) {
|
||||||
|
let scale_factor = viewport.scale_factor() as f32;
|
||||||
|
|
||||||
|
for layer in self.layers.iter_mut() {
|
||||||
|
if !layer.quads.is_empty() {
|
||||||
|
engine.quad_pipeline.prepare(
|
||||||
|
device,
|
||||||
|
encoder,
|
||||||
|
&mut engine.staging_belt,
|
||||||
|
&layer.quads,
|
||||||
|
viewport.projection(),
|
||||||
|
scale_factor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !layer.triangles.is_empty() {
|
||||||
|
engine.triangle_pipeline.prepare(
|
||||||
|
device,
|
||||||
|
encoder,
|
||||||
|
&mut engine.staging_belt,
|
||||||
|
&mut self.triangle_storage,
|
||||||
|
&layer.triangles,
|
||||||
|
Transformation::scale(scale_factor),
|
||||||
|
viewport.physical_size(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !layer.primitives.is_empty() {
|
||||||
|
for instance in &layer.primitives {
|
||||||
|
instance.primitive.prepare(
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
engine.format,
|
||||||
|
&mut engine.primitive_storage,
|
||||||
|
&instance.bounds,
|
||||||
|
viewport,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !layer.text.is_empty() {
|
||||||
|
engine.text_pipeline.prepare(
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
encoder,
|
||||||
|
&mut self.text_storage,
|
||||||
|
&layer.text,
|
||||||
|
layer.bounds,
|
||||||
|
Transformation::scale(scale_factor),
|
||||||
|
viewport.physical_size(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "svg", feature = "image"))]
|
||||||
|
if !layer.images.is_empty() {
|
||||||
|
engine.image_pipeline.prepare(
|
||||||
|
device,
|
||||||
|
encoder,
|
||||||
|
&mut engine.staging_belt,
|
||||||
|
&layer.images,
|
||||||
|
viewport.projection(),
|
||||||
|
scale_factor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
engine: &mut Engine,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
frame: &wgpu::TextureView,
|
||||||
|
clear_color: Option<Color>,
|
||||||
|
viewport: &Viewport,
|
||||||
|
) {
|
||||||
|
use std::mem::ManuallyDrop;
|
||||||
|
|
||||||
|
let mut render_pass = ManuallyDrop::new(encoder.begin_render_pass(
|
||||||
|
&wgpu::RenderPassDescriptor {
|
||||||
|
label: Some("iced_wgpu render pass"),
|
||||||
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
|
view: frame,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: match clear_color {
|
||||||
|
Some(background_color) => wgpu::LoadOp::Clear({
|
||||||
|
let [r, g, b, a] =
|
||||||
|
graphics::color::pack(background_color)
|
||||||
|
.components();
|
||||||
|
|
||||||
|
wgpu::Color {
|
||||||
|
r: f64::from(r),
|
||||||
|
g: f64::from(g),
|
||||||
|
b: f64::from(b),
|
||||||
|
a: f64::from(a),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
None => wgpu::LoadOp::Load,
|
||||||
|
},
|
||||||
|
store: wgpu::StoreOp::Store,
|
||||||
|
},
|
||||||
|
})],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
timestamp_writes: None,
|
||||||
|
occlusion_query_set: None,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
let mut quad_layer = 0;
|
||||||
|
let mut mesh_layer = 0;
|
||||||
|
let mut text_layer = 0;
|
||||||
|
|
||||||
|
#[cfg(any(feature = "svg", feature = "image"))]
|
||||||
|
let mut image_layer = 0;
|
||||||
|
|
||||||
|
let scale_factor = viewport.scale_factor() as f32;
|
||||||
|
let physical_bounds = Rectangle::<f32>::from(Rectangle::with_size(
|
||||||
|
viewport.physical_size(),
|
||||||
|
));
|
||||||
|
|
||||||
|
let scale = Transformation::scale(scale_factor);
|
||||||
|
|
||||||
|
for layer in self.layers.iter() {
|
||||||
|
let Some(physical_bounds) =
|
||||||
|
physical_bounds.intersection(&(layer.bounds * scale))
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(scissor_rect) = physical_bounds.snap() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !layer.quads.is_empty() {
|
||||||
|
engine.quad_pipeline.render(
|
||||||
|
quad_layer,
|
||||||
|
scissor_rect,
|
||||||
|
&layer.quads,
|
||||||
|
&mut render_pass,
|
||||||
|
);
|
||||||
|
|
||||||
|
quad_layer += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !layer.triangles.is_empty() {
|
||||||
|
let _ = ManuallyDrop::into_inner(render_pass);
|
||||||
|
|
||||||
|
mesh_layer += engine.triangle_pipeline.render(
|
||||||
|
encoder,
|
||||||
|
frame,
|
||||||
|
&self.triangle_storage,
|
||||||
|
mesh_layer,
|
||||||
|
&layer.triangles,
|
||||||
|
physical_bounds,
|
||||||
|
scale,
|
||||||
|
);
|
||||||
|
|
||||||
|
render_pass = ManuallyDrop::new(encoder.begin_render_pass(
|
||||||
|
&wgpu::RenderPassDescriptor {
|
||||||
|
label: Some("iced_wgpu render pass"),
|
||||||
|
color_attachments: &[Some(
|
||||||
|
wgpu::RenderPassColorAttachment {
|
||||||
|
view: frame,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Load,
|
||||||
|
store: wgpu::StoreOp::Store,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
timestamp_writes: None,
|
||||||
|
occlusion_query_set: None,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !layer.primitives.is_empty() {
|
||||||
|
let _ = ManuallyDrop::into_inner(render_pass);
|
||||||
|
|
||||||
|
for instance in &layer.primitives {
|
||||||
|
if let Some(clip_bounds) = (instance.bounds * scale)
|
||||||
|
.intersection(&physical_bounds)
|
||||||
|
.and_then(Rectangle::snap)
|
||||||
|
{
|
||||||
|
instance.primitive.render(
|
||||||
|
encoder,
|
||||||
|
&engine.primitive_storage,
|
||||||
|
frame,
|
||||||
|
&clip_bounds,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render_pass = ManuallyDrop::new(encoder.begin_render_pass(
|
||||||
|
&wgpu::RenderPassDescriptor {
|
||||||
|
label: Some("iced_wgpu render pass"),
|
||||||
|
color_attachments: &[Some(
|
||||||
|
wgpu::RenderPassColorAttachment {
|
||||||
|
view: frame,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Load,
|
||||||
|
store: wgpu::StoreOp::Store,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
timestamp_writes: None,
|
||||||
|
occlusion_query_set: None,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !layer.text.is_empty() {
|
||||||
|
text_layer += engine.text_pipeline.render(
|
||||||
|
&self.text_storage,
|
||||||
|
text_layer,
|
||||||
|
&layer.text,
|
||||||
|
scissor_rect,
|
||||||
|
&mut render_pass,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "svg", feature = "image"))]
|
||||||
|
if !layer.images.is_empty() {
|
||||||
|
engine.image_pipeline.render(
|
||||||
|
image_layer,
|
||||||
|
scissor_rect,
|
||||||
|
&mut render_pass,
|
||||||
|
);
|
||||||
|
|
||||||
|
image_layer += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = ManuallyDrop::into_inner(render_pass);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_overlay(
|
||||||
|
&mut self,
|
||||||
|
overlay: &[impl AsRef<str>],
|
||||||
|
viewport: &Viewport,
|
||||||
|
) {
|
||||||
|
use crate::core::alignment;
|
||||||
|
use crate::core::text::Renderer as _;
|
||||||
|
use crate::core::Renderer as _;
|
||||||
|
use crate::core::Vector;
|
||||||
|
|
||||||
|
self.with_layer(
|
||||||
|
Rectangle::with_size(viewport.logical_size()),
|
||||||
|
|renderer| {
|
||||||
|
for (i, line) in overlay.iter().enumerate() {
|
||||||
|
let text = crate::core::Text {
|
||||||
|
content: line.as_ref().to_owned(),
|
||||||
|
bounds: viewport.logical_size(),
|
||||||
|
size: Pixels(20.0),
|
||||||
|
line_height: core::text::LineHeight::default(),
|
||||||
|
font: Font::MONOSPACE,
|
||||||
|
horizontal_alignment: alignment::Horizontal::Left,
|
||||||
|
vertical_alignment: alignment::Vertical::Top,
|
||||||
|
shaping: core::text::Shaping::Basic,
|
||||||
|
};
|
||||||
|
|
||||||
|
renderer.fill_text(
|
||||||
|
text.clone(),
|
||||||
|
Point::new(11.0, 11.0 + 25.0 * i as f32),
|
||||||
|
Color::new(0.9, 0.9, 0.9, 1.0),
|
||||||
|
Rectangle::with_size(Size::INFINITY),
|
||||||
|
);
|
||||||
|
|
||||||
|
renderer.fill_text(
|
||||||
|
text,
|
||||||
|
Point::new(11.0, 11.0 + 25.0 * i as f32)
|
||||||
|
+ Vector::new(-1.0, -1.0),
|
||||||
|
Color::BLACK,
|
||||||
|
Rectangle::with_size(Size::INFINITY),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::Renderer for Renderer {
|
||||||
|
fn start_layer(&mut self, bounds: Rectangle) {
|
||||||
|
self.layers.push_clip(bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end_layer(&mut self) {
|
||||||
|
self.layers.pop_clip();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_transformation(&mut self, transformation: Transformation) {
|
||||||
|
self.layers.push_transformation(transformation);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end_transformation(&mut self) {
|
||||||
|
self.layers.pop_transformation();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_quad(
|
||||||
|
&mut self,
|
||||||
|
quad: core::renderer::Quad,
|
||||||
|
background: impl Into<Background>,
|
||||||
|
) {
|
||||||
|
let (layer, transformation) = self.layers.current_mut();
|
||||||
|
layer.draw_quad(quad, background.into(), transformation);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&mut self) {
|
||||||
|
self.layers.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::text::Renderer for Renderer {
|
||||||
|
type Font = Font;
|
||||||
|
type Paragraph = Paragraph;
|
||||||
|
type Editor = Editor;
|
||||||
|
|
||||||
|
const ICON_FONT: Font = Font::with_name("Iced-Icons");
|
||||||
|
const CHECKMARK_ICON: char = '\u{f00c}';
|
||||||
|
const ARROW_DOWN_ICON: char = '\u{e800}';
|
||||||
|
|
||||||
|
fn default_font(&self) -> Self::Font {
|
||||||
|
self.default_font
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_size(&self) -> Pixels {
|
||||||
|
self.default_text_size
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_paragraph(
|
||||||
|
&mut self,
|
||||||
|
text: &Self::Paragraph,
|
||||||
|
position: Point,
|
||||||
|
color: Color,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
) {
|
||||||
|
let (layer, transformation) = self.layers.current_mut();
|
||||||
|
|
||||||
|
layer.draw_paragraph(
|
||||||
|
text,
|
||||||
|
position,
|
||||||
|
color,
|
||||||
|
clip_bounds,
|
||||||
|
transformation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_editor(
|
||||||
|
&mut self,
|
||||||
|
editor: &Self::Editor,
|
||||||
|
position: Point,
|
||||||
|
color: Color,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
) {
|
||||||
|
let (layer, transformation) = self.layers.current_mut();
|
||||||
|
layer.draw_editor(editor, position, color, clip_bounds, transformation);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_text(
|
||||||
|
&mut self,
|
||||||
|
text: core::Text,
|
||||||
|
position: Point,
|
||||||
|
color: Color,
|
||||||
|
clip_bounds: Rectangle,
|
||||||
|
) {
|
||||||
|
let (layer, transformation) = self.layers.current_mut();
|
||||||
|
layer.draw_text(text, position, color, clip_bounds, transformation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
impl core::image::Renderer for Renderer {
|
||||||
|
type Handle = core::image::Handle;
|
||||||
|
|
||||||
|
fn measure_image(&self, handle: &Self::Handle) -> Size<u32> {
|
||||||
|
self.image_cache.lock().measure_image(handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_image(
|
||||||
|
&mut self,
|
||||||
|
handle: Self::Handle,
|
||||||
|
filter_method: core::image::FilterMethod,
|
||||||
|
bounds: Rectangle,
|
||||||
|
) {
|
||||||
|
let (layer, transformation) = self.layers.current_mut();
|
||||||
|
layer.draw_image(handle, filter_method, bounds, transformation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
impl core::svg::Renderer for Renderer {
|
||||||
|
fn measure_svg(&self, handle: &core::svg::Handle) -> Size<u32> {
|
||||||
|
self.image_cache.lock().measure_svg(handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_svg(
|
||||||
|
&mut self,
|
||||||
|
handle: core::svg::Handle,
|
||||||
|
color_filter: Option<Color>,
|
||||||
|
bounds: Rectangle,
|
||||||
|
) {
|
||||||
|
let (layer, transformation) = self.layers.current_mut();
|
||||||
|
layer.draw_svg(handle, color_filter, bounds, transformation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl graphics::mesh::Renderer for Renderer {
|
||||||
|
fn draw_mesh(&mut self, mesh: graphics::Mesh) {
|
||||||
|
let (layer, transformation) = self.layers.current_mut();
|
||||||
|
layer.draw_mesh(mesh, transformation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "geometry")]
|
||||||
|
impl graphics::geometry::Renderer for Renderer {
|
||||||
|
type Geometry = Geometry;
|
||||||
|
type Frame = geometry::Frame;
|
||||||
|
|
||||||
|
fn new_frame(&self, size: Size) -> Self::Frame {
|
||||||
|
geometry::Frame::new(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_geometry(&mut self, geometry: Self::Geometry) {
|
||||||
|
let (layer, transformation) = self.layers.current_mut();
|
||||||
|
|
||||||
|
match geometry {
|
||||||
|
Geometry::Live { meshes, text } => {
|
||||||
|
layer.draw_mesh_group(meshes, transformation);
|
||||||
|
layer.draw_text_group(text, transformation);
|
||||||
|
}
|
||||||
|
Geometry::Cached(cache) => {
|
||||||
|
if let Some(meshes) = cache.meshes {
|
||||||
|
layer.draw_mesh_cache(meshes, transformation);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(text) = cache.text {
|
||||||
|
layer.draw_text_cache(text, transformation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl primitive::Renderer for Renderer {
|
||||||
|
fn draw_primitive(&mut self, bounds: Rectangle, primitive: impl Primitive) {
|
||||||
|
let (layer, transformation) = self.layers.current_mut();
|
||||||
|
layer.draw_primitive(bounds, Box::new(primitive), transformation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl graphics::compositor::Default for crate::Renderer {
|
||||||
|
type Compositor = window::Compositor;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,95 @@
|
||||||
//! Draw using different graphical primitives.
|
//! Draw custom primitives.
|
||||||
pub mod pipeline;
|
use crate::core::{self, Rectangle};
|
||||||
|
use crate::graphics::Viewport;
|
||||||
pub use pipeline::Pipeline;
|
|
||||||
|
|
||||||
use crate::core::Rectangle;
|
|
||||||
use crate::graphics::{Damage, Mesh};
|
|
||||||
|
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
use std::any::{Any, TypeId};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
/// The graphical primitives supported by `iced_wgpu`.
|
/// A batch of primitives.
|
||||||
pub type Primitive = crate::graphics::Primitive<Custom>;
|
pub type Batch = Vec<Instance>;
|
||||||
|
|
||||||
/// The custom primitives supported by `iced_wgpu`.
|
/// A set of methods which allows a [`Primitive`] to be rendered.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
pub trait Primitive: Debug + Send + Sync + 'static {
|
||||||
pub enum Custom {
|
/// Processes the [`Primitive`], allowing for GPU buffer allocation.
|
||||||
/// A mesh primitive.
|
fn prepare(
|
||||||
Mesh(Mesh),
|
&self,
|
||||||
/// A custom pipeline primitive.
|
device: &wgpu::Device,
|
||||||
Pipeline(Pipeline),
|
queue: &wgpu::Queue,
|
||||||
|
format: wgpu::TextureFormat,
|
||||||
|
storage: &mut Storage,
|
||||||
|
bounds: &Rectangle,
|
||||||
|
viewport: &Viewport,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Renders the [`Primitive`].
|
||||||
|
fn render(
|
||||||
|
&self,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
storage: &Storage,
|
||||||
|
target: &wgpu::TextureView,
|
||||||
|
clip_bounds: &Rectangle<u32>,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Damage for Custom {
|
#[derive(Debug)]
|
||||||
fn bounds(&self) -> Rectangle {
|
/// An instance of a specific [`Primitive`].
|
||||||
match self {
|
pub struct Instance {
|
||||||
Self::Mesh(mesh) => mesh.bounds(),
|
/// The bounds of the [`Instance`].
|
||||||
Self::Pipeline(pipeline) => pipeline.bounds,
|
pub bounds: Rectangle,
|
||||||
|
|
||||||
|
/// The [`Primitive`] to render.
|
||||||
|
pub primitive: Box<dyn Primitive>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Instance {
|
||||||
|
/// Creates a new [`Instance`] with the given [`Primitive`].
|
||||||
|
pub fn new(bounds: Rectangle, primitive: impl Primitive) -> Self {
|
||||||
|
Instance {
|
||||||
|
bounds,
|
||||||
|
primitive: Box::new(primitive),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<Mesh> for Custom {
|
/// A renderer than can draw custom primitives.
|
||||||
type Error = &'static str;
|
pub trait Renderer: core::Renderer {
|
||||||
|
/// Draws a custom primitive.
|
||||||
|
fn draw_primitive(&mut self, bounds: Rectangle, primitive: impl Primitive);
|
||||||
|
}
|
||||||
|
|
||||||
fn try_from(mesh: Mesh) -> Result<Self, Self::Error> {
|
/// Stores custom, user-provided types.
|
||||||
Ok(Custom::Mesh(mesh))
|
#[derive(Default, Debug)]
|
||||||
|
pub struct Storage {
|
||||||
|
pipelines: FxHashMap<TypeId, Box<dyn Any + Send>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Storage {
|
||||||
|
/// Returns `true` if `Storage` contains a type `T`.
|
||||||
|
pub fn has<T: 'static>(&self) -> bool {
|
||||||
|
self.pipelines.get(&TypeId::of::<T>()).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts the data `T` in to [`Storage`].
|
||||||
|
pub fn store<T: 'static + Send>(&mut self, data: T) {
|
||||||
|
let _ = self.pipelines.insert(TypeId::of::<T>(), Box::new(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the data with type `T` if it exists in [`Storage`].
|
||||||
|
pub fn get<T: 'static>(&self) -> Option<&T> {
|
||||||
|
self.pipelines.get(&TypeId::of::<T>()).map(|pipeline| {
|
||||||
|
pipeline
|
||||||
|
.downcast_ref::<T>()
|
||||||
|
.expect("Value with this type does not exist in Storage.")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable reference to the data with type `T` if it exists in [`Storage`].
|
||||||
|
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
|
||||||
|
self.pipelines.get_mut(&TypeId::of::<T>()).map(|pipeline| {
|
||||||
|
pipeline
|
||||||
|
.downcast_mut::<T>()
|
||||||
|
.expect("Value with this type does not exist in Storage.")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
//! Draw primitives using custom pipelines.
|
|
||||||
use crate::core::{self, Rectangle, Size};
|
|
||||||
|
|
||||||
use rustc_hash::FxHashMap;
|
|
||||||
use std::any::{Any, TypeId};
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
/// A custom primitive which can be used to render primitives associated with a custom pipeline.
|
|
||||||
pub struct Pipeline {
|
|
||||||
/// The bounds of the [`Pipeline`].
|
|
||||||
pub bounds: Rectangle,
|
|
||||||
|
|
||||||
/// The [`Primitive`] to render.
|
|
||||||
pub primitive: Arc<dyn Primitive>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pipeline {
|
|
||||||
/// Creates a new [`Pipeline`] with the given [`Primitive`].
|
|
||||||
pub fn new(bounds: Rectangle, primitive: impl Primitive) -> Self {
|
|
||||||
Pipeline {
|
|
||||||
bounds,
|
|
||||||
primitive: Arc::new(primitive),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for Pipeline {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.primitive.type_id() == other.primitive.type_id()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A set of methods which allows a [`Primitive`] to be rendered.
|
|
||||||
pub trait Primitive: Debug + Send + Sync + 'static {
|
|
||||||
/// Processes the [`Primitive`], allowing for GPU buffer allocation.
|
|
||||||
fn prepare(
|
|
||||||
&self,
|
|
||||||
format: wgpu::TextureFormat,
|
|
||||||
device: &wgpu::Device,
|
|
||||||
queue: &wgpu::Queue,
|
|
||||||
bounds: Rectangle,
|
|
||||||
target_size: Size<u32>,
|
|
||||||
scale_factor: f32,
|
|
||||||
storage: &mut Storage,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Renders the [`Primitive`].
|
|
||||||
fn render(
|
|
||||||
&self,
|
|
||||||
storage: &Storage,
|
|
||||||
target: &wgpu::TextureView,
|
|
||||||
target_size: Size<u32>,
|
|
||||||
viewport: Rectangle<u32>,
|
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A renderer than can draw custom pipeline primitives.
|
|
||||||
pub trait Renderer: core::Renderer {
|
|
||||||
/// Draws a custom pipeline primitive.
|
|
||||||
fn draw_pipeline_primitive(
|
|
||||||
&mut self,
|
|
||||||
bounds: Rectangle,
|
|
||||||
primitive: impl Primitive,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Renderer for crate::Renderer {
|
|
||||||
fn draw_pipeline_primitive(
|
|
||||||
&mut self,
|
|
||||||
bounds: Rectangle,
|
|
||||||
primitive: impl Primitive,
|
|
||||||
) {
|
|
||||||
self.draw_primitive(super::Primitive::Custom(super::Custom::Pipeline(
|
|
||||||
Pipeline::new(bounds, primitive),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores custom, user-provided pipelines.
|
|
||||||
#[derive(Default, Debug)]
|
|
||||||
pub struct Storage {
|
|
||||||
pipelines: FxHashMap<TypeId, Box<dyn Any + Send>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Storage {
|
|
||||||
/// Returns `true` if `Storage` contains a pipeline with type `T`.
|
|
||||||
pub fn has<T: 'static>(&self) -> bool {
|
|
||||||
self.pipelines.get(&TypeId::of::<T>()).is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Inserts the pipeline `T` in to [`Storage`].
|
|
||||||
pub fn store<T: 'static + Send>(&mut self, pipeline: T) {
|
|
||||||
let _ = self.pipelines.insert(TypeId::of::<T>(), Box::new(pipeline));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a reference to pipeline with type `T` if it exists in [`Storage`].
|
|
||||||
pub fn get<T: 'static>(&self) -> Option<&T> {
|
|
||||||
self.pipelines.get(&TypeId::of::<T>()).map(|pipeline| {
|
|
||||||
pipeline
|
|
||||||
.downcast_ref::<T>()
|
|
||||||
.expect("Pipeline with this type does not exist in Storage.")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a mutable reference to pipeline `T` if it exists in [`Storage`].
|
|
||||||
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
|
|
||||||
self.pipelines.get_mut(&TypeId::of::<T>()).map(|pipeline| {
|
|
||||||
pipeline
|
|
||||||
.downcast_mut::<T>()
|
|
||||||
.expect("Pipeline with this type does not exist in Storage.")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
106
wgpu/src/quad.rs
106
wgpu/src/quad.rs
|
|
@ -12,11 +12,37 @@ use bytemuck::{Pod, Zeroable};
|
||||||
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
#[cfg(feature = "tracing")]
|
|
||||||
use tracing::info_span;
|
|
||||||
|
|
||||||
const INITIAL_INSTANCES: usize = 2_000;
|
const INITIAL_INSTANCES: usize = 2_000;
|
||||||
|
|
||||||
|
/// The properties of a quad.
|
||||||
|
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Quad {
|
||||||
|
/// The position of the [`Quad`].
|
||||||
|
pub position: [f32; 2],
|
||||||
|
|
||||||
|
/// The size of the [`Quad`].
|
||||||
|
pub size: [f32; 2],
|
||||||
|
|
||||||
|
/// The border color of the [`Quad`], in __linear RGB__.
|
||||||
|
pub border_color: color::Packed,
|
||||||
|
|
||||||
|
/// The border radii of the [`Quad`].
|
||||||
|
pub border_radius: [f32; 4],
|
||||||
|
|
||||||
|
/// The border width of the [`Quad`].
|
||||||
|
pub border_width: f32,
|
||||||
|
|
||||||
|
/// The shadow color of the [`Quad`].
|
||||||
|
pub shadow_color: color::Packed,
|
||||||
|
|
||||||
|
/// The shadow offset of the [`Quad`].
|
||||||
|
pub shadow_offset: [f32; 2],
|
||||||
|
|
||||||
|
/// The shadow blur radius of the [`Quad`].
|
||||||
|
pub shadow_blur_radius: f32,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Pipeline {
|
pub struct Pipeline {
|
||||||
solid: solid::Pipeline,
|
solid: solid::Pipeline,
|
||||||
|
|
@ -124,7 +150,7 @@ impl Pipeline {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Layer {
|
pub struct Layer {
|
||||||
constants: wgpu::BindGroup,
|
constants: wgpu::BindGroup,
|
||||||
constants_buffer: wgpu::Buffer,
|
constants_buffer: wgpu::Buffer,
|
||||||
solid: solid::Layer,
|
solid: solid::Layer,
|
||||||
|
|
@ -169,9 +195,26 @@ impl Layer {
|
||||||
transformation: Transformation,
|
transformation: Transformation,
|
||||||
scale: f32,
|
scale: f32,
|
||||||
) {
|
) {
|
||||||
#[cfg(feature = "tracing")]
|
self.update(device, encoder, belt, transformation, scale);
|
||||||
let _ = info_span!("Wgpu::Quad", "PREPARE").entered();
|
|
||||||
|
|
||||||
|
if !quads.solids.is_empty() {
|
||||||
|
self.solid.prepare(device, encoder, belt, &quads.solids);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !quads.gradients.is_empty() {
|
||||||
|
self.gradient
|
||||||
|
.prepare(device, encoder, belt, &quads.gradients);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(
|
||||||
|
&mut self,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
belt: &mut wgpu::util::StagingBelt,
|
||||||
|
transformation: Transformation,
|
||||||
|
scale: f32,
|
||||||
|
) {
|
||||||
let uniforms = Uniforms::new(transformation, scale);
|
let uniforms = Uniforms::new(transformation, scale);
|
||||||
let bytes = bytemuck::bytes_of(&uniforms);
|
let bytes = bytemuck::bytes_of(&uniforms);
|
||||||
|
|
||||||
|
|
@ -183,47 +226,9 @@ impl Layer {
|
||||||
device,
|
device,
|
||||||
)
|
)
|
||||||
.copy_from_slice(bytes);
|
.copy_from_slice(bytes);
|
||||||
|
|
||||||
if !quads.solids.is_empty() {
|
|
||||||
self.solid.prepare(device, encoder, belt, &quads.solids);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !quads.gradients.is_empty() {
|
|
||||||
self.gradient
|
|
||||||
.prepare(device, encoder, belt, &quads.gradients);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The properties of a quad.
|
|
||||||
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct Quad {
|
|
||||||
/// The position of the [`Quad`].
|
|
||||||
pub position: [f32; 2],
|
|
||||||
|
|
||||||
/// The size of the [`Quad`].
|
|
||||||
pub size: [f32; 2],
|
|
||||||
|
|
||||||
/// The border color of the [`Quad`], in __linear RGB__.
|
|
||||||
pub border_color: color::Packed,
|
|
||||||
|
|
||||||
/// The border radii of the [`Quad`].
|
|
||||||
pub border_radius: [f32; 4],
|
|
||||||
|
|
||||||
/// The border width of the [`Quad`].
|
|
||||||
pub border_width: f32,
|
|
||||||
|
|
||||||
/// The shadow color of the [`Quad`].
|
|
||||||
pub shadow_color: [f32; 4],
|
|
||||||
|
|
||||||
/// The shadow offset of the [`Quad`].
|
|
||||||
pub shadow_offset: [f32; 2],
|
|
||||||
|
|
||||||
/// The shadow blur radius of the [`Quad`].
|
|
||||||
pub shadow_blur_radius: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A group of [`Quad`]s rendered together.
|
/// A group of [`Quad`]s rendered together.
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct Batch {
|
pub struct Batch {
|
||||||
|
|
@ -233,10 +238,13 @@ pub struct Batch {
|
||||||
/// The gradient quads of the [`Layer`].
|
/// The gradient quads of the [`Layer`].
|
||||||
gradients: Vec<Gradient>,
|
gradients: Vec<Gradient>,
|
||||||
|
|
||||||
/// The quad order of the [`Layer`]; stored as a tuple of the quad type & its count.
|
/// The quad order of the [`Layer`].
|
||||||
order: Vec<(Kind, usize)>,
|
order: Order,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The quad order of a [`Layer`]; stored as a tuple of the quad type & its count.
|
||||||
|
type Order = Vec<(Kind, usize)>;
|
||||||
|
|
||||||
impl Batch {
|
impl Batch {
|
||||||
/// Returns true if there are no quads of any type in [`Quads`].
|
/// Returns true if there are no quads of any type in [`Quads`].
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
|
|
@ -276,6 +284,12 @@ impl Batch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.solids.clear();
|
||||||
|
self.gradients.clear();
|
||||||
|
self.order.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,18 @@
|
||||||
use crate::core::{Font, Pixels};
|
use crate::core::{Font, Pixels};
|
||||||
use crate::graphics::{self, Antialiasing};
|
use crate::graphics::{self, Antialiasing};
|
||||||
|
|
||||||
/// The settings of a [`Backend`].
|
/// The settings of a [`Renderer`].
|
||||||
///
|
///
|
||||||
/// [`Backend`]: crate::Backend
|
/// [`Renderer`]: crate::Renderer
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
/// The present mode of the [`Backend`].
|
/// The present mode of the [`Renderer`].
|
||||||
///
|
///
|
||||||
/// [`Backend`]: crate::Backend
|
/// [`Renderer`]: crate::Renderer
|
||||||
pub present_mode: wgpu::PresentMode,
|
pub present_mode: wgpu::PresentMode,
|
||||||
|
|
||||||
/// The internal graphics backend to use.
|
/// The graphics backends to use.
|
||||||
pub internal_backend: wgpu::Backends,
|
pub backends: wgpu::Backends,
|
||||||
|
|
||||||
/// The default [`Font`] to use.
|
/// The default [`Font`] to use.
|
||||||
pub default_font: Font,
|
pub default_font: Font,
|
||||||
|
|
@ -33,7 +33,7 @@ impl Default for Settings {
|
||||||
fn default() -> Settings {
|
fn default() -> Settings {
|
||||||
Settings {
|
Settings {
|
||||||
present_mode: wgpu::PresentMode::AutoVsync,
|
present_mode: wgpu::PresentMode::AutoVsync,
|
||||||
internal_backend: wgpu::Backends::all(),
|
backends: wgpu::Backends::all(),
|
||||||
default_font: Font::default(),
|
default_font: Font::default(),
|
||||||
default_text_size: Pixels(16.0),
|
default_text_size: Pixels(16.0),
|
||||||
antialiasing: None,
|
antialiasing: None,
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,14 @@
|
||||||
var<private> positions: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
|
|
||||||
vec2<f32>(-1.0, 1.0),
|
|
||||||
vec2<f32>(-1.0, -1.0),
|
|
||||||
vec2<f32>(1.0, -1.0),
|
|
||||||
vec2<f32>(-1.0, 1.0),
|
|
||||||
vec2<f32>(1.0, 1.0),
|
|
||||||
vec2<f32>(1.0, -1.0)
|
|
||||||
);
|
|
||||||
|
|
||||||
var<private> uvs: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
|
var<private> uvs: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
|
||||||
vec2<f32>(0.0, 0.0),
|
vec2<f32>(0.0, 0.0),
|
||||||
vec2<f32>(0.0, 1.0),
|
vec2<f32>(1.0, 0.0),
|
||||||
vec2<f32>(1.0, 1.0),
|
vec2<f32>(1.0, 1.0),
|
||||||
vec2<f32>(0.0, 0.0),
|
vec2<f32>(0.0, 0.0),
|
||||||
vec2<f32>(1.0, 0.0),
|
vec2<f32>(0.0, 1.0),
|
||||||
vec2<f32>(1.0, 1.0)
|
vec2<f32>(1.0, 1.0)
|
||||||
);
|
);
|
||||||
|
|
||||||
@group(0) @binding(0) var u_sampler: sampler;
|
@group(0) @binding(0) var u_sampler: sampler;
|
||||||
|
@group(0) @binding(1) var<uniform> u_ratio: vec2<f32>;
|
||||||
@group(1) @binding(0) var u_texture: texture_2d<f32>;
|
@group(1) @binding(0) var u_texture: texture_2d<f32>;
|
||||||
|
|
||||||
struct VertexInput {
|
struct VertexInput {
|
||||||
|
|
@ -30,9 +22,11 @@ struct VertexOutput {
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||||
|
let uv = uvs[input.vertex_index];
|
||||||
|
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
out.uv = uvs[input.vertex_index];
|
out.uv = uv * u_ratio;
|
||||||
out.position = vec4<f32>(positions[input.vertex_index], 0.0, 1.0);
|
out.position = vec4<f32>(uv * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
731
wgpu/src/text.rs
731
wgpu/src/text.rs
|
|
@ -1,20 +1,193 @@
|
||||||
use crate::core::alignment;
|
use crate::core::alignment;
|
||||||
use crate::core::{Rectangle, Size, Transformation};
|
use crate::core::{Rectangle, Size, Transformation};
|
||||||
use crate::graphics::color;
|
use crate::graphics::color;
|
||||||
use crate::graphics::text::cache::{self, Cache};
|
use crate::graphics::text::cache::{self, Cache as BufferCache};
|
||||||
use crate::graphics::text::{font_system, to_color, Editor, Paragraph};
|
use crate::graphics::text::{font_system, to_color, Editor, Paragraph};
|
||||||
use crate::layer::Text;
|
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use std::cell::RefCell;
|
use std::collections::hash_map;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::sync::atomic::{self, AtomicU64};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub use crate::graphics::Text;
|
||||||
|
|
||||||
|
const COLOR_MODE: glyphon::ColorMode = if color::GAMMA_CORRECTION {
|
||||||
|
glyphon::ColorMode::Accurate
|
||||||
|
} else {
|
||||||
|
glyphon::ColorMode::Web
|
||||||
|
};
|
||||||
|
|
||||||
|
pub type Batch = Vec<Item>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Item {
|
||||||
|
Group {
|
||||||
|
transformation: Transformation,
|
||||||
|
text: Vec<Text>,
|
||||||
|
},
|
||||||
|
Cached {
|
||||||
|
transformation: Transformation,
|
||||||
|
cache: Cache,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Cache {
|
||||||
|
id: Id,
|
||||||
|
text: Rc<[Text]>,
|
||||||
|
version: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct Id(u64);
|
||||||
|
|
||||||
|
impl Cache {
|
||||||
|
pub fn new(text: Vec<Text>) -> Option<Self> {
|
||||||
|
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
if text.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)),
|
||||||
|
text: Rc::from(text),
|
||||||
|
version: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, text: Vec<Text>) {
|
||||||
|
self.text = Rc::from(text);
|
||||||
|
self.version += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Upload {
|
||||||
|
renderer: glyphon::TextRenderer,
|
||||||
|
atlas: glyphon::TextAtlas,
|
||||||
|
buffer_cache: BufferCache,
|
||||||
|
transformation: Transformation,
|
||||||
|
version: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Storage {
|
||||||
|
uploads: FxHashMap<Id, Upload>,
|
||||||
|
recently_used: FxHashSet<Id>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Storage {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, cache: &Cache) -> Option<&Upload> {
|
||||||
|
if cache.text.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.uploads.get(&cache.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare(
|
||||||
|
&mut self,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
format: wgpu::TextureFormat,
|
||||||
|
cache: &Cache,
|
||||||
|
new_transformation: Transformation,
|
||||||
|
bounds: Rectangle,
|
||||||
|
target_size: Size<u32>,
|
||||||
|
) {
|
||||||
|
match self.uploads.entry(cache.id) {
|
||||||
|
hash_map::Entry::Occupied(entry) => {
|
||||||
|
let upload = entry.into_mut();
|
||||||
|
|
||||||
|
if !cache.text.is_empty()
|
||||||
|
&& (upload.version != cache.version
|
||||||
|
|| upload.transformation != new_transformation)
|
||||||
|
{
|
||||||
|
let _ = prepare(
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
encoder,
|
||||||
|
&mut upload.renderer,
|
||||||
|
&mut upload.atlas,
|
||||||
|
&mut upload.buffer_cache,
|
||||||
|
&cache.text,
|
||||||
|
bounds,
|
||||||
|
new_transformation,
|
||||||
|
target_size,
|
||||||
|
);
|
||||||
|
|
||||||
|
upload.version = cache.version;
|
||||||
|
upload.transformation = new_transformation;
|
||||||
|
|
||||||
|
upload.buffer_cache.trim();
|
||||||
|
upload.atlas.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hash_map::Entry::Vacant(entry) => {
|
||||||
|
let mut atlas = glyphon::TextAtlas::with_color_mode(
|
||||||
|
device, queue, format, COLOR_MODE,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut renderer = glyphon::TextRenderer::new(
|
||||||
|
&mut atlas,
|
||||||
|
device,
|
||||||
|
wgpu::MultisampleState::default(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut buffer_cache = BufferCache::new();
|
||||||
|
|
||||||
|
let _ = prepare(
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
encoder,
|
||||||
|
&mut renderer,
|
||||||
|
&mut atlas,
|
||||||
|
&mut buffer_cache,
|
||||||
|
&cache.text,
|
||||||
|
bounds,
|
||||||
|
new_transformation,
|
||||||
|
target_size,
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = entry.insert(Upload {
|
||||||
|
renderer,
|
||||||
|
atlas,
|
||||||
|
buffer_cache,
|
||||||
|
transformation: new_transformation,
|
||||||
|
version: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
log::info!(
|
||||||
|
"New text upload: {} (total: {})",
|
||||||
|
cache.id.0,
|
||||||
|
self.uploads.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = self.recently_used.insert(cache.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trim(&mut self) {
|
||||||
|
self.uploads.retain(|id, _| self.recently_used.contains(id));
|
||||||
|
self.recently_used.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Pipeline {
|
pub struct Pipeline {
|
||||||
renderers: Vec<glyphon::TextRenderer>,
|
format: wgpu::TextureFormat,
|
||||||
atlas: glyphon::TextAtlas,
|
atlas: glyphon::TextAtlas,
|
||||||
|
renderers: Vec<glyphon::TextRenderer>,
|
||||||
prepare_layer: usize,
|
prepare_layer: usize,
|
||||||
cache: RefCell<Cache>,
|
cache: BufferCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pipeline {
|
impl Pipeline {
|
||||||
|
|
@ -24,275 +197,95 @@ impl Pipeline {
|
||||||
format: wgpu::TextureFormat,
|
format: wgpu::TextureFormat,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Pipeline {
|
Pipeline {
|
||||||
|
format,
|
||||||
renderers: Vec::new(),
|
renderers: Vec::new(),
|
||||||
atlas: glyphon::TextAtlas::with_color_mode(
|
atlas: glyphon::TextAtlas::with_color_mode(
|
||||||
device,
|
device, queue, format, COLOR_MODE,
|
||||||
queue,
|
|
||||||
format,
|
|
||||||
if color::GAMMA_CORRECTION {
|
|
||||||
glyphon::ColorMode::Accurate
|
|
||||||
} else {
|
|
||||||
glyphon::ColorMode::Web
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
prepare_layer: 0,
|
prepare_layer: 0,
|
||||||
cache: RefCell::new(Cache::new()),
|
cache: BufferCache::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
|
|
||||||
font_system()
|
|
||||||
.write()
|
|
||||||
.expect("Write font system")
|
|
||||||
.load_font(bytes);
|
|
||||||
|
|
||||||
self.cache = RefCell::new(Cache::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prepare(
|
pub fn prepare(
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
queue: &wgpu::Queue,
|
queue: &wgpu::Queue,
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
sections: &[Text<'_>],
|
storage: &mut Storage,
|
||||||
|
batch: &Batch,
|
||||||
layer_bounds: Rectangle,
|
layer_bounds: Rectangle,
|
||||||
scale_factor: f32,
|
layer_transformation: Transformation,
|
||||||
target_size: Size<u32>,
|
target_size: Size<u32>,
|
||||||
) {
|
) {
|
||||||
if self.renderers.len() <= self.prepare_layer {
|
for item in batch {
|
||||||
self.renderers.push(glyphon::TextRenderer::new(
|
match item {
|
||||||
&mut self.atlas,
|
Item::Group {
|
||||||
device,
|
transformation,
|
||||||
wgpu::MultisampleState::default(),
|
text,
|
||||||
None,
|
} => {
|
||||||
));
|
if self.renderers.len() <= self.prepare_layer {
|
||||||
}
|
self.renderers.push(glyphon::TextRenderer::new(
|
||||||
|
&mut self.atlas,
|
||||||
|
device,
|
||||||
|
wgpu::MultisampleState::default(),
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let mut font_system = font_system().write().expect("Write font system");
|
let renderer = &mut self.renderers[self.prepare_layer];
|
||||||
let font_system = font_system.raw();
|
let result = prepare(
|
||||||
|
device,
|
||||||
let renderer = &mut self.renderers[self.prepare_layer];
|
queue,
|
||||||
let cache = self.cache.get_mut();
|
encoder,
|
||||||
|
renderer,
|
||||||
enum Allocation {
|
&mut self.atlas,
|
||||||
Paragraph(Paragraph),
|
&mut self.cache,
|
||||||
Editor(Editor),
|
text,
|
||||||
Cache(cache::KeyHash),
|
layer_bounds * layer_transformation,
|
||||||
Raw(Arc<glyphon::Buffer>),
|
layer_transformation * *transformation,
|
||||||
}
|
target_size,
|
||||||
|
|
||||||
let allocations: Vec<_> = sections
|
|
||||||
.iter()
|
|
||||||
.map(|section| match section {
|
|
||||||
Text::Paragraph { paragraph, .. } => {
|
|
||||||
paragraph.upgrade().map(Allocation::Paragraph)
|
|
||||||
}
|
|
||||||
Text::Editor { editor, .. } => {
|
|
||||||
editor.upgrade().map(Allocation::Editor)
|
|
||||||
}
|
|
||||||
Text::Cached(text) => {
|
|
||||||
let (key, _) = cache.allocate(
|
|
||||||
font_system,
|
|
||||||
cache::Key {
|
|
||||||
content: text.content,
|
|
||||||
size: text.size.into(),
|
|
||||||
line_height: f32::from(
|
|
||||||
text.line_height.to_absolute(text.size),
|
|
||||||
),
|
|
||||||
font: text.font,
|
|
||||||
bounds: Size {
|
|
||||||
width: text.bounds.width,
|
|
||||||
height: text.bounds.height,
|
|
||||||
},
|
|
||||||
shaping: text.shaping,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Some(Allocation::Cache(key))
|
match result {
|
||||||
|
Ok(()) => {
|
||||||
|
self.prepare_layer += 1;
|
||||||
|
}
|
||||||
|
Err(glyphon::PrepareError::AtlasFull) => {
|
||||||
|
// If the atlas cannot grow, then all bets are off.
|
||||||
|
// Instead of panicking, we will just pray that the result
|
||||||
|
// will be somewhat readable...
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Text::Raw { raw, .. } => {
|
Item::Cached {
|
||||||
raw.buffer.upgrade().map(Allocation::Raw)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let layer_bounds = layer_bounds * scale_factor;
|
|
||||||
|
|
||||||
let text_areas = sections.iter().zip(allocations.iter()).filter_map(
|
|
||||||
|(section, allocation)| {
|
|
||||||
let (
|
|
||||||
buffer,
|
|
||||||
bounds,
|
|
||||||
horizontal_alignment,
|
|
||||||
vertical_alignment,
|
|
||||||
color,
|
|
||||||
clip_bounds,
|
|
||||||
transformation,
|
transformation,
|
||||||
) = match section {
|
cache,
|
||||||
Text::Paragraph {
|
} => {
|
||||||
position,
|
storage.prepare(
|
||||||
color,
|
device,
|
||||||
clip_bounds,
|
queue,
|
||||||
transformation,
|
encoder,
|
||||||
..
|
self.format,
|
||||||
} => {
|
cache,
|
||||||
use crate::core::text::Paragraph as _;
|
layer_transformation * *transformation,
|
||||||
|
layer_bounds * layer_transformation,
|
||||||
let Some(Allocation::Paragraph(paragraph)) = allocation
|
target_size,
|
||||||
else {
|
);
|
||||||
return None;
|
}
|
||||||
};
|
|
||||||
|
|
||||||
(
|
|
||||||
paragraph.buffer(),
|
|
||||||
Rectangle::new(*position, paragraph.min_bounds()),
|
|
||||||
paragraph.horizontal_alignment(),
|
|
||||||
paragraph.vertical_alignment(),
|
|
||||||
*color,
|
|
||||||
*clip_bounds,
|
|
||||||
*transformation,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Text::Editor {
|
|
||||||
position,
|
|
||||||
color,
|
|
||||||
clip_bounds,
|
|
||||||
transformation,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
use crate::core::text::Editor as _;
|
|
||||||
|
|
||||||
let Some(Allocation::Editor(editor)) = allocation
|
|
||||||
else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
(
|
|
||||||
editor.buffer(),
|
|
||||||
Rectangle::new(*position, editor.bounds()),
|
|
||||||
alignment::Horizontal::Left,
|
|
||||||
alignment::Vertical::Top,
|
|
||||||
*color,
|
|
||||||
*clip_bounds,
|
|
||||||
*transformation,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Text::Cached(text) => {
|
|
||||||
let Some(Allocation::Cache(key)) = allocation else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
let entry = cache.get(key).expect("Get cached buffer");
|
|
||||||
|
|
||||||
(
|
|
||||||
&entry.buffer,
|
|
||||||
Rectangle::new(
|
|
||||||
text.bounds.position(),
|
|
||||||
entry.min_bounds,
|
|
||||||
),
|
|
||||||
text.horizontal_alignment,
|
|
||||||
text.vertical_alignment,
|
|
||||||
text.color,
|
|
||||||
text.clip_bounds,
|
|
||||||
Transformation::IDENTITY,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Text::Raw {
|
|
||||||
raw,
|
|
||||||
transformation,
|
|
||||||
} => {
|
|
||||||
let Some(Allocation::Raw(buffer)) = allocation else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
let (width, height) = buffer.size();
|
|
||||||
|
|
||||||
(
|
|
||||||
buffer.as_ref(),
|
|
||||||
Rectangle::new(
|
|
||||||
raw.position,
|
|
||||||
Size::new(width, height),
|
|
||||||
),
|
|
||||||
alignment::Horizontal::Left,
|
|
||||||
alignment::Vertical::Top,
|
|
||||||
raw.color,
|
|
||||||
raw.clip_bounds,
|
|
||||||
*transformation,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let bounds = bounds * transformation * scale_factor;
|
|
||||||
|
|
||||||
let left = match horizontal_alignment {
|
|
||||||
alignment::Horizontal::Left => bounds.x,
|
|
||||||
alignment::Horizontal::Center => {
|
|
||||||
bounds.x - bounds.width / 2.0
|
|
||||||
}
|
|
||||||
alignment::Horizontal::Right => bounds.x - bounds.width,
|
|
||||||
};
|
|
||||||
|
|
||||||
let top = match vertical_alignment {
|
|
||||||
alignment::Vertical::Top => bounds.y,
|
|
||||||
alignment::Vertical::Center => {
|
|
||||||
bounds.y - bounds.height / 2.0
|
|
||||||
}
|
|
||||||
alignment::Vertical::Bottom => bounds.y - bounds.height,
|
|
||||||
};
|
|
||||||
|
|
||||||
let clip_bounds = layer_bounds.intersection(
|
|
||||||
&(clip_bounds * transformation * scale_factor),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Some(glyphon::TextArea {
|
|
||||||
buffer,
|
|
||||||
left,
|
|
||||||
top,
|
|
||||||
scale: scale_factor * transformation.scale_factor(),
|
|
||||||
bounds: glyphon::TextBounds {
|
|
||||||
left: clip_bounds.x as i32,
|
|
||||||
top: clip_bounds.y as i32,
|
|
||||||
right: (clip_bounds.x + clip_bounds.width) as i32,
|
|
||||||
bottom: (clip_bounds.y + clip_bounds.height) as i32,
|
|
||||||
},
|
|
||||||
default_color: to_color(color),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let result = renderer.prepare(
|
|
||||||
device,
|
|
||||||
queue,
|
|
||||||
encoder,
|
|
||||||
font_system,
|
|
||||||
&mut self.atlas,
|
|
||||||
glyphon::Resolution {
|
|
||||||
width: target_size.width,
|
|
||||||
height: target_size.height,
|
|
||||||
},
|
|
||||||
text_areas,
|
|
||||||
&mut glyphon::SwashCache::new(),
|
|
||||||
);
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(()) => {
|
|
||||||
self.prepare_layer += 1;
|
|
||||||
}
|
|
||||||
Err(glyphon::PrepareError::AtlasFull) => {
|
|
||||||
// If the atlas cannot grow, then all bets are off.
|
|
||||||
// Instead of panicking, we will just pray that the result
|
|
||||||
// will be somewhat readable...
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render<'a>(
|
pub fn render<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
layer: usize,
|
storage: &'a Storage,
|
||||||
|
start: usize,
|
||||||
|
batch: &'a Batch,
|
||||||
bounds: Rectangle<u32>,
|
bounds: Rectangle<u32>,
|
||||||
render_pass: &mut wgpu::RenderPass<'a>,
|
render_pass: &mut wgpu::RenderPass<'a>,
|
||||||
) {
|
) -> usize {
|
||||||
let renderer = &self.renderers[layer];
|
let mut layer_count = 0;
|
||||||
|
|
||||||
render_pass.set_scissor_rect(
|
render_pass.set_scissor_rect(
|
||||||
bounds.x,
|
bounds.x,
|
||||||
|
|
@ -301,15 +294,251 @@ impl Pipeline {
|
||||||
bounds.height,
|
bounds.height,
|
||||||
);
|
);
|
||||||
|
|
||||||
renderer
|
for item in batch {
|
||||||
.render(&self.atlas, render_pass)
|
match item {
|
||||||
.expect("Render text");
|
Item::Group { .. } => {
|
||||||
|
let renderer = &self.renderers[start + layer_count];
|
||||||
|
|
||||||
|
renderer
|
||||||
|
.render(&self.atlas, render_pass)
|
||||||
|
.expect("Render text");
|
||||||
|
|
||||||
|
layer_count += 1;
|
||||||
|
}
|
||||||
|
Item::Cached { cache, .. } => {
|
||||||
|
if let Some(upload) = storage.get(cache) {
|
||||||
|
upload
|
||||||
|
.renderer
|
||||||
|
.render(&upload.atlas, render_pass)
|
||||||
|
.expect("Render cached text");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
layer_count
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn end_frame(&mut self) {
|
pub fn end_frame(&mut self) {
|
||||||
self.atlas.trim();
|
self.atlas.trim();
|
||||||
self.cache.get_mut().trim();
|
self.cache.trim();
|
||||||
|
|
||||||
self.prepare_layer = 0;
|
self.prepare_layer = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prepare(
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
renderer: &mut glyphon::TextRenderer,
|
||||||
|
atlas: &mut glyphon::TextAtlas,
|
||||||
|
buffer_cache: &mut BufferCache,
|
||||||
|
sections: &[Text],
|
||||||
|
layer_bounds: Rectangle,
|
||||||
|
layer_transformation: Transformation,
|
||||||
|
target_size: Size<u32>,
|
||||||
|
) -> Result<(), glyphon::PrepareError> {
|
||||||
|
let mut font_system = font_system().write().expect("Write font system");
|
||||||
|
let font_system = font_system.raw();
|
||||||
|
|
||||||
|
enum Allocation {
|
||||||
|
Paragraph(Paragraph),
|
||||||
|
Editor(Editor),
|
||||||
|
Cache(cache::KeyHash),
|
||||||
|
Raw(Arc<glyphon::Buffer>),
|
||||||
|
}
|
||||||
|
|
||||||
|
let allocations: Vec<_> = sections
|
||||||
|
.iter()
|
||||||
|
.map(|section| match section {
|
||||||
|
Text::Paragraph { paragraph, .. } => {
|
||||||
|
paragraph.upgrade().map(Allocation::Paragraph)
|
||||||
|
}
|
||||||
|
Text::Editor { editor, .. } => {
|
||||||
|
editor.upgrade().map(Allocation::Editor)
|
||||||
|
}
|
||||||
|
Text::Cached {
|
||||||
|
content,
|
||||||
|
bounds,
|
||||||
|
size,
|
||||||
|
line_height,
|
||||||
|
font,
|
||||||
|
shaping,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let (key, _) = buffer_cache.allocate(
|
||||||
|
font_system,
|
||||||
|
cache::Key {
|
||||||
|
content,
|
||||||
|
size: f32::from(*size),
|
||||||
|
line_height: f32::from(*line_height),
|
||||||
|
font: *font,
|
||||||
|
bounds: Size {
|
||||||
|
width: bounds.width,
|
||||||
|
height: bounds.height,
|
||||||
|
},
|
||||||
|
shaping: *shaping,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(Allocation::Cache(key))
|
||||||
|
}
|
||||||
|
Text::Raw { raw, .. } => raw.buffer.upgrade().map(Allocation::Raw),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let text_areas = sections.iter().zip(allocations.iter()).filter_map(
|
||||||
|
|(section, allocation)| {
|
||||||
|
let (
|
||||||
|
buffer,
|
||||||
|
bounds,
|
||||||
|
horizontal_alignment,
|
||||||
|
vertical_alignment,
|
||||||
|
color,
|
||||||
|
clip_bounds,
|
||||||
|
transformation,
|
||||||
|
) = match section {
|
||||||
|
Text::Paragraph {
|
||||||
|
position,
|
||||||
|
color,
|
||||||
|
clip_bounds,
|
||||||
|
transformation,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
use crate::core::text::Paragraph as _;
|
||||||
|
|
||||||
|
let Some(Allocation::Paragraph(paragraph)) = allocation
|
||||||
|
else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
paragraph.buffer(),
|
||||||
|
Rectangle::new(*position, paragraph.min_bounds()),
|
||||||
|
paragraph.horizontal_alignment(),
|
||||||
|
paragraph.vertical_alignment(),
|
||||||
|
*color,
|
||||||
|
*clip_bounds,
|
||||||
|
*transformation,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text::Editor {
|
||||||
|
position,
|
||||||
|
color,
|
||||||
|
clip_bounds,
|
||||||
|
transformation,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
use crate::core::text::Editor as _;
|
||||||
|
|
||||||
|
let Some(Allocation::Editor(editor)) = allocation else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
editor.buffer(),
|
||||||
|
Rectangle::new(*position, editor.bounds()),
|
||||||
|
alignment::Horizontal::Left,
|
||||||
|
alignment::Vertical::Top,
|
||||||
|
*color,
|
||||||
|
*clip_bounds,
|
||||||
|
*transformation,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text::Cached {
|
||||||
|
bounds,
|
||||||
|
horizontal_alignment,
|
||||||
|
vertical_alignment,
|
||||||
|
color,
|
||||||
|
clip_bounds,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let Some(Allocation::Cache(key)) = allocation else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let entry =
|
||||||
|
buffer_cache.get(key).expect("Get cached buffer");
|
||||||
|
|
||||||
|
(
|
||||||
|
&entry.buffer,
|
||||||
|
Rectangle::new(bounds.position(), entry.min_bounds),
|
||||||
|
*horizontal_alignment,
|
||||||
|
*vertical_alignment,
|
||||||
|
*color,
|
||||||
|
*clip_bounds,
|
||||||
|
Transformation::IDENTITY,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text::Raw {
|
||||||
|
raw,
|
||||||
|
transformation,
|
||||||
|
} => {
|
||||||
|
let Some(Allocation::Raw(buffer)) = allocation else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let (width, height) = buffer.size();
|
||||||
|
|
||||||
|
(
|
||||||
|
buffer.as_ref(),
|
||||||
|
Rectangle::new(raw.position, Size::new(width, height)),
|
||||||
|
alignment::Horizontal::Left,
|
||||||
|
alignment::Vertical::Top,
|
||||||
|
raw.color,
|
||||||
|
raw.clip_bounds,
|
||||||
|
*transformation,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let bounds = bounds * transformation * layer_transformation;
|
||||||
|
|
||||||
|
let left = match horizontal_alignment {
|
||||||
|
alignment::Horizontal::Left => bounds.x,
|
||||||
|
alignment::Horizontal::Center => bounds.x - bounds.width / 2.0,
|
||||||
|
alignment::Horizontal::Right => bounds.x - bounds.width,
|
||||||
|
};
|
||||||
|
|
||||||
|
let top = match vertical_alignment {
|
||||||
|
alignment::Vertical::Top => bounds.y,
|
||||||
|
alignment::Vertical::Center => bounds.y - bounds.height / 2.0,
|
||||||
|
alignment::Vertical::Bottom => bounds.y - bounds.height,
|
||||||
|
};
|
||||||
|
|
||||||
|
let clip_bounds = layer_bounds.intersection(
|
||||||
|
&(clip_bounds * transformation * layer_transformation),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Some(glyphon::TextArea {
|
||||||
|
buffer,
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
scale: transformation.scale_factor()
|
||||||
|
* layer_transformation.scale_factor(),
|
||||||
|
bounds: glyphon::TextBounds {
|
||||||
|
left: clip_bounds.x as i32,
|
||||||
|
top: clip_bounds.y as i32,
|
||||||
|
right: (clip_bounds.x + clip_bounds.width) as i32,
|
||||||
|
bottom: (clip_bounds.y + clip_bounds.height) as i32,
|
||||||
|
},
|
||||||
|
default_color: to_color(color),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
renderer.prepare(
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
encoder,
|
||||||
|
font_system,
|
||||||
|
atlas,
|
||||||
|
glyphon::Resolution {
|
||||||
|
width: target_size.width,
|
||||||
|
height: target_size.height,
|
||||||
|
},
|
||||||
|
text_areas,
|
||||||
|
&mut glyphon::SwashCache::new(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,158 @@
|
||||||
//! Draw meshes of triangles.
|
//! Draw meshes of triangles.
|
||||||
mod msaa;
|
mod msaa;
|
||||||
|
|
||||||
use crate::core::{Size, Transformation};
|
use crate::core::{Rectangle, Size, Transformation};
|
||||||
|
use crate::graphics::mesh::{self, Mesh};
|
||||||
use crate::graphics::Antialiasing;
|
use crate::graphics::Antialiasing;
|
||||||
use crate::layer::mesh::{self, Mesh};
|
|
||||||
use crate::Buffer;
|
use crate::Buffer;
|
||||||
|
|
||||||
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
|
use std::collections::hash_map;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::sync::atomic::{self, AtomicU64};
|
||||||
|
|
||||||
const INITIAL_INDEX_COUNT: usize = 1_000;
|
const INITIAL_INDEX_COUNT: usize = 1_000;
|
||||||
const INITIAL_VERTEX_COUNT: usize = 1_000;
|
const INITIAL_VERTEX_COUNT: usize = 1_000;
|
||||||
|
|
||||||
|
pub type Batch = Vec<Item>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Item {
|
||||||
|
Group {
|
||||||
|
transformation: Transformation,
|
||||||
|
meshes: Vec<Mesh>,
|
||||||
|
},
|
||||||
|
Cached {
|
||||||
|
transformation: Transformation,
|
||||||
|
cache: Cache,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Cache {
|
||||||
|
id: Id,
|
||||||
|
batch: Rc<[Mesh]>,
|
||||||
|
version: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct Id(u64);
|
||||||
|
|
||||||
|
impl Cache {
|
||||||
|
pub fn new(meshes: Vec<Mesh>) -> Option<Self> {
|
||||||
|
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
if meshes.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)),
|
||||||
|
batch: Rc::from(meshes),
|
||||||
|
version: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, meshes: Vec<Mesh>) {
|
||||||
|
self.batch = Rc::from(meshes);
|
||||||
|
self.version += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Upload {
|
||||||
|
layer: Layer,
|
||||||
|
transformation: Transformation,
|
||||||
|
version: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Storage {
|
||||||
|
uploads: FxHashMap<Id, Upload>,
|
||||||
|
recently_used: FxHashSet<Id>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Storage {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, cache: &Cache) -> Option<&Upload> {
|
||||||
|
if cache.batch.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.uploads.get(&cache.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare(
|
||||||
|
&mut self,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
belt: &mut wgpu::util::StagingBelt,
|
||||||
|
solid: &solid::Pipeline,
|
||||||
|
gradient: &gradient::Pipeline,
|
||||||
|
cache: &Cache,
|
||||||
|
new_transformation: Transformation,
|
||||||
|
) {
|
||||||
|
match self.uploads.entry(cache.id) {
|
||||||
|
hash_map::Entry::Occupied(entry) => {
|
||||||
|
let upload = entry.into_mut();
|
||||||
|
|
||||||
|
if !cache.batch.is_empty()
|
||||||
|
&& (upload.version != cache.version
|
||||||
|
|| upload.transformation != new_transformation)
|
||||||
|
{
|
||||||
|
upload.layer.prepare(
|
||||||
|
device,
|
||||||
|
encoder,
|
||||||
|
belt,
|
||||||
|
solid,
|
||||||
|
gradient,
|
||||||
|
&cache.batch,
|
||||||
|
new_transformation,
|
||||||
|
);
|
||||||
|
|
||||||
|
upload.version = cache.version;
|
||||||
|
upload.transformation = new_transformation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hash_map::Entry::Vacant(entry) => {
|
||||||
|
let mut layer = Layer::new(device, solid, gradient);
|
||||||
|
|
||||||
|
layer.prepare(
|
||||||
|
device,
|
||||||
|
encoder,
|
||||||
|
belt,
|
||||||
|
solid,
|
||||||
|
gradient,
|
||||||
|
&cache.batch,
|
||||||
|
new_transformation,
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = entry.insert(Upload {
|
||||||
|
layer,
|
||||||
|
transformation: new_transformation,
|
||||||
|
version: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
log::info!(
|
||||||
|
"New mesh upload: {} (total: {})",
|
||||||
|
cache.id.0,
|
||||||
|
self.uploads.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = self.recently_used.insert(cache.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trim(&mut self) {
|
||||||
|
self.uploads.retain(|id, _| self.recently_used.contains(id));
|
||||||
|
self.recently_used.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Pipeline {
|
pub struct Pipeline {
|
||||||
blit: Option<msaa::Blit>,
|
blit: Option<msaa::Blit>,
|
||||||
|
|
@ -18,8 +162,198 @@ pub struct Pipeline {
|
||||||
prepare_layer: usize,
|
prepare_layer: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Pipeline {
|
||||||
|
pub fn new(
|
||||||
|
device: &wgpu::Device,
|
||||||
|
format: wgpu::TextureFormat,
|
||||||
|
antialiasing: Option<Antialiasing>,
|
||||||
|
) -> Pipeline {
|
||||||
|
Pipeline {
|
||||||
|
blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)),
|
||||||
|
solid: solid::Pipeline::new(device, format, antialiasing),
|
||||||
|
gradient: gradient::Pipeline::new(device, format, antialiasing),
|
||||||
|
layers: Vec::new(),
|
||||||
|
prepare_layer: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prepare(
|
||||||
|
&mut self,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
belt: &mut wgpu::util::StagingBelt,
|
||||||
|
storage: &mut Storage,
|
||||||
|
items: &[Item],
|
||||||
|
scale: Transformation,
|
||||||
|
target_size: Size<u32>,
|
||||||
|
) {
|
||||||
|
let projection = if let Some(blit) = &mut self.blit {
|
||||||
|
blit.prepare(device, encoder, belt, target_size) * scale
|
||||||
|
} else {
|
||||||
|
Transformation::orthographic(target_size.width, target_size.height)
|
||||||
|
* scale
|
||||||
|
};
|
||||||
|
|
||||||
|
for item in items {
|
||||||
|
match item {
|
||||||
|
Item::Group {
|
||||||
|
transformation,
|
||||||
|
meshes,
|
||||||
|
} => {
|
||||||
|
if self.layers.len() <= self.prepare_layer {
|
||||||
|
self.layers.push(Layer::new(
|
||||||
|
device,
|
||||||
|
&self.solid,
|
||||||
|
&self.gradient,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let layer = &mut self.layers[self.prepare_layer];
|
||||||
|
layer.prepare(
|
||||||
|
device,
|
||||||
|
encoder,
|
||||||
|
belt,
|
||||||
|
&self.solid,
|
||||||
|
&self.gradient,
|
||||||
|
meshes,
|
||||||
|
projection * *transformation,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.prepare_layer += 1;
|
||||||
|
}
|
||||||
|
Item::Cached {
|
||||||
|
transformation,
|
||||||
|
cache,
|
||||||
|
} => {
|
||||||
|
storage.prepare(
|
||||||
|
device,
|
||||||
|
encoder,
|
||||||
|
belt,
|
||||||
|
&self.solid,
|
||||||
|
&self.gradient,
|
||||||
|
cache,
|
||||||
|
projection * *transformation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(
|
||||||
|
&mut self,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
target: &wgpu::TextureView,
|
||||||
|
storage: &Storage,
|
||||||
|
start: usize,
|
||||||
|
batch: &Batch,
|
||||||
|
bounds: Rectangle,
|
||||||
|
screen_transformation: Transformation,
|
||||||
|
) -> usize {
|
||||||
|
let mut layer_count = 0;
|
||||||
|
|
||||||
|
let items = batch.iter().filter_map(|item| match item {
|
||||||
|
Item::Group {
|
||||||
|
transformation,
|
||||||
|
meshes,
|
||||||
|
} => {
|
||||||
|
let layer = &self.layers[start + layer_count];
|
||||||
|
layer_count += 1;
|
||||||
|
|
||||||
|
Some((
|
||||||
|
layer,
|
||||||
|
meshes.as_slice(),
|
||||||
|
screen_transformation * *transformation,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Item::Cached {
|
||||||
|
transformation,
|
||||||
|
cache,
|
||||||
|
} => {
|
||||||
|
let upload = storage.get(cache)?;
|
||||||
|
|
||||||
|
Some((
|
||||||
|
&upload.layer,
|
||||||
|
&cache.batch,
|
||||||
|
screen_transformation * *transformation,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
encoder,
|
||||||
|
target,
|
||||||
|
self.blit.as_mut(),
|
||||||
|
&self.solid,
|
||||||
|
&self.gradient,
|
||||||
|
bounds,
|
||||||
|
items,
|
||||||
|
);
|
||||||
|
|
||||||
|
layer_count
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn end_frame(&mut self) {
|
||||||
|
self.prepare_layer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render<'a>(
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
target: &wgpu::TextureView,
|
||||||
|
mut blit: Option<&mut msaa::Blit>,
|
||||||
|
solid: &solid::Pipeline,
|
||||||
|
gradient: &gradient::Pipeline,
|
||||||
|
bounds: Rectangle,
|
||||||
|
group: impl Iterator<Item = (&'a Layer, &'a [Mesh], Transformation)>,
|
||||||
|
) {
|
||||||
|
{
|
||||||
|
let (attachment, resolve_target, load) = if let Some(blit) = &mut blit {
|
||||||
|
let (attachment, resolve_target) = blit.targets();
|
||||||
|
|
||||||
|
(
|
||||||
|
attachment,
|
||||||
|
Some(resolve_target),
|
||||||
|
wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(target, None, wgpu::LoadOp::Load)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut render_pass =
|
||||||
|
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: Some("iced_wgpu.triangle.render_pass"),
|
||||||
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
|
view: attachment,
|
||||||
|
resolve_target,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load,
|
||||||
|
store: wgpu::StoreOp::Store,
|
||||||
|
},
|
||||||
|
})],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
timestamp_writes: None,
|
||||||
|
occlusion_query_set: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (layer, meshes, transformation) in group {
|
||||||
|
layer.render(
|
||||||
|
solid,
|
||||||
|
gradient,
|
||||||
|
meshes,
|
||||||
|
bounds,
|
||||||
|
transformation,
|
||||||
|
&mut render_pass,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(blit) = blit {
|
||||||
|
blit.draw(encoder, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Layer {
|
pub struct Layer {
|
||||||
index_buffer: Buffer<u32>,
|
index_buffer: Buffer<u32>,
|
||||||
index_strides: Vec<u32>,
|
index_strides: Vec<u32>,
|
||||||
solid: solid::Layer,
|
solid: solid::Layer,
|
||||||
|
|
@ -52,7 +386,7 @@ impl Layer {
|
||||||
belt: &mut wgpu::util::StagingBelt,
|
belt: &mut wgpu::util::StagingBelt,
|
||||||
solid: &solid::Pipeline,
|
solid: &solid::Pipeline,
|
||||||
gradient: &gradient::Pipeline,
|
gradient: &gradient::Pipeline,
|
||||||
meshes: &[Mesh<'_>],
|
meshes: &[Mesh],
|
||||||
transformation: Transformation,
|
transformation: Transformation,
|
||||||
) {
|
) {
|
||||||
// Count the total amount of vertices & indices we need to handle
|
// Count the total amount of vertices & indices we need to handle
|
||||||
|
|
@ -157,8 +491,9 @@ impl Layer {
|
||||||
&'a self,
|
&'a self,
|
||||||
solid: &'a solid::Pipeline,
|
solid: &'a solid::Pipeline,
|
||||||
gradient: &'a gradient::Pipeline,
|
gradient: &'a gradient::Pipeline,
|
||||||
meshes: &[Mesh<'_>],
|
meshes: &[Mesh],
|
||||||
scale_factor: f32,
|
bounds: Rectangle,
|
||||||
|
transformation: Transformation,
|
||||||
render_pass: &mut wgpu::RenderPass<'a>,
|
render_pass: &mut wgpu::RenderPass<'a>,
|
||||||
) {
|
) {
|
||||||
let mut num_solids = 0;
|
let mut num_solids = 0;
|
||||||
|
|
@ -166,11 +501,12 @@ impl Layer {
|
||||||
let mut last_is_solid = None;
|
let mut last_is_solid = None;
|
||||||
|
|
||||||
for (index, mesh) in meshes.iter().enumerate() {
|
for (index, mesh) in meshes.iter().enumerate() {
|
||||||
let clip_bounds = (mesh.clip_bounds() * scale_factor).snap();
|
let Some(clip_bounds) = bounds
|
||||||
|
.intersection(&(mesh.clip_bounds() * transformation))
|
||||||
if clip_bounds.width < 1 || clip_bounds.height < 1 {
|
.and_then(Rectangle::snap)
|
||||||
|
else {
|
||||||
continue;
|
continue;
|
||||||
}
|
};
|
||||||
|
|
||||||
render_pass.set_scissor_rect(
|
render_pass.set_scissor_rect(
|
||||||
clip_bounds.x,
|
clip_bounds.x,
|
||||||
|
|
@ -234,119 +570,6 @@ impl Layer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pipeline {
|
|
||||||
pub fn new(
|
|
||||||
device: &wgpu::Device,
|
|
||||||
format: wgpu::TextureFormat,
|
|
||||||
antialiasing: Option<Antialiasing>,
|
|
||||||
) -> Pipeline {
|
|
||||||
Pipeline {
|
|
||||||
blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)),
|
|
||||||
solid: solid::Pipeline::new(device, format, antialiasing),
|
|
||||||
gradient: gradient::Pipeline::new(device, format, antialiasing),
|
|
||||||
layers: Vec::new(),
|
|
||||||
prepare_layer: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prepare(
|
|
||||||
&mut self,
|
|
||||||
device: &wgpu::Device,
|
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
|
||||||
belt: &mut wgpu::util::StagingBelt,
|
|
||||||
meshes: &[Mesh<'_>],
|
|
||||||
transformation: Transformation,
|
|
||||||
) {
|
|
||||||
#[cfg(feature = "tracing")]
|
|
||||||
let _ = tracing::info_span!("Wgpu::Triangle", "PREPARE").entered();
|
|
||||||
|
|
||||||
if self.layers.len() <= self.prepare_layer {
|
|
||||||
self.layers
|
|
||||||
.push(Layer::new(device, &self.solid, &self.gradient));
|
|
||||||
}
|
|
||||||
|
|
||||||
let layer = &mut self.layers[self.prepare_layer];
|
|
||||||
layer.prepare(
|
|
||||||
device,
|
|
||||||
encoder,
|
|
||||||
belt,
|
|
||||||
&self.solid,
|
|
||||||
&self.gradient,
|
|
||||||
meshes,
|
|
||||||
transformation,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.prepare_layer += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render(
|
|
||||||
&mut self,
|
|
||||||
device: &wgpu::Device,
|
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
|
||||||
target: &wgpu::TextureView,
|
|
||||||
layer: usize,
|
|
||||||
target_size: Size<u32>,
|
|
||||||
meshes: &[Mesh<'_>],
|
|
||||||
scale_factor: f32,
|
|
||||||
) {
|
|
||||||
#[cfg(feature = "tracing")]
|
|
||||||
let _ = tracing::info_span!("Wgpu::Triangle", "DRAW").entered();
|
|
||||||
|
|
||||||
{
|
|
||||||
let (attachment, resolve_target, load) = if let Some(blit) =
|
|
||||||
&mut self.blit
|
|
||||||
{
|
|
||||||
let (attachment, resolve_target) =
|
|
||||||
blit.targets(device, target_size.width, target_size.height);
|
|
||||||
|
|
||||||
(
|
|
||||||
attachment,
|
|
||||||
Some(resolve_target),
|
|
||||||
wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(target, None, wgpu::LoadOp::Load)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut render_pass =
|
|
||||||
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
||||||
label: Some("iced_wgpu.triangle.render_pass"),
|
|
||||||
color_attachments: &[Some(
|
|
||||||
wgpu::RenderPassColorAttachment {
|
|
||||||
view: attachment,
|
|
||||||
resolve_target,
|
|
||||||
ops: wgpu::Operations {
|
|
||||||
load,
|
|
||||||
store: wgpu::StoreOp::Store,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)],
|
|
||||||
depth_stencil_attachment: None,
|
|
||||||
timestamp_writes: None,
|
|
||||||
occlusion_query_set: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
let layer = &mut self.layers[layer];
|
|
||||||
|
|
||||||
layer.render(
|
|
||||||
&self.solid,
|
|
||||||
&self.gradient,
|
|
||||||
meshes,
|
|
||||||
scale_factor,
|
|
||||||
&mut render_pass,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(blit) = &mut self.blit {
|
|
||||||
blit.draw(encoder, target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn end_frame(&mut self) {
|
|
||||||
self.prepare_layer = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fragment_target(
|
fn fragment_target(
|
||||||
texture_format: wgpu::TextureFormat,
|
texture_format: wgpu::TextureFormat,
|
||||||
) -> wgpu::ColorTargetState {
|
) -> wgpu::ColorTargetState {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,18 @@
|
||||||
|
use crate::core::{Size, Transformation};
|
||||||
use crate::graphics;
|
use crate::graphics;
|
||||||
|
|
||||||
|
use std::num::NonZeroU64;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Blit {
|
pub struct Blit {
|
||||||
format: wgpu::TextureFormat,
|
format: wgpu::TextureFormat,
|
||||||
pipeline: wgpu::RenderPipeline,
|
pipeline: wgpu::RenderPipeline,
|
||||||
constants: wgpu::BindGroup,
|
constants: wgpu::BindGroup,
|
||||||
|
ratio: wgpu::Buffer,
|
||||||
texture_layout: wgpu::BindGroupLayout,
|
texture_layout: wgpu::BindGroupLayout,
|
||||||
sample_count: u32,
|
sample_count: u32,
|
||||||
targets: Option<Targets>,
|
targets: Option<Targets>,
|
||||||
|
last_region: Option<Size<u32>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Blit {
|
impl Blit {
|
||||||
|
|
@ -19,27 +24,52 @@ impl Blit {
|
||||||
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"),
|
||||||
entries: &[wgpu::BindGroupLayoutEntry {
|
entries: &[
|
||||||
binding: 0,
|
wgpu::BindGroupLayoutEntry {
|
||||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
binding: 0,
|
||||||
ty: wgpu::BindingType::Sampler(
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
wgpu::SamplerBindingType::NonFiltering,
|
ty: wgpu::BindingType::Sampler(
|
||||||
),
|
wgpu::SamplerBindingType::NonFiltering,
|
||||||
count: None,
|
),
|
||||||
}],
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 1,
|
||||||
|
visibility: wgpu::ShaderStages::VERTEX,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: None,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
let constant_bind_group =
|
let constant_bind_group =
|
||||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
label: Some("iced_wgpu::triangle::msaa uniforms bind group"),
|
label: Some("iced_wgpu::triangle::msaa uniforms bind group"),
|
||||||
layout: &constant_layout,
|
layout: &constant_layout,
|
||||||
entries: &[wgpu::BindGroupEntry {
|
entries: &[
|
||||||
binding: 0,
|
wgpu::BindGroupEntry {
|
||||||
resource: wgpu::BindingResource::Sampler(&sampler),
|
binding: 0,
|
||||||
}],
|
resource: wgpu::BindingResource::Sampler(&sampler),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: ratio.as_entire_binding(),
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
let texture_layout =
|
let texture_layout =
|
||||||
|
|
@ -112,43 +142,61 @@ impl Blit {
|
||||||
format,
|
format,
|
||||||
pipeline,
|
pipeline,
|
||||||
constants: constant_bind_group,
|
constants: constant_bind_group,
|
||||||
|
ratio,
|
||||||
texture_layout,
|
texture_layout,
|
||||||
sample_count: antialiasing.sample_count(),
|
sample_count: antialiasing.sample_count(),
|
||||||
targets: None,
|
targets: None,
|
||||||
|
last_region: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn targets(
|
pub fn prepare(
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
width: u32,
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
height: u32,
|
belt: &mut wgpu::util::StagingBelt,
|
||||||
) -> (&wgpu::TextureView, &wgpu::TextureView) {
|
region_size: Size<u32>,
|
||||||
|
) -> Transformation {
|
||||||
match &mut self.targets {
|
match &mut self.targets {
|
||||||
None => {
|
Some(targets)
|
||||||
|
if region_size.width <= targets.size.width
|
||||||
|
&& region_size.height <= targets.size.height => {}
|
||||||
|
_ => {
|
||||||
self.targets = Some(Targets::new(
|
self.targets = Some(Targets::new(
|
||||||
device,
|
device,
|
||||||
self.format,
|
self.format,
|
||||||
&self.texture_layout,
|
&self.texture_layout,
|
||||||
self.sample_count,
|
self.sample_count,
|
||||||
width,
|
region_size,
|
||||||
height,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Some(targets) => {
|
|
||||||
if targets.width != width || targets.height != height {
|
|
||||||
self.targets = Some(Targets::new(
|
|
||||||
device,
|
|
||||||
self.format,
|
|
||||||
&self.texture_layout,
|
|
||||||
self.sample_count,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let targets = self.targets.as_mut().unwrap();
|
||||||
|
|
||||||
|
if Some(region_size) != self.last_region {
|
||||||
|
let ratio = Ratio {
|
||||||
|
u: region_size.width as f32 / targets.size.width as f32,
|
||||||
|
v: region_size.height as f32 / targets.size.height as f32,
|
||||||
|
};
|
||||||
|
|
||||||
|
belt.write_buffer(
|
||||||
|
encoder,
|
||||||
|
&self.ratio,
|
||||||
|
0,
|
||||||
|
NonZeroU64::new(std::mem::size_of::<Ratio>() as u64)
|
||||||
|
.expect("non-empty ratio"),
|
||||||
|
device,
|
||||||
|
)
|
||||||
|
.copy_from_slice(bytemuck::bytes_of(&ratio));
|
||||||
|
|
||||||
|
self.last_region = Some(region_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
Transformation::orthographic(targets.size.width, targets.size.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn targets(&self) -> (&wgpu::TextureView, &wgpu::TextureView) {
|
||||||
let targets = self.targets.as_ref().unwrap();
|
let targets = self.targets.as_ref().unwrap();
|
||||||
|
|
||||||
(&targets.attachment, &targets.resolve)
|
(&targets.attachment, &targets.resolve)
|
||||||
|
|
@ -191,8 +239,7 @@ struct Targets {
|
||||||
attachment: wgpu::TextureView,
|
attachment: wgpu::TextureView,
|
||||||
resolve: wgpu::TextureView,
|
resolve: wgpu::TextureView,
|
||||||
bind_group: wgpu::BindGroup,
|
bind_group: wgpu::BindGroup,
|
||||||
width: u32,
|
size: Size<u32>,
|
||||||
height: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Targets {
|
impl Targets {
|
||||||
|
|
@ -201,12 +248,11 @@ impl Targets {
|
||||||
format: wgpu::TextureFormat,
|
format: wgpu::TextureFormat,
|
||||||
texture_layout: &wgpu::BindGroupLayout,
|
texture_layout: &wgpu::BindGroupLayout,
|
||||||
sample_count: u32,
|
sample_count: u32,
|
||||||
width: u32,
|
size: Size<u32>,
|
||||||
height: u32,
|
|
||||||
) -> Targets {
|
) -> Targets {
|
||||||
let extent = wgpu::Extent3d {
|
let extent = wgpu::Extent3d {
|
||||||
width,
|
width: size.width,
|
||||||
height,
|
height: size.height,
|
||||||
depth_or_array_layers: 1,
|
depth_or_array_layers: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -252,8 +298,14 @@ impl Targets {
|
||||||
attachment,
|
attachment,
|
||||||
resolve,
|
resolve,
|
||||||
bind_group,
|
bind_group,
|
||||||
width,
|
size,
|
||||||
height,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
|
#[repr(C)]
|
||||||
|
struct Ratio {
|
||||||
|
u: f32,
|
||||||
|
v: f32,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,19 @@ use crate::graphics::color;
|
||||||
use crate::graphics::compositor;
|
use crate::graphics::compositor;
|
||||||
use crate::graphics::error;
|
use crate::graphics::error;
|
||||||
use crate::graphics::{self, Viewport};
|
use crate::graphics::{self, Viewport};
|
||||||
use crate::{Backend, Primitive, Renderer, Settings};
|
use crate::{Engine, Renderer, Settings};
|
||||||
|
|
||||||
/// A window graphics backend for iced powered by `wgpu`.
|
/// A window graphics backend for iced powered by `wgpu`.
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Compositor {
|
pub struct Compositor {
|
||||||
settings: Settings,
|
|
||||||
instance: wgpu::Instance,
|
instance: wgpu::Instance,
|
||||||
adapter: wgpu::Adapter,
|
adapter: wgpu::Adapter,
|
||||||
device: wgpu::Device,
|
device: wgpu::Device,
|
||||||
queue: wgpu::Queue,
|
queue: wgpu::Queue,
|
||||||
format: wgpu::TextureFormat,
|
format: wgpu::TextureFormat,
|
||||||
alpha_mode: wgpu::CompositeAlphaMode,
|
alpha_mode: wgpu::CompositeAlphaMode,
|
||||||
|
engine: Engine,
|
||||||
|
settings: Settings,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A compositor error.
|
/// A compositor error.
|
||||||
|
|
@ -53,7 +54,7 @@ impl Compositor {
|
||||||
compatible_window: Option<W>,
|
compatible_window: Option<W>,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||||
backends: settings.internal_backend,
|
backends: settings.backends,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -62,7 +63,7 @@ impl Compositor {
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
if log::max_level() >= log::LevelFilter::Info {
|
if log::max_level() >= log::LevelFilter::Info {
|
||||||
let available_adapters: Vec<_> = instance
|
let available_adapters: Vec<_> = instance
|
||||||
.enumerate_adapters(settings.internal_backend)
|
.enumerate_adapters(settings.backends)
|
||||||
.iter()
|
.iter()
|
||||||
.map(wgpu::Adapter::get_info)
|
.map(wgpu::Adapter::get_info)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
@ -167,15 +168,24 @@ impl Compositor {
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok((device, queue)) => {
|
Ok((device, queue)) => {
|
||||||
|
let engine = Engine::new(
|
||||||
|
&adapter,
|
||||||
|
&device,
|
||||||
|
&queue,
|
||||||
|
format,
|
||||||
|
settings.antialiasing,
|
||||||
|
);
|
||||||
|
|
||||||
return Ok(Compositor {
|
return Ok(Compositor {
|
||||||
instance,
|
instance,
|
||||||
settings,
|
|
||||||
adapter,
|
adapter,
|
||||||
device,
|
device,
|
||||||
queue,
|
queue,
|
||||||
format,
|
format,
|
||||||
alpha_mode,
|
alpha_mode,
|
||||||
})
|
engine,
|
||||||
|
settings,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
errors.push((required_limits, error));
|
errors.push((required_limits, error));
|
||||||
|
|
@ -185,21 +195,9 @@ impl Compositor {
|
||||||
|
|
||||||
Err(Error::RequestDeviceFailed(errors))
|
Err(Error::RequestDeviceFailed(errors))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new rendering [`Backend`] for this [`Compositor`].
|
|
||||||
pub fn create_backend(&self) -> Backend {
|
|
||||||
Backend::new(
|
|
||||||
&self.adapter,
|
|
||||||
&self.device,
|
|
||||||
&self.queue,
|
|
||||||
self.settings,
|
|
||||||
self.format,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a [`Compositor`] and its [`Backend`] for the given [`Settings`] and
|
/// Creates a [`Compositor`] with the given [`Settings`] and window.
|
||||||
/// window.
|
|
||||||
pub async fn new<W: compositor::Window>(
|
pub async fn new<W: compositor::Window>(
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
compatible_window: W,
|
compatible_window: W,
|
||||||
|
|
@ -207,12 +205,11 @@ pub async fn new<W: compositor::Window>(
|
||||||
Compositor::request(settings, Some(compatible_window)).await
|
Compositor::request(settings, Some(compatible_window)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Presents the given primitives with the given [`Compositor`] and [`Backend`].
|
/// Presents the given primitives with the given [`Compositor`].
|
||||||
pub fn present<T: AsRef<str>>(
|
pub fn present<T: AsRef<str>>(
|
||||||
compositor: &mut Compositor,
|
compositor: &mut Compositor,
|
||||||
backend: &mut Backend,
|
renderer: &mut Renderer,
|
||||||
surface: &mut wgpu::Surface<'static>,
|
surface: &mut wgpu::Surface<'static>,
|
||||||
primitives: &[Primitive],
|
|
||||||
viewport: &Viewport,
|
viewport: &Viewport,
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
overlay: &[T],
|
overlay: &[T],
|
||||||
|
|
@ -229,21 +226,21 @@ pub fn present<T: AsRef<str>>(
|
||||||
.texture
|
.texture
|
||||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
|
||||||
backend.present(
|
renderer.present(
|
||||||
|
&mut compositor.engine,
|
||||||
&compositor.device,
|
&compositor.device,
|
||||||
&compositor.queue,
|
&compositor.queue,
|
||||||
&mut encoder,
|
&mut encoder,
|
||||||
Some(background_color),
|
Some(background_color),
|
||||||
frame.texture.format(),
|
frame.texture.format(),
|
||||||
view,
|
view,
|
||||||
primitives,
|
|
||||||
viewport,
|
viewport,
|
||||||
overlay,
|
overlay,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Submit work
|
let _ = compositor.engine.submit(&compositor.queue, encoder);
|
||||||
let _submission = compositor.queue.submit(Some(encoder.finish()));
|
|
||||||
backend.recall();
|
// Present the frame
|
||||||
frame.present();
|
frame.present();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -275,7 +272,7 @@ impl graphics::Compositor for Compositor {
|
||||||
match backend {
|
match backend {
|
||||||
None | Some("wgpu") => Ok(new(
|
None | Some("wgpu") => Ok(new(
|
||||||
Settings {
|
Settings {
|
||||||
internal_backend: wgpu::util::backend_bits_from_env()
|
backends: wgpu::util::backend_bits_from_env()
|
||||||
.unwrap_or(wgpu::Backends::all()),
|
.unwrap_or(wgpu::Backends::all()),
|
||||||
..settings.into()
|
..settings.into()
|
||||||
},
|
},
|
||||||
|
|
@ -293,7 +290,7 @@ impl graphics::Compositor for Compositor {
|
||||||
|
|
||||||
fn create_renderer(&self) -> Self::Renderer {
|
fn create_renderer(&self) -> Self::Renderer {
|
||||||
Renderer::new(
|
Renderer::new(
|
||||||
self.create_backend(),
|
&self.engine,
|
||||||
self.settings.default_font,
|
self.settings.default_font,
|
||||||
self.settings.default_text_size,
|
self.settings.default_text_size,
|
||||||
)
|
)
|
||||||
|
|
@ -333,7 +330,7 @@ impl graphics::Compositor for Compositor {
|
||||||
height,
|
height,
|
||||||
alpha_mode: self.alpha_mode,
|
alpha_mode: self.alpha_mode,
|
||||||
view_formats: vec![],
|
view_formats: vec![],
|
||||||
desired_maximum_frame_latency: 2,
|
desired_maximum_frame_latency: 1,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -355,17 +352,7 @@ impl graphics::Compositor for Compositor {
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
overlay: &[T],
|
overlay: &[T],
|
||||||
) -> Result<(), compositor::SurfaceError> {
|
) -> Result<(), compositor::SurfaceError> {
|
||||||
renderer.with_primitives(|backend, primitives| {
|
present(self, renderer, surface, viewport, background_color, overlay)
|
||||||
present(
|
|
||||||
self,
|
|
||||||
backend,
|
|
||||||
surface,
|
|
||||||
primitives,
|
|
||||||
viewport,
|
|
||||||
background_color,
|
|
||||||
overlay,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn screenshot<T: AsRef<str>>(
|
fn screenshot<T: AsRef<str>>(
|
||||||
|
|
@ -376,16 +363,7 @@ impl graphics::Compositor for Compositor {
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
overlay: &[T],
|
overlay: &[T],
|
||||||
) -> Vec<u8> {
|
) -> Vec<u8> {
|
||||||
renderer.with_primitives(|backend, primitives| {
|
screenshot(self, renderer, viewport, background_color, overlay)
|
||||||
screenshot(
|
|
||||||
self,
|
|
||||||
backend,
|
|
||||||
primitives,
|
|
||||||
viewport,
|
|
||||||
background_color,
|
|
||||||
overlay,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -393,19 +371,12 @@ impl graphics::Compositor for Compositor {
|
||||||
///
|
///
|
||||||
/// Returns RGBA bytes of the texture data.
|
/// Returns RGBA bytes of the texture data.
|
||||||
pub fn screenshot<T: AsRef<str>>(
|
pub fn screenshot<T: AsRef<str>>(
|
||||||
compositor: &Compositor,
|
compositor: &mut Compositor,
|
||||||
backend: &mut Backend,
|
renderer: &mut Renderer,
|
||||||
primitives: &[Primitive],
|
|
||||||
viewport: &Viewport,
|
viewport: &Viewport,
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
overlay: &[T],
|
overlay: &[T],
|
||||||
) -> Vec<u8> {
|
) -> Vec<u8> {
|
||||||
let mut encoder = compositor.device.create_command_encoder(
|
|
||||||
&wgpu::CommandEncoderDescriptor {
|
|
||||||
label: Some("iced_wgpu.offscreen.encoder"),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let dimensions = BufferDimensions::new(viewport.physical_size());
|
let dimensions = BufferDimensions::new(viewport.physical_size());
|
||||||
|
|
||||||
let texture_extent = wgpu::Extent3d {
|
let texture_extent = wgpu::Extent3d {
|
||||||
|
|
@ -429,14 +400,20 @@ pub fn screenshot<T: AsRef<str>>(
|
||||||
|
|
||||||
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
|
||||||
backend.present(
|
let mut encoder = compositor.device.create_command_encoder(
|
||||||
|
&wgpu::CommandEncoderDescriptor {
|
||||||
|
label: Some("iced_wgpu.offscreen.encoder"),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
renderer.present(
|
||||||
|
&mut compositor.engine,
|
||||||
&compositor.device,
|
&compositor.device,
|
||||||
&compositor.queue,
|
&compositor.queue,
|
||||||
&mut encoder,
|
&mut encoder,
|
||||||
Some(background_color),
|
Some(background_color),
|
||||||
texture.format(),
|
texture.format(),
|
||||||
&view,
|
&view,
|
||||||
primitives,
|
|
||||||
viewport,
|
viewport,
|
||||||
overlay,
|
overlay,
|
||||||
);
|
);
|
||||||
|
|
@ -474,7 +451,7 @@ pub fn screenshot<T: AsRef<str>>(
|
||||||
texture_extent,
|
texture_extent,
|
||||||
);
|
);
|
||||||
|
|
||||||
let index = compositor.queue.submit(Some(encoder.finish()));
|
let index = compositor.engine.submit(&compositor.queue, encoder);
|
||||||
|
|
||||||
let slice = output_buffer.slice(..);
|
let slice = output_buffer.slice(..);
|
||||||
slice.map_async(wgpu::MapMode::Read, |_| {});
|
slice.map_async(wgpu::MapMode::Read, |_| {});
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ use crate::core::mouse;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Clipboard, Element, Length, Rectangle, Shell, Size, Transformation, Widget,
|
Clipboard, Element, Length, Rectangle, Shell, Size, Vector, Widget,
|
||||||
};
|
};
|
||||||
use crate::graphics::geometry;
|
use crate::graphics::geometry;
|
||||||
|
|
||||||
|
|
@ -222,8 +222,8 @@ where
|
||||||
|
|
||||||
let state = tree.state.downcast_ref::<P::State>();
|
let state = tree.state.downcast_ref::<P::State>();
|
||||||
|
|
||||||
renderer.with_transformation(
|
renderer.with_translation(
|
||||||
Transformation::translate(bounds.x, bounds.y),
|
Vector::new(bounds.x, bounds.y),
|
||||||
|renderer| {
|
|renderer| {
|
||||||
let layers =
|
let layers =
|
||||||
self.program.draw(state, renderer, theme, bounds, cursor);
|
self.program.draw(state, renderer, theme, bounds, cursor);
|
||||||
|
|
|
||||||
|
|
@ -651,7 +651,7 @@ where
|
||||||
defaults: &renderer::Style,
|
defaults: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
_viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
let state = tree.state.downcast_ref::<State>();
|
let state = tree.state.downcast_ref::<State>();
|
||||||
|
|
||||||
|
|
@ -767,8 +767,8 @@ where
|
||||||
|
|
||||||
renderer.with_layer(
|
renderer.with_layer(
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: bounds.width + 2.0,
|
width: (bounds.width + 2.0).min(viewport.width),
|
||||||
height: bounds.height + 2.0,
|
height: (bounds.height + 2.0).min(viewport.height),
|
||||||
..bounds
|
..bounds
|
||||||
},
|
},
|
||||||
|renderer| {
|
|renderer| {
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,13 @@ use crate::core::widget::tree::{self, Tree};
|
||||||
use crate::core::widget::{self, Widget};
|
use crate::core::widget::{self, Widget};
|
||||||
use crate::core::window;
|
use crate::core::window;
|
||||||
use crate::core::{Clipboard, Element, Length, Rectangle, Shell, Size};
|
use crate::core::{Clipboard, Element, Length, Rectangle, Shell, Size};
|
||||||
use crate::renderer::wgpu::primitive::pipeline;
|
use crate::renderer::wgpu::primitive;
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
pub use crate::graphics::Viewport;
|
||||||
pub use crate::renderer::wgpu::wgpu;
|
pub use crate::renderer::wgpu::wgpu;
|
||||||
pub use pipeline::{Primitive, Storage};
|
pub use primitive::{Primitive, Storage};
|
||||||
|
|
||||||
/// A widget which can render custom shaders with Iced's `wgpu` backend.
|
/// A widget which can render custom shaders with Iced's `wgpu` backend.
|
||||||
///
|
///
|
||||||
|
|
@ -60,7 +61,7 @@ impl<P, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||||
for Shader<Message, P>
|
for Shader<Message, P>
|
||||||
where
|
where
|
||||||
P: Program<Message>,
|
P: Program<Message>,
|
||||||
Renderer: pipeline::Renderer,
|
Renderer: primitive::Renderer,
|
||||||
{
|
{
|
||||||
fn tag(&self) -> tree::Tag {
|
fn tag(&self) -> tree::Tag {
|
||||||
struct Tag<T>(T);
|
struct Tag<T>(T);
|
||||||
|
|
@ -160,7 +161,7 @@ where
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
let state = tree.state.downcast_ref::<P::State>();
|
let state = tree.state.downcast_ref::<P::State>();
|
||||||
|
|
||||||
renderer.draw_pipeline_primitive(
|
renderer.draw_primitive(
|
||||||
bounds,
|
bounds,
|
||||||
self.program.draw(state, cursor_position, bounds),
|
self.program.draw(state, cursor_position, bounds),
|
||||||
);
|
);
|
||||||
|
|
@ -171,7 +172,7 @@ impl<'a, Message, Theme, Renderer, P> From<Shader<Message, P>>
|
||||||
for Element<'a, Message, Theme, Renderer>
|
for Element<'a, Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
Message: 'a,
|
Message: 'a,
|
||||||
Renderer: pipeline::Renderer,
|
Renderer: primitive::Renderer,
|
||||||
P: Program<Message> + 'a,
|
P: Program<Message> + 'a,
|
||||||
{
|
{
|
||||||
fn from(
|
fn from(
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::core::event;
|
use crate::core::event;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::{Rectangle, Shell};
|
use crate::core::{Rectangle, Shell};
|
||||||
use crate::renderer::wgpu::primitive::pipeline;
|
use crate::renderer::wgpu::Primitive;
|
||||||
use crate::shader;
|
use crate::shader;
|
||||||
|
|
||||||
/// The state and logic of a [`Shader`] widget.
|
/// The state and logic of a [`Shader`] widget.
|
||||||
|
|
@ -15,7 +15,7 @@ pub trait Program<Message> {
|
||||||
type State: Default + 'static;
|
type State: Default + 'static;
|
||||||
|
|
||||||
/// The type of primitive this [`Program`] can draw.
|
/// The type of primitive this [`Program`] can draw.
|
||||||
type Primitive: pipeline::Primitive + 'static;
|
type Primitive: Primitive + 'static;
|
||||||
|
|
||||||
/// Update the internal [`State`] of the [`Program`]. This can be used to reflect state changes
|
/// Update the internal [`State`] of the [`Program`]. This can be used to reflect state changes
|
||||||
/// based on mouse & other events. You can use the [`Shell`] to publish messages, request a
|
/// based on mouse & other events. You can use the [`Shell`] to publish messages, request a
|
||||||
|
|
|
||||||
|
|
@ -368,7 +368,7 @@ where
|
||||||
|
|
||||||
let text = value.to_string();
|
let text = value.to_string();
|
||||||
|
|
||||||
let (cursor, offset) = if let Some(focus) = state
|
let (cursor, offset, is_selecting) = if let Some(focus) = state
|
||||||
.is_focused
|
.is_focused
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.filter(|focus| focus.is_window_focused)
|
.filter(|focus| focus.is_window_focused)
|
||||||
|
|
@ -406,7 +406,7 @@ where
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
(cursor, offset)
|
(cursor, offset, false)
|
||||||
}
|
}
|
||||||
cursor::State::Selection { start, end } => {
|
cursor::State::Selection { start, end } => {
|
||||||
let left = start.min(end);
|
let left = start.min(end);
|
||||||
|
|
@ -446,11 +446,12 @@ where
|
||||||
} else {
|
} else {
|
||||||
left_offset
|
left_offset
|
||||||
},
|
},
|
||||||
|
true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
(None, 0.0)
|
(None, 0.0, false)
|
||||||
};
|
};
|
||||||
|
|
||||||
let draw = |renderer: &mut Renderer, viewport| {
|
let draw = |renderer: &mut Renderer, viewport| {
|
||||||
|
|
@ -482,7 +483,7 @@ where
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
if cursor.is_some() {
|
if is_selecting {
|
||||||
renderer
|
renderer
|
||||||
.with_layer(text_bounds, |renderer| draw(renderer, *viewport));
|
.with_layer(text_bounds, |renderer| draw(renderer, *viewport));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -220,13 +220,11 @@ where
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let compositor = C::new(graphics_settings, window.clone()).await?;
|
let mut compositor = C::new(graphics_settings, window.clone()).await?;
|
||||||
let mut renderer = compositor.create_renderer();
|
let renderer = compositor.create_renderer();
|
||||||
|
|
||||||
for font in settings.fonts {
|
for font in settings.fonts {
|
||||||
use crate::core::text::Renderer;
|
compositor.load_font(font);
|
||||||
|
|
||||||
renderer.load_font(font);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let (mut event_sender, event_receiver) = mpsc::unbounded();
|
let (mut event_sender, event_receiver) = mpsc::unbounded();
|
||||||
|
|
@ -950,10 +948,8 @@ pub fn run_command<A, C, E>(
|
||||||
*cache = current_cache;
|
*cache = current_cache;
|
||||||
}
|
}
|
||||||
command::Action::LoadFont { bytes, tagger } => {
|
command::Action::LoadFont { bytes, tagger } => {
|
||||||
use crate::core::text::Renderer;
|
|
||||||
|
|
||||||
// TODO: Error handling (?)
|
// TODO: Error handling (?)
|
||||||
renderer.load_font(bytes);
|
compositor.load_font(bytes);
|
||||||
|
|
||||||
proxy
|
proxy
|
||||||
.send_event(tagger(Ok(())))
|
.send_event(tagger(Ok(())))
|
||||||
|
|
|
||||||
|
|
@ -1194,13 +1194,8 @@ fn run_command<A, C, E>(
|
||||||
uis.drain().map(|(id, ui)| (id, ui.into_cache())).collect();
|
uis.drain().map(|(id, ui)| (id, ui.into_cache())).collect();
|
||||||
}
|
}
|
||||||
command::Action::LoadFont { bytes, tagger } => {
|
command::Action::LoadFont { bytes, tagger } => {
|
||||||
use crate::core::text::Renderer;
|
|
||||||
|
|
||||||
// TODO change this once we change each renderer to having a single backend reference.. :pain:
|
|
||||||
// TODO: Error handling (?)
|
// TODO: Error handling (?)
|
||||||
for (_, window) in window_manager.iter_mut() {
|
compositor.load_font(bytes.clone());
|
||||||
window.renderer.load_font(bytes.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy
|
proxy
|
||||||
.send_event(tagger(Ok(())))
|
.send_event(tagger(Ok(())))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue