revert(web): drop vendored iced renderer
Keep iced_wgpu external and leave the WebGPU storage-buffer optimization out of scope.
This commit is contained in:
parent
deec48ad99
commit
fe49cc8eb5
46 changed files with 47 additions and 9347 deletions
|
|
@ -1,146 +0,0 @@
|
|||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2024"
|
||||
name = "iced_wgpu"
|
||||
version = "0.14.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector@hecrj.dev>"]
|
||||
build = false
|
||||
autolib = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "A renderer for iced on top of wgpu"
|
||||
homepage = "https://iced.rs"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"gui",
|
||||
"ui",
|
||||
"graphics",
|
||||
"interface",
|
||||
"widgets",
|
||||
]
|
||||
categories = ["gui"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/iced-rs/iced"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = [
|
||||
"--cfg",
|
||||
"docsrs",
|
||||
]
|
||||
all-features = true
|
||||
|
||||
[features]
|
||||
default = ["wgpu/default"]
|
||||
geometry = [
|
||||
"iced_graphics/geometry",
|
||||
"lyon",
|
||||
]
|
||||
image = ["iced_graphics/image"]
|
||||
strict-assertions = []
|
||||
svg = [
|
||||
"iced_graphics/svg",
|
||||
"resvg/text",
|
||||
]
|
||||
web-colors = ["iced_graphics/web-colors"]
|
||||
webgl = ["wgpu/webgl"]
|
||||
|
||||
[lib]
|
||||
name = "iced_wgpu"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies.bitflags]
|
||||
version = "2.0"
|
||||
|
||||
[dependencies.bytemuck]
|
||||
version = "1.0"
|
||||
features = ["derive"]
|
||||
|
||||
[dependencies.cryoglyph]
|
||||
version = "0.1"
|
||||
|
||||
[dependencies.futures]
|
||||
version = "0.3"
|
||||
features = [
|
||||
"std",
|
||||
"async-await",
|
||||
]
|
||||
default-features = false
|
||||
|
||||
[dependencies.glam]
|
||||
version = "0.25"
|
||||
|
||||
[dependencies.guillotiere]
|
||||
version = "0.6"
|
||||
|
||||
[dependencies.iced_debug]
|
||||
version = "0.14.0"
|
||||
|
||||
[dependencies.iced_graphics]
|
||||
version = "0.14.0"
|
||||
|
||||
[dependencies.log]
|
||||
version = "0.4"
|
||||
|
||||
[dependencies.lyon]
|
||||
version = "1.0"
|
||||
optional = true
|
||||
|
||||
[dependencies.resvg]
|
||||
version = "0.45"
|
||||
optional = true
|
||||
|
||||
[dependencies.rustc-hash]
|
||||
version = "2.0"
|
||||
|
||||
[dependencies.thiserror]
|
||||
version = "2"
|
||||
|
||||
[dependencies.wgpu]
|
||||
version = "27.0"
|
||||
features = [
|
||||
"std",
|
||||
"wgsl",
|
||||
]
|
||||
default-features = false
|
||||
|
||||
[lints.clippy]
|
||||
default_trait_access = "deny"
|
||||
filter_map_next = "deny"
|
||||
from_over_into = "deny"
|
||||
large-enum-variant = "allow"
|
||||
manual_let_else = "deny"
|
||||
map-entry = "allow"
|
||||
match-wildcard-for-single-variants = "deny"
|
||||
needless_borrow = "deny"
|
||||
new_without_default = "deny"
|
||||
redundant-closure-for-method-calls = "deny"
|
||||
result_large_err = "allow"
|
||||
semicolon_if_nothing_returned = "deny"
|
||||
trivially-copy-pass-by-ref = "deny"
|
||||
type-complexity = "allow"
|
||||
unused_async = "deny"
|
||||
useless_conversion = "deny"
|
||||
|
||||
[lints.rust]
|
||||
missing_docs = "deny"
|
||||
unsafe_code = "deny"
|
||||
unused_results = "deny"
|
||||
|
||||
[lints.rust.rust_2018_idioms]
|
||||
level = "deny"
|
||||
priority = -1
|
||||
|
||||
[lints.rustdoc]
|
||||
broken_intra_doc_links = "forbid"
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
Copyright 2019 Héctor Ramón, Iced contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
# OpenCADStudio patch
|
||||
|
||||
This directory vendors `iced_wgpu` 0.14.0 under its MIT license.
|
||||
|
||||
OpenCADStudio changes only the WASM device-limit selection in
|
||||
`src/window/compositor.rs`: Browser WebGPU first requests normal WebGPU limits
|
||||
so storage-buffer pipelines are available, then falls back to WebGL2 limits.
|
||||
An actual WebGL adapter continues to request WebGL2 limits directly.
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
# `iced_wgpu`
|
||||
[][documentation]
|
||||
[](https://crates.io/crates/iced_wgpu)
|
||||
[](https://github.com/iced-rs/iced/blob/master/LICENSE)
|
||||
[](https://discord.gg/3xZJ65GAhd)
|
||||
|
||||
`iced_wgpu` is a [`wgpu`] renderer for [`iced_runtime`]. For now, it is the default renderer of Iced on [native platforms].
|
||||
|
||||
[`wgpu`] supports most modern graphics backends: Vulkan, Metal, DX12, OpenGL, and WebGPU.
|
||||
|
||||
<p align="center">
|
||||
<img alt="The native target" src="../docs/graphs/native.png" width="80%">
|
||||
</p>
|
||||
|
||||
[documentation]: https://docs.rs/iced_wgpu
|
||||
[`iced_runtime`]: ../runtime
|
||||
[`wgpu`]: https://github.com/gfx-rs/wgpu
|
||||
[native platforms]: https://github.com/gfx-rs/wgpu#supported-platforms
|
||||
[WebGPU API]: https://gpuweb.github.io/gpuweb/
|
||||
[`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
use std::marker::PhantomData;
|
||||
use std::num::NonZeroU64;
|
||||
use std::ops::RangeBounds;
|
||||
|
||||
pub const MAX_WRITE_SIZE: usize = 100 * 1024;
|
||||
|
||||
const MAX_WRITE_SIZE_U64: NonZeroU64 = NonZeroU64::new(MAX_WRITE_SIZE as u64)
|
||||
.expect("MAX_WRITE_SIZE must be non-zero");
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Buffer<T> {
|
||||
label: &'static str,
|
||||
size: u64,
|
||||
usage: wgpu::BufferUsages,
|
||||
pub(crate) raw: wgpu::Buffer,
|
||||
type_: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: bytemuck::Pod> Buffer<T> {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
label: &'static str,
|
||||
amount: usize,
|
||||
usage: wgpu::BufferUsages,
|
||||
) -> Self {
|
||||
let size = next_copy_size::<T>(amount);
|
||||
|
||||
let raw = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some(label),
|
||||
size,
|
||||
usage,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
Self {
|
||||
label,
|
||||
size,
|
||||
usage,
|
||||
raw,
|
||||
type_: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, device: &wgpu::Device, new_count: usize) -> bool {
|
||||
let new_size = next_copy_size::<T>(new_count);
|
||||
|
||||
if self.size < new_size {
|
||||
self.raw = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some(self.label),
|
||||
size: new_size,
|
||||
usage: self.usage,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
self.size = new_size;
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the size of the written bytes.
|
||||
pub fn write(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
offset: usize,
|
||||
contents: &[T],
|
||||
) -> usize {
|
||||
let bytes: &[u8] = bytemuck::cast_slice(contents);
|
||||
let mut bytes_written = 0;
|
||||
|
||||
// Split write into multiple chunks if necessary
|
||||
while bytes_written + MAX_WRITE_SIZE < bytes.len() {
|
||||
belt.write_buffer(
|
||||
encoder,
|
||||
&self.raw,
|
||||
(offset + bytes_written) as u64,
|
||||
MAX_WRITE_SIZE_U64,
|
||||
device,
|
||||
)
|
||||
.copy_from_slice(
|
||||
&bytes[bytes_written..bytes_written + MAX_WRITE_SIZE],
|
||||
);
|
||||
|
||||
bytes_written += MAX_WRITE_SIZE;
|
||||
}
|
||||
|
||||
// There will always be some bytes left, since the previous
|
||||
// loop guarantees `bytes_written < bytes.len()`
|
||||
let bytes_left = ((bytes.len() - bytes_written) as u64)
|
||||
.try_into()
|
||||
.expect("non-empty write");
|
||||
|
||||
// Write them
|
||||
belt.write_buffer(
|
||||
encoder,
|
||||
&self.raw,
|
||||
(offset + bytes_written) as u64,
|
||||
bytes_left,
|
||||
device,
|
||||
)
|
||||
.copy_from_slice(&bytes[bytes_written..]);
|
||||
|
||||
bytes.len()
|
||||
}
|
||||
|
||||
pub fn slice(
|
||||
&self,
|
||||
bounds: impl RangeBounds<wgpu::BufferAddress>,
|
||||
) -> wgpu::BufferSlice<'_> {
|
||||
self.raw.slice(bounds)
|
||||
}
|
||||
|
||||
pub fn range(&self, start: usize, end: usize) -> wgpu::BufferSlice<'_> {
|
||||
self.slice(
|
||||
start as u64 * std::mem::size_of::<T>() as u64
|
||||
..end as u64 * std::mem::size_of::<T>() as u64,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn next_copy_size<T>(amount: usize) -> u64 {
|
||||
let align_mask = wgpu::COPY_BUFFER_ALIGNMENT - 1;
|
||||
|
||||
(((std::mem::size_of::<T>() * amount).next_power_of_two() as u64
|
||||
+ align_mask)
|
||||
& !align_mask)
|
||||
.max(wgpu::COPY_BUFFER_ALIGNMENT)
|
||||
}
|
||||
|
|
@ -1,205 +0,0 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
pub fn convert(
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
source: wgpu::Texture,
|
||||
format: wgpu::TextureFormat,
|
||||
) -> wgpu::Texture {
|
||||
if source.format() == format {
|
||||
return source;
|
||||
}
|
||||
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("iced_wgpu.offscreen.sampler"),
|
||||
..wgpu::SamplerDescriptor::default()
|
||||
});
|
||||
|
||||
#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
|
||||
#[repr(C)]
|
||||
struct Ratio {
|
||||
u: f32,
|
||||
v: f32,
|
||||
// Padding field for 16-byte alignment.
|
||||
// See https://docs.rs/wgpu/latest/wgpu/struct.DownlevelFlags.html#associatedconstant.BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED
|
||||
_padding: [f32; 2],
|
||||
}
|
||||
|
||||
let ratio = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("iced-wgpu::triangle::msaa ratio"),
|
||||
contents: bytemuck::bytes_of(&Ratio {
|
||||
u: 1.0,
|
||||
v: 1.0,
|
||||
_padding: [0.0; 2],
|
||||
}),
|
||||
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM,
|
||||
});
|
||||
|
||||
let constant_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("iced_wgpu.offscreen.blit.sampler_layout"),
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(
|
||||
wgpu::SamplerBindingType::NonFiltering,
|
||||
),
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let constant_bind_group =
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu.offscreen.sampler.bind_group"),
|
||||
layout: &constant_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Sampler(&sampler),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: ratio.as_entire_binding(),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let texture_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("iced_wgpu.offscreen.blit.texture_layout"),
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float {
|
||||
filterable: false,
|
||||
},
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
});
|
||||
|
||||
let pipeline_layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("iced_wgpu.offscreen.blit.pipeline_layout"),
|
||||
bind_group_layouts: &[&constant_layout, &texture_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("iced_wgpu.offscreen.blit.shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
|
||||
"shader/blit.wgsl"
|
||||
))),
|
||||
});
|
||||
|
||||
let pipeline =
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("iced_wgpu.offscreen.blit.pipeline"),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(
|
||||
),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("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,
|
||||
})],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(
|
||||
),
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
front_face: wgpu::FrontFace::Cw,
|
||||
..wgpu::PrimitiveState::default()
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("iced_wgpu.offscreen.conversion.source_texture"),
|
||||
size: source.size(),
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
|
||||
| wgpu::TextureUsages::COPY_SRC,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
let view = &texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let texture_bind_group =
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu.offscreen.blit.texture_bind_group"),
|
||||
layout: &texture_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(
|
||||
&source
|
||||
.create_view(&wgpu::TextureViewDescriptor::default()),
|
||||
),
|
||||
}],
|
||||
});
|
||||
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("iced_wgpu.offscreen.blit.render_pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view,
|
||||
depth_slice: None,
|
||||
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,
|
||||
});
|
||||
|
||||
pass.set_pipeline(&pipeline);
|
||||
pass.set_bind_group(0, &constant_bind_group, &[]);
|
||||
pass.set_bind_group(1, &texture_bind_group, &[]);
|
||||
pass.draw(0..6, 0..1);
|
||||
|
||||
texture
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
use crate::graphics::{Antialiasing, Shell};
|
||||
use crate::primitive;
|
||||
use crate::quad;
|
||||
use crate::text;
|
||||
use crate::triangle;
|
||||
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Engine {
|
||||
pub(crate) device: wgpu::Device,
|
||||
pub(crate) queue: wgpu::Queue,
|
||||
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: Arc<RwLock<primitive::Storage>>,
|
||||
_shell: Shell,
|
||||
}
|
||||
|
||||
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
|
||||
shell: Shell,
|
||||
) -> Self {
|
||||
Self {
|
||||
format,
|
||||
|
||||
quad_pipeline: quad::Pipeline::new(&device, format),
|
||||
text_pipeline: text::Pipeline::new(&device, &queue, format),
|
||||
triangle_pipeline: triangle::Pipeline::new(
|
||||
&device,
|
||||
format,
|
||||
antialiasing,
|
||||
),
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
image_pipeline: {
|
||||
let backend = _adapter.get_info().backend;
|
||||
|
||||
crate::image::Pipeline::new(&device, format, backend)
|
||||
},
|
||||
|
||||
primitive_storage: Arc::new(RwLock::new(
|
||||
primitive::Storage::default(),
|
||||
)),
|
||||
|
||||
device,
|
||||
queue,
|
||||
_shell: shell,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
pub fn create_image_cache(&self) -> crate::image::Cache {
|
||||
self.image_pipeline.create_cache(
|
||||
&self.device,
|
||||
&self.queue,
|
||||
&self._shell,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn trim(&mut self) {
|
||||
self.text_pipeline.trim();
|
||||
|
||||
self.primitive_storage
|
||||
.write()
|
||||
.expect("primitive storage should be writable")
|
||||
.trim();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,785 +0,0 @@
|
|||
//! Build and draw geometry.
|
||||
use crate::core::text::LineHeight;
|
||||
use crate::core::{
|
||||
self, Pixels, Point, Radians, Rectangle, Size, Svg, Transformation, Vector,
|
||||
};
|
||||
use crate::graphics::cache::{self, Cached};
|
||||
use crate::graphics::color;
|
||||
use crate::graphics::geometry::fill::{self, Fill};
|
||||
use crate::graphics::geometry::{
|
||||
self, LineCap, LineDash, LineJoin, Path, Stroke, Style,
|
||||
};
|
||||
use crate::graphics::gradient::{self, Gradient};
|
||||
use crate::graphics::mesh::{self, Mesh};
|
||||
use crate::graphics::{Image, Text};
|
||||
use crate::text;
|
||||
|
||||
use lyon::geom::euclid;
|
||||
use lyon::tessellation;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Geometry {
|
||||
Live {
|
||||
meshes: Vec<Mesh>,
|
||||
images: Vec<Image>,
|
||||
text: Vec<Text>,
|
||||
},
|
||||
Cached(Cache),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Cache {
|
||||
pub meshes: Option<mesh::Cache>,
|
||||
pub images: Option<Arc<[Image]>>,
|
||||
pub text: Option<text::Cache>,
|
||||
}
|
||||
|
||||
impl Cached for Geometry {
|
||||
type Cache = Cache;
|
||||
|
||||
fn load(cache: &Self::Cache) -> Self {
|
||||
Geometry::Cached(cache.clone())
|
||||
}
|
||||
|
||||
fn cache(
|
||||
self,
|
||||
group: cache::Group,
|
||||
previous: Option<Self::Cache>,
|
||||
) -> Self::Cache {
|
||||
match self {
|
||||
Self::Live {
|
||||
meshes,
|
||||
images,
|
||||
text,
|
||||
} => {
|
||||
let images = if images.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Arc::from(images))
|
||||
};
|
||||
|
||||
let meshes = Arc::from(meshes);
|
||||
|
||||
if let Some(mut previous) = previous {
|
||||
if let Some(cache) = &mut previous.meshes {
|
||||
cache.update(meshes);
|
||||
} else {
|
||||
previous.meshes = if meshes.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(mesh::Cache::new(meshes))
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(cache) = &mut previous.text {
|
||||
cache.update(text);
|
||||
} else {
|
||||
previous.text = text::Cache::new(group, text);
|
||||
}
|
||||
|
||||
previous.images = images;
|
||||
|
||||
previous
|
||||
} else {
|
||||
Cache {
|
||||
meshes: if meshes.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(mesh::Cache::new(meshes))
|
||||
},
|
||||
images,
|
||||
text: text::Cache::new(group, text),
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::Cached(cache) => cache,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A frame for drawing some geometry.
|
||||
pub struct Frame {
|
||||
clip_bounds: Rectangle,
|
||||
buffers: BufferStack,
|
||||
meshes: Vec<Mesh>,
|
||||
images: Vec<Image>,
|
||||
text: Vec<Text>,
|
||||
transforms: Transforms,
|
||||
fill_tessellator: tessellation::FillTessellator,
|
||||
stroke_tessellator: tessellation::StrokeTessellator,
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
/// Creates a new [`Frame`] with the given clip bounds.
|
||||
pub fn new(bounds: Rectangle) -> Frame {
|
||||
Frame {
|
||||
clip_bounds: bounds,
|
||||
buffers: BufferStack::new(),
|
||||
meshes: Vec::new(),
|
||||
images: Vec::new(),
|
||||
text: Vec::new(),
|
||||
transforms: Transforms {
|
||||
previous: Vec::new(),
|
||||
current: Transform(lyon::math::Transform::identity()),
|
||||
},
|
||||
fill_tessellator: tessellation::FillTessellator::new(),
|
||||
stroke_tessellator: tessellation::StrokeTessellator::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl geometry::frame::Backend for Frame {
|
||||
type Geometry = Geometry;
|
||||
|
||||
#[inline]
|
||||
fn width(&self) -> f32 {
|
||||
self.clip_bounds.width
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn height(&self) -> f32 {
|
||||
self.clip_bounds.height
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> Size {
|
||||
self.clip_bounds.size()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn center(&self) -> Point {
|
||||
Point::new(self.clip_bounds.width / 2.0, self.clip_bounds.height / 2.0)
|
||||
}
|
||||
|
||||
fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
|
||||
let Fill { style, rule } = fill.into();
|
||||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get_fill(&self.transforms.current.transform_style(style));
|
||||
|
||||
let options = tessellation::FillOptions::default()
|
||||
.with_fill_rule(into_fill_rule(rule));
|
||||
|
||||
if self.transforms.current.is_identity() {
|
||||
self.fill_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
} else {
|
||||
let path = path.transform(&self.transforms.current.0);
|
||||
|
||||
self.fill_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
}
|
||||
.expect("Tessellate path.");
|
||||
}
|
||||
|
||||
fn fill_rectangle(
|
||||
&mut self,
|
||||
top_left: Point,
|
||||
size: Size,
|
||||
fill: impl Into<Fill>,
|
||||
) {
|
||||
let Fill { style, rule } = fill.into();
|
||||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get_fill(&self.transforms.current.transform_style(style));
|
||||
|
||||
let top_left = self
|
||||
.transforms
|
||||
.current
|
||||
.0
|
||||
.transform_point(lyon::math::Point::new(top_left.x, top_left.y));
|
||||
|
||||
let size =
|
||||
self.transforms.current.0.transform_vector(
|
||||
lyon::math::Vector::new(size.width, size.height),
|
||||
);
|
||||
|
||||
let options = tessellation::FillOptions::default()
|
||||
.with_fill_rule(into_fill_rule(rule));
|
||||
|
||||
self.fill_tessellator
|
||||
.tessellate_rectangle(
|
||||
&lyon::math::Box2D::new(top_left, top_left + size),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
.expect("Fill rectangle");
|
||||
}
|
||||
|
||||
fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
|
||||
let stroke = stroke.into();
|
||||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get_stroke(&self.transforms.current.transform_style(stroke.style));
|
||||
|
||||
let mut options = tessellation::StrokeOptions::default();
|
||||
options.line_width = stroke.width;
|
||||
options.start_cap = into_line_cap(stroke.line_cap);
|
||||
options.end_cap = into_line_cap(stroke.line_cap);
|
||||
options.line_join = into_line_join(stroke.line_join);
|
||||
|
||||
let path = if stroke.line_dash.segments.is_empty() {
|
||||
Cow::Borrowed(path)
|
||||
} else {
|
||||
Cow::Owned(dashed(path, stroke.line_dash))
|
||||
};
|
||||
|
||||
if self.transforms.current.is_identity() {
|
||||
self.stroke_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
} else {
|
||||
let path = path.transform(&self.transforms.current.0);
|
||||
|
||||
self.stroke_tessellator.tessellate_path(
|
||||
path.raw(),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
}
|
||||
.expect("Stroke path");
|
||||
}
|
||||
|
||||
fn stroke_rectangle<'a>(
|
||||
&mut self,
|
||||
top_left: Point,
|
||||
size: Size,
|
||||
stroke: impl Into<Stroke<'a>>,
|
||||
) {
|
||||
let stroke = stroke.into();
|
||||
|
||||
let mut buffer = self
|
||||
.buffers
|
||||
.get_stroke(&self.transforms.current.transform_style(stroke.style));
|
||||
|
||||
let top_left = self
|
||||
.transforms
|
||||
.current
|
||||
.0
|
||||
.transform_point(lyon::math::Point::new(top_left.x, top_left.y));
|
||||
|
||||
let size =
|
||||
self.transforms.current.0.transform_vector(
|
||||
lyon::math::Vector::new(size.width, size.height),
|
||||
);
|
||||
|
||||
let mut options = tessellation::StrokeOptions::default();
|
||||
options.line_width = stroke.width;
|
||||
options.start_cap = into_line_cap(stroke.line_cap);
|
||||
options.end_cap = into_line_cap(stroke.line_cap);
|
||||
options.line_join = into_line_join(stroke.line_join);
|
||||
|
||||
self.stroke_tessellator
|
||||
.tessellate_rectangle(
|
||||
&lyon::math::Box2D::new(top_left, top_left + size),
|
||||
&options,
|
||||
buffer.as_mut(),
|
||||
)
|
||||
.expect("Stroke rectangle");
|
||||
}
|
||||
|
||||
fn stroke_text<'a>(
|
||||
&mut self,
|
||||
text: impl Into<geometry::Text>,
|
||||
stroke: impl Into<Stroke<'a>>,
|
||||
) {
|
||||
let text = text.into();
|
||||
let stroke = stroke.into();
|
||||
|
||||
text.draw_with(|glyph, _color| self.stroke(&glyph, stroke));
|
||||
}
|
||||
|
||||
fn fill_text(&mut self, text: impl Into<geometry::Text>) {
|
||||
let text = text.into();
|
||||
|
||||
let (scale_x, scale_y) = self.transforms.current.scale();
|
||||
|
||||
if self.transforms.current.is_scale_translation()
|
||||
&& scale_x == scale_y
|
||||
&& scale_x > 0.0
|
||||
&& scale_y > 0.0
|
||||
{
|
||||
let (bounds, size, line_height) =
|
||||
if self.transforms.current.is_identity() {
|
||||
(
|
||||
Rectangle::new(
|
||||
text.position,
|
||||
Size::new(text.max_width, f32::INFINITY),
|
||||
),
|
||||
text.size,
|
||||
text.line_height,
|
||||
)
|
||||
} else {
|
||||
let position =
|
||||
self.transforms.current.transform_point(text.position);
|
||||
|
||||
let size = Pixels(text.size.0 * scale_y);
|
||||
|
||||
let line_height = match text.line_height {
|
||||
LineHeight::Absolute(size) => {
|
||||
LineHeight::Absolute(Pixels(size.0 * scale_y))
|
||||
}
|
||||
LineHeight::Relative(factor) => {
|
||||
LineHeight::Relative(factor)
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
Rectangle::new(
|
||||
position,
|
||||
Size::new(text.max_width, f32::INFINITY),
|
||||
),
|
||||
size,
|
||||
line_height,
|
||||
)
|
||||
};
|
||||
|
||||
self.text.push(Text::Cached {
|
||||
content: text.content,
|
||||
bounds,
|
||||
color: text.color,
|
||||
size,
|
||||
line_height: line_height.to_absolute(size),
|
||||
font: text.font,
|
||||
align_x: text.align_x,
|
||||
align_y: text.align_y,
|
||||
shaping: text.shaping,
|
||||
clip_bounds: self.clip_bounds,
|
||||
});
|
||||
} else {
|
||||
text.draw_with(|path, color| self.fill(&path, color));
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn translate(&mut self, translation: Vector) {
|
||||
self.transforms.current.0 =
|
||||
self.transforms
|
||||
.current
|
||||
.0
|
||||
.pre_translate(lyon::math::Vector::new(
|
||||
translation.x,
|
||||
translation.y,
|
||||
));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn rotate(&mut self, angle: impl Into<Radians>) {
|
||||
self.transforms.current.0 = self
|
||||
.transforms
|
||||
.current
|
||||
.0
|
||||
.pre_rotate(lyon::math::Angle::radians(angle.into().0));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn scale(&mut self, scale: impl Into<f32>) {
|
||||
let scale = scale.into();
|
||||
|
||||
self.scale_nonuniform(Vector { x: scale, y: scale });
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
|
||||
let scale = scale.into();
|
||||
|
||||
self.transforms.current.0 =
|
||||
self.transforms.current.0.pre_scale(scale.x, scale.y);
|
||||
}
|
||||
|
||||
fn push_transform(&mut self) {
|
||||
self.transforms.previous.push(self.transforms.current);
|
||||
}
|
||||
|
||||
fn pop_transform(&mut self) {
|
||||
self.transforms.current = self.transforms.previous.pop().unwrap();
|
||||
}
|
||||
|
||||
fn draft(&mut self, clip_bounds: Rectangle) -> Frame {
|
||||
Frame::new(clip_bounds)
|
||||
}
|
||||
|
||||
fn paste(&mut self, frame: Frame) {
|
||||
self.meshes.extend(frame.meshes);
|
||||
self.meshes
|
||||
.extend(frame.buffers.into_meshes(frame.clip_bounds));
|
||||
|
||||
self.images.extend(frame.images);
|
||||
self.text.extend(frame.text);
|
||||
}
|
||||
|
||||
fn into_geometry(mut self) -> Self::Geometry {
|
||||
self.meshes
|
||||
.extend(self.buffers.into_meshes(self.clip_bounds));
|
||||
|
||||
Geometry::Live {
|
||||
meshes: self.meshes,
|
||||
images: self.images,
|
||||
text: self.text,
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_image(&mut self, bounds: Rectangle, image: impl Into<core::Image>) {
|
||||
let mut image = image.into();
|
||||
|
||||
let (bounds, external_rotation) =
|
||||
self.transforms.current.transform_rectangle(bounds);
|
||||
|
||||
image.rotation += external_rotation;
|
||||
image.border_radius =
|
||||
image.border_radius * self.transforms.current.scale().0;
|
||||
|
||||
self.images.push(Image::Raster {
|
||||
image,
|
||||
bounds,
|
||||
clip_bounds: self.clip_bounds,
|
||||
});
|
||||
}
|
||||
|
||||
fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>) {
|
||||
let mut svg = svg.into();
|
||||
|
||||
let (bounds, external_rotation) =
|
||||
self.transforms.current.transform_rectangle(bounds);
|
||||
|
||||
svg.rotation += external_rotation;
|
||||
|
||||
self.images.push(Image::Vector {
|
||||
svg,
|
||||
bounds,
|
||||
clip_bounds: self.clip_bounds,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
enum Buffer {
|
||||
Solid(tessellation::VertexBuffers<mesh::SolidVertex2D, u32>),
|
||||
Gradient(tessellation::VertexBuffers<mesh::GradientVertex2D, u32>),
|
||||
}
|
||||
|
||||
struct BufferStack {
|
||||
stack: Vec<Buffer>,
|
||||
}
|
||||
|
||||
impl BufferStack {
|
||||
fn new() -> Self {
|
||||
Self { stack: Vec::new() }
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, style: &Style) -> &mut Buffer {
|
||||
match style {
|
||||
Style::Solid(_) => match self.stack.last() {
|
||||
Some(Buffer::Solid(_)) => {}
|
||||
_ => {
|
||||
self.stack.push(Buffer::Solid(
|
||||
tessellation::VertexBuffers::new(),
|
||||
));
|
||||
}
|
||||
},
|
||||
Style::Gradient(_) => match self.stack.last() {
|
||||
Some(Buffer::Gradient(_)) => {}
|
||||
_ => {
|
||||
self.stack.push(Buffer::Gradient(
|
||||
tessellation::VertexBuffers::new(),
|
||||
));
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
self.stack.last_mut().unwrap()
|
||||
}
|
||||
|
||||
fn get_fill<'a>(
|
||||
&'a mut self,
|
||||
style: &Style,
|
||||
) -> Box<dyn tessellation::FillGeometryBuilder + 'a> {
|
||||
match (style, self.get_mut(style)) {
|
||||
(Style::Solid(color), Buffer::Solid(buffer)) => {
|
||||
Box::new(tessellation::BuffersBuilder::new(
|
||||
buffer,
|
||||
TriangleVertex2DBuilder(color::pack(*color)),
|
||||
))
|
||||
}
|
||||
(Style::Gradient(gradient), Buffer::Gradient(buffer)) => {
|
||||
Box::new(tessellation::BuffersBuilder::new(
|
||||
buffer,
|
||||
GradientVertex2DBuilder {
|
||||
gradient: gradient.pack(),
|
||||
},
|
||||
))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_stroke<'a>(
|
||||
&'a mut self,
|
||||
style: &Style,
|
||||
) -> Box<dyn tessellation::StrokeGeometryBuilder + 'a> {
|
||||
match (style, self.get_mut(style)) {
|
||||
(Style::Solid(color), Buffer::Solid(buffer)) => {
|
||||
Box::new(tessellation::BuffersBuilder::new(
|
||||
buffer,
|
||||
TriangleVertex2DBuilder(color::pack(*color)),
|
||||
))
|
||||
}
|
||||
(Style::Gradient(gradient), Buffer::Gradient(buffer)) => {
|
||||
Box::new(tessellation::BuffersBuilder::new(
|
||||
buffer,
|
||||
GradientVertex2DBuilder {
|
||||
gradient: gradient.pack(),
|
||||
},
|
||||
))
|
||||
}
|
||||
_ => 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)]
|
||||
struct Transforms {
|
||||
previous: Vec<Transform>,
|
||||
current: Transform,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Transform(lyon::math::Transform);
|
||||
|
||||
impl Transform {
|
||||
fn is_identity(&self) -> bool {
|
||||
self.0 == lyon::math::Transform::identity()
|
||||
}
|
||||
|
||||
fn is_scale_translation(&self) -> bool {
|
||||
self.0.m12.abs() < 2.0 * f32::EPSILON
|
||||
&& self.0.m21.abs() < 2.0 * f32::EPSILON
|
||||
}
|
||||
|
||||
fn scale(&self) -> (f32, f32) {
|
||||
(self.0.m11, self.0.m22)
|
||||
}
|
||||
|
||||
fn transform_point(&self, point: Point) -> Point {
|
||||
let transformed = self
|
||||
.0
|
||||
.transform_point(euclid::Point2D::new(point.x, point.y));
|
||||
|
||||
Point {
|
||||
x: transformed.x,
|
||||
y: transformed.y,
|
||||
}
|
||||
}
|
||||
|
||||
fn transform_style(&self, style: Style) -> Style {
|
||||
match style {
|
||||
Style::Solid(color) => Style::Solid(color),
|
||||
Style::Gradient(gradient) => {
|
||||
Style::Gradient(self.transform_gradient(gradient))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn transform_gradient(&self, mut gradient: Gradient) -> Gradient {
|
||||
match &mut gradient {
|
||||
Gradient::Linear(linear) => {
|
||||
linear.start = self.transform_point(linear.start);
|
||||
linear.end = self.transform_point(linear.end);
|
||||
}
|
||||
}
|
||||
|
||||
gradient
|
||||
}
|
||||
|
||||
fn transform_rectangle(
|
||||
&self,
|
||||
rectangle: Rectangle,
|
||||
) -> (Rectangle, Radians) {
|
||||
let top_left = self.transform_point(rectangle.position());
|
||||
let top_right = self.transform_point(
|
||||
rectangle.position() + Vector::new(rectangle.width, 0.0),
|
||||
);
|
||||
let bottom_left = self.transform_point(
|
||||
rectangle.position() + Vector::new(0.0, rectangle.height),
|
||||
);
|
||||
|
||||
Rectangle::with_vertices(top_left, top_right, bottom_left)
|
||||
}
|
||||
}
|
||||
struct GradientVertex2DBuilder {
|
||||
gradient: gradient::Packed,
|
||||
}
|
||||
|
||||
impl tessellation::FillVertexConstructor<mesh::GradientVertex2D>
|
||||
for GradientVertex2DBuilder
|
||||
{
|
||||
fn new_vertex(
|
||||
&mut self,
|
||||
vertex: tessellation::FillVertex<'_>,
|
||||
) -> mesh::GradientVertex2D {
|
||||
let position = vertex.position();
|
||||
|
||||
mesh::GradientVertex2D {
|
||||
position: [position.x, position.y],
|
||||
gradient: self.gradient,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl tessellation::StrokeVertexConstructor<mesh::GradientVertex2D>
|
||||
for GradientVertex2DBuilder
|
||||
{
|
||||
fn new_vertex(
|
||||
&mut self,
|
||||
vertex: tessellation::StrokeVertex<'_, '_>,
|
||||
) -> mesh::GradientVertex2D {
|
||||
let position = vertex.position();
|
||||
|
||||
mesh::GradientVertex2D {
|
||||
position: [position.x, position.y],
|
||||
gradient: self.gradient,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TriangleVertex2DBuilder(color::Packed);
|
||||
|
||||
impl tessellation::FillVertexConstructor<mesh::SolidVertex2D>
|
||||
for TriangleVertex2DBuilder
|
||||
{
|
||||
fn new_vertex(
|
||||
&mut self,
|
||||
vertex: tessellation::FillVertex<'_>,
|
||||
) -> mesh::SolidVertex2D {
|
||||
let position = vertex.position();
|
||||
|
||||
mesh::SolidVertex2D {
|
||||
position: [position.x, position.y],
|
||||
color: self.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl tessellation::StrokeVertexConstructor<mesh::SolidVertex2D>
|
||||
for TriangleVertex2DBuilder
|
||||
{
|
||||
fn new_vertex(
|
||||
&mut self,
|
||||
vertex: tessellation::StrokeVertex<'_, '_>,
|
||||
) -> mesh::SolidVertex2D {
|
||||
let position = vertex.position();
|
||||
|
||||
mesh::SolidVertex2D {
|
||||
position: [position.x, position.y],
|
||||
color: self.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn into_line_join(line_join: LineJoin) -> lyon::tessellation::LineJoin {
|
||||
match line_join {
|
||||
LineJoin::Miter => lyon::tessellation::LineJoin::Miter,
|
||||
LineJoin::Round => lyon::tessellation::LineJoin::Round,
|
||||
LineJoin::Bevel => lyon::tessellation::LineJoin::Bevel,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_line_cap(line_cap: LineCap) -> lyon::tessellation::LineCap {
|
||||
match line_cap {
|
||||
LineCap::Butt => lyon::tessellation::LineCap::Butt,
|
||||
LineCap::Square => lyon::tessellation::LineCap::Square,
|
||||
LineCap::Round => lyon::tessellation::LineCap::Round,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_fill_rule(rule: fill::Rule) -> lyon::tessellation::FillRule {
|
||||
match rule {
|
||||
fill::Rule::NonZero => lyon::tessellation::FillRule::NonZero,
|
||||
fill::Rule::EvenOdd => lyon::tessellation::FillRule::EvenOdd,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path {
|
||||
use lyon::algorithms::walk::{
|
||||
RepeatedPattern, WalkerEvent, walk_along_path,
|
||||
};
|
||||
use lyon::path::iterator::PathIterator;
|
||||
|
||||
Path::new(|builder| {
|
||||
let segments_odd = (line_dash.segments.len() % 2 == 1)
|
||||
.then(|| [line_dash.segments, line_dash.segments].concat());
|
||||
|
||||
let mut draw_line = false;
|
||||
|
||||
walk_along_path(
|
||||
path.raw().iter().flattened(
|
||||
lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE,
|
||||
),
|
||||
0.0,
|
||||
lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE,
|
||||
&mut RepeatedPattern {
|
||||
callback: |event: WalkerEvent<'_>| {
|
||||
let point = Point {
|
||||
x: event.position.x,
|
||||
y: event.position.y,
|
||||
};
|
||||
|
||||
if draw_line {
|
||||
builder.line_to(point);
|
||||
} else {
|
||||
builder.move_to(point);
|
||||
}
|
||||
|
||||
draw_line = !draw_line;
|
||||
|
||||
true
|
||||
},
|
||||
index: line_dash.offset,
|
||||
intervals: segments_odd
|
||||
.as_deref()
|
||||
.unwrap_or(line_dash.segments),
|
||||
},
|
||||
);
|
||||
})
|
||||
}
|
||||
|
|
@ -1,542 +0,0 @@
|
|||
pub mod entry;
|
||||
|
||||
mod allocation;
|
||||
mod allocator;
|
||||
mod layer;
|
||||
|
||||
pub use allocation::Allocation;
|
||||
pub use entry::Entry;
|
||||
pub use layer::Layer;
|
||||
|
||||
use allocator::Allocator;
|
||||
|
||||
pub const DEFAULT_SIZE: u32 = 2048;
|
||||
pub const MAX_SIZE: u32 = 2048;
|
||||
|
||||
use crate::core::Size;
|
||||
use crate::graphics::color;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Atlas {
|
||||
size: u32,
|
||||
backend: wgpu::Backend,
|
||||
texture: wgpu::Texture,
|
||||
texture_view: wgpu::TextureView,
|
||||
texture_bind_group: Arc<wgpu::BindGroup>,
|
||||
texture_layout: wgpu::BindGroupLayout,
|
||||
layers: Vec<Layer>,
|
||||
}
|
||||
|
||||
impl Atlas {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
backend: wgpu::Backend,
|
||||
texture_layout: wgpu::BindGroupLayout,
|
||||
) -> Self {
|
||||
Self::with_size(device, backend, texture_layout, DEFAULT_SIZE)
|
||||
}
|
||||
|
||||
pub fn with_size(
|
||||
device: &wgpu::Device,
|
||||
backend: wgpu::Backend,
|
||||
texture_layout: wgpu::BindGroupLayout,
|
||||
size: u32,
|
||||
) -> Self {
|
||||
let size = size.min(MAX_SIZE);
|
||||
|
||||
let layers = match backend {
|
||||
// On the GL backend we start with 2 layers, to help wgpu figure
|
||||
// out that this texture is `GL_TEXTURE_2D_ARRAY` rather than `GL_TEXTURE_2D`
|
||||
// https://github.com/gfx-rs/wgpu/blob/004e3efe84a320d9331371ed31fa50baa2414911/wgpu-hal/src/gles/mod.rs#L371
|
||||
wgpu::Backend::Gl => vec![Layer::Empty, Layer::Empty],
|
||||
_ => vec![Layer::Empty],
|
||||
};
|
||||
|
||||
let extent = wgpu::Extent3d {
|
||||
width: size,
|
||||
height: size,
|
||||
depth_or_array_layers: layers.len() as u32,
|
||||
};
|
||||
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("iced_wgpu::image texture atlas"),
|
||||
size: extent,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: if color::GAMMA_CORRECTION {
|
||||
wgpu::TextureFormat::Rgba8UnormSrgb
|
||||
} else {
|
||||
wgpu::TextureFormat::Rgba8Unorm
|
||||
},
|
||||
usage: wgpu::TextureUsages::COPY_DST
|
||||
| wgpu::TextureUsages::COPY_SRC
|
||||
| wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor {
|
||||
dimension: Some(wgpu::TextureViewDimension::D2Array),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let texture_bind_group =
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::image texture atlas bind group"),
|
||||
layout: &texture_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(&texture_view),
|
||||
}],
|
||||
});
|
||||
|
||||
Atlas {
|
||||
size,
|
||||
backend,
|
||||
texture,
|
||||
texture_view,
|
||||
texture_bind_group: Arc::new(texture_bind_group),
|
||||
texture_layout,
|
||||
layers,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bind_group(&self) -> &Arc<wgpu::BindGroup> {
|
||||
&self.texture_bind_group
|
||||
}
|
||||
|
||||
pub fn upload(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
width: u32,
|
||||
height: u32,
|
||||
pixels: &[u8],
|
||||
) -> Option<Entry> {
|
||||
let entry = {
|
||||
let current_size = self.layers.len();
|
||||
let entry = self.allocate(width, height)?;
|
||||
|
||||
// We grow the internal texture after allocating if necessary
|
||||
let new_layers = self.layers.len() - current_size;
|
||||
self.grow(new_layers, device, encoder, self.backend);
|
||||
|
||||
entry
|
||||
};
|
||||
|
||||
log::debug!("Allocated atlas entry: {entry:?}");
|
||||
|
||||
match &entry {
|
||||
Entry::Contiguous(allocation) => {
|
||||
self.upload_allocation(
|
||||
pixels, width, 0, allocation, device, encoder, belt,
|
||||
);
|
||||
}
|
||||
Entry::Fragmented { fragments, .. } => {
|
||||
for fragment in fragments {
|
||||
let (x, y) = fragment.position;
|
||||
let offset = 4 * (y * width + x) as usize;
|
||||
|
||||
self.upload_allocation(
|
||||
pixels,
|
||||
width,
|
||||
offset,
|
||||
&fragment.allocation,
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if log::log_enabled!(log::Level::Debug) {
|
||||
log::debug!(
|
||||
"Atlas layers: {} (busy: {}, allocations: {})",
|
||||
self.layers.len(),
|
||||
self.layers.iter().filter(|layer| !layer.is_empty()).count(),
|
||||
self.layers.iter().map(Layer::allocations).sum::<usize>(),
|
||||
);
|
||||
}
|
||||
|
||||
Some(entry)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, entry: &Entry) {
|
||||
log::debug!("Removing atlas entry: {entry:?}");
|
||||
|
||||
match entry {
|
||||
Entry::Contiguous(allocation) => {
|
||||
self.deallocate(allocation);
|
||||
}
|
||||
Entry::Fragmented { fragments, .. } => {
|
||||
for fragment in fragments {
|
||||
self.deallocate(&fragment.allocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn allocate(&mut self, width: u32, height: u32) -> Option<Entry> {
|
||||
// Allocate one layer if texture fits perfectly
|
||||
if width == self.size && height == self.size {
|
||||
let mut empty_layers = self
|
||||
.layers
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.filter(|(_, layer)| layer.is_empty());
|
||||
|
||||
if let Some((i, layer)) = empty_layers.next() {
|
||||
*layer = Layer::Full;
|
||||
|
||||
return Some(Entry::Contiguous(Allocation::Full {
|
||||
layer: i,
|
||||
size: self.size,
|
||||
}));
|
||||
}
|
||||
|
||||
self.layers.push(Layer::Full);
|
||||
|
||||
return Some(Entry::Contiguous(Allocation::Full {
|
||||
layer: self.layers.len() - 1,
|
||||
size: self.size,
|
||||
}));
|
||||
}
|
||||
|
||||
// Split big textures across multiple layers
|
||||
if width > self.size || height > self.size {
|
||||
let mut fragments = Vec::new();
|
||||
let mut y = 0;
|
||||
|
||||
while y < height {
|
||||
let height = std::cmp::min(height - y, self.size);
|
||||
let mut x = 0;
|
||||
|
||||
while x < width {
|
||||
let width = std::cmp::min(width - x, self.size);
|
||||
|
||||
let allocation = self.allocate(width, height)?;
|
||||
|
||||
if let Entry::Contiguous(allocation) = allocation {
|
||||
fragments.push(entry::Fragment {
|
||||
position: (x, y),
|
||||
allocation,
|
||||
});
|
||||
}
|
||||
|
||||
x += width;
|
||||
}
|
||||
|
||||
y += height;
|
||||
}
|
||||
|
||||
return Some(Entry::Fragmented {
|
||||
size: Size::new(width, height),
|
||||
fragments,
|
||||
});
|
||||
}
|
||||
|
||||
// Try allocating on an existing layer
|
||||
for (i, layer) in self.layers.iter_mut().enumerate() {
|
||||
match layer {
|
||||
Layer::Empty => {
|
||||
let mut allocator = Allocator::new(self.size);
|
||||
|
||||
if let Some(region) = allocator.allocate(width, height) {
|
||||
*layer = Layer::Busy(allocator);
|
||||
|
||||
return Some(Entry::Contiguous(Allocation::Partial {
|
||||
region,
|
||||
layer: i,
|
||||
atlas_size: self.size,
|
||||
}));
|
||||
}
|
||||
}
|
||||
Layer::Busy(allocator) => {
|
||||
if let Some(region) = allocator.allocate(width, height) {
|
||||
return Some(Entry::Contiguous(Allocation::Partial {
|
||||
region,
|
||||
layer: i,
|
||||
atlas_size: self.size,
|
||||
}));
|
||||
}
|
||||
}
|
||||
Layer::Full => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Create new layer with atlas allocator
|
||||
let mut allocator = Allocator::new(self.size);
|
||||
|
||||
if let Some(region) = allocator.allocate(width, height) {
|
||||
self.layers.push(Layer::Busy(allocator));
|
||||
|
||||
return Some(Entry::Contiguous(Allocation::Partial {
|
||||
region,
|
||||
layer: self.layers.len() - 1,
|
||||
atlas_size: self.size,
|
||||
}));
|
||||
}
|
||||
|
||||
// We ran out of memory (?)
|
||||
None
|
||||
}
|
||||
|
||||
fn deallocate(&mut self, allocation: &Allocation) {
|
||||
log::debug!("Deallocating atlas: {allocation:?}");
|
||||
|
||||
match allocation {
|
||||
Allocation::Full { layer, .. } => {
|
||||
self.layers[*layer] = Layer::Empty;
|
||||
}
|
||||
Allocation::Partial { layer, region, .. } => {
|
||||
let layer = &mut self.layers[*layer];
|
||||
|
||||
if let Layer::Busy(allocator) = layer {
|
||||
allocator.deallocate(region);
|
||||
|
||||
if allocator.is_empty() {
|
||||
*layer = Layer::Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn upload_allocation(
|
||||
&self,
|
||||
pixels: &[u8],
|
||||
image_width: u32,
|
||||
offset: usize,
|
||||
allocation: &Allocation,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
) {
|
||||
let (x, y) = allocation.position();
|
||||
let Size { width, height } = allocation.size();
|
||||
let layer = allocation.layer();
|
||||
let padding = allocation.padding();
|
||||
|
||||
// It is a webgpu requirement that:
|
||||
// BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0
|
||||
// So we calculate bytes_per_row by rounding width up to the next
|
||||
// multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT.
|
||||
let bytes_per_row = (4 * (width + padding.width * 2))
|
||||
.next_multiple_of(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT)
|
||||
as usize;
|
||||
let total_bytes =
|
||||
bytes_per_row * (height + padding.height * 2) as usize;
|
||||
|
||||
let buffer_slice = belt.allocate(
|
||||
wgpu::BufferSize::new(total_bytes as u64).unwrap(),
|
||||
wgpu::BufferSize::new(8 * 4).unwrap(),
|
||||
device,
|
||||
);
|
||||
|
||||
const PIXEL: usize = 4;
|
||||
|
||||
let mut fragment = buffer_slice.get_mapped_range_mut();
|
||||
let w = width as usize;
|
||||
let h = height as usize;
|
||||
let pad_w = padding.width as usize;
|
||||
let pad_h = padding.height as usize;
|
||||
let stride = PIXEL * w;
|
||||
|
||||
// Copy image rows
|
||||
for row in 0..h {
|
||||
let src = offset + row * PIXEL * image_width as usize;
|
||||
let dst = (row + pad_h) * bytes_per_row;
|
||||
|
||||
fragment[dst + PIXEL * pad_w..dst + PIXEL * pad_w + stride]
|
||||
.copy_from_slice(&pixels[src..src + stride]);
|
||||
|
||||
// Add padding to the sides, if needed
|
||||
for i in 0..pad_w {
|
||||
fragment[dst + PIXEL * i..dst + PIXEL * (i + 1)]
|
||||
.copy_from_slice(&pixels[src..src + PIXEL]);
|
||||
|
||||
fragment[dst + stride + PIXEL * (pad_w + i)
|
||||
..dst + stride + PIXEL * (pad_w + i + 1)]
|
||||
.copy_from_slice(
|
||||
&pixels[src + stride - PIXEL..src + stride],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Add padding on top and bottom
|
||||
for row in 0..pad_h {
|
||||
let dst_top = row * bytes_per_row;
|
||||
let dst_bottom = (pad_h + h + row) * bytes_per_row;
|
||||
let src_top = offset;
|
||||
let src_bottom = offset + (h - 1) * PIXEL * image_width as usize;
|
||||
|
||||
// Top
|
||||
fragment[dst_top + PIXEL * pad_w..dst_top + PIXEL * (pad_w + w)]
|
||||
.copy_from_slice(&pixels[src_top..src_top + PIXEL * w]);
|
||||
|
||||
// Bottom
|
||||
fragment
|
||||
[dst_bottom + PIXEL * pad_w..dst_bottom + PIXEL * (pad_w + w)]
|
||||
.copy_from_slice(&pixels[src_bottom..src_bottom + PIXEL * w]);
|
||||
|
||||
// Corners
|
||||
for i in 0..pad_w {
|
||||
// Top left
|
||||
fragment[dst_top + PIXEL * i..dst_top + PIXEL * (i + 1)]
|
||||
.copy_from_slice(&pixels[offset..offset + PIXEL]);
|
||||
|
||||
// Top right
|
||||
fragment[dst_top + PIXEL * (w + pad_w + i)
|
||||
..dst_top + PIXEL * (w + pad_w + i + 1)]
|
||||
.copy_from_slice(
|
||||
&pixels[offset + PIXEL * (w - 1)..offset + PIXEL * w],
|
||||
);
|
||||
|
||||
// Bottom left
|
||||
fragment[dst_bottom + PIXEL * i..dst_bottom + PIXEL * (i + 1)]
|
||||
.copy_from_slice(&pixels[src_bottom..src_bottom + PIXEL]);
|
||||
|
||||
// Bottom right
|
||||
fragment[dst_bottom + PIXEL * (w + pad_w + i)
|
||||
..dst_bottom + PIXEL * (w + pad_w + i + 1)]
|
||||
.copy_from_slice(
|
||||
&pixels[src_bottom + PIXEL * (w - 1)
|
||||
..src_bottom + PIXEL * w],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy actual image
|
||||
encoder.copy_buffer_to_texture(
|
||||
wgpu::TexelCopyBufferInfo {
|
||||
buffer: buffer_slice.buffer(),
|
||||
layout: wgpu::TexelCopyBufferLayout {
|
||||
offset: buffer_slice.offset(),
|
||||
bytes_per_row: Some(bytes_per_row as u32),
|
||||
rows_per_image: Some(height + padding.height * 2),
|
||||
},
|
||||
},
|
||||
wgpu::TexelCopyTextureInfo {
|
||||
texture: &self.texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d {
|
||||
x: x - padding.width,
|
||||
y: y - padding.height,
|
||||
z: layer as u32,
|
||||
},
|
||||
aspect: wgpu::TextureAspect::default(),
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width: width + padding.width * 2,
|
||||
height: height + padding.height * 2,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn grow(
|
||||
&mut self,
|
||||
amount: usize,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
backend: wgpu::Backend,
|
||||
) {
|
||||
if amount == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// On the GL backend if layers.len() is a multiple of 6 we need to help wgpu figure out that this texture
|
||||
// is still a `GL_TEXTURE_2D_ARRAY` rather than `GL_TEXTURE_CUBE_MAP` or `GL_TEXTURE_CUBE_ARRAY`.
|
||||
// This will over-allocate some unused memory on GL, but it's better than not being able to
|
||||
// grow the atlas past multiples of 6!
|
||||
// https://github.com/gfx-rs/wgpu/blob/004e3efe84a320d9331371ed31fa50baa2414911/wgpu-hal/src/gles/mod.rs#L371
|
||||
let depth_or_array_layers = match backend {
|
||||
wgpu::Backend::Gl if self.layers.len().is_multiple_of(6) => {
|
||||
self.layers.len() as u32 + 1
|
||||
}
|
||||
_ => self.layers.len() as u32,
|
||||
};
|
||||
|
||||
let new_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("iced_wgpu::image texture atlas"),
|
||||
size: wgpu::Extent3d {
|
||||
width: self.size,
|
||||
height: self.size,
|
||||
depth_or_array_layers,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: if color::GAMMA_CORRECTION {
|
||||
wgpu::TextureFormat::Rgba8UnormSrgb
|
||||
} else {
|
||||
wgpu::TextureFormat::Rgba8Unorm
|
||||
},
|
||||
usage: wgpu::TextureUsages::COPY_DST
|
||||
| wgpu::TextureUsages::COPY_SRC
|
||||
| wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
let amount_to_copy = self.layers.len() - amount;
|
||||
|
||||
for (i, layer) in
|
||||
self.layers.iter_mut().take(amount_to_copy).enumerate()
|
||||
{
|
||||
if layer.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
encoder.copy_texture_to_texture(
|
||||
wgpu::TexelCopyTextureInfo {
|
||||
texture: &self.texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: i as u32,
|
||||
},
|
||||
aspect: wgpu::TextureAspect::default(),
|
||||
},
|
||||
wgpu::TexelCopyTextureInfo {
|
||||
texture: &new_texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: i as u32,
|
||||
},
|
||||
aspect: wgpu::TextureAspect::default(),
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width: self.size,
|
||||
height: self.size,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
self.texture = new_texture;
|
||||
self.texture_view =
|
||||
self.texture.create_view(&wgpu::TextureViewDescriptor {
|
||||
dimension: Some(wgpu::TextureViewDimension::D2Array),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
self.texture_bind_group =
|
||||
Arc::new(device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::image texture atlas bind group"),
|
||||
layout: &self.texture_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(
|
||||
&self.texture_view,
|
||||
),
|
||||
}],
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
use crate::core::Size;
|
||||
use crate::image::atlas::allocator;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Allocation {
|
||||
Partial {
|
||||
layer: usize,
|
||||
region: allocator::Region,
|
||||
atlas_size: u32,
|
||||
},
|
||||
Full {
|
||||
layer: usize,
|
||||
size: u32,
|
||||
},
|
||||
}
|
||||
|
||||
impl Allocation {
|
||||
pub fn position(&self) -> (u32, u32) {
|
||||
match self {
|
||||
Allocation::Partial { region, .. } => region.position(),
|
||||
Allocation::Full { .. } => (0, 0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&self) -> Size<u32> {
|
||||
match self {
|
||||
Allocation::Partial { region, .. } => region.size(),
|
||||
Allocation::Full { size, .. } => Size::new(*size, *size),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn padding(&self) -> Size<u32> {
|
||||
match self {
|
||||
Allocation::Partial { region, .. } => region.padding(),
|
||||
Allocation::Full { .. } => Size::new(0, 0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layer(&self) -> usize {
|
||||
match self {
|
||||
Allocation::Partial { layer, .. } => *layer,
|
||||
Allocation::Full { layer, .. } => *layer,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn atlas_size(&self) -> u32 {
|
||||
match self {
|
||||
Allocation::Partial { atlas_size, .. } => *atlas_size,
|
||||
Allocation::Full { size, .. } => *size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
use crate::core;
|
||||
|
||||
use guillotiere::{AtlasAllocator, Size};
|
||||
|
||||
pub struct Allocator {
|
||||
raw: AtlasAllocator,
|
||||
allocations: usize,
|
||||
}
|
||||
|
||||
impl Allocator {
|
||||
const PADDING: u32 = 1;
|
||||
|
||||
pub fn new(size: u32) -> Allocator {
|
||||
let raw = AtlasAllocator::new(Size::new(size as i32, size as i32));
|
||||
|
||||
Allocator {
|
||||
raw,
|
||||
allocations: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allocate(&mut self, width: u32, height: u32) -> Option<Region> {
|
||||
let size = self.raw.size();
|
||||
|
||||
let padded_width = width + Self::PADDING * 2;
|
||||
let padded_height = height + Self::PADDING * 2;
|
||||
|
||||
let pad_width = padded_width as i32 <= size.width;
|
||||
let pad_height = padded_height as i32 <= size.height;
|
||||
|
||||
let mut allocation = self.raw.allocate(Size::new(
|
||||
if pad_width { padded_width } else { width } as i32,
|
||||
if pad_height { padded_height } else { height } as i32,
|
||||
))?;
|
||||
|
||||
if pad_width {
|
||||
allocation.rectangle.min.x += Self::PADDING as i32;
|
||||
allocation.rectangle.max.x -= Self::PADDING as i32;
|
||||
}
|
||||
|
||||
if pad_height {
|
||||
allocation.rectangle.min.y += Self::PADDING as i32;
|
||||
allocation.rectangle.max.y -= Self::PADDING as i32;
|
||||
}
|
||||
|
||||
self.allocations += 1;
|
||||
|
||||
Some(Region {
|
||||
allocation,
|
||||
padding: core::Size::new(
|
||||
if pad_width { Self::PADDING } else { 0 },
|
||||
if pad_height { Self::PADDING } else { 0 },
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn deallocate(&mut self, region: &Region) {
|
||||
self.raw.deallocate(region.allocation.id);
|
||||
|
||||
self.allocations = self.allocations.saturating_sub(1);
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.allocations == 0
|
||||
}
|
||||
|
||||
pub fn allocations(&self) -> usize {
|
||||
self.allocations
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Region {
|
||||
allocation: guillotiere::Allocation,
|
||||
padding: core::Size<u32>,
|
||||
}
|
||||
|
||||
impl Region {
|
||||
pub fn position(&self) -> (u32, u32) {
|
||||
let rectangle = &self.allocation.rectangle;
|
||||
|
||||
(rectangle.min.x as u32, rectangle.min.y as u32)
|
||||
}
|
||||
|
||||
pub fn size(&self) -> core::Size<u32> {
|
||||
let size = self.allocation.rectangle.size();
|
||||
|
||||
core::Size::new(size.width as u32, size.height as u32)
|
||||
}
|
||||
|
||||
pub fn padding(&self) -> crate::core::Size<u32> {
|
||||
self.padding
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Allocator {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Allocator")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Region {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Region")
|
||||
.field("id", &self.allocation.id)
|
||||
.field("rectangle", &self.allocation.rectangle)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
use crate::core::Size;
|
||||
use crate::image::atlas;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Entry {
|
||||
Contiguous(atlas::Allocation),
|
||||
Fragmented {
|
||||
size: Size<u32>,
|
||||
fragments: Vec<Fragment>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
#[cfg(feature = "image")]
|
||||
pub fn size(&self) -> Size<u32> {
|
||||
match self {
|
||||
Entry::Contiguous(allocation) => allocation.size(),
|
||||
Entry::Fragmented { size, .. } => *size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Fragment {
|
||||
pub position: (u32, u32),
|
||||
pub allocation: atlas::Allocation,
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
use crate::image::atlas::Allocator;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Layer {
|
||||
Empty,
|
||||
Busy(Allocator),
|
||||
Full,
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
matches!(self, Layer::Empty)
|
||||
}
|
||||
|
||||
pub fn allocations(&self) -> usize {
|
||||
match self {
|
||||
Layer::Empty => 0,
|
||||
Layer::Busy(allocator) => allocator.allocations(),
|
||||
Layer::Full => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,636 +0,0 @@
|
|||
use crate::core::{self, Size};
|
||||
use crate::graphics::Shell;
|
||||
use crate::image::atlas::{self, Atlas};
|
||||
|
||||
#[cfg(all(feature = "image", not(target_arch = "wasm32")))]
|
||||
use worker::Worker;
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
use std::collections::HashMap;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct Cache {
|
||||
atlas: Atlas,
|
||||
#[cfg(feature = "image")]
|
||||
raster: Raster,
|
||||
#[cfg(feature = "svg")]
|
||||
vector: crate::image::vector::Cache,
|
||||
#[cfg(all(feature = "image", not(target_arch = "wasm32")))]
|
||||
worker: Worker,
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
_queue: &wgpu::Queue,
|
||||
backend: wgpu::Backend,
|
||||
layout: wgpu::BindGroupLayout,
|
||||
_shell: &Shell,
|
||||
) -> Self {
|
||||
#[cfg(all(feature = "image", not(target_arch = "wasm32")))]
|
||||
let worker =
|
||||
Worker::new(device, _queue, backend, layout.clone(), _shell);
|
||||
|
||||
Self {
|
||||
atlas: Atlas::new(device, backend, layout),
|
||||
#[cfg(feature = "image")]
|
||||
raster: Raster {
|
||||
cache: crate::image::raster::Cache::default(),
|
||||
pending: HashMap::new(),
|
||||
belt: wgpu::util::StagingBelt::new(2 * 1024 * 1024),
|
||||
},
|
||||
#[cfg(feature = "svg")]
|
||||
vector: crate::image::vector::Cache::default(),
|
||||
#[cfg(all(feature = "image", not(target_arch = "wasm32")))]
|
||||
worker,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
pub fn allocate_image(
|
||||
&mut self,
|
||||
handle: &core::image::Handle,
|
||||
callback: impl FnOnce(Result<core::image::Allocation, core::image::Error>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
) {
|
||||
use crate::image::raster::Memory;
|
||||
|
||||
let callback = Box::new(callback);
|
||||
|
||||
if let Some(callbacks) = self.raster.pending.get_mut(&handle.id()) {
|
||||
callbacks.push(callback);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(Memory::Device {
|
||||
allocation, entry, ..
|
||||
}) = self.raster.cache.get_mut(handle)
|
||||
{
|
||||
if let Some(allocation) = allocation
|
||||
.as_ref()
|
||||
.and_then(core::image::Allocation::upgrade)
|
||||
{
|
||||
callback(Ok(allocation));
|
||||
return;
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
let new = unsafe { core::image::allocate(handle, entry.size()) };
|
||||
*allocation = Some(new.downgrade());
|
||||
callback(Ok(new));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let _ = self.raster.pending.insert(handle.id(), vec![callback]);
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
self.worker.load(handle);
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
pub fn load_image(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
handle: &core::image::Handle,
|
||||
) -> Result<core::image::Allocation, core::image::Error> {
|
||||
use crate::image::raster::Memory;
|
||||
|
||||
if !self.raster.cache.contains(handle) {
|
||||
self.raster.cache.insert(handle, Memory::load(handle));
|
||||
}
|
||||
|
||||
match self.raster.cache.get_mut(handle).unwrap() {
|
||||
Memory::Host(image) => {
|
||||
let mut encoder = device.create_command_encoder(
|
||||
&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("raster image upload"),
|
||||
},
|
||||
);
|
||||
|
||||
let entry = self.atlas.upload(
|
||||
device,
|
||||
&mut encoder,
|
||||
&mut self.raster.belt,
|
||||
image.width(),
|
||||
image.height(),
|
||||
image,
|
||||
);
|
||||
|
||||
self.raster.belt.finish();
|
||||
let submission = queue.submit([encoder.finish()]);
|
||||
self.raster.belt.recall();
|
||||
|
||||
let Some(entry) = entry else {
|
||||
return Err(core::image::Error::OutOfMemory);
|
||||
};
|
||||
|
||||
let _ = device.poll(wgpu::PollType::Wait {
|
||||
submission_index: Some(submission),
|
||||
timeout: None,
|
||||
});
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
let allocation = unsafe {
|
||||
core::image::allocate(
|
||||
handle,
|
||||
Size::new(image.width(), image.height()),
|
||||
)
|
||||
};
|
||||
|
||||
self.raster.cache.insert(
|
||||
handle,
|
||||
Memory::Device {
|
||||
entry,
|
||||
bind_group: None,
|
||||
allocation: Some(allocation.downgrade()),
|
||||
},
|
||||
);
|
||||
|
||||
Ok(allocation)
|
||||
}
|
||||
Memory::Device {
|
||||
entry, allocation, ..
|
||||
} => {
|
||||
if let Some(allocation) = allocation
|
||||
.as_ref()
|
||||
.and_then(core::image::Allocation::upgrade)
|
||||
{
|
||||
return Ok(allocation);
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
let new =
|
||||
unsafe { core::image::allocate(handle, entry.size()) };
|
||||
|
||||
*allocation = Some(new.downgrade());
|
||||
|
||||
Ok(new)
|
||||
}
|
||||
Memory::Error(error) => Err(error.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
pub fn measure_image(
|
||||
&mut self,
|
||||
handle: &core::image::Handle,
|
||||
) -> Option<Size<u32>> {
|
||||
self.receive();
|
||||
|
||||
let image = load_image(
|
||||
&mut self.raster.cache,
|
||||
&mut self.raster.pending,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
&self.worker,
|
||||
handle,
|
||||
None,
|
||||
)?;
|
||||
|
||||
Some(image.dimensions())
|
||||
}
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
pub fn measure_svg(&mut self, handle: &core::svg::Handle) -> Size<u32> {
|
||||
// TODO: Concurrency
|
||||
self.vector.load(handle).viewport_dimensions()
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
pub fn upload_raster(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
handle: &core::image::Handle,
|
||||
) -> Option<(&atlas::Entry, &Arc<wgpu::BindGroup>)> {
|
||||
use crate::image::raster::Memory;
|
||||
|
||||
self.receive();
|
||||
|
||||
let memory = load_image(
|
||||
&mut self.raster.cache,
|
||||
&mut self.raster.pending,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
&self.worker,
|
||||
handle,
|
||||
None,
|
||||
)?;
|
||||
|
||||
if let Memory::Device {
|
||||
entry, bind_group, ..
|
||||
} = memory
|
||||
{
|
||||
return Some((
|
||||
entry,
|
||||
bind_group.as_ref().unwrap_or(self.atlas.bind_group()),
|
||||
));
|
||||
}
|
||||
|
||||
let image = memory.host()?;
|
||||
|
||||
const MAX_SYNC_SIZE: usize = 2 * 1024 * 1024;
|
||||
|
||||
// TODO: Concurrent Wasm support
|
||||
if image.len() < MAX_SYNC_SIZE || cfg!(target_arch = "wasm32") {
|
||||
let entry = self.atlas.upload(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
image.width(),
|
||||
image.height(),
|
||||
&image,
|
||||
)?;
|
||||
|
||||
*memory = Memory::Device {
|
||||
entry,
|
||||
bind_group: None,
|
||||
allocation: None,
|
||||
};
|
||||
|
||||
if let Memory::Device { entry, .. } = memory {
|
||||
return Some((entry, self.atlas.bind_group()));
|
||||
}
|
||||
}
|
||||
|
||||
if !self.raster.pending.contains_key(&handle.id()) {
|
||||
let _ = self.raster.pending.insert(handle.id(), Vec::new());
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
self.worker.upload(handle, image);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
pub fn upload_vector(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
handle: &core::svg::Handle,
|
||||
color: Option<core::Color>,
|
||||
size: Size,
|
||||
scale: f32,
|
||||
) -> Option<(&atlas::Entry, &Arc<wgpu::BindGroup>)> {
|
||||
// TODO: Concurrency
|
||||
self.vector
|
||||
.upload(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
handle,
|
||||
color,
|
||||
size,
|
||||
scale,
|
||||
&mut self.atlas,
|
||||
)
|
||||
.map(|entry| (entry, self.atlas.bind_group()))
|
||||
}
|
||||
|
||||
pub fn trim(&mut self) {
|
||||
#[cfg(feature = "image")]
|
||||
{
|
||||
self.receive();
|
||||
self.raster.cache.trim(&mut self.atlas, |_bind_group| {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
self.worker.drop(_bind_group);
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
self.vector.trim(&mut self.atlas); // TODO: Concurrency
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
fn receive(&mut self) {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
while let Ok(work) = self.worker.try_recv() {
|
||||
use crate::image::raster::Memory;
|
||||
|
||||
match work {
|
||||
worker::Work::Upload {
|
||||
handle,
|
||||
entry,
|
||||
bind_group,
|
||||
} => {
|
||||
let callbacks = self.raster.pending.remove(&handle.id());
|
||||
|
||||
let allocation = if let Some(callbacks) = callbacks {
|
||||
#[allow(unsafe_code)]
|
||||
let allocation = unsafe {
|
||||
core::image::allocate(&handle, entry.size())
|
||||
};
|
||||
|
||||
let reference = allocation.downgrade();
|
||||
|
||||
for callback in callbacks {
|
||||
callback(Ok(allocation.clone()));
|
||||
}
|
||||
|
||||
Some(reference)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.raster.cache.insert(
|
||||
&handle,
|
||||
Memory::Device {
|
||||
entry,
|
||||
bind_group: Some(bind_group),
|
||||
allocation,
|
||||
},
|
||||
);
|
||||
}
|
||||
worker::Work::Error { handle, error } => {
|
||||
let callbacks = self.raster.pending.remove(&handle.id());
|
||||
|
||||
if let Some(callbacks) = callbacks {
|
||||
for callback in callbacks {
|
||||
callback(Err(error.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
self.raster.cache.insert(&handle, Memory::Error(error));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "image", not(target_arch = "wasm32")))]
|
||||
impl Drop for Cache {
|
||||
fn drop(&mut self) {
|
||||
self.worker.quit();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
struct Raster {
|
||||
cache: crate::image::raster::Cache,
|
||||
pending: HashMap<core::image::Id, Vec<Callback>>,
|
||||
belt: wgpu::util::StagingBelt,
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
type Callback =
|
||||
Box<dyn FnOnce(Result<core::image::Allocation, core::image::Error>) + Send>;
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
fn load_image<'a>(
|
||||
cache: &'a mut crate::image::raster::Cache,
|
||||
pending: &mut HashMap<core::image::Id, Vec<Callback>>,
|
||||
#[cfg(not(target_arch = "wasm32"))] worker: &Worker,
|
||||
handle: &core::image::Handle,
|
||||
callback: Option<Callback>,
|
||||
) -> Option<&'a mut crate::image::raster::Memory> {
|
||||
use crate::image::raster::Memory;
|
||||
|
||||
if !cache.contains(handle) {
|
||||
if cfg!(target_arch = "wasm32") {
|
||||
// TODO: Concurrent support for Wasm
|
||||
cache.insert(handle, Memory::load(handle));
|
||||
} else if let core::image::Handle::Rgba { .. } = handle {
|
||||
// Load RGBA handles synchronously, since it's very cheap
|
||||
cache.insert(handle, Memory::load(handle));
|
||||
} else if !pending.contains_key(&handle.id()) {
|
||||
let _ = pending.insert(handle.id(), Vec::from_iter(callback));
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
worker.load(handle);
|
||||
}
|
||||
}
|
||||
|
||||
cache.get_mut(handle)
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "image", not(target_arch = "wasm32")))]
|
||||
mod worker {
|
||||
use crate::core::Bytes;
|
||||
use crate::core::image;
|
||||
use crate::graphics::Shell;
|
||||
use crate::image::atlas::{self, Atlas};
|
||||
use crate::image::raster;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
|
||||
pub struct Worker {
|
||||
jobs: mpsc::SyncSender<Job>,
|
||||
quit: mpsc::SyncSender<()>,
|
||||
work: mpsc::Receiver<Work>,
|
||||
handle: Option<std::thread::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl Worker {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
backend: wgpu::Backend,
|
||||
texture_layout: wgpu::BindGroupLayout,
|
||||
shell: &Shell,
|
||||
) -> Self {
|
||||
let (jobs_sender, jobs_receiver) = mpsc::sync_channel(1_000);
|
||||
let (quit_sender, quit_receiver) = mpsc::sync_channel(1);
|
||||
let (work_sender, work_receiver) = mpsc::sync_channel(1_000);
|
||||
|
||||
let instance = Instance {
|
||||
device: device.clone(),
|
||||
queue: queue.clone(),
|
||||
backend,
|
||||
texture_layout,
|
||||
shell: shell.clone(),
|
||||
belt: wgpu::util::StagingBelt::new(4 * 1024 * 1024),
|
||||
jobs: jobs_receiver,
|
||||
output: work_sender,
|
||||
quit: quit_receiver,
|
||||
};
|
||||
|
||||
let handle = thread::spawn(move || instance.run());
|
||||
|
||||
Self {
|
||||
jobs: jobs_sender,
|
||||
quit: quit_sender,
|
||||
work: work_receiver,
|
||||
handle: Some(handle),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(&self, handle: &image::Handle) {
|
||||
let _ = self.jobs.send(Job::Load(handle.clone()));
|
||||
}
|
||||
|
||||
pub fn upload(&self, handle: &image::Handle, image: raster::Image) {
|
||||
let _ = self.jobs.send(Job::Upload {
|
||||
handle: handle.clone(),
|
||||
width: image.width(),
|
||||
height: image.height(),
|
||||
rgba: image.into_raw(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn drop(&self, bind_group: Arc<wgpu::BindGroup>) {
|
||||
let _ = self.jobs.send(Job::Drop(bind_group));
|
||||
}
|
||||
|
||||
pub fn try_recv(&self) -> Result<Work, mpsc::TryRecvError> {
|
||||
self.work.try_recv()
|
||||
}
|
||||
|
||||
pub fn quit(&mut self) {
|
||||
let _ = self.quit.try_send(());
|
||||
let _ = self.jobs.send(Job::Quit);
|
||||
let _ = self.handle.take().map(thread::JoinHandle::join);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Instance {
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
backend: wgpu::Backend,
|
||||
texture_layout: wgpu::BindGroupLayout,
|
||||
shell: Shell,
|
||||
belt: wgpu::util::StagingBelt,
|
||||
jobs: mpsc::Receiver<Job>,
|
||||
output: mpsc::SyncSender<Work>,
|
||||
quit: mpsc::Receiver<()>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Job {
|
||||
Load(image::Handle),
|
||||
Upload {
|
||||
handle: image::Handle,
|
||||
rgba: Bytes,
|
||||
width: u32,
|
||||
height: u32,
|
||||
},
|
||||
Drop(Arc<wgpu::BindGroup>),
|
||||
Quit,
|
||||
}
|
||||
|
||||
pub enum Work {
|
||||
Upload {
|
||||
handle: image::Handle,
|
||||
entry: atlas::Entry,
|
||||
bind_group: Arc<wgpu::BindGroup>,
|
||||
},
|
||||
Error {
|
||||
handle: image::Handle,
|
||||
error: image::Error,
|
||||
},
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
fn run(mut self) {
|
||||
loop {
|
||||
if self.quit.try_recv().is_ok() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Ok(job) = self.jobs.recv() else {
|
||||
return;
|
||||
};
|
||||
|
||||
match job {
|
||||
Job::Load(handle) => {
|
||||
match crate::graphics::image::load(&handle) {
|
||||
Ok(image) => self.upload(
|
||||
handle,
|
||||
image.width(),
|
||||
image.height(),
|
||||
image.into_raw(),
|
||||
Shell::invalidate_layout,
|
||||
),
|
||||
Err(error) => {
|
||||
let _ = self
|
||||
.output
|
||||
.send(Work::Error { handle, error });
|
||||
}
|
||||
}
|
||||
}
|
||||
Job::Upload {
|
||||
handle,
|
||||
rgba,
|
||||
width,
|
||||
height,
|
||||
} => {
|
||||
self.upload(
|
||||
handle,
|
||||
width,
|
||||
height,
|
||||
rgba,
|
||||
Shell::request_redraw,
|
||||
);
|
||||
}
|
||||
Job::Drop(bind_group) => {
|
||||
drop(bind_group);
|
||||
}
|
||||
Job::Quit => return,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn upload(
|
||||
&mut self,
|
||||
handle: image::Handle,
|
||||
width: u32,
|
||||
height: u32,
|
||||
rgba: Bytes,
|
||||
callback: fn(&Shell),
|
||||
) {
|
||||
let mut encoder = self.device.create_command_encoder(
|
||||
&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("raster image upload"),
|
||||
},
|
||||
);
|
||||
|
||||
let mut atlas = Atlas::with_size(
|
||||
&self.device,
|
||||
self.backend,
|
||||
self.texture_layout.clone(),
|
||||
width.max(height),
|
||||
);
|
||||
|
||||
let Some(entry) = atlas.upload(
|
||||
&self.device,
|
||||
&mut encoder,
|
||||
&mut self.belt,
|
||||
width,
|
||||
height,
|
||||
&rgba,
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let output = self.output.clone();
|
||||
let shell = self.shell.clone();
|
||||
|
||||
self.belt.finish();
|
||||
let submission = self.queue.submit([encoder.finish()]);
|
||||
self.belt.recall();
|
||||
|
||||
let bind_group = atlas.bind_group().clone();
|
||||
|
||||
self.queue.on_submitted_work_done(move || {
|
||||
let _ = output.send(Work::Upload {
|
||||
handle,
|
||||
entry,
|
||||
bind_group,
|
||||
});
|
||||
|
||||
callback(&shell);
|
||||
});
|
||||
|
||||
let _ = self.device.poll(wgpu::PollType::Wait {
|
||||
submission_index: Some(submission),
|
||||
timeout: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,758 +0,0 @@
|
|||
pub(crate) mod cache;
|
||||
pub(crate) use cache::Cache;
|
||||
|
||||
mod atlas;
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
mod raster;
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
mod vector;
|
||||
|
||||
use crate::Buffer;
|
||||
use crate::core::border;
|
||||
use crate::core::{Rectangle, Size, Transformation};
|
||||
use crate::graphics::Shell;
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
use std::mem;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use crate::graphics::Image;
|
||||
|
||||
pub type Batch = Vec<Image>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Pipeline {
|
||||
raw: wgpu::RenderPipeline,
|
||||
backend: wgpu::Backend,
|
||||
nearest_sampler: wgpu::Sampler,
|
||||
linear_sampler: wgpu::Sampler,
|
||||
texture_layout: wgpu::BindGroupLayout,
|
||||
constant_layout: wgpu::BindGroupLayout,
|
||||
}
|
||||
|
||||
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: Some("vs_main"),
|
||||
buffers: &[wgpu::VertexBufferLayout {
|
||||
array_stride: mem::size_of::<Instance>() as u64,
|
||||
step_mode: wgpu::VertexStepMode::Instance,
|
||||
attributes: &wgpu::vertex_attr_array!(
|
||||
// Center
|
||||
0 => Float32x2,
|
||||
// Clip bounds
|
||||
1 => Float32x4,
|
||||
// Border radius
|
||||
2 => Float32x4,
|
||||
// Tile
|
||||
3 => Float32x4,
|
||||
// Rotation
|
||||
4 => Float32,
|
||||
// Opacity
|
||||
5 => Float32,
|
||||
// Atlas position
|
||||
6 => Float32x2,
|
||||
// Atlas scale
|
||||
7 => Float32x2,
|
||||
// Layer
|
||||
8 => Sint32,
|
||||
// Snap
|
||||
9 => Uint32,
|
||||
),
|
||||
}],
|
||||
compilation_options:
|
||||
wgpu::PipelineCompilationOptions::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("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,
|
||||
})],
|
||||
compilation_options:
|
||||
wgpu::PipelineCompilationOptions::default(),
|
||||
}),
|
||||
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,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
Pipeline {
|
||||
raw: pipeline,
|
||||
backend,
|
||||
nearest_sampler,
|
||||
linear_sampler,
|
||||
texture_layout,
|
||||
constant_layout,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_cache(
|
||||
&self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
shell: &Shell,
|
||||
) -> Cache {
|
||||
Cache::new(
|
||||
device,
|
||||
queue,
|
||||
self.backend,
|
||||
self.texture_layout.clone(),
|
||||
shell,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct State {
|
||||
layers: Vec<Layer>,
|
||||
prepare_layer: usize,
|
||||
nearest_instances: Vec<Instance>,
|
||||
linear_instances: Vec<Instance>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn prepare(
|
||||
&mut self,
|
||||
pipeline: &Pipeline,
|
||||
device: &wgpu::Device,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
cache: &mut Cache,
|
||||
images: &Batch,
|
||||
transformation: Transformation,
|
||||
scale: f32,
|
||||
) {
|
||||
if self.layers.len() <= self.prepare_layer {
|
||||
self.layers.push(Layer::new(
|
||||
device,
|
||||
&pipeline.constant_layout,
|
||||
&pipeline.nearest_sampler,
|
||||
&pipeline.linear_sampler,
|
||||
));
|
||||
}
|
||||
|
||||
let layer = &mut self.layers[self.prepare_layer];
|
||||
|
||||
let mut atlas: Option<Arc<wgpu::BindGroup>> = None;
|
||||
|
||||
for image in images {
|
||||
match &image {
|
||||
#[cfg(feature = "image")]
|
||||
Image::Raster {
|
||||
image,
|
||||
bounds,
|
||||
clip_bounds,
|
||||
} => {
|
||||
if let Some((atlas_entry, bind_group)) = cache
|
||||
.upload_raster(device, encoder, belt, &image.handle)
|
||||
{
|
||||
match atlas.as_mut() {
|
||||
None => {
|
||||
atlas = Some(bind_group.clone());
|
||||
}
|
||||
Some(atlas) if atlas != bind_group => {
|
||||
layer.push(
|
||||
atlas,
|
||||
&self.nearest_instances,
|
||||
&self.linear_instances,
|
||||
);
|
||||
|
||||
*atlas = Arc::clone(bind_group);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
add_instances(
|
||||
*bounds,
|
||||
*clip_bounds,
|
||||
image.border_radius,
|
||||
f32::from(image.rotation),
|
||||
image.opacity,
|
||||
image.snap,
|
||||
atlas_entry,
|
||||
match image.filter_method {
|
||||
crate::core::image::FilterMethod::Nearest => {
|
||||
&mut self.nearest_instances
|
||||
}
|
||||
crate::core::image::FilterMethod::Linear => {
|
||||
&mut self.linear_instances
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "image"))]
|
||||
Image::Raster { .. } => continue,
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
Image::Vector {
|
||||
svg,
|
||||
bounds,
|
||||
clip_bounds,
|
||||
} => {
|
||||
if let Some((atlas_entry, bind_group)) = cache
|
||||
.upload_vector(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
&svg.handle,
|
||||
svg.color,
|
||||
bounds.size(),
|
||||
scale,
|
||||
)
|
||||
{
|
||||
match atlas.as_mut() {
|
||||
None => {
|
||||
atlas = Some(bind_group.clone());
|
||||
}
|
||||
Some(atlas) if atlas != bind_group => {
|
||||
layer.push(
|
||||
atlas,
|
||||
&self.nearest_instances,
|
||||
&self.linear_instances,
|
||||
);
|
||||
|
||||
*atlas = bind_group.clone();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
add_instances(
|
||||
*bounds,
|
||||
*clip_bounds,
|
||||
border::radius(0),
|
||||
f32::from(svg.rotation),
|
||||
svg.opacity,
|
||||
true,
|
||||
atlas_entry,
|
||||
&mut self.nearest_instances,
|
||||
);
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "svg"))]
|
||||
Image::Vector { .. } => continue,
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(atlas) = &atlas {
|
||||
layer.push(atlas, &self.nearest_instances, &self.linear_instances);
|
||||
}
|
||||
|
||||
layer.prepare(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
transformation,
|
||||
scale,
|
||||
&self.nearest_instances,
|
||||
&self.linear_instances,
|
||||
);
|
||||
|
||||
self.prepare_layer += 1;
|
||||
self.nearest_instances.clear();
|
||||
self.linear_instances.clear();
|
||||
}
|
||||
|
||||
pub fn render<'a>(
|
||||
&'a self,
|
||||
pipeline: &'a Pipeline,
|
||||
layer: usize,
|
||||
bounds: Rectangle<u32>,
|
||||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
) {
|
||||
if let Some(layer) = self.layers.get(layer) {
|
||||
render_pass.set_pipeline(&pipeline.raw);
|
||||
|
||||
render_pass.set_scissor_rect(
|
||||
bounds.x,
|
||||
bounds.y,
|
||||
bounds.width,
|
||||
bounds.height,
|
||||
);
|
||||
|
||||
layer.render(render_pass);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trim(&mut self) {
|
||||
for layer in &mut self.layers[..self.prepare_layer] {
|
||||
layer.clear();
|
||||
}
|
||||
|
||||
self.prepare_layer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Layer {
|
||||
uniforms: wgpu::Buffer,
|
||||
instances: Buffer<Instance>,
|
||||
nearest: Vec<Group>,
|
||||
nearest_layout: wgpu::BindGroup,
|
||||
nearest_total: usize,
|
||||
linear: Vec<Group>,
|
||||
linear_layout: wgpu::BindGroup,
|
||||
linear_total: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Group {
|
||||
atlas: Arc<wgpu::BindGroup>,
|
||||
instance_count: usize,
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
fn new(
|
||||
device: &wgpu::Device,
|
||||
constant_layout: &wgpu::BindGroupLayout,
|
||||
nearest_sampler: &wgpu::Sampler,
|
||||
linear_sampler: &wgpu::Sampler,
|
||||
) -> Self {
|
||||
let uniforms = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("iced_wgpu::image uniforms buffer"),
|
||||
size: mem::size_of::<Uniforms>() as u64,
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let instances = Buffer::new(
|
||||
device,
|
||||
"iced_wgpu::image instance buffer",
|
||||
Instance::INITIAL,
|
||||
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
);
|
||||
|
||||
let nearest_layout =
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::image constants bind group"),
|
||||
layout: constant_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(
|
||||
wgpu::BufferBinding {
|
||||
buffer: &uniforms,
|
||||
offset: 0,
|
||||
size: None,
|
||||
},
|
||||
),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(
|
||||
nearest_sampler,
|
||||
),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let linear_layout =
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::image constants bind group"),
|
||||
layout: constant_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(
|
||||
wgpu::BufferBinding {
|
||||
buffer: &uniforms,
|
||||
offset: 0,
|
||||
size: None,
|
||||
},
|
||||
),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(
|
||||
linear_sampler,
|
||||
),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
Self {
|
||||
uniforms,
|
||||
instances,
|
||||
nearest: Vec::new(),
|
||||
nearest_layout,
|
||||
nearest_total: 0,
|
||||
linear: Vec::new(),
|
||||
linear_layout,
|
||||
linear_total: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
transformation: Transformation,
|
||||
scale_factor: f32,
|
||||
nearest: &[Instance],
|
||||
linear: &[Instance],
|
||||
) {
|
||||
let uniforms = Uniforms {
|
||||
transform: transformation.into(),
|
||||
scale_factor,
|
||||
_padding: [0.0; 3],
|
||||
};
|
||||
|
||||
let bytes = bytemuck::bytes_of(&uniforms);
|
||||
|
||||
belt.write_buffer(
|
||||
encoder,
|
||||
&self.uniforms,
|
||||
0,
|
||||
(bytes.len() as u64).try_into().expect("Sized uniforms"),
|
||||
device,
|
||||
)
|
||||
.copy_from_slice(bytes);
|
||||
|
||||
let _ = self
|
||||
.instances
|
||||
.resize(device, self.nearest_total + self.linear_total);
|
||||
|
||||
let mut offset = 0;
|
||||
|
||||
if !nearest.is_empty() {
|
||||
offset += self.instances.write(device, encoder, belt, 0, nearest);
|
||||
}
|
||||
|
||||
if !linear.is_empty() {
|
||||
let _ = self.instances.write(device, encoder, belt, offset, linear);
|
||||
}
|
||||
}
|
||||
|
||||
fn push(
|
||||
&mut self,
|
||||
atlas: &Arc<wgpu::BindGroup>,
|
||||
nearest: &[Instance],
|
||||
linear: &[Instance],
|
||||
) {
|
||||
let new_nearest = nearest.len() - self.nearest_total;
|
||||
|
||||
if new_nearest > 0 {
|
||||
self.nearest.push(Group {
|
||||
atlas: atlas.clone(),
|
||||
instance_count: new_nearest,
|
||||
});
|
||||
|
||||
self.nearest_total = nearest.len();
|
||||
}
|
||||
|
||||
let new_linear = linear.len() - self.linear_total;
|
||||
|
||||
if new_linear > 0 {
|
||||
self.linear.push(Group {
|
||||
atlas: atlas.clone(),
|
||||
instance_count: new_linear,
|
||||
});
|
||||
|
||||
self.linear_total = linear.len();
|
||||
}
|
||||
}
|
||||
|
||||
fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
|
||||
render_pass.set_vertex_buffer(0, self.instances.slice(..));
|
||||
|
||||
let mut offset = 0;
|
||||
|
||||
if !self.nearest.is_empty() {
|
||||
render_pass.set_bind_group(0, &self.nearest_layout, &[]);
|
||||
|
||||
for group in &self.nearest {
|
||||
render_pass.set_bind_group(1, group.atlas.as_ref(), &[]);
|
||||
render_pass
|
||||
.draw(0..6, offset..offset + group.instance_count as u32);
|
||||
|
||||
offset += group.instance_count as u32;
|
||||
}
|
||||
}
|
||||
|
||||
if !self.linear.is_empty() {
|
||||
render_pass.set_bind_group(0, &self.linear_layout, &[]);
|
||||
|
||||
for group in &self.linear {
|
||||
render_pass.set_bind_group(1, group.atlas.as_ref(), &[]);
|
||||
render_pass
|
||||
.draw(0..6, offset..offset + group.instance_count as u32);
|
||||
|
||||
offset += group.instance_count as u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.nearest.clear();
|
||||
self.nearest_total = 0;
|
||||
|
||||
self.linear.clear();
|
||||
self.linear_total = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
struct Instance {
|
||||
_center: [f32; 2],
|
||||
_clip_bounds: [f32; 4],
|
||||
_border_radius: [f32; 4],
|
||||
_tile: [f32; 4],
|
||||
_rotation: f32,
|
||||
_opacity: f32,
|
||||
_position_in_atlas: [f32; 2],
|
||||
_size_in_atlas: [f32; 2],
|
||||
_layer: u32,
|
||||
_snap: u32,
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
pub const INITIAL: usize = 20;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
struct Uniforms {
|
||||
transform: [f32; 16],
|
||||
scale_factor: f32,
|
||||
// Uniforms must be aligned to their largest member,
|
||||
// this uses a mat4x4<f32> which aligns to 16, so align to that
|
||||
_padding: [f32; 3],
|
||||
}
|
||||
|
||||
fn add_instances(
|
||||
bounds: Rectangle,
|
||||
clip_bounds: Rectangle,
|
||||
border_radius: border::Radius,
|
||||
rotation: f32,
|
||||
opacity: f32,
|
||||
snap: bool,
|
||||
entry: &atlas::Entry,
|
||||
instances: &mut Vec<Instance>,
|
||||
) {
|
||||
let center = [
|
||||
bounds.x + bounds.width / 2.0,
|
||||
bounds.y + bounds.height / 2.0,
|
||||
];
|
||||
|
||||
let clip_bounds = [
|
||||
clip_bounds.x,
|
||||
clip_bounds.y,
|
||||
clip_bounds.width,
|
||||
clip_bounds.height,
|
||||
];
|
||||
|
||||
let border_radius = border_radius.into();
|
||||
|
||||
match entry {
|
||||
atlas::Entry::Contiguous(allocation) => {
|
||||
add_instance(
|
||||
center,
|
||||
clip_bounds,
|
||||
border_radius,
|
||||
[bounds.x, bounds.y, bounds.width, bounds.height],
|
||||
rotation,
|
||||
opacity,
|
||||
snap,
|
||||
allocation,
|
||||
instances,
|
||||
);
|
||||
}
|
||||
atlas::Entry::Fragmented { fragments, size } => {
|
||||
let scaling_x = bounds.width / size.width as f32;
|
||||
let scaling_y = bounds.height / size.height as f32;
|
||||
|
||||
for fragment in fragments {
|
||||
let allocation = &fragment.allocation;
|
||||
let (fragment_x, fragment_y) = fragment.position;
|
||||
|
||||
let Size {
|
||||
width: fragment_width,
|
||||
height: fragment_height,
|
||||
} = allocation.size();
|
||||
|
||||
let tile = [
|
||||
bounds.x + fragment_x as f32 * scaling_x,
|
||||
bounds.y + fragment_y as f32 * scaling_y,
|
||||
fragment_width as f32 * scaling_x,
|
||||
fragment_height as f32 * scaling_y,
|
||||
];
|
||||
|
||||
add_instance(
|
||||
center,
|
||||
clip_bounds,
|
||||
border_radius,
|
||||
tile,
|
||||
rotation,
|
||||
opacity,
|
||||
snap,
|
||||
allocation,
|
||||
instances,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn add_instance(
|
||||
center: [f32; 2],
|
||||
clip_bounds: [f32; 4],
|
||||
border_radius: [f32; 4],
|
||||
tile: [f32; 4],
|
||||
rotation: f32,
|
||||
opacity: f32,
|
||||
snap: bool,
|
||||
allocation: &atlas::Allocation,
|
||||
instances: &mut Vec<Instance>,
|
||||
) {
|
||||
let (x, y) = allocation.position();
|
||||
let Size { width, height } = allocation.size();
|
||||
let layer = allocation.layer();
|
||||
let atlas_size = allocation.atlas_size();
|
||||
|
||||
let instance = Instance {
|
||||
_center: center,
|
||||
_clip_bounds: clip_bounds,
|
||||
_border_radius: border_radius,
|
||||
_tile: tile,
|
||||
_rotation: rotation,
|
||||
_opacity: opacity,
|
||||
_position_in_atlas: [
|
||||
x as f32 / atlas_size as f32,
|
||||
y as f32 / atlas_size as f32,
|
||||
],
|
||||
_size_in_atlas: [
|
||||
width as f32 / atlas_size as f32,
|
||||
height as f32 / atlas_size as f32,
|
||||
],
|
||||
_layer: layer as u32,
|
||||
_snap: snap as u32,
|
||||
};
|
||||
|
||||
instances.push(instance);
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
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) {}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn append(&mut self, _batch: &mut Self) {}
|
||||
}
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
use crate::core::Size;
|
||||
use crate::core::image;
|
||||
use crate::graphics;
|
||||
use crate::image::atlas::{self, Atlas};
|
||||
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
pub type Image = graphics::image::Buffer;
|
||||
|
||||
/// Entry in cache corresponding to an image handle
|
||||
#[derive(Debug)]
|
||||
pub enum Memory {
|
||||
/// Image data on host
|
||||
Host(Image),
|
||||
/// Storage entry
|
||||
Device {
|
||||
entry: atlas::Entry,
|
||||
bind_group: Option<Arc<wgpu::BindGroup>>,
|
||||
allocation: Option<Weak<image::Memory>>,
|
||||
},
|
||||
Error(image::Error),
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
pub fn load(handle: &image::Handle) -> Self {
|
||||
match graphics::image::load(handle) {
|
||||
Ok(image) => Self::Host(image),
|
||||
Err(error) => Self::Error(error),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dimensions(&self) -> Size<u32> {
|
||||
match self {
|
||||
Memory::Host(image) => {
|
||||
let (width, height) = image.dimensions();
|
||||
|
||||
Size::new(width, height)
|
||||
}
|
||||
Memory::Device { entry, .. } => entry.size(),
|
||||
Memory::Error(_) => Size::new(1, 1),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn host(&self) -> Option<Image> {
|
||||
match self {
|
||||
Memory::Host(image) => Some(image.clone()),
|
||||
Memory::Device { .. } | Memory::Error(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Cache {
|
||||
map: FxHashMap<image::Id, Memory>,
|
||||
hits: FxHashSet<image::Id>,
|
||||
should_trim: bool,
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
pub fn get_mut(&mut self, handle: &image::Handle) -> Option<&mut Memory> {
|
||||
let _ = self.hits.insert(handle.id());
|
||||
|
||||
self.map.get_mut(&handle.id())
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, handle: &image::Handle, memory: Memory) {
|
||||
let _ = self.map.insert(handle.id(), memory);
|
||||
let _ = self.hits.insert(handle.id());
|
||||
|
||||
self.should_trim = true;
|
||||
}
|
||||
|
||||
pub fn contains(&self, handle: &image::Handle) -> bool {
|
||||
self.map.contains_key(&handle.id())
|
||||
}
|
||||
|
||||
pub fn trim(
|
||||
&mut self,
|
||||
atlas: &mut Atlas,
|
||||
on_drop: impl Fn(Arc<wgpu::BindGroup>),
|
||||
) {
|
||||
// Only trim if new entries have landed in the `Cache`
|
||||
if !self.should_trim {
|
||||
return;
|
||||
}
|
||||
|
||||
let hits = &self.hits;
|
||||
|
||||
self.map.retain(|id, memory| {
|
||||
// Retain active allocations
|
||||
if let Memory::Device { allocation, .. } = memory
|
||||
&& allocation
|
||||
.as_ref()
|
||||
.is_some_and(|allocation| allocation.strong_count() > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
let retain = hits.contains(id);
|
||||
|
||||
if !retain {
|
||||
log::debug!("Dropping image allocation: {id:?}");
|
||||
|
||||
if let Memory::Device {
|
||||
entry, bind_group, ..
|
||||
} = memory
|
||||
{
|
||||
if let Some(bind_group) = bind_group.take() {
|
||||
on_drop(bind_group);
|
||||
} else {
|
||||
atlas.remove(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
retain
|
||||
});
|
||||
|
||||
self.hits.clear();
|
||||
self.should_trim = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,231 +0,0 @@
|
|||
use crate::core::svg;
|
||||
use crate::core::{Color, Size};
|
||||
use crate::image::atlas::{self, Atlas};
|
||||
|
||||
use resvg::tiny_skia;
|
||||
use resvg::usvg;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use std::fs;
|
||||
use std::panic;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Entry in cache corresponding to an svg handle
|
||||
pub enum Svg {
|
||||
/// Parsed svg
|
||||
Loaded(usvg::Tree),
|
||||
/// Svg not found or failed to parse
|
||||
NotFound,
|
||||
}
|
||||
|
||||
impl Svg {
|
||||
/// Viewport width and height
|
||||
pub fn viewport_dimensions(&self) -> Size<u32> {
|
||||
match self {
|
||||
Svg::Loaded(tree) => {
|
||||
let size = tree.size();
|
||||
|
||||
Size::new(size.width() as u32, size.height() as u32)
|
||||
}
|
||||
Svg::NotFound => Size::new(1, 1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Caches svg vector and raster data
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Cache {
|
||||
svgs: FxHashMap<u64, Svg>,
|
||||
rasterized: FxHashMap<(u64, u32, u32, ColorFilter), atlas::Entry>,
|
||||
svg_hits: FxHashSet<u64>,
|
||||
rasterized_hits: FxHashSet<(u64, u32, u32, ColorFilter)>,
|
||||
should_trim: bool,
|
||||
fontdb: Option<Arc<usvg::fontdb::Database>>,
|
||||
}
|
||||
|
||||
type ColorFilter = Option<[u8; 4]>;
|
||||
|
||||
impl Cache {
|
||||
/// Load svg
|
||||
pub fn load(&mut self, handle: &svg::Handle) -> &Svg {
|
||||
if self.svgs.contains_key(&handle.id()) {
|
||||
return self.svgs.get(&handle.id()).unwrap();
|
||||
}
|
||||
|
||||
// TODO: Reuse `cosmic-text` font database
|
||||
if self.fontdb.is_none() {
|
||||
let mut fontdb = usvg::fontdb::Database::new();
|
||||
fontdb.load_system_fonts();
|
||||
|
||||
self.fontdb = Some(Arc::new(fontdb));
|
||||
}
|
||||
|
||||
let options = usvg::Options {
|
||||
fontdb: self
|
||||
.fontdb
|
||||
.as_ref()
|
||||
.expect("fontdb must be initialized")
|
||||
.clone(),
|
||||
..usvg::Options::default()
|
||||
};
|
||||
|
||||
let svg = match handle.data() {
|
||||
svg::Data::Path(path) => fs::read_to_string(path)
|
||||
.ok()
|
||||
.and_then(|contents| {
|
||||
usvg::Tree::from_str(&contents, &options).ok()
|
||||
})
|
||||
.map(Svg::Loaded)
|
||||
.unwrap_or(Svg::NotFound),
|
||||
svg::Data::Bytes(bytes) => {
|
||||
match usvg::Tree::from_data(bytes, &options) {
|
||||
Ok(tree) => Svg::Loaded(tree),
|
||||
Err(_) => Svg::NotFound,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.should_trim = true;
|
||||
|
||||
let _ = self.svgs.insert(handle.id(), svg);
|
||||
self.svgs.get(&handle.id()).unwrap()
|
||||
}
|
||||
|
||||
/// Load svg and upload raster data
|
||||
pub fn upload(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
handle: &svg::Handle,
|
||||
color: Option<Color>,
|
||||
size: Size,
|
||||
scale: f32,
|
||||
atlas: &mut Atlas,
|
||||
) -> Option<&atlas::Entry> {
|
||||
let id = handle.id();
|
||||
|
||||
let (width, height) = (
|
||||
(scale * size.width).ceil() as u32,
|
||||
(scale * size.height).ceil() as u32,
|
||||
);
|
||||
|
||||
let color = color.map(Color::into_rgba8);
|
||||
let key = (id, width, height, color);
|
||||
|
||||
// TODO: Optimize!
|
||||
// We currently rerasterize the SVG when its size changes. This is slow
|
||||
// as heck. A GPU rasterizer like `pathfinder` may perform better.
|
||||
// It would be cool to be able to smooth resize the `svg` example.
|
||||
if self.rasterized.contains_key(&key) {
|
||||
let _ = self.svg_hits.insert(id);
|
||||
let _ = self.rasterized_hits.insert(key);
|
||||
|
||||
return self.rasterized.get(&key);
|
||||
}
|
||||
|
||||
match self.load(handle) {
|
||||
Svg::Loaded(tree) => {
|
||||
if width == 0 || height == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// TODO: Optimize!
|
||||
// We currently rerasterize the SVG when its size changes. This is slow
|
||||
// as heck. A GPU rasterizer like `pathfinder` may perform better.
|
||||
// It would be cool to be able to smooth resize the `svg` example.
|
||||
let mut img = tiny_skia::Pixmap::new(width, height)?;
|
||||
|
||||
let tree_size = tree.size().to_int_size();
|
||||
|
||||
let target_size = if width > height {
|
||||
tree_size.scale_to_width(width)
|
||||
} else {
|
||||
tree_size.scale_to_height(height)
|
||||
};
|
||||
|
||||
let transform = if let Some(target_size) = target_size {
|
||||
let tree_size = tree_size.to_size();
|
||||
let target_size = target_size.to_size();
|
||||
|
||||
tiny_skia::Transform::from_scale(
|
||||
target_size.width() / tree_size.width(),
|
||||
target_size.height() / tree_size.height(),
|
||||
)
|
||||
} else {
|
||||
tiny_skia::Transform::default()
|
||||
};
|
||||
|
||||
// SVG rendering can panic on malformed or complex vectors.
|
||||
// We catch panics to prevent crashes and continue gracefully.
|
||||
let render =
|
||||
panic::catch_unwind(panic::AssertUnwindSafe(|| {
|
||||
resvg::render(tree, transform, &mut img.as_mut());
|
||||
}));
|
||||
|
||||
if let Err(error) = render {
|
||||
log::warn!(
|
||||
"SVG rendering for {handle:?} panicked: {error:?}"
|
||||
);
|
||||
}
|
||||
|
||||
let mut rgba = img.take();
|
||||
|
||||
if let Some(color) = color {
|
||||
rgba.chunks_exact_mut(4).for_each(|rgba| {
|
||||
if rgba[3] > 0 {
|
||||
rgba[0] = color[0];
|
||||
rgba[1] = color[1];
|
||||
rgba[2] = color[2];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let allocation = atlas
|
||||
.upload(device, encoder, belt, width, height, &rgba)?;
|
||||
|
||||
log::debug!("allocating {id} {width}x{height}");
|
||||
|
||||
let _ = self.svg_hits.insert(id);
|
||||
let _ = self.rasterized_hits.insert(key);
|
||||
let _ = self.rasterized.insert(key, allocation);
|
||||
self.should_trim = true;
|
||||
|
||||
self.rasterized.get(&key)
|
||||
}
|
||||
Svg::NotFound => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Load svg and upload raster data
|
||||
pub fn trim(&mut self, atlas: &mut Atlas) {
|
||||
if !self.should_trim {
|
||||
return;
|
||||
}
|
||||
|
||||
let svg_hits = &self.svg_hits;
|
||||
let rasterized_hits = &self.rasterized_hits;
|
||||
|
||||
self.svgs.retain(|k, _| svg_hits.contains(k));
|
||||
self.rasterized.retain(|k, entry| {
|
||||
let retain = rasterized_hits.contains(k);
|
||||
|
||||
if !retain {
|
||||
atlas.remove(entry);
|
||||
}
|
||||
|
||||
retain
|
||||
});
|
||||
self.svg_hits.clear();
|
||||
self.rasterized_hits.clear();
|
||||
self.should_trim = false;
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Svg {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Svg::Loaded(_) => write!(f, "Svg::Loaded"),
|
||||
Svg::NotFound => write!(f, "Svg::NotFound"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,405 +0,0 @@
|
|||
use crate::core::{
|
||||
self, Background, Color, Point, Rectangle, Svg, Transformation, renderer,
|
||||
};
|
||||
use crate::graphics;
|
||||
use crate::graphics::Mesh;
|
||||
use crate::graphics::color;
|
||||
use crate::graphics::layer;
|
||||
use crate::graphics::mesh;
|
||||
use crate::graphics::text::{Editor, Paragraph};
|
||||
use crate::image::{self, Image};
|
||||
use crate::primitive::{self, Primitive};
|
||||
use crate::quad::{self, Quad};
|
||||
use crate::text::{self, Text};
|
||||
use crate::triangle;
|
||||
|
||||
pub type Stack = layer::Stack<Layer>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Layer {
|
||||
pub bounds: Rectangle,
|
||||
pub quads: quad::Batch,
|
||||
pub triangles: triangle::Batch,
|
||||
pub primitives: primitive::Batch,
|
||||
pub images: image::Batch,
|
||||
pub text: text::Batch,
|
||||
pending_meshes: Vec<Mesh>,
|
||||
pending_text: Vec<Text>,
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.quads.is_empty()
|
||||
&& self.triangles.is_empty()
|
||||
&& self.primitives.is_empty()
|
||||
&& self.images.is_empty()
|
||||
&& self.text.is_empty()
|
||||
&& self.pending_meshes.is_empty()
|
||||
&& self.pending_text.is_empty()
|
||||
}
|
||||
|
||||
pub fn draw_quad(
|
||||
&mut self,
|
||||
quad: renderer::Quad,
|
||||
background: Background,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
let bounds = quad.bounds * transformation;
|
||||
|
||||
let quad = Quad {
|
||||
position: [bounds.x, bounds.y],
|
||||
size: [bounds.width, bounds.height],
|
||||
border_color: color::pack(quad.border.color),
|
||||
border_radius: (quad.border.radius * transformation.scale_factor())
|
||||
.into(),
|
||||
border_width: quad.border.width * transformation.scale_factor(),
|
||||
shadow_color: color::pack(quad.shadow.color),
|
||||
shadow_offset: (quad.shadow.offset * transformation.scale_factor())
|
||||
.into(),
|
||||
shadow_blur_radius: quad.shadow.blur_radius
|
||||
* transformation.scale_factor(),
|
||||
snap: quad.snap as u32,
|
||||
};
|
||||
|
||||
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,
|
||||
align_x: text.align_x,
|
||||
align_y: text.align_y,
|
||||
shaping: text.shaping,
|
||||
clip_bounds: clip_bounds * transformation,
|
||||
};
|
||||
|
||||
self.pending_text.push(text);
|
||||
}
|
||||
|
||||
pub fn draw_text_raw(
|
||||
&mut self,
|
||||
raw: graphics::text::Raw,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
let raw = Text::Raw {
|
||||
raw,
|
||||
transformation,
|
||||
};
|
||||
|
||||
self.pending_text.push(raw);
|
||||
}
|
||||
|
||||
pub fn draw_image(&mut self, image: Image, transformation: Transformation) {
|
||||
match image {
|
||||
Image::Raster {
|
||||
image,
|
||||
bounds,
|
||||
clip_bounds,
|
||||
} => {
|
||||
self.draw_raster(image, bounds, clip_bounds, transformation);
|
||||
}
|
||||
Image::Vector {
|
||||
svg,
|
||||
bounds,
|
||||
clip_bounds,
|
||||
} => {
|
||||
self.draw_svg(svg, bounds, clip_bounds, transformation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_raster(
|
||||
&mut self,
|
||||
image: core::Image,
|
||||
bounds: Rectangle,
|
||||
clip_bounds: Rectangle,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
let image = Image::Raster {
|
||||
image: core::Image {
|
||||
border_radius: image.border_radius
|
||||
* transformation.scale_factor(),
|
||||
..image
|
||||
},
|
||||
bounds: bounds * transformation,
|
||||
clip_bounds: clip_bounds * transformation,
|
||||
};
|
||||
|
||||
self.images.push(image);
|
||||
}
|
||||
|
||||
pub fn draw_svg(
|
||||
&mut self,
|
||||
svg: Svg,
|
||||
bounds: Rectangle,
|
||||
clip_bounds: Rectangle,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
let svg = Image::Vector {
|
||||
svg,
|
||||
bounds: bounds * transformation,
|
||||
clip_bounds: clip_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: mesh::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: impl Primitive,
|
||||
transformation: Transformation,
|
||||
) {
|
||||
let bounds = bounds * transformation;
|
||||
|
||||
self.primitives
|
||||
.push(primitive::Instance::new(bounds, primitive));
|
||||
}
|
||||
|
||||
fn flush_meshes(&mut self) {
|
||||
if !self.pending_meshes.is_empty() {
|
||||
self.triangles.push(triangle::Item::Group {
|
||||
transformation: Transformation::IDENTITY,
|
||||
meshes: self.pending_meshes.drain(..).collect(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_text(&mut self) {
|
||||
if !self.pending_text.is_empty() {
|
||||
self.text.push(text::Item::Group {
|
||||
transformation: Transformation::IDENTITY,
|
||||
text: self.pending_text.drain(..).collect(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl graphics::Layer for Layer {
|
||||
fn with_bounds(bounds: Rectangle) -> Self {
|
||||
Self {
|
||||
bounds,
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Rectangle {
|
||||
self.bounds
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
fn start(&self) -> usize {
|
||||
if !self.quads.is_empty() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if !self.triangles.is_empty() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
if !self.primitives.is_empty() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
if !self.images.is_empty() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
if !self.text.is_empty() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
usize::MAX
|
||||
}
|
||||
|
||||
fn end(&self) -> usize {
|
||||
if !self.text.is_empty() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
if !self.images.is_empty() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
if !self.primitives.is_empty() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
if !self.triangles.is_empty() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
if !self.quads.is_empty() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
fn merge(&mut self, layer: &mut Self) {
|
||||
self.quads.append(&mut layer.quads);
|
||||
self.triangles.append(&mut layer.triangles);
|
||||
self.primitives.append(&mut layer.primitives);
|
||||
self.images.append(&mut layer.images);
|
||||
self.text.append(&mut layer.text);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Layer {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bounds: Rectangle::INFINITE,
|
||||
quads: quad::Batch::default(),
|
||||
triangles: triangle::Batch::default(),
|
||||
primitives: primitive::Batch::default(),
|
||||
text: text::Batch::default(),
|
||||
images: image::Batch::default(),
|
||||
pending_meshes: Vec::new(),
|
||||
pending_text: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,984 +0,0 @@
|
|||
//! A [`wgpu`] renderer for [Iced].
|
||||
//!
|
||||
//! 
|
||||
//!
|
||||
//! [`wgpu`] supports most modern graphics backends: Vulkan, Metal, DX11, and
|
||||
//! DX12 (OpenGL and WebGL are still WIP). Additionally, it will support the
|
||||
//! incoming [WebGPU API].
|
||||
//!
|
||||
//! Currently, `iced_wgpu` supports the following primitives:
|
||||
//! - Text, which is rendered using [`glyphon`].
|
||||
//! - Quads or rectangles, with rounded borders and a solid background color.
|
||||
//! - Clip areas, useful to implement scrollables or hide overflowing content.
|
||||
//! - Images and SVG, loaded from memory or the file system.
|
||||
//! - Meshes of triangles, useful to draw geometry freely.
|
||||
//!
|
||||
//! [Iced]: https://github.com/iced-rs/iced
|
||||
//! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
|
||||
//! [WebGPU API]: https://gpuweb.github.io/gpuweb/
|
||||
//! [`glyphon`]: https://github.com/grovesNL/glyphon
|
||||
#![doc(
|
||||
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
|
||||
)]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![allow(missing_docs)]
|
||||
pub mod layer;
|
||||
pub mod primitive;
|
||||
pub mod settings;
|
||||
pub mod window;
|
||||
|
||||
#[cfg(feature = "geometry")]
|
||||
pub mod geometry;
|
||||
|
||||
mod buffer;
|
||||
mod color;
|
||||
mod engine;
|
||||
mod quad;
|
||||
mod text;
|
||||
mod triangle;
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
#[path = "image/mod.rs"]
|
||||
mod image;
|
||||
|
||||
#[cfg(not(any(feature = "image", feature = "svg")))]
|
||||
#[path = "image/null.rs"]
|
||||
mod image;
|
||||
|
||||
use buffer::Buffer;
|
||||
|
||||
use iced_debug as debug;
|
||||
pub use iced_graphics as graphics;
|
||||
pub use iced_graphics::core;
|
||||
|
||||
pub use wgpu;
|
||||
|
||||
pub use engine::Engine;
|
||||
pub use layer::Layer;
|
||||
pub use primitive::Primitive;
|
||||
pub use settings::Settings;
|
||||
|
||||
#[cfg(feature = "geometry")]
|
||||
pub use geometry::Geometry;
|
||||
|
||||
use crate::core::renderer;
|
||||
use crate::core::{
|
||||
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
|
||||
};
|
||||
use crate::graphics::mesh;
|
||||
use crate::graphics::text::{Editor, Paragraph};
|
||||
use crate::graphics::{Shell, Viewport};
|
||||
|
||||
/// A [`wgpu`] graphics renderer for [`iced`].
|
||||
///
|
||||
/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
|
||||
/// [`iced`]: https://github.com/iced-rs/iced
|
||||
pub struct Renderer {
|
||||
engine: Engine,
|
||||
|
||||
default_font: Font,
|
||||
default_text_size: Pixels,
|
||||
layers: layer::Stack,
|
||||
|
||||
quad: quad::State,
|
||||
triangle: triangle::State,
|
||||
text: text::State,
|
||||
text_viewport: text::Viewport,
|
||||
|
||||
#[cfg(any(feature = "svg", feature = "image"))]
|
||||
image: image::State,
|
||||
|
||||
// TODO: Centralize all the image feature handling
|
||||
#[cfg(any(feature = "svg", feature = "image"))]
|
||||
image_cache: std::cell::RefCell<image::Cache>,
|
||||
|
||||
staging_belt: wgpu::util::StagingBelt,
|
||||
}
|
||||
|
||||
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(),
|
||||
|
||||
quad: quad::State::new(),
|
||||
triangle: triangle::State::new(
|
||||
&engine.device,
|
||||
&engine.triangle_pipeline,
|
||||
),
|
||||
text: text::State::new(),
|
||||
text_viewport: engine.text_pipeline.create_viewport(&engine.device),
|
||||
|
||||
#[cfg(any(feature = "svg", feature = "image"))]
|
||||
image: image::State::new(),
|
||||
|
||||
#[cfg(any(feature = "svg", feature = "image"))]
|
||||
image_cache: std::cell::RefCell::new(engine.create_image_cache()),
|
||||
|
||||
// TODO: Resize belt smartly (?)
|
||||
// It would be great if the `StagingBelt` API exposed methods
|
||||
// for introspection to detect when a resize may be worth it.
|
||||
staging_belt: wgpu::util::StagingBelt::new(
|
||||
buffer::MAX_WRITE_SIZE as u64,
|
||||
),
|
||||
|
||||
engine,
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&mut self,
|
||||
clear_color: Option<Color>,
|
||||
target: &wgpu::TextureView,
|
||||
viewport: &Viewport,
|
||||
) -> wgpu::CommandEncoder {
|
||||
let mut encoder = self.engine.device.create_command_encoder(
|
||||
&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("iced_wgpu encoder"),
|
||||
},
|
||||
);
|
||||
|
||||
self.prepare(&mut encoder, viewport);
|
||||
self.render(&mut encoder, target, clear_color, viewport);
|
||||
|
||||
self.quad.trim();
|
||||
self.triangle.trim();
|
||||
self.text.trim();
|
||||
|
||||
// TODO: Provide window id (?)
|
||||
self.engine.trim();
|
||||
|
||||
#[cfg(any(feature = "svg", feature = "image"))]
|
||||
{
|
||||
self.image.trim();
|
||||
self.image_cache.borrow_mut().trim();
|
||||
}
|
||||
|
||||
encoder
|
||||
}
|
||||
|
||||
pub fn present(
|
||||
&mut self,
|
||||
clear_color: Option<Color>,
|
||||
_format: wgpu::TextureFormat,
|
||||
frame: &wgpu::TextureView,
|
||||
viewport: &Viewport,
|
||||
) -> wgpu::SubmissionIndex {
|
||||
let encoder = self.draw(clear_color, frame, viewport);
|
||||
|
||||
self.staging_belt.finish();
|
||||
let submission = self.engine.queue.submit([encoder.finish()]);
|
||||
self.staging_belt.recall();
|
||||
submission
|
||||
}
|
||||
|
||||
/// Renders the current surface to an offscreen buffer.
|
||||
///
|
||||
/// Returns RGBA bytes of the texture data.
|
||||
pub fn screenshot(
|
||||
&mut self,
|
||||
viewport: &Viewport,
|
||||
background_color: Color,
|
||||
) -> Vec<u8> {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct BufferDimensions {
|
||||
width: u32,
|
||||
height: u32,
|
||||
unpadded_bytes_per_row: usize,
|
||||
padded_bytes_per_row: usize,
|
||||
}
|
||||
|
||||
impl BufferDimensions {
|
||||
fn new(size: Size<u32>) -> Self {
|
||||
let unpadded_bytes_per_row = size.width as usize * 4; //slice of buffer per row; always RGBA
|
||||
let alignment = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize; //256
|
||||
let padded_bytes_per_row_padding = (alignment
|
||||
- unpadded_bytes_per_row % alignment)
|
||||
% alignment;
|
||||
let padded_bytes_per_row =
|
||||
unpadded_bytes_per_row + padded_bytes_per_row_padding;
|
||||
|
||||
Self {
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
unpadded_bytes_per_row,
|
||||
padded_bytes_per_row,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let dimensions = BufferDimensions::new(viewport.physical_size());
|
||||
|
||||
let texture_extent = wgpu::Extent3d {
|
||||
width: dimensions.width,
|
||||
height: dimensions.height,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
|
||||
let texture =
|
||||
self.engine.device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("iced_wgpu.offscreen.source_texture"),
|
||||
size: texture_extent,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: self.engine.format,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
|
||||
| wgpu::TextureUsages::COPY_SRC
|
||||
| wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let mut encoder = self.draw(Some(background_color), &view, viewport);
|
||||
|
||||
let texture = crate::color::convert(
|
||||
&self.engine.device,
|
||||
&mut encoder,
|
||||
texture,
|
||||
if graphics::color::GAMMA_CORRECTION {
|
||||
wgpu::TextureFormat::Rgba8UnormSrgb
|
||||
} else {
|
||||
wgpu::TextureFormat::Rgba8Unorm
|
||||
},
|
||||
);
|
||||
|
||||
let output_buffer =
|
||||
self.engine.device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("iced_wgpu.offscreen.output_texture_buffer"),
|
||||
size: (dimensions.padded_bytes_per_row
|
||||
* dimensions.height as usize) as u64,
|
||||
usage: wgpu::BufferUsages::MAP_READ
|
||||
| wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
encoder.copy_texture_to_buffer(
|
||||
texture.as_image_copy(),
|
||||
wgpu::TexelCopyBufferInfo {
|
||||
buffer: &output_buffer,
|
||||
layout: wgpu::TexelCopyBufferLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(dimensions.padded_bytes_per_row as u32),
|
||||
rows_per_image: None,
|
||||
},
|
||||
},
|
||||
texture_extent,
|
||||
);
|
||||
|
||||
self.staging_belt.finish();
|
||||
let index = self.engine.queue.submit([encoder.finish()]);
|
||||
self.staging_belt.recall();
|
||||
|
||||
let slice = output_buffer.slice(..);
|
||||
slice.map_async(wgpu::MapMode::Read, |_| {});
|
||||
|
||||
let _ = self.engine.device.poll(wgpu::PollType::Wait {
|
||||
submission_index: Some(index),
|
||||
timeout: None,
|
||||
});
|
||||
|
||||
let mapped_buffer = slice.get_mapped_range();
|
||||
|
||||
mapped_buffer.chunks(dimensions.padded_bytes_per_row).fold(
|
||||
vec![],
|
||||
|mut acc, row| {
|
||||
acc.extend(&row[..dimensions.unpadded_bytes_per_row]);
|
||||
acc
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn prepare(
|
||||
&mut self,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
viewport: &Viewport,
|
||||
) {
|
||||
let scale_factor = viewport.scale_factor();
|
||||
|
||||
self.text_viewport
|
||||
.update(&self.engine.queue, viewport.physical_size());
|
||||
|
||||
let physical_bounds = Rectangle::<f32>::from(Rectangle::with_size(
|
||||
viewport.physical_size(),
|
||||
));
|
||||
|
||||
self.layers.merge();
|
||||
|
||||
for layer in self.layers.iter() {
|
||||
let clip_bounds = layer.bounds * scale_factor;
|
||||
|
||||
if physical_bounds
|
||||
.intersection(&clip_bounds)
|
||||
.and_then(Rectangle::snap)
|
||||
.is_none()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if !layer.quads.is_empty() {
|
||||
let prepare_span = debug::prepare(debug::Primitive::Quad);
|
||||
|
||||
self.quad.prepare(
|
||||
&self.engine.quad_pipeline,
|
||||
&self.engine.device,
|
||||
&mut self.staging_belt,
|
||||
encoder,
|
||||
&layer.quads,
|
||||
viewport.projection(),
|
||||
scale_factor,
|
||||
);
|
||||
|
||||
prepare_span.finish();
|
||||
}
|
||||
|
||||
if !layer.triangles.is_empty() {
|
||||
let prepare_span = debug::prepare(debug::Primitive::Triangle);
|
||||
|
||||
self.triangle.prepare(
|
||||
&self.engine.triangle_pipeline,
|
||||
&self.engine.device,
|
||||
&mut self.staging_belt,
|
||||
encoder,
|
||||
&layer.triangles,
|
||||
Transformation::scale(scale_factor),
|
||||
viewport.physical_size(),
|
||||
);
|
||||
|
||||
prepare_span.finish();
|
||||
}
|
||||
|
||||
if !layer.primitives.is_empty() {
|
||||
let prepare_span = debug::prepare(debug::Primitive::Shader);
|
||||
|
||||
let mut primitive_storage = self
|
||||
.engine
|
||||
.primitive_storage
|
||||
.write()
|
||||
.expect("Write primitive storage");
|
||||
|
||||
for instance in &layer.primitives {
|
||||
instance.primitive.prepare(
|
||||
&mut primitive_storage,
|
||||
&self.engine.device,
|
||||
&self.engine.queue,
|
||||
self.engine.format,
|
||||
&instance.bounds,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
|
||||
prepare_span.finish();
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "svg", feature = "image"))]
|
||||
if !layer.images.is_empty() {
|
||||
let prepare_span = debug::prepare(debug::Primitive::Image);
|
||||
|
||||
self.image.prepare(
|
||||
&self.engine.image_pipeline,
|
||||
&self.engine.device,
|
||||
&mut self.staging_belt,
|
||||
encoder,
|
||||
&mut self.image_cache.borrow_mut(),
|
||||
&layer.images,
|
||||
viewport.projection(),
|
||||
scale_factor,
|
||||
);
|
||||
|
||||
prepare_span.finish();
|
||||
}
|
||||
|
||||
if !layer.text.is_empty() {
|
||||
let prepare_span = debug::prepare(debug::Primitive::Text);
|
||||
|
||||
self.text.prepare(
|
||||
&self.engine.text_pipeline,
|
||||
&self.engine.device,
|
||||
&self.engine.queue,
|
||||
&self.text_viewport,
|
||||
encoder,
|
||||
&layer.text,
|
||||
layer.bounds,
|
||||
Transformation::scale(scale_factor),
|
||||
);
|
||||
|
||||
prepare_span.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render(
|
||||
&mut self,
|
||||
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,
|
||||
depth_slice: None,
|
||||
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();
|
||||
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_factor))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(scissor_rect) = physical_bounds.snap() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !layer.quads.is_empty() {
|
||||
let render_span = debug::render(debug::Primitive::Quad);
|
||||
self.quad.render(
|
||||
&self.engine.quad_pipeline,
|
||||
quad_layer,
|
||||
scissor_rect,
|
||||
&layer.quads,
|
||||
&mut render_pass,
|
||||
);
|
||||
render_span.finish();
|
||||
|
||||
quad_layer += 1;
|
||||
}
|
||||
|
||||
if !layer.triangles.is_empty() {
|
||||
let _ = ManuallyDrop::into_inner(render_pass);
|
||||
|
||||
let render_span = debug::render(debug::Primitive::Triangle);
|
||||
mesh_layer += self.triangle.render(
|
||||
&self.engine.triangle_pipeline,
|
||||
encoder,
|
||||
frame,
|
||||
mesh_layer,
|
||||
&layer.triangles,
|
||||
physical_bounds,
|
||||
scale,
|
||||
);
|
||||
render_span.finish();
|
||||
|
||||
render_pass = ManuallyDrop::new(encoder.begin_render_pass(
|
||||
&wgpu::RenderPassDescriptor {
|
||||
label: Some("iced_wgpu render pass"),
|
||||
color_attachments: &[Some(
|
||||
wgpu::RenderPassColorAttachment {
|
||||
view: frame,
|
||||
depth_slice: None,
|
||||
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 render_span = debug::render(debug::Primitive::Shader);
|
||||
|
||||
let primitive_storage = self
|
||||
.engine
|
||||
.primitive_storage
|
||||
.read()
|
||||
.expect("Read primitive storage");
|
||||
|
||||
let mut need_render = Vec::new();
|
||||
|
||||
for instance in &layer.primitives {
|
||||
let bounds = instance.bounds * scale;
|
||||
|
||||
if let Some(clip_bounds) = (instance.bounds * scale)
|
||||
.intersection(&physical_bounds)
|
||||
.and_then(Rectangle::snap)
|
||||
{
|
||||
render_pass.set_viewport(
|
||||
bounds.x,
|
||||
bounds.y,
|
||||
bounds.width,
|
||||
bounds.height,
|
||||
0.0,
|
||||
1.0,
|
||||
);
|
||||
|
||||
render_pass.set_scissor_rect(
|
||||
clip_bounds.x,
|
||||
clip_bounds.y,
|
||||
clip_bounds.width,
|
||||
clip_bounds.height,
|
||||
);
|
||||
|
||||
let drawn = instance
|
||||
.primitive
|
||||
.draw(&primitive_storage, &mut render_pass);
|
||||
|
||||
if !drawn {
|
||||
need_render.push((instance, clip_bounds));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render_pass.set_viewport(
|
||||
0.0,
|
||||
0.0,
|
||||
viewport.physical_width() as f32,
|
||||
viewport.physical_height() as f32,
|
||||
0.0,
|
||||
1.0,
|
||||
);
|
||||
|
||||
render_pass.set_scissor_rect(
|
||||
0,
|
||||
0,
|
||||
viewport.physical_width(),
|
||||
viewport.physical_height(),
|
||||
);
|
||||
|
||||
if !need_render.is_empty() {
|
||||
let _ = ManuallyDrop::into_inner(render_pass);
|
||||
|
||||
for (instance, clip_bounds) in need_render {
|
||||
instance.primitive.render(
|
||||
&primitive_storage,
|
||||
encoder,
|
||||
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,
|
||||
depth_slice: None,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
},
|
||||
)],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
render_span.finish();
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "svg", feature = "image"))]
|
||||
if !layer.images.is_empty() {
|
||||
let render_span = debug::render(debug::Primitive::Image);
|
||||
self.image.render(
|
||||
&self.engine.image_pipeline,
|
||||
image_layer,
|
||||
scissor_rect,
|
||||
&mut render_pass,
|
||||
);
|
||||
render_span.finish();
|
||||
|
||||
image_layer += 1;
|
||||
}
|
||||
|
||||
if !layer.text.is_empty() {
|
||||
let render_span = debug::render(debug::Primitive::Text);
|
||||
text_layer += self.text.render(
|
||||
&self.engine.text_pipeline,
|
||||
&self.text_viewport,
|
||||
text_layer,
|
||||
&layer.text,
|
||||
scissor_rect,
|
||||
&mut render_pass,
|
||||
);
|
||||
render_span.finish();
|
||||
}
|
||||
}
|
||||
|
||||
let _ = ManuallyDrop::into_inner(render_pass);
|
||||
|
||||
debug::layers_rendered(|| {
|
||||
self.layers
|
||||
.iter()
|
||||
.filter(|layer| {
|
||||
!layer.is_empty()
|
||||
&& physical_bounds
|
||||
.intersection(&(layer.bounds * scale_factor))
|
||||
.is_some_and(|viewport| viewport.snap().is_some())
|
||||
})
|
||||
.count()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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 reset(&mut self, new_bounds: Rectangle) {
|
||||
self.layers.reset(new_bounds);
|
||||
}
|
||||
|
||||
fn allocate_image(
|
||||
&mut self,
|
||||
_handle: &core::image::Handle,
|
||||
_callback: impl FnOnce(Result<core::image::Allocation, core::image::Error>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
) {
|
||||
#[cfg(feature = "image")]
|
||||
self.image_cache
|
||||
.get_mut()
|
||||
.allocate_image(_handle, _callback);
|
||||
}
|
||||
}
|
||||
|
||||
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}';
|
||||
const ICED_LOGO: char = '\u{e801}';
|
||||
const SCROLL_UP_ICON: char = '\u{e802}';
|
||||
const SCROLL_DOWN_ICON: char = '\u{e803}';
|
||||
const SCROLL_LEFT_ICON: char = '\u{e804}';
|
||||
const SCROLL_RIGHT_ICON: char = '\u{e805}';
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
impl graphics::text::Renderer for Renderer {
|
||||
fn fill_raw(&mut self, raw: graphics::text::Raw) {
|
||||
let (layer, transformation) = self.layers.current_mut();
|
||||
layer.draw_text_raw(raw, transformation);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
impl core::image::Renderer for Renderer {
|
||||
type Handle = core::image::Handle;
|
||||
|
||||
fn load_image(
|
||||
&self,
|
||||
handle: &Self::Handle,
|
||||
) -> Result<core::image::Allocation, core::image::Error> {
|
||||
self.image_cache.borrow_mut().load_image(
|
||||
&self.engine.device,
|
||||
&self.engine.queue,
|
||||
handle,
|
||||
)
|
||||
}
|
||||
|
||||
fn measure_image(&self, handle: &Self::Handle) -> Option<core::Size<u32>> {
|
||||
self.image_cache.borrow_mut().measure_image(handle)
|
||||
}
|
||||
|
||||
fn draw_image(
|
||||
&mut self,
|
||||
image: core::Image,
|
||||
bounds: Rectangle,
|
||||
clip_bounds: Rectangle,
|
||||
) {
|
||||
let (layer, transformation) = self.layers.current_mut();
|
||||
layer.draw_raster(image, bounds, clip_bounds, transformation);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
impl core::svg::Renderer for Renderer {
|
||||
fn measure_svg(&self, handle: &core::svg::Handle) -> core::Size<u32> {
|
||||
self.image_cache.borrow_mut().measure_svg(handle)
|
||||
}
|
||||
|
||||
fn draw_svg(
|
||||
&mut self,
|
||||
svg: core::Svg,
|
||||
bounds: Rectangle,
|
||||
clip_bounds: Rectangle,
|
||||
) {
|
||||
let (layer, transformation) = self.layers.current_mut();
|
||||
layer.draw_svg(svg, bounds, clip_bounds, transformation);
|
||||
}
|
||||
}
|
||||
|
||||
impl graphics::mesh::Renderer for Renderer {
|
||||
fn draw_mesh(&mut self, mesh: graphics::Mesh) {
|
||||
debug_assert!(
|
||||
!mesh.indices().is_empty(),
|
||||
"Mesh must not have empty indices"
|
||||
);
|
||||
|
||||
debug_assert!(
|
||||
mesh.indices().len().is_multiple_of(3),
|
||||
"Mesh indices length must be a multiple of 3"
|
||||
);
|
||||
|
||||
let (layer, transformation) = self.layers.current_mut();
|
||||
layer.draw_mesh(mesh, transformation);
|
||||
}
|
||||
|
||||
fn draw_mesh_cache(&mut self, cache: mesh::Cache) {
|
||||
let (layer, transformation) = self.layers.current_mut();
|
||||
layer.draw_mesh_cache(cache, transformation);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "geometry")]
|
||||
impl graphics::geometry::Renderer for Renderer {
|
||||
type Geometry = Geometry;
|
||||
type Frame = geometry::Frame;
|
||||
|
||||
fn new_frame(&self, bounds: Rectangle) -> Self::Frame {
|
||||
geometry::Frame::new(bounds)
|
||||
}
|
||||
|
||||
fn draw_geometry(&mut self, geometry: Self::Geometry) {
|
||||
let (layer, transformation) = self.layers.current_mut();
|
||||
|
||||
match geometry {
|
||||
Geometry::Live {
|
||||
meshes,
|
||||
images,
|
||||
text,
|
||||
} => {
|
||||
layer.draw_mesh_group(meshes, transformation);
|
||||
|
||||
for image in images {
|
||||
layer.draw_image(image, 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(images) = cache.images {
|
||||
for image in images.iter().cloned() {
|
||||
layer.draw_image(image, 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, primitive, transformation);
|
||||
}
|
||||
}
|
||||
|
||||
impl graphics::compositor::Default for crate::Renderer {
|
||||
type Compositor = window::Compositor;
|
||||
}
|
||||
|
||||
impl renderer::Headless for Renderer {
|
||||
async fn new(
|
||||
default_font: Font,
|
||||
default_text_size: Pixels,
|
||||
backend: Option<&str>,
|
||||
) -> Option<Self> {
|
||||
if backend.is_some_and(|backend| backend != "wgpu") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
|
||||
backends: wgpu::Backends::from_env()
|
||||
.unwrap_or(wgpu::Backends::PRIMARY),
|
||||
flags: wgpu::InstanceFlags::empty(),
|
||||
..wgpu::InstanceDescriptor::default()
|
||||
});
|
||||
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::HighPerformance,
|
||||
force_fallback_adapter: false,
|
||||
compatible_surface: None,
|
||||
})
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
let (device, queue) = adapter
|
||||
.request_device(&wgpu::DeviceDescriptor {
|
||||
label: Some("iced_wgpu [headless]"),
|
||||
required_features: wgpu::Features::empty(),
|
||||
required_limits: wgpu::Limits {
|
||||
max_bind_groups: 2,
|
||||
..wgpu::Limits::default()
|
||||
},
|
||||
memory_hints: wgpu::MemoryHints::MemoryUsage,
|
||||
trace: wgpu::Trace::Off,
|
||||
experimental_features: wgpu::ExperimentalFeatures::disabled(),
|
||||
})
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
let engine = Engine::new(
|
||||
&adapter,
|
||||
device,
|
||||
queue,
|
||||
if graphics::color::GAMMA_CORRECTION {
|
||||
wgpu::TextureFormat::Rgba8UnormSrgb
|
||||
} else {
|
||||
wgpu::TextureFormat::Rgba8Unorm
|
||||
},
|
||||
Some(graphics::Antialiasing::MSAAx4),
|
||||
Shell::headless(),
|
||||
);
|
||||
|
||||
Some(Self::new(engine, default_font, default_text_size))
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
"wgpu".to_owned()
|
||||
}
|
||||
|
||||
fn screenshot(
|
||||
&mut self,
|
||||
size: Size<u32>,
|
||||
scale_factor: f32,
|
||||
background_color: Color,
|
||||
) -> Vec<u8> {
|
||||
self.screenshot(
|
||||
&Viewport::with_physical_size(size, scale_factor),
|
||||
background_color,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,242 +0,0 @@
|
|||
//! Draw custom primitives.
|
||||
use crate::core::{self, Rectangle};
|
||||
use crate::graphics::Viewport;
|
||||
use crate::graphics::futures::{MaybeSend, MaybeSync};
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::any::{Any, TypeId};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// A batch of primitives.
|
||||
pub type Batch = Vec<Instance>;
|
||||
|
||||
/// A set of methods which allows a [`Primitive`] to be rendered.
|
||||
pub trait Primitive: Debug + MaybeSend + MaybeSync + 'static {
|
||||
/// The shared renderer of this [`Primitive`].
|
||||
///
|
||||
/// Normally, this will contain a bunch of [`wgpu`] state; like
|
||||
/// a rendering pipeline, buffers, and textures.
|
||||
///
|
||||
/// All instances of this [`Primitive`] type will share the same
|
||||
/// [`Renderer`].
|
||||
type Pipeline: Pipeline + MaybeSend + MaybeSync;
|
||||
|
||||
/// Processes the [`Primitive`], allowing for GPU buffer allocation.
|
||||
fn prepare(
|
||||
&self,
|
||||
pipeline: &mut Self::Pipeline,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
bounds: &Rectangle,
|
||||
viewport: &Viewport,
|
||||
);
|
||||
|
||||
/// Draws the [`Primitive`] in the given [`wgpu::RenderPass`].
|
||||
///
|
||||
/// When possible, this should be implemented over [`render`](Self::render)
|
||||
/// since reusing the existing render pass should be considerably more
|
||||
/// efficient than issuing a new one.
|
||||
///
|
||||
/// The viewport and scissor rect of the render pass provided is set
|
||||
/// to the bounds and clip bounds of the [`Primitive`], respectively.
|
||||
///
|
||||
/// If you have complex composition needs, then you can leverage
|
||||
/// [`render`](Self::render) by returning `false` here.
|
||||
///
|
||||
/// By default, it does nothing and returns `false`.
|
||||
fn draw(
|
||||
&self,
|
||||
_pipeline: &Self::Pipeline,
|
||||
_render_pass: &mut wgpu::RenderPass<'_>,
|
||||
) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Renders the [`Primitive`], using the given [`wgpu::CommandEncoder`].
|
||||
///
|
||||
/// This will only be called if [`draw`](Self::draw) returns `false`.
|
||||
///
|
||||
/// By default, it does nothing.
|
||||
fn render(
|
||||
&self,
|
||||
_pipeline: &Self::Pipeline,
|
||||
_encoder: &mut wgpu::CommandEncoder,
|
||||
_target: &wgpu::TextureView,
|
||||
_clip_bounds: &Rectangle<u32>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
/// The pipeline of a graphics [`Primitive`].
|
||||
pub trait Pipeline: Any + MaybeSend + MaybeSync {
|
||||
/// Creates the [`Pipeline`] of a [`Primitive`].
|
||||
///
|
||||
/// This will only be called once, when the first [`Primitive`] with this kind
|
||||
/// of [`Pipeline`] is encountered.
|
||||
fn new(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
format: wgpu::TextureFormat,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Trims any cached data in the [`Pipeline`].
|
||||
///
|
||||
/// This will normally be called at the end of a frame.
|
||||
fn trim(&mut self) {}
|
||||
}
|
||||
|
||||
pub(crate) trait Stored:
|
||||
Debug + MaybeSend + MaybeSync + 'static
|
||||
{
|
||||
fn prepare(
|
||||
&self,
|
||||
storage: &mut Storage,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
format: wgpu::TextureFormat,
|
||||
bounds: &Rectangle,
|
||||
viewport: &Viewport,
|
||||
);
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
storage: &Storage,
|
||||
render_pass: &mut wgpu::RenderPass<'_>,
|
||||
) -> bool;
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
storage: &Storage,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
target: &wgpu::TextureView,
|
||||
clip_bounds: &Rectangle<u32>,
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BlackBox<P: Primitive> {
|
||||
primitive: P,
|
||||
}
|
||||
|
||||
impl<P: Primitive> Stored for BlackBox<P> {
|
||||
fn prepare(
|
||||
&self,
|
||||
storage: &mut Storage,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
format: wgpu::TextureFormat,
|
||||
bounds: &Rectangle,
|
||||
viewport: &Viewport,
|
||||
) {
|
||||
if !storage.has::<P>() {
|
||||
storage.store::<P, _>(P::Pipeline::new(device, queue, format));
|
||||
}
|
||||
|
||||
let renderer = storage
|
||||
.get_mut::<P>()
|
||||
.expect("renderer should be initialized")
|
||||
.downcast_mut::<P::Pipeline>()
|
||||
.expect("renderer should have the proper type");
|
||||
|
||||
self.primitive
|
||||
.prepare(renderer, device, queue, bounds, viewport);
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
storage: &Storage,
|
||||
render_pass: &mut wgpu::RenderPass<'_>,
|
||||
) -> bool {
|
||||
let renderer = storage
|
||||
.get::<P>()
|
||||
.expect("renderer should be initialized")
|
||||
.downcast_ref::<P::Pipeline>()
|
||||
.expect("renderer should have the proper type");
|
||||
|
||||
self.primitive.draw(renderer, render_pass)
|
||||
}
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
storage: &Storage,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
target: &wgpu::TextureView,
|
||||
clip_bounds: &Rectangle<u32>,
|
||||
) {
|
||||
let renderer = storage
|
||||
.get::<P>()
|
||||
.expect("renderer should be initialized")
|
||||
.downcast_ref::<P::Pipeline>()
|
||||
.expect("renderer should have the proper type");
|
||||
|
||||
self.primitive
|
||||
.render(renderer, encoder, target, clip_bounds);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// An instance of a specific [`Primitive`].
|
||||
pub struct Instance {
|
||||
/// The bounds of the [`Instance`].
|
||||
pub(crate) bounds: Rectangle,
|
||||
|
||||
/// The [`Primitive`] to render.
|
||||
pub(crate) primitive: Box<dyn Stored>,
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
/// Creates a new [`Instance`] with the given [`Primitive`].
|
||||
pub fn new(bounds: Rectangle, primitive: impl Primitive) -> Self {
|
||||
Instance {
|
||||
bounds,
|
||||
primitive: Box::new(BlackBox { primitive }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A renderer than can draw custom primitives.
|
||||
pub trait Renderer: core::Renderer {
|
||||
/// Draws a custom primitive.
|
||||
fn draw_primitive(&mut self, bounds: Rectangle, primitive: impl Primitive);
|
||||
}
|
||||
|
||||
/// Stores custom, user-provided types.
|
||||
#[derive(Default)]
|
||||
pub struct Storage {
|
||||
pipelines: FxHashMap<TypeId, Box<dyn Pipeline>>,
|
||||
}
|
||||
|
||||
impl Storage {
|
||||
/// Returns `true` if `Storage` contains a type `T`.
|
||||
pub fn has<T: 'static>(&self) -> bool {
|
||||
self.pipelines.contains_key(&TypeId::of::<T>())
|
||||
}
|
||||
|
||||
/// Inserts the data `T` in to [`Storage`].
|
||||
pub fn store<T: 'static, P: Pipeline>(&mut self, pipeline: P) {
|
||||
let _ = self.pipelines.insert(TypeId::of::<T>(), Box::new(pipeline));
|
||||
}
|
||||
|
||||
/// Returns a reference to the data with type `T` if it exists in [`Storage`].
|
||||
pub fn get<T: 'static>(&self) -> Option<&dyn Any> {
|
||||
self.pipelines
|
||||
.get(&TypeId::of::<T>())
|
||||
.map(|pipeline| pipeline.as_ref() as &dyn Any)
|
||||
}
|
||||
|
||||
/// 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 dyn Any> {
|
||||
self.pipelines
|
||||
.get_mut(&TypeId::of::<T>())
|
||||
.map(|pipeline| pipeline.as_mut() as &mut dyn Any)
|
||||
}
|
||||
|
||||
/// Trims the cache of all the pipelines in the [`Storage`].
|
||||
pub fn trim(&mut self) {
|
||||
for pipeline in self.pipelines.values_mut() {
|
||||
pipeline.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,362 +0,0 @@
|
|||
mod gradient;
|
||||
mod solid;
|
||||
|
||||
use gradient::Gradient;
|
||||
use solid::Solid;
|
||||
|
||||
use crate::core::{Background, Rectangle, Transformation};
|
||||
use crate::graphics;
|
||||
use crate::graphics::color;
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
use std::mem;
|
||||
|
||||
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,
|
||||
|
||||
/// Whether the [`Quad`] should be snapped to the pixel grid.
|
||||
pub snap: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Pipeline {
|
||||
solid: solid::Pipeline,
|
||||
gradient: gradient::Pipeline,
|
||||
constant_layout: wgpu::BindGroupLayout,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct State {
|
||||
layers: Vec<Layer>,
|
||||
prepare_layer: usize,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn prepare(
|
||||
&mut self,
|
||||
pipeline: &Pipeline,
|
||||
device: &wgpu::Device,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
quads: &Batch,
|
||||
transformation: Transformation,
|
||||
scale: f32,
|
||||
) {
|
||||
if self.layers.len() <= self.prepare_layer {
|
||||
self.layers
|
||||
.push(Layer::new(device, &pipeline.constant_layout));
|
||||
}
|
||||
|
||||
let layer = &mut self.layers[self.prepare_layer];
|
||||
layer.prepare(device, encoder, belt, quads, transformation, scale);
|
||||
|
||||
self.prepare_layer += 1;
|
||||
}
|
||||
|
||||
pub fn render<'a>(
|
||||
&'a self,
|
||||
pipeline: &'a Pipeline,
|
||||
layer: usize,
|
||||
bounds: Rectangle<u32>,
|
||||
quads: &Batch,
|
||||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
) {
|
||||
if let Some(layer) = self.layers.get(layer) {
|
||||
render_pass.set_scissor_rect(
|
||||
bounds.x,
|
||||
bounds.y,
|
||||
bounds.width,
|
||||
bounds.height,
|
||||
);
|
||||
|
||||
let mut solid_offset = 0;
|
||||
let mut gradient_offset = 0;
|
||||
|
||||
for (kind, count) in &quads.order {
|
||||
match kind {
|
||||
Kind::Solid => {
|
||||
pipeline.solid.render(
|
||||
render_pass,
|
||||
&layer.constants,
|
||||
&layer.solid,
|
||||
solid_offset..(solid_offset + count),
|
||||
);
|
||||
|
||||
solid_offset += count;
|
||||
}
|
||||
Kind::Gradient => {
|
||||
pipeline.gradient.render(
|
||||
render_pass,
|
||||
&layer.constants,
|
||||
&layer.gradient,
|
||||
gradient_offset..(gradient_offset + count),
|
||||
);
|
||||
|
||||
gradient_offset += count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trim(&mut self) {
|
||||
self.prepare_layer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Pipeline {
|
||||
let constant_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("iced_wgpu::quad uniforms 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 wgpu::BufferAddress,
|
||||
),
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
});
|
||||
|
||||
Self {
|
||||
solid: solid::Pipeline::new(device, format, &constant_layout),
|
||||
gradient: gradient::Pipeline::new(device, format, &constant_layout),
|
||||
constant_layout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Layer {
|
||||
constants: wgpu::BindGroup,
|
||||
constants_buffer: wgpu::Buffer,
|
||||
solid: solid::Layer,
|
||||
gradient: gradient::Layer,
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
constant_layout: &wgpu::BindGroupLayout,
|
||||
) -> Self {
|
||||
let constants_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("iced_wgpu::quad uniforms buffer"),
|
||||
size: mem::size_of::<Uniforms>() as wgpu::BufferAddress,
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::quad uniforms bind group"),
|
||||
layout: constant_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: constants_buffer.as_entire_binding(),
|
||||
}],
|
||||
});
|
||||
|
||||
Self {
|
||||
constants,
|
||||
constants_buffer,
|
||||
solid: solid::Layer::new(device),
|
||||
gradient: gradient::Layer::new(device),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
quads: &Batch,
|
||||
transformation: Transformation,
|
||||
scale: f32,
|
||||
) {
|
||||
self.update(device, encoder, belt, transformation, scale);
|
||||
|
||||
if !quads.solids.is_empty() {
|
||||
self.solid.prepare(device, encoder, belt, &quads.solids);
|
||||
}
|
||||
|
||||
if !quads.gradients.is_empty() {
|
||||
self.gradient
|
||||
.prepare(device, encoder, belt, &quads.gradients);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
transformation: Transformation,
|
||||
scale: f32,
|
||||
) {
|
||||
let uniforms = Uniforms::new(transformation, scale);
|
||||
let bytes = bytemuck::bytes_of(&uniforms);
|
||||
|
||||
belt.write_buffer(
|
||||
encoder,
|
||||
&self.constants_buffer,
|
||||
0,
|
||||
(bytes.len() as u64).try_into().expect("Sized uniforms"),
|
||||
device,
|
||||
)
|
||||
.copy_from_slice(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
/// A group of [`Quad`]s rendered together.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Batch {
|
||||
/// The solid quads of the [`Layer`].
|
||||
solids: Vec<Solid>,
|
||||
|
||||
/// The gradient quads of the [`Layer`].
|
||||
gradients: Vec<Gradient>,
|
||||
|
||||
/// The quad order of the [`Layer`].
|
||||
order: Order,
|
||||
}
|
||||
|
||||
/// The quad order of a [`Layer`]; stored as a tuple of the quad type & its count.
|
||||
type Order = Vec<(Kind, usize)>;
|
||||
|
||||
impl Batch {
|
||||
/// Returns true if there are no quads of any type in [`Quads`].
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.solids.is_empty() && self.gradients.is_empty()
|
||||
}
|
||||
|
||||
/// Adds a [`Quad`] with the provided `Background` type to the quad [`Layer`].
|
||||
pub fn add(&mut self, quad: Quad, background: &Background) {
|
||||
let kind = match background {
|
||||
Background::Color(color) => {
|
||||
self.solids.push(Solid {
|
||||
color: color::pack(*color),
|
||||
quad,
|
||||
});
|
||||
|
||||
Kind::Solid
|
||||
}
|
||||
Background::Gradient(gradient) => {
|
||||
self.gradients.push(Gradient {
|
||||
gradient: graphics::gradient::pack(
|
||||
gradient,
|
||||
Rectangle::new(quad.position.into(), quad.size.into()),
|
||||
),
|
||||
quad,
|
||||
});
|
||||
|
||||
Kind::Gradient
|
||||
}
|
||||
};
|
||||
|
||||
match self.order.last_mut() {
|
||||
Some((last_kind, count)) if kind == *last_kind => {
|
||||
*count += 1;
|
||||
}
|
||||
_ => {
|
||||
self.order.push((kind, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.solids.clear();
|
||||
self.gradients.clear();
|
||||
self.order.clear();
|
||||
}
|
||||
|
||||
pub fn append(&mut self, batch: &mut Batch) {
|
||||
self.solids.append(&mut batch.solids);
|
||||
self.gradients.append(&mut batch.gradients);
|
||||
self.order.append(&mut batch.order);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
/// The kind of a quad.
|
||||
enum Kind {
|
||||
/// A solid quad
|
||||
Solid,
|
||||
/// A gradient quad
|
||||
Gradient,
|
||||
}
|
||||
|
||||
fn color_target_state(
|
||||
format: wgpu::TextureFormat,
|
||||
) -> [Option<wgpu::ColorTargetState>; 1] {
|
||||
[Some(wgpu::ColorTargetState {
|
||||
format,
|
||||
blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})]
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
|
||||
struct Uniforms {
|
||||
transform: [f32; 16],
|
||||
scale: f32,
|
||||
// Uniforms must be aligned to their largest member,
|
||||
// this uses a mat4x4<f32> which aligns to 16, so align to that
|
||||
_padding: [f32; 3],
|
||||
}
|
||||
|
||||
impl Uniforms {
|
||||
fn new(transformation: Transformation, scale: f32) -> Uniforms {
|
||||
Self {
|
||||
transform: *transformation.as_ref(),
|
||||
scale,
|
||||
_padding: [0.0; 3],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Uniforms {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
transform: *Transformation::IDENTITY.as_ref(),
|
||||
scale: 1.0,
|
||||
_padding: [0.0; 3],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,187 +0,0 @@
|
|||
use crate::Buffer;
|
||||
use crate::graphics::gradient;
|
||||
use crate::quad::{self, Quad};
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use std::ops::Range;
|
||||
|
||||
/// A quad filled with interpolated colors.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct Gradient {
|
||||
/// The background gradient data of the quad.
|
||||
pub gradient: gradient::Packed,
|
||||
|
||||
/// The [`Quad`] data of the [`Gradient`].
|
||||
pub quad: Quad,
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
unsafe impl Pod for Gradient {}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
unsafe impl Zeroable for Gradient {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Layer {
|
||||
instances: Buffer<Gradient>,
|
||||
instance_count: usize,
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
pub fn new(device: &wgpu::Device) -> Self {
|
||||
let instances = Buffer::new(
|
||||
device,
|
||||
"iced_wgpu.quad.gradient.buffer",
|
||||
quad::INITIAL_INSTANCES,
|
||||
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
);
|
||||
|
||||
Self {
|
||||
instances,
|
||||
instance_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
instances: &[Gradient],
|
||||
) {
|
||||
let _ = self.instances.resize(device, instances.len());
|
||||
let _ = self.instances.write(device, encoder, belt, 0, instances);
|
||||
|
||||
self.instance_count = instances.len();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Pipeline {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
#[allow(unused_variables)]
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
constants_layout: &wgpu::BindGroupLayout,
|
||||
) -> Self {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let layout = device.create_pipeline_layout(
|
||||
&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("iced_wgpu.quad.gradient.pipeline"),
|
||||
push_constant_ranges: &[],
|
||||
bind_group_layouts: &[constants_layout],
|
||||
},
|
||||
);
|
||||
|
||||
let shader =
|
||||
device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("iced_wgpu.quad.gradient.shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(
|
||||
std::borrow::Cow::Borrowed(concat!(
|
||||
include_str!("../shader/quad.wgsl"),
|
||||
"\n",
|
||||
include_str!("../shader/vertex.wgsl"),
|
||||
"\n",
|
||||
include_str!("../shader/quad/gradient.wgsl"),
|
||||
"\n",
|
||||
include_str!("../shader/color.wgsl"),
|
||||
"\n",
|
||||
include_str!("../shader/color/linear_rgb.wgsl")
|
||||
)),
|
||||
),
|
||||
});
|
||||
|
||||
let pipeline = device.create_render_pipeline(
|
||||
&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("iced_wgpu.quad.gradient.pipeline"),
|
||||
layout: Some(&layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("gradient_vs_main"),
|
||||
buffers: &[wgpu::VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<Gradient>()
|
||||
as u64,
|
||||
step_mode: wgpu::VertexStepMode::Instance,
|
||||
attributes: &wgpu::vertex_attr_array!(
|
||||
// Colors 1-2
|
||||
0 => Uint32x4,
|
||||
// Colors 3-4
|
||||
1 => Uint32x4,
|
||||
// Colors 5-6
|
||||
2 => Uint32x4,
|
||||
// Colors 7-8
|
||||
3 => Uint32x4,
|
||||
// Offsets 1-8
|
||||
4 => Uint32x4,
|
||||
// Direction
|
||||
5 => Float32x4,
|
||||
// Position & Scale
|
||||
6 => Float32x4,
|
||||
// Border color
|
||||
7 => Float32x4,
|
||||
// Border radius
|
||||
8 => Float32x4,
|
||||
// Border width
|
||||
9 => Float32,
|
||||
// Snap
|
||||
10 => Uint32,
|
||||
),
|
||||
}],
|
||||
compilation_options:
|
||||
wgpu::PipelineCompilationOptions::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("gradient_fs_main"),
|
||||
targets: &quad::color_target_state(format),
|
||||
compilation_options:
|
||||
wgpu::PipelineCompilationOptions::default(),
|
||||
}),
|
||||
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,
|
||||
cache: None,
|
||||
},
|
||||
);
|
||||
|
||||
Self { pipeline }
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
Self {}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
pub fn render<'a>(
|
||||
&'a self,
|
||||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
constants: &'a wgpu::BindGroup,
|
||||
layer: &'a Layer,
|
||||
range: Range<usize>,
|
||||
) {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
render_pass.set_pipeline(&self.pipeline);
|
||||
render_pass.set_bind_group(0, constants, &[]);
|
||||
render_pass.set_vertex_buffer(0, layer.instances.slice(..));
|
||||
|
||||
render_pass.draw(0..6, range.start as u32..range.end as u32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,162 +0,0 @@
|
|||
use crate::Buffer;
|
||||
use crate::graphics::color;
|
||||
use crate::quad::{self, Quad};
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use std::ops::Range;
|
||||
|
||||
/// A quad filled with a solid color.
|
||||
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct Solid {
|
||||
/// The background color data of the quad.
|
||||
pub color: color::Packed,
|
||||
|
||||
/// The [`Quad`] data of the [`Solid`].
|
||||
pub quad: Quad,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Layer {
|
||||
instances: Buffer<Solid>,
|
||||
instance_count: usize,
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
pub fn new(device: &wgpu::Device) -> Self {
|
||||
let instances = Buffer::new(
|
||||
device,
|
||||
"iced_wgpu.quad.solid.buffer",
|
||||
quad::INITIAL_INSTANCES,
|
||||
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
);
|
||||
|
||||
Self {
|
||||
instances,
|
||||
instance_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
instances: &[Solid],
|
||||
) {
|
||||
let _ = self.instances.resize(device, instances.len());
|
||||
let _ = self.instances.write(device, encoder, belt, 0, instances);
|
||||
|
||||
self.instance_count = instances.len();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Pipeline {
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
constants_layout: &wgpu::BindGroupLayout,
|
||||
) -> Self {
|
||||
let layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("iced_wgpu.quad.solid.pipeline"),
|
||||
push_constant_ranges: &[],
|
||||
bind_group_layouts: &[constants_layout],
|
||||
});
|
||||
|
||||
let shader =
|
||||
device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("iced_wgpu.quad.solid.shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
|
||||
concat!(
|
||||
include_str!("../shader/color.wgsl"),
|
||||
"\n",
|
||||
include_str!("../shader/quad.wgsl"),
|
||||
"\n",
|
||||
include_str!("../shader/vertex.wgsl"),
|
||||
"\n",
|
||||
include_str!("../shader/quad/solid.wgsl"),
|
||||
),
|
||||
)),
|
||||
});
|
||||
|
||||
let pipeline =
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("iced_wgpu.quad.solid.pipeline"),
|
||||
layout: Some(&layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("solid_vs_main"),
|
||||
buffers: &[wgpu::VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<Solid>() as u64,
|
||||
step_mode: wgpu::VertexStepMode::Instance,
|
||||
attributes: &wgpu::vertex_attr_array!(
|
||||
// Color
|
||||
0 => Float32x4,
|
||||
// Position
|
||||
1 => Float32x2,
|
||||
// Size
|
||||
2 => Float32x2,
|
||||
// Border color
|
||||
3 => Float32x4,
|
||||
// Border radius
|
||||
4 => Float32x4,
|
||||
// Border width
|
||||
5 => Float32,
|
||||
// Shadow color
|
||||
6 => Float32x4,
|
||||
// Shadow offset
|
||||
7 => Float32x2,
|
||||
// Shadow blur radius
|
||||
8 => Float32,
|
||||
// Snap
|
||||
9 => Uint32,
|
||||
),
|
||||
}],
|
||||
compilation_options:
|
||||
wgpu::PipelineCompilationOptions::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("solid_fs_main"),
|
||||
targets: &quad::color_target_state(format),
|
||||
compilation_options:
|
||||
wgpu::PipelineCompilationOptions::default(),
|
||||
}),
|
||||
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,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
Self { pipeline }
|
||||
}
|
||||
|
||||
pub fn render<'a>(
|
||||
&'a self,
|
||||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
constants: &'a wgpu::BindGroup,
|
||||
layer: &'a Layer,
|
||||
range: Range<usize>,
|
||||
) {
|
||||
render_pass.set_pipeline(&self.pipeline);
|
||||
render_pass.set_bind_group(0, constants, &[]);
|
||||
render_pass.set_vertex_buffer(0, layer.instances.slice(..));
|
||||
|
||||
render_pass.draw(0..6, range.start as u32..range.end as u32);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
//! Configure a renderer.
|
||||
use crate::core::{Font, Pixels};
|
||||
use crate::graphics::{self, Antialiasing};
|
||||
|
||||
/// The settings of a [`Renderer`].
|
||||
///
|
||||
/// [`Renderer`]: crate::Renderer
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Settings {
|
||||
/// The present mode of the [`Renderer`].
|
||||
///
|
||||
/// [`Renderer`]: crate::Renderer
|
||||
pub present_mode: wgpu::PresentMode,
|
||||
|
||||
/// The graphics backends to use.
|
||||
pub backends: wgpu::Backends,
|
||||
|
||||
/// The default [`Font`] to use.
|
||||
pub default_font: Font,
|
||||
|
||||
/// The default size of text.
|
||||
///
|
||||
/// By default, it will be set to `16.0`.
|
||||
pub default_text_size: Pixels,
|
||||
|
||||
/// The antialiasing strategy that will be used for triangle primitives.
|
||||
///
|
||||
/// By default, it is `None`.
|
||||
pub antialiasing: Option<Antialiasing>,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Settings {
|
||||
Settings {
|
||||
present_mode: wgpu::PresentMode::AutoVsync,
|
||||
backends: wgpu::Backends::all(),
|
||||
default_font: Font::default(),
|
||||
default_text_size: Pixels(16.0),
|
||||
antialiasing: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<graphics::Settings> for Settings {
|
||||
fn from(settings: graphics::Settings) -> Self {
|
||||
Self {
|
||||
present_mode: if settings.vsync {
|
||||
wgpu::PresentMode::AutoVsync
|
||||
} else {
|
||||
wgpu::PresentMode::AutoNoVsync
|
||||
},
|
||||
default_font: settings.default_font,
|
||||
default_text_size: settings.default_text_size,
|
||||
antialiasing: settings.antialiasing,
|
||||
..Settings::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtains a [`wgpu::PresentMode`] from the current environment
|
||||
/// configuration, if set.
|
||||
///
|
||||
/// The value returned by this function can be changed by setting
|
||||
/// the `ICED_PRESENT_MODE` env variable. The possible values are:
|
||||
///
|
||||
/// - `vsync` → [`wgpu::PresentMode::AutoVsync`]
|
||||
/// - `no_vsync` → [`wgpu::PresentMode::AutoNoVsync`]
|
||||
/// - `immediate` → [`wgpu::PresentMode::Immediate`]
|
||||
/// - `fifo` → [`wgpu::PresentMode::Fifo`]
|
||||
/// - `fifo_relaxed` → [`wgpu::PresentMode::FifoRelaxed`]
|
||||
/// - `mailbox` → [`wgpu::PresentMode::Mailbox`]
|
||||
pub fn present_mode_from_env() -> Option<wgpu::PresentMode> {
|
||||
let present_mode = std::env::var("ICED_PRESENT_MODE").ok()?;
|
||||
|
||||
match present_mode.to_lowercase().as_str() {
|
||||
"vsync" => Some(wgpu::PresentMode::AutoVsync),
|
||||
"no_vsync" => Some(wgpu::PresentMode::AutoNoVsync),
|
||||
"immediate" => Some(wgpu::PresentMode::Immediate),
|
||||
"fifo" => Some(wgpu::PresentMode::Fifo),
|
||||
"fifo_relaxed" => Some(wgpu::PresentMode::FifoRelaxed),
|
||||
"mailbox" => Some(wgpu::PresentMode::Mailbox),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
var<private> uvs: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
|
||||
vec2<f32>(0.0, 0.0),
|
||||
vec2<f32>(1.0, 0.0),
|
||||
vec2<f32>(1.0, 1.0),
|
||||
vec2<f32>(0.0, 0.0),
|
||||
vec2<f32>(0.0, 1.0),
|
||||
vec2<f32>(1.0, 1.0)
|
||||
);
|
||||
|
||||
@group(0) @binding(0) var u_sampler: sampler;
|
||||
@group(0) @binding(1) var<uniform> u_ratio: vec4<f32>;
|
||||
@group(1) @binding(0) var u_texture: texture_2d<f32>;
|
||||
|
||||
struct VertexInput {
|
||||
@builtin(vertex_index) vertex_index: u32,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
let uv = uvs[input.vertex_index];
|
||||
|
||||
var out: VertexOutput;
|
||||
out.uv = uv * u_ratio.xy;
|
||||
out.position = vec4<f32>(uv * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 0.0, 1.0);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
|
||||
return textureSample(u_texture, u_sampler, input.uv);
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
fn premultiply(color: vec4<f32>) -> vec4<f32> {
|
||||
return vec4(color.xyz * color.a, color.a);
|
||||
}
|
||||
|
||||
fn unpack_color(data: vec2<u32>) -> vec4<f32> {
|
||||
return premultiply(unpack_u32(data));
|
||||
}
|
||||
|
||||
fn unpack_u32(data: vec2<u32>) -> vec4<f32> {
|
||||
let rg: vec2<f32> = unpack2x16float(data.x);
|
||||
let ba: vec2<f32> = unpack2x16float(data.y);
|
||||
|
||||
return vec4<f32>(rg.y, rg.x, ba.y, ba.x);
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
fn interpolate_color(from_: vec4<f32>, to_: vec4<f32>, factor: f32) -> vec4<f32> {
|
||||
return mix(from_, to_, factor);
|
||||
}
|
||||
|
|
@ -1,129 +0,0 @@
|
|||
struct Globals {
|
||||
transform: mat4x4<f32>,
|
||||
scale_factor: f32,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var<uniform> globals: Globals;
|
||||
@group(0) @binding(1) var u_sampler: sampler;
|
||||
@group(1) @binding(0) var u_texture: texture_2d_array<f32>;
|
||||
|
||||
struct VertexInput {
|
||||
@builtin(vertex_index) vertex_index: u32,
|
||||
@location(0) center: vec2<f32>,
|
||||
@location(1) clip_bounds: vec4<f32>,
|
||||
@location(2) border_radius: vec4<f32>,
|
||||
@location(3) tile: vec4<f32>,
|
||||
@location(4) rotation: f32,
|
||||
@location(5) opacity: f32,
|
||||
@location(6) atlas_pos: vec2<f32>,
|
||||
@location(7) atlas_scale: vec2<f32>,
|
||||
@location(8) layer: i32,
|
||||
@location(9) snap: u32,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) @interpolate(flat) clip_bounds: vec4<f32>,
|
||||
@location(1) @interpolate(flat) border_radius: vec4<f32>,
|
||||
@location(2) @interpolate(flat) atlas: vec4<f32>,
|
||||
@location(3) @interpolate(flat) layer: i32,
|
||||
@location(4) @interpolate(flat) opacity: f32,
|
||||
@location(5) uv: vec2<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
|
||||
// Generate a vertex position in the range [0, 1] from the vertex index
|
||||
let corner = vertex_position(input.vertex_index);
|
||||
|
||||
let tile = input.tile;
|
||||
let center = input.center;
|
||||
|
||||
// List the unrotated tile corners
|
||||
let corners = array<vec2<f32>, 4>(
|
||||
tile.xy, // Top left
|
||||
tile.xy + vec2<f32>(tile.z, 0.0), // Top right
|
||||
tile.xy + vec2<f32>(0.0, tile.w), // Bottom left
|
||||
tile.xy + tile.zw // Bottom right
|
||||
);
|
||||
|
||||
// Rotate tile corners around center
|
||||
let cos_r = cos(-input.rotation); // Clockwise
|
||||
let sin_r = sin(-input.rotation);
|
||||
var rotated = array<vec2<f32>, 4>();
|
||||
|
||||
for (var i = 0u; i < 4u; i++) {
|
||||
let c = corners[i] - input.center;
|
||||
rotated[i] = vec2<f32>(c.x * cos_r - c.y * sin_r, c.x * sin_r + c.y * cos_r) + input.center;
|
||||
}
|
||||
|
||||
// Find bounding box of rotated tile
|
||||
var min_xy = rotated[0];
|
||||
var max_xy = rotated[0];
|
||||
for (var i = 1u; i < 4u; i++) {
|
||||
min_xy = min(min_xy, rotated[i]);
|
||||
max_xy = max(max_xy, rotated[i]);
|
||||
}
|
||||
let rotated_bounds = vec4<f32>(min_xy, max_xy - min_xy);
|
||||
|
||||
// Intersect with clip bounds
|
||||
let clip_min = max(rotated_bounds.xy, input.clip_bounds.xy);
|
||||
let clip_max = min(rotated_bounds.xy + rotated_bounds.zw, input.clip_bounds.xy + input.clip_bounds.zw);
|
||||
let clipped_tile = vec4<f32>(clip_min, max(vec2<f32>(0.0), clip_max - clip_min));
|
||||
|
||||
// Calculate the vertex position
|
||||
let v_pos = clipped_tile.xy + corner * clipped_tile.zw;
|
||||
out.position = vec4(vec2(globals.scale_factor), 1.0, 1.0) * vec4<f32>(v_pos, 0.0, 1.0);
|
||||
out.clip_bounds = globals.scale_factor * input.clip_bounds;
|
||||
|
||||
// Calculate rotated UV
|
||||
let uv = input.atlas_pos + (v_pos - tile.xy) / tile.zw * input.atlas_scale;
|
||||
let uv_center = input.atlas_pos + input.atlas_scale / 2.0;
|
||||
|
||||
let d = uv - uv_center;
|
||||
out.uv = vec2<f32>(d.x * cos_r - d.y * sin_r, d.x * sin_r + d.y * cos_r) + uv_center;
|
||||
|
||||
// Snap position to the pixel grid
|
||||
if bool(input.snap) {
|
||||
out.position = round(out.position);
|
||||
out.clip_bounds = vec4(
|
||||
round(out.clip_bounds.xy),
|
||||
round(out.clip_bounds.xy + out.clip_bounds.zw) - out.clip_bounds.xy,
|
||||
);
|
||||
}
|
||||
|
||||
out.position = globals.transform * out.position;
|
||||
out.border_radius = globals.scale_factor * min(input.border_radius, vec4(min(input.clip_bounds.z, input.clip_bounds.w) / 2.0));
|
||||
out.atlas = vec4(input.atlas_pos, input.atlas_pos + input.atlas_scale);
|
||||
out.layer = input.layer;
|
||||
out.opacity = input.opacity;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let fragment = input.position.xy;
|
||||
let position = input.clip_bounds.xy;
|
||||
let scale = input.clip_bounds.zw;
|
||||
|
||||
let d = rounded_box_sdf(
|
||||
2.0 * (fragment - position - scale / 2.0),
|
||||
scale,
|
||||
input.border_radius * 2.0,
|
||||
) / 2.0;
|
||||
|
||||
let antialias: f32 = clamp(1.0 - d, 0.0, 1.0);
|
||||
let inside = all(input.uv >= input.atlas.xy) && all(input.uv <= input.atlas.zw);
|
||||
|
||||
return textureSample(u_texture, u_sampler, input.uv, input.layer) * vec4<f32>(1.0, 1.0, 1.0, antialias * input.opacity * f32(inside));
|
||||
}
|
||||
|
||||
fn rounded_box_sdf(p: vec2<f32>, size: vec2<f32>, corners: vec4<f32>) -> f32 {
|
||||
var box_half = select(corners.yz, corners.xw, p.x > 0.0);
|
||||
var corner = select(box_half.y, box_half.x, p.y > 0.0);
|
||||
var q = abs(p) - size + corner;
|
||||
return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - corner;
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
struct Globals {
|
||||
transform: mat4x4<f32>,
|
||||
scale: f32,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var<uniform> globals: Globals;
|
||||
|
||||
fn rounded_box_sdf(p: vec2<f32>, size: vec2<f32>, corners: vec4<f32>) -> f32 {
|
||||
var box_half = select(corners.yz, corners.xw, p.x > 0.0);
|
||||
var corner = select(box_half.y, box_half.x, p.y > 0.0);
|
||||
var q = abs(p) - size + corner;
|
||||
return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - corner;
|
||||
}
|
||||
|
|
@ -1,184 +0,0 @@
|
|||
struct GradientVertexInput {
|
||||
@builtin(vertex_index) vertex_index: u32,
|
||||
@location(0) @interpolate(flat) colors_1: vec4<u32>,
|
||||
@location(1) @interpolate(flat) colors_2: vec4<u32>,
|
||||
@location(2) @interpolate(flat) colors_3: vec4<u32>,
|
||||
@location(3) @interpolate(flat) colors_4: vec4<u32>,
|
||||
@location(4) @interpolate(flat) offsets: vec4<u32>,
|
||||
@location(5) direction: vec4<f32>,
|
||||
@location(6) position_and_scale: vec4<f32>,
|
||||
@location(7) border_color: vec4<f32>,
|
||||
@location(8) border_radius: vec4<f32>,
|
||||
@location(9) border_width: f32,
|
||||
@location(10) snap: u32,
|
||||
}
|
||||
|
||||
struct GradientVertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(1) @interpolate(flat) colors_1: vec4<u32>,
|
||||
@location(2) @interpolate(flat) colors_2: vec4<u32>,
|
||||
@location(3) @interpolate(flat) colors_3: vec4<u32>,
|
||||
@location(4) @interpolate(flat) colors_4: vec4<u32>,
|
||||
@location(5) @interpolate(flat) offsets: vec4<u32>,
|
||||
@location(6) direction: vec4<f32>,
|
||||
@location(7) position_and_scale: vec4<f32>,
|
||||
@location(8) border_color: vec4<f32>,
|
||||
@location(9) border_radius: vec4<f32>,
|
||||
@location(10) border_width: f32,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput {
|
||||
var out: GradientVertexOutput;
|
||||
|
||||
var pos: vec2<f32> = input.position_and_scale.xy * globals.scale;
|
||||
var scale: vec2<f32> = input.position_and_scale.zw * globals.scale;
|
||||
|
||||
var pos_snap = vec2<f32>(0.0, 0.0);
|
||||
var scale_snap = vec2<f32>(0.0, 0.0);
|
||||
|
||||
if bool(input.snap) {
|
||||
pos_snap = round(pos + vec2(0.001, 0.001)) - pos;
|
||||
scale_snap = round(pos + scale + vec2(0.001, 0.001)) - pos - pos_snap - scale;
|
||||
}
|
||||
|
||||
var min_border_radius = min(input.position_and_scale.z, input.position_and_scale.w) * 0.5;
|
||||
var border_radius: vec4<f32> = vec4<f32>(
|
||||
min(input.border_radius.x, min_border_radius),
|
||||
min(input.border_radius.y, min_border_radius),
|
||||
min(input.border_radius.z, min_border_radius),
|
||||
min(input.border_radius.w, min_border_radius)
|
||||
);
|
||||
|
||||
var transform: mat4x4<f32> = mat4x4<f32>(
|
||||
vec4<f32>(scale.x + scale_snap.x + 1.0, 0.0, 0.0, 0.0),
|
||||
vec4<f32>(0.0, scale.y + scale_snap.y + 1.0, 0.0, 0.0),
|
||||
vec4<f32>(0.0, 0.0, 1.0, 0.0),
|
||||
vec4<f32>(pos + pos_snap - vec2<f32>(0.5, 0.5), 0.0, 1.0)
|
||||
);
|
||||
|
||||
out.position = globals.transform * transform * vec4<f32>(vertex_position(input.vertex_index), 0.0, 1.0);
|
||||
out.colors_1 = input.colors_1;
|
||||
out.colors_2 = input.colors_2;
|
||||
out.colors_3 = input.colors_3;
|
||||
out.colors_4 = input.colors_4;
|
||||
out.offsets = input.offsets;
|
||||
out.direction = input.direction * globals.scale;
|
||||
out.position_and_scale = vec4<f32>(pos + pos_snap, scale + scale_snap);
|
||||
out.border_color = premultiply(input.border_color);
|
||||
out.border_radius = border_radius * globals.scale;
|
||||
out.border_width = input.border_width * globals.scale;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
fn random(coords: vec2<f32>) -> f32 {
|
||||
return fract(sin(dot(coords, vec2(12.9898,78.233))) * 43758.5453);
|
||||
}
|
||||
|
||||
/// Returns the current interpolated color with a max 8-stop gradient
|
||||
fn gradient(
|
||||
raw_position: vec2<f32>,
|
||||
direction: vec4<f32>,
|
||||
colors: array<vec4<f32>, 8>,
|
||||
offsets: array<f32, 8>,
|
||||
last_index: i32
|
||||
) -> vec4<f32> {
|
||||
let start = direction.xy;
|
||||
let end = direction.zw;
|
||||
|
||||
let v1 = end - start;
|
||||
let v2 = raw_position - start;
|
||||
let unit = normalize(v1);
|
||||
let coord_offset = dot(unit, v2) / length(v1);
|
||||
|
||||
//need to store these as a var to use dynamic indexing in a loop
|
||||
//this is already added to wgsl spec but not in wgpu yet
|
||||
var colors_arr = colors;
|
||||
var offsets_arr = offsets;
|
||||
|
||||
var color: vec4<f32>;
|
||||
|
||||
let noise_granularity: f32 = 0.3/255.0;
|
||||
|
||||
for (var i: i32 = 0; i < last_index; i++) {
|
||||
let curr_offset = offsets_arr[i];
|
||||
let next_offset = offsets_arr[i+1];
|
||||
|
||||
if (coord_offset <= offsets_arr[0]) {
|
||||
color = colors_arr[0];
|
||||
}
|
||||
|
||||
if (curr_offset <= coord_offset && coord_offset <= next_offset) {
|
||||
let from_ = colors_arr[i];
|
||||
let to_ = colors_arr[i+1];
|
||||
let factor = smoothstep(curr_offset, next_offset, coord_offset);
|
||||
|
||||
color = interpolate_color(from_, to_, factor);
|
||||
}
|
||||
|
||||
if (coord_offset >= offsets_arr[last_index]) {
|
||||
color = colors_arr[last_index];
|
||||
}
|
||||
}
|
||||
|
||||
return color + mix(-noise_granularity, noise_granularity, random(raw_position));
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4<f32> {
|
||||
let colors = array<vec4<f32>, 8>(
|
||||
unpack_color(input.colors_1.xy),
|
||||
unpack_color(input.colors_1.zw),
|
||||
unpack_color(input.colors_2.xy),
|
||||
unpack_color(input.colors_2.zw),
|
||||
unpack_color(input.colors_3.xy),
|
||||
unpack_color(input.colors_3.zw),
|
||||
unpack_color(input.colors_4.xy),
|
||||
unpack_color(input.colors_4.zw),
|
||||
);
|
||||
|
||||
let offsets_1: vec4<f32> = unpack_u32(input.offsets.xy);
|
||||
let offsets_2: vec4<f32> = unpack_u32(input.offsets.zw);
|
||||
|
||||
var offsets = array<f32, 8>(
|
||||
offsets_1.x,
|
||||
offsets_1.y,
|
||||
offsets_1.z,
|
||||
offsets_1.w,
|
||||
offsets_2.x,
|
||||
offsets_2.y,
|
||||
offsets_2.z,
|
||||
offsets_2.w,
|
||||
);
|
||||
|
||||
//TODO could just pass this in to the shader but is probably more performant to just check it here
|
||||
var last_index = 7;
|
||||
for (var i: i32 = 0; i <= 7; i++) {
|
||||
if (offsets[i] > 1.0) {
|
||||
last_index = i - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var mixed_color: vec4<f32> = gradient(input.position.xy, input.direction, colors, offsets, last_index);
|
||||
|
||||
let pos = input.position_and_scale.xy;
|
||||
let scale = input.position_and_scale.zw;
|
||||
|
||||
var dist: f32 = rounded_box_sdf(
|
||||
-(input.position.xy - pos - scale / 2.0) * 2.0,
|
||||
scale,
|
||||
input.border_radius * 2.0
|
||||
) / 2.0;
|
||||
|
||||
if (input.border_width > 0.0) {
|
||||
mixed_color = mix(
|
||||
mixed_color,
|
||||
input.border_color,
|
||||
clamp(0.5 + dist + input.border_width, 0.0, 1.0)
|
||||
);
|
||||
}
|
||||
|
||||
return mixed_color * clamp(0.5-dist, 0.0, 1.0);
|
||||
}
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
struct SolidVertexInput {
|
||||
@builtin(vertex_index) vertex_index: u32,
|
||||
@location(0) color: vec4<f32>,
|
||||
@location(1) pos: vec2<f32>,
|
||||
@location(2) scale: vec2<f32>,
|
||||
@location(3) border_color: vec4<f32>,
|
||||
@location(4) border_radius: vec4<f32>,
|
||||
@location(5) border_width: f32,
|
||||
@location(6) shadow_color: vec4<f32>,
|
||||
@location(7) shadow_offset: vec2<f32>,
|
||||
@location(8) shadow_blur_radius: f32,
|
||||
@location(9) snap: u32,
|
||||
}
|
||||
|
||||
struct SolidVertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) color: vec4<f32>,
|
||||
@location(1) border_color: vec4<f32>,
|
||||
@location(2) pos: vec2<f32>,
|
||||
@location(3) scale: vec2<f32>,
|
||||
@location(4) border_radius: vec4<f32>,
|
||||
@location(5) border_width: f32,
|
||||
@location(6) shadow_color: vec4<f32>,
|
||||
@location(7) shadow_offset: vec2<f32>,
|
||||
@location(8) shadow_blur_radius: f32,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput {
|
||||
var out: SolidVertexOutput;
|
||||
|
||||
var pos: vec2<f32> = (input.pos + min(input.shadow_offset, vec2<f32>(0.0, 0.0)) - input.shadow_blur_radius) * globals.scale;
|
||||
var scale: vec2<f32> = (input.scale + vec2<f32>(abs(input.shadow_offset.x), abs(input.shadow_offset.y)) + input.shadow_blur_radius * 2.0) * globals.scale;
|
||||
|
||||
var pos_snap = vec2<f32>(0.0, 0.0);
|
||||
var scale_snap = vec2<f32>(0.0, 0.0);
|
||||
|
||||
if bool(input.snap) {
|
||||
pos_snap = round(pos + vec2(0.001, 0.001)) - pos;
|
||||
scale_snap = round(pos + scale + vec2(0.001, 0.001)) - pos - pos_snap - scale;
|
||||
}
|
||||
|
||||
let border_radius = min(input.border_radius, vec4(min(input.scale.x, input.scale.y) / 2.0));
|
||||
|
||||
var transform: mat4x4<f32> = mat4x4<f32>(
|
||||
vec4<f32>(scale.x + scale_snap.x + 1.0, 0.0, 0.0, 0.0),
|
||||
vec4<f32>(0.0, scale.y + scale_snap.y + 1.0, 0.0, 0.0),
|
||||
vec4<f32>(0.0, 0.0, 1.0, 0.0),
|
||||
vec4<f32>(pos + pos_snap - vec2<f32>(0.5, 0.5), 0.0, 1.0)
|
||||
);
|
||||
|
||||
out.position = globals.transform * transform * vec4<f32>(vertex_position(input.vertex_index), 0.0, 1.0);
|
||||
out.color = premultiply(input.color);
|
||||
out.border_color = premultiply(input.border_color);
|
||||
out.pos = input.pos * globals.scale + pos_snap;
|
||||
out.scale = input.scale * globals.scale + scale_snap;
|
||||
out.border_radius = border_radius * globals.scale;
|
||||
out.border_width = input.border_width * globals.scale;
|
||||
out.shadow_color = premultiply(input.shadow_color);
|
||||
out.shadow_offset = input.shadow_offset * globals.scale;
|
||||
out.shadow_blur_radius = input.shadow_blur_radius * globals.scale;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn solid_fs_main(
|
||||
input: SolidVertexOutput
|
||||
) -> @location(0) vec4<f32> {
|
||||
var mixed_color: vec4<f32> = input.color;
|
||||
|
||||
var dist = rounded_box_sdf(
|
||||
-(input.position.xy - input.pos - input.scale * 0.5) * 2.0,
|
||||
input.scale,
|
||||
input.border_radius * 2.0
|
||||
) / 2.0;
|
||||
|
||||
if (input.border_width > 0.0) {
|
||||
mixed_color = mix(
|
||||
input.color,
|
||||
input.border_color,
|
||||
clamp(0.5 + dist + input.border_width, 0.0, 1.0)
|
||||
);
|
||||
}
|
||||
|
||||
var quad_alpha: f32 = clamp(0.5-dist, 0.0, 1.0);
|
||||
|
||||
let quad_color = mixed_color * quad_alpha;
|
||||
|
||||
if input.shadow_color.a > 0.0 {
|
||||
var shadow_dist: f32 = rounded_box_sdf(
|
||||
-(input.position.xy - input.pos - input.shadow_offset - input.scale/2.0) * 2.0,
|
||||
input.scale,
|
||||
input.border_radius * 2.0
|
||||
) / 2.0;
|
||||
let shadow_alpha = 1.0 - smoothstep(-input.shadow_blur_radius, input.shadow_blur_radius, max(shadow_dist, 0.0));
|
||||
|
||||
return mix(quad_color, input.shadow_color, (1.0 - quad_alpha) * shadow_alpha);
|
||||
} else {
|
||||
return quad_color;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
struct Globals {
|
||||
transform: mat4x4<f32>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var<uniform> globals: Globals;
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
struct GradientVertexInput {
|
||||
@location(0) v_pos: vec2<f32>,
|
||||
@location(1) @interpolate(flat) colors_1: vec4<u32>,
|
||||
@location(2) @interpolate(flat) colors_2: vec4<u32>,
|
||||
@location(3) @interpolate(flat) colors_3: vec4<u32>,
|
||||
@location(4) @interpolate(flat) colors_4: vec4<u32>,
|
||||
@location(5) @interpolate(flat) offsets: vec4<u32>,
|
||||
@location(6) direction: vec4<f32>,
|
||||
}
|
||||
|
||||
struct GradientVertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) raw_position: vec2<f32>,
|
||||
@location(1) @interpolate(flat) colors_1: vec4<u32>,
|
||||
@location(2) @interpolate(flat) colors_2: vec4<u32>,
|
||||
@location(3) @interpolate(flat) colors_3: vec4<u32>,
|
||||
@location(4) @interpolate(flat) colors_4: vec4<u32>,
|
||||
@location(5) @interpolate(flat) offsets: vec4<u32>,
|
||||
@location(6) direction: vec4<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput {
|
||||
var output: GradientVertexOutput;
|
||||
|
||||
output.position = globals.transform * vec4<f32>(input.v_pos, 0.0, 1.0);
|
||||
output.raw_position = input.v_pos;
|
||||
output.colors_1 = input.colors_1;
|
||||
output.colors_2 = input.colors_2;
|
||||
output.colors_3 = input.colors_3;
|
||||
output.colors_4 = input.colors_4;
|
||||
output.offsets = input.offsets;
|
||||
output.direction = input.direction;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// Returns the current interpolated color with a max 8-stop gradient
|
||||
fn gradient(
|
||||
raw_position: vec2<f32>,
|
||||
direction: vec4<f32>,
|
||||
colors: array<vec4<f32>, 8>,
|
||||
offsets: array<f32, 8>,
|
||||
last_index: i32
|
||||
) -> vec4<f32> {
|
||||
let start = direction.xy;
|
||||
let end = direction.zw;
|
||||
|
||||
let v1 = end - start;
|
||||
let v2 = raw_position - start;
|
||||
let unit = normalize(v1);
|
||||
let coord_offset = dot(unit, v2) / length(v1);
|
||||
|
||||
//need to store these as a var to use dynamic indexing in a loop
|
||||
//this is already added to wgsl spec but not in wgpu yet
|
||||
var colors_arr = colors;
|
||||
var offsets_arr = offsets;
|
||||
|
||||
var color: vec4<f32>;
|
||||
|
||||
let noise_granularity: f32 = 0.3/255.0;
|
||||
|
||||
for (var i: i32 = 0; i < last_index; i++) {
|
||||
let curr_offset = offsets_arr[i];
|
||||
let next_offset = offsets_arr[i+1];
|
||||
|
||||
if (coord_offset <= offsets_arr[0]) {
|
||||
color = colors_arr[0];
|
||||
}
|
||||
|
||||
if (curr_offset <= coord_offset && coord_offset <= next_offset) {
|
||||
let from_ = colors_arr[i];
|
||||
let to_ = colors_arr[i+1];
|
||||
let factor = smoothstep(curr_offset, next_offset, coord_offset);
|
||||
|
||||
color = interpolate_color(from_, to_, factor);
|
||||
}
|
||||
|
||||
if (coord_offset >= offsets_arr[last_index]) {
|
||||
color = colors_arr[last_index];
|
||||
}
|
||||
}
|
||||
|
||||
return color + mix(-noise_granularity, noise_granularity, random(raw_position));
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4<f32> {
|
||||
let colors = array<vec4<f32>, 8>(
|
||||
unpack_color(input.colors_1.xy),
|
||||
unpack_color(input.colors_1.zw),
|
||||
unpack_color(input.colors_2.xy),
|
||||
unpack_color(input.colors_2.zw),
|
||||
unpack_color(input.colors_3.xy),
|
||||
unpack_color(input.colors_3.zw),
|
||||
unpack_color(input.colors_4.xy),
|
||||
unpack_color(input.colors_4.zw),
|
||||
);
|
||||
|
||||
let offsets_1: vec4<f32> = unpack_u32(input.offsets.xy);
|
||||
let offsets_2: vec4<f32> = unpack_u32(input.offsets.zw);
|
||||
|
||||
var offsets = array<f32, 8>(
|
||||
offsets_1.x,
|
||||
offsets_1.y,
|
||||
offsets_1.z,
|
||||
offsets_1.w,
|
||||
offsets_2.x,
|
||||
offsets_2.y,
|
||||
offsets_2.z,
|
||||
offsets_2.w,
|
||||
);
|
||||
|
||||
var last_index = 7;
|
||||
for (var i: i32 = 0; i <= 7; i++) {
|
||||
if (offsets[i] >= 1.0) {
|
||||
last_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return gradient(input.raw_position, input.direction, colors, offsets, last_index);
|
||||
}
|
||||
|
||||
fn random(coords: vec2<f32>) -> f32 {
|
||||
return fract(sin(dot(coords, vec2(12.9898,78.233))) * 43758.5453);
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
struct SolidVertexInput {
|
||||
@location(0) position: vec2<f32>,
|
||||
@location(1) color: vec4<f32>,
|
||||
}
|
||||
|
||||
struct SolidVertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) color: vec4<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput {
|
||||
var out: SolidVertexOutput;
|
||||
|
||||
out.color = premultiply(input.color);
|
||||
out.position = globals.transform * vec4<f32>(input.position, 0.0, 1.0);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn solid_fs_main(input: SolidVertexOutput) -> @location(0) vec4<f32> {
|
||||
return input.color;
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
// Compute the normalized quad coordinates based on the vertex index.
|
||||
fn vertex_position(vertex_index: u32) -> vec2<f32> {
|
||||
// #: 0 1 2 3 4 5
|
||||
// x: 1 1 0 0 0 1
|
||||
// y: 1 0 0 0 1 1
|
||||
return vec2<f32>((vec2(1u, 2u) + vertex_index) % vec2(6u) < vec2(3u));
|
||||
}
|
||||
|
|
@ -1,648 +0,0 @@
|
|||
use crate::core::alignment;
|
||||
use crate::core::text::Alignment;
|
||||
use crate::core::{Rectangle, Size, Transformation};
|
||||
use crate::graphics::cache;
|
||||
use crate::graphics::color;
|
||||
use crate::graphics::text::cache::{self as text_cache, Cache as BufferCache};
|
||||
use crate::graphics::text::{Editor, Paragraph, font_system, to_color};
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::collections::hash_map;
|
||||
use std::sync::atomic::{self, AtomicU64};
|
||||
use std::sync::{self, Arc, RwLock};
|
||||
|
||||
pub use crate::graphics::Text;
|
||||
|
||||
const COLOR_MODE: cryoglyph::ColorMode = if color::GAMMA_CORRECTION {
|
||||
cryoglyph::ColorMode::Accurate
|
||||
} else {
|
||||
cryoglyph::ColorMode::Web
|
||||
};
|
||||
|
||||
pub type Batch = Vec<Item>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Item {
|
||||
Group {
|
||||
transformation: Transformation,
|
||||
text: Vec<Text>,
|
||||
},
|
||||
Cached {
|
||||
transformation: Transformation,
|
||||
cache: Cache,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Cache {
|
||||
id: Id,
|
||||
group: cache::Group,
|
||||
text: Arc<[Text]>,
|
||||
version: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Id(u64);
|
||||
|
||||
impl Cache {
|
||||
pub fn new(group: cache::Group, text: Vec<Text>) -> Option<Self> {
|
||||
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
if text.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Self {
|
||||
id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)),
|
||||
group,
|
||||
text: Arc::from(text),
|
||||
version: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update(&mut self, text: Vec<Text>) {
|
||||
if self.text.is_empty() && text.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.text = Arc::from(text);
|
||||
self.version += 1;
|
||||
}
|
||||
}
|
||||
|
||||
struct Upload {
|
||||
renderer: cryoglyph::TextRenderer,
|
||||
buffer_cache: BufferCache,
|
||||
transformation: Transformation,
|
||||
version: usize,
|
||||
group_version: usize,
|
||||
text: sync::Weak<[Text]>,
|
||||
_atlas: sync::Weak<()>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Storage {
|
||||
groups: FxHashMap<cache::Group, Group>,
|
||||
uploads: FxHashMap<Id, Upload>,
|
||||
}
|
||||
|
||||
struct Group {
|
||||
atlas: cryoglyph::TextAtlas,
|
||||
version: usize,
|
||||
should_trim: bool,
|
||||
handle: Arc<()>, // Keeps track of active uploads
|
||||
}
|
||||
|
||||
impl Storage {
|
||||
fn get(&self, cache: &Cache) -> Option<(&cryoglyph::TextAtlas, &Upload)> {
|
||||
if cache.text.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.groups
|
||||
.get(&cache.group)
|
||||
.map(|group| &group.atlas)
|
||||
.zip(self.uploads.get(&cache.id))
|
||||
}
|
||||
|
||||
fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
viewport: &cryoglyph::Viewport,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
format: wgpu::TextureFormat,
|
||||
state: &cryoglyph::Cache,
|
||||
cache: &Cache,
|
||||
new_transformation: Transformation,
|
||||
bounds: Rectangle,
|
||||
) {
|
||||
let group_count = self.groups.len();
|
||||
|
||||
let group = self.groups.entry(cache.group).or_insert_with(|| {
|
||||
log::debug!(
|
||||
"New text atlas: {:?} (total: {})",
|
||||
cache.group,
|
||||
group_count + 1
|
||||
);
|
||||
|
||||
Group {
|
||||
atlas: cryoglyph::TextAtlas::with_color_mode(
|
||||
device, queue, state, format, COLOR_MODE,
|
||||
),
|
||||
version: 0,
|
||||
should_trim: false,
|
||||
handle: Arc::new(()),
|
||||
}
|
||||
});
|
||||
|
||||
match self.uploads.entry(cache.id) {
|
||||
hash_map::Entry::Occupied(entry) => {
|
||||
let upload = entry.into_mut();
|
||||
|
||||
if upload.version != cache.version
|
||||
|| upload.group_version != group.version
|
||||
|| upload.transformation != new_transformation
|
||||
{
|
||||
if !cache.text.is_empty() {
|
||||
let _ = prepare(
|
||||
device,
|
||||
queue,
|
||||
viewport,
|
||||
encoder,
|
||||
&mut upload.renderer,
|
||||
&mut group.atlas,
|
||||
&mut upload.buffer_cache,
|
||||
&cache.text,
|
||||
bounds,
|
||||
new_transformation,
|
||||
);
|
||||
}
|
||||
|
||||
// Only trim if glyphs have changed
|
||||
group.should_trim =
|
||||
group.should_trim || upload.version != cache.version;
|
||||
|
||||
upload.text = Arc::downgrade(&cache.text);
|
||||
upload.version = cache.version;
|
||||
upload.group_version = group.version;
|
||||
upload.transformation = new_transformation;
|
||||
|
||||
upload.buffer_cache.trim();
|
||||
}
|
||||
}
|
||||
hash_map::Entry::Vacant(entry) => {
|
||||
let mut renderer = cryoglyph::TextRenderer::new(
|
||||
&mut group.atlas,
|
||||
device,
|
||||
wgpu::MultisampleState::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
let mut buffer_cache = BufferCache::new();
|
||||
|
||||
if !cache.text.is_empty() {
|
||||
let _ = prepare(
|
||||
device,
|
||||
queue,
|
||||
viewport,
|
||||
encoder,
|
||||
&mut renderer,
|
||||
&mut group.atlas,
|
||||
&mut buffer_cache,
|
||||
&cache.text,
|
||||
bounds,
|
||||
new_transformation,
|
||||
);
|
||||
}
|
||||
|
||||
let _ = entry.insert(Upload {
|
||||
renderer,
|
||||
buffer_cache,
|
||||
transformation: new_transformation,
|
||||
version: 0,
|
||||
group_version: group.version,
|
||||
text: Arc::downgrade(&cache.text),
|
||||
_atlas: Arc::downgrade(&group.handle),
|
||||
});
|
||||
|
||||
group.should_trim = cache.group.is_singleton();
|
||||
|
||||
log::debug!(
|
||||
"New text upload: {} (total: {})",
|
||||
cache.id.0,
|
||||
self.uploads.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trim(&mut self) {
|
||||
self.uploads
|
||||
.retain(|_id, upload| upload.text.strong_count() > 0);
|
||||
|
||||
self.groups.retain(|id, group| {
|
||||
let active_uploads = Arc::weak_count(&group.handle);
|
||||
|
||||
if active_uploads == 0 {
|
||||
log::debug!("Dropping text atlas: {id:?}");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if group.should_trim {
|
||||
log::trace!("Trimming text atlas: {id:?}");
|
||||
|
||||
group.atlas.trim();
|
||||
group.should_trim = false;
|
||||
|
||||
// We only need to worry about glyph fighting
|
||||
// when the atlas may be shared by multiple
|
||||
// uploads.
|
||||
if !id.is_singleton() {
|
||||
log::debug!(
|
||||
"Invalidating text atlas: {id:?} \
|
||||
(uploads: {active_uploads})"
|
||||
);
|
||||
|
||||
group.version += 1;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Viewport(cryoglyph::Viewport);
|
||||
|
||||
impl Viewport {
|
||||
pub fn update(&mut self, queue: &wgpu::Queue, resolution: Size<u32>) {
|
||||
self.0.update(
|
||||
queue,
|
||||
cryoglyph::Resolution {
|
||||
width: resolution.width,
|
||||
height: resolution.height,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Pipeline {
|
||||
format: wgpu::TextureFormat,
|
||||
cache: cryoglyph::Cache,
|
||||
atlas: Arc<RwLock<cryoglyph::TextAtlas>>,
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
format: wgpu::TextureFormat,
|
||||
) -> Self {
|
||||
let cache = cryoglyph::Cache::new(device);
|
||||
let atlas = cryoglyph::TextAtlas::with_color_mode(
|
||||
device, queue, &cache, format, COLOR_MODE,
|
||||
);
|
||||
|
||||
Pipeline {
|
||||
format,
|
||||
cache,
|
||||
atlas: Arc::new(RwLock::new(atlas)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_viewport(&self, device: &wgpu::Device) -> Viewport {
|
||||
Viewport(cryoglyph::Viewport::new(device, &self.cache))
|
||||
}
|
||||
|
||||
pub fn trim(&self) {
|
||||
self.atlas.write().expect("Write text atlas").trim();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct State {
|
||||
renderers: Vec<cryoglyph::TextRenderer>,
|
||||
prepare_layer: usize,
|
||||
cache: BufferCache,
|
||||
storage: Storage,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn prepare(
|
||||
&mut self,
|
||||
pipeline: &Pipeline,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
viewport: &Viewport,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
batch: &Batch,
|
||||
layer_bounds: Rectangle,
|
||||
layer_transformation: Transformation,
|
||||
) {
|
||||
let mut atlas = pipeline.atlas.write().expect("Write to text atlas");
|
||||
|
||||
for item in batch {
|
||||
match item {
|
||||
Item::Group {
|
||||
transformation,
|
||||
text,
|
||||
} => {
|
||||
if self.renderers.len() <= self.prepare_layer {
|
||||
self.renderers.push(cryoglyph::TextRenderer::new(
|
||||
&mut atlas,
|
||||
device,
|
||||
wgpu::MultisampleState::default(),
|
||||
None,
|
||||
));
|
||||
}
|
||||
|
||||
let renderer = &mut self.renderers[self.prepare_layer];
|
||||
let result = prepare(
|
||||
device,
|
||||
queue,
|
||||
&viewport.0,
|
||||
encoder,
|
||||
renderer,
|
||||
&mut atlas,
|
||||
&mut self.cache,
|
||||
text,
|
||||
layer_bounds * layer_transformation,
|
||||
layer_transformation * *transformation,
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(()) => {
|
||||
self.prepare_layer += 1;
|
||||
}
|
||||
Err(cryoglyph::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...
|
||||
}
|
||||
}
|
||||
}
|
||||
Item::Cached {
|
||||
transformation,
|
||||
cache,
|
||||
} => {
|
||||
self.storage.prepare(
|
||||
device,
|
||||
queue,
|
||||
&viewport.0,
|
||||
encoder,
|
||||
pipeline.format,
|
||||
&pipeline.cache,
|
||||
cache,
|
||||
layer_transformation * *transformation,
|
||||
layer_bounds * layer_transformation,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render<'a>(
|
||||
&'a self,
|
||||
pipeline: &'a Pipeline,
|
||||
viewport: &'a Viewport,
|
||||
start: usize,
|
||||
batch: &'a Batch,
|
||||
bounds: Rectangle<u32>,
|
||||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
) -> usize {
|
||||
let atlas = pipeline.atlas.read().expect("Read text atlas");
|
||||
let mut layer_count = 0;
|
||||
|
||||
render_pass.set_scissor_rect(
|
||||
bounds.x,
|
||||
bounds.y,
|
||||
bounds.width,
|
||||
bounds.height,
|
||||
);
|
||||
|
||||
for item in batch {
|
||||
match item {
|
||||
Item::Group { .. } => {
|
||||
let renderer = &self.renderers[start + layer_count];
|
||||
|
||||
renderer
|
||||
.render(&atlas, &viewport.0, render_pass)
|
||||
.expect("Render text");
|
||||
|
||||
layer_count += 1;
|
||||
}
|
||||
Item::Cached { cache, .. } => {
|
||||
if let Some((atlas, upload)) = self.storage.get(cache) {
|
||||
upload
|
||||
.renderer
|
||||
.render(atlas, &viewport.0, render_pass)
|
||||
.expect("Render cached text");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layer_count
|
||||
}
|
||||
|
||||
pub fn trim(&mut self) {
|
||||
self.cache.trim();
|
||||
self.storage.trim();
|
||||
|
||||
self.prepare_layer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
viewport: &cryoglyph::Viewport,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
renderer: &mut cryoglyph::TextRenderer,
|
||||
atlas: &mut cryoglyph::TextAtlas,
|
||||
buffer_cache: &mut BufferCache,
|
||||
sections: &[Text],
|
||||
layer_bounds: Rectangle,
|
||||
layer_transformation: Transformation,
|
||||
) -> Result<(), cryoglyph::PrepareError> {
|
||||
let mut font_system = font_system().write().expect("Write font system");
|
||||
let font_system = font_system.raw();
|
||||
|
||||
enum Allocation {
|
||||
Paragraph(Paragraph),
|
||||
Editor(Editor),
|
||||
Cache(text_cache::KeyHash),
|
||||
Raw(Arc<cryoglyph::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,
|
||||
align_x,
|
||||
..
|
||||
} => {
|
||||
let (key, _) = buffer_cache.allocate(
|
||||
font_system,
|
||||
text_cache::Key {
|
||||
content,
|
||||
size: f32::from(*size),
|
||||
line_height: f32::from(*line_height),
|
||||
font: *font,
|
||||
align_x: *align_x,
|
||||
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, position, color, clip_bounds, transformation) =
|
||||
match section {
|
||||
Text::Paragraph {
|
||||
position,
|
||||
color,
|
||||
clip_bounds,
|
||||
transformation,
|
||||
..
|
||||
} => {
|
||||
let Some(Allocation::Paragraph(paragraph)) = allocation
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
(
|
||||
paragraph.buffer(),
|
||||
*position,
|
||||
*color,
|
||||
*clip_bounds,
|
||||
*transformation,
|
||||
)
|
||||
}
|
||||
Text::Editor {
|
||||
position,
|
||||
color,
|
||||
clip_bounds,
|
||||
transformation,
|
||||
..
|
||||
} => {
|
||||
let Some(Allocation::Editor(editor)) = allocation
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
(
|
||||
editor.buffer(),
|
||||
*position,
|
||||
*color,
|
||||
*clip_bounds,
|
||||
*transformation,
|
||||
)
|
||||
}
|
||||
Text::Cached {
|
||||
bounds,
|
||||
align_x,
|
||||
align_y,
|
||||
color,
|
||||
clip_bounds,
|
||||
..
|
||||
} => {
|
||||
let Some(Allocation::Cache(key)) = allocation else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let entry =
|
||||
buffer_cache.get(key).expect("Get cached buffer");
|
||||
|
||||
let mut position = bounds.position();
|
||||
|
||||
position.x = match align_x {
|
||||
Alignment::Default
|
||||
| Alignment::Left
|
||||
| Alignment::Justified => position.x,
|
||||
Alignment::Center => {
|
||||
position.x - entry.min_bounds.width / 2.0
|
||||
}
|
||||
Alignment::Right => {
|
||||
position.x - entry.min_bounds.width
|
||||
}
|
||||
};
|
||||
|
||||
position.y = match align_y {
|
||||
alignment::Vertical::Top => position.y,
|
||||
alignment::Vertical::Center => {
|
||||
position.y - entry.min_bounds.height / 2.0
|
||||
}
|
||||
alignment::Vertical::Bottom => {
|
||||
position.y - entry.min_bounds.height
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
&entry.buffer,
|
||||
position,
|
||||
*color,
|
||||
*clip_bounds,
|
||||
Transformation::IDENTITY,
|
||||
)
|
||||
}
|
||||
Text::Raw {
|
||||
raw,
|
||||
transformation,
|
||||
} => {
|
||||
let Some(Allocation::Raw(buffer)) = allocation else {
|
||||
return None;
|
||||
};
|
||||
|
||||
(
|
||||
buffer.as_ref(),
|
||||
raw.position,
|
||||
raw.color,
|
||||
raw.clip_bounds,
|
||||
*transformation,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let position = position * transformation * layer_transformation;
|
||||
|
||||
let clip_bounds = layer_bounds.intersection(
|
||||
&(clip_bounds * transformation * layer_transformation),
|
||||
)?;
|
||||
|
||||
Some(cryoglyph::TextArea {
|
||||
buffer,
|
||||
left: position.x,
|
||||
top: position.y,
|
||||
scale: transformation.scale_factor()
|
||||
* layer_transformation.scale_factor(),
|
||||
bounds: cryoglyph::TextBounds {
|
||||
left: clip_bounds.x.round() as i32,
|
||||
top: clip_bounds.y.round() as i32,
|
||||
right: (clip_bounds.x + clip_bounds.width).round() as i32,
|
||||
bottom: (clip_bounds.y + clip_bounds.height).round() as i32,
|
||||
},
|
||||
default_color: to_color(color),
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
renderer.prepare(
|
||||
device,
|
||||
queue,
|
||||
encoder,
|
||||
font_system,
|
||||
atlas,
|
||||
viewport,
|
||||
text_areas,
|
||||
&mut cryoglyph::SwashCache::new(),
|
||||
)
|
||||
}
|
||||
|
|
@ -1,963 +0,0 @@
|
|||
//! Draw meshes of triangles.
|
||||
mod msaa;
|
||||
|
||||
use crate::Buffer;
|
||||
use crate::core::{Point, Rectangle, Size, Transformation, Vector};
|
||||
use crate::graphics::Antialiasing;
|
||||
use crate::graphics::mesh::{self, Mesh};
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::collections::hash_map;
|
||||
use std::sync::Weak;
|
||||
|
||||
const INITIAL_INDEX_COUNT: usize = 1_000;
|
||||
const INITIAL_VERTEX_COUNT: usize = 1_000;
|
||||
|
||||
pub type Batch = Vec<Item>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Item {
|
||||
Group {
|
||||
transformation: Transformation,
|
||||
meshes: Vec<Mesh>,
|
||||
},
|
||||
Cached {
|
||||
transformation: Transformation,
|
||||
cache: mesh::Cache,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Upload {
|
||||
layer: Layer,
|
||||
transformation: Transformation,
|
||||
version: usize,
|
||||
batch: Weak<[Mesh]>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Storage {
|
||||
uploads: FxHashMap<mesh::Id, Upload>,
|
||||
}
|
||||
|
||||
impl Storage {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn get(&self, cache: &mesh::Cache) -> Option<&Upload> {
|
||||
if cache.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: &mesh::Cache,
|
||||
new_transformation: Transformation,
|
||||
) {
|
||||
match self.uploads.entry(cache.id()) {
|
||||
hash_map::Entry::Occupied(entry) => {
|
||||
let upload = entry.into_mut();
|
||||
|
||||
if !cache.is_empty()
|
||||
&& (upload.version != cache.version()
|
||||
|| upload.transformation != new_transformation)
|
||||
{
|
||||
upload.layer.prepare(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
solid,
|
||||
gradient,
|
||||
cache.batch(),
|
||||
new_transformation,
|
||||
);
|
||||
|
||||
upload.batch = cache.downgrade();
|
||||
upload.version = cache.version();
|
||||
upload.transformation = new_transformation;
|
||||
}
|
||||
}
|
||||
hash_map::Entry::Vacant(entry) => {
|
||||
let mut layer = Layer::new(device, solid, gradient);
|
||||
|
||||
layer.prepare(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
solid,
|
||||
gradient,
|
||||
cache.batch(),
|
||||
new_transformation,
|
||||
);
|
||||
|
||||
let _ = entry.insert(Upload {
|
||||
layer,
|
||||
transformation: new_transformation,
|
||||
version: 0,
|
||||
batch: cache.downgrade(),
|
||||
});
|
||||
|
||||
log::debug!(
|
||||
"New mesh upload: {:?} (total: {})",
|
||||
cache.id(),
|
||||
self.uploads.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trim(&mut self) {
|
||||
self.uploads
|
||||
.retain(|_id, upload| upload.batch.strong_count() > 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Pipeline {
|
||||
msaa: Option<msaa::Pipeline>,
|
||||
solid: solid::Pipeline,
|
||||
gradient: gradient::Pipeline,
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
msaa: Option<msaa::State>,
|
||||
layers: Vec<Layer>,
|
||||
prepare_layer: usize,
|
||||
storage: Storage,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new(device: &wgpu::Device, pipeline: &Pipeline) -> Self {
|
||||
Self {
|
||||
msaa: pipeline
|
||||
.msaa
|
||||
.as_ref()
|
||||
.map(|pipeline| msaa::State::new(device, pipeline)),
|
||||
layers: Vec::new(),
|
||||
prepare_layer: 0,
|
||||
storage: Storage::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare(
|
||||
&mut self,
|
||||
pipeline: &Pipeline,
|
||||
device: &wgpu::Device,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
items: &[Item],
|
||||
scale: Transformation,
|
||||
target_size: Size<u32>,
|
||||
) {
|
||||
let projection = if let Some((state, pipeline)) =
|
||||
self.msaa.as_mut().zip(pipeline.msaa.as_ref())
|
||||
{
|
||||
state.prepare(device, encoder, belt, pipeline, 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,
|
||||
&pipeline.solid,
|
||||
&pipeline.gradient,
|
||||
));
|
||||
}
|
||||
|
||||
let layer = &mut self.layers[self.prepare_layer];
|
||||
layer.prepare(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
&pipeline.solid,
|
||||
&pipeline.gradient,
|
||||
meshes,
|
||||
projection * *transformation,
|
||||
);
|
||||
|
||||
self.prepare_layer += 1;
|
||||
}
|
||||
Item::Cached {
|
||||
transformation,
|
||||
cache,
|
||||
} => {
|
||||
self.storage.prepare(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
&pipeline.solid,
|
||||
&pipeline.gradient,
|
||||
cache,
|
||||
projection * *transformation,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
&mut self,
|
||||
pipeline: &Pipeline,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
target: &wgpu::TextureView,
|
||||
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 = self.storage.get(cache)?;
|
||||
|
||||
Some((
|
||||
&upload.layer,
|
||||
cache.batch(),
|
||||
screen_transformation * *transformation,
|
||||
))
|
||||
}
|
||||
});
|
||||
|
||||
render(
|
||||
encoder,
|
||||
target,
|
||||
self.msaa.as_ref().zip(pipeline.msaa.as_ref()),
|
||||
&pipeline.solid,
|
||||
&pipeline.gradient,
|
||||
bounds,
|
||||
items,
|
||||
);
|
||||
|
||||
layer_count
|
||||
}
|
||||
|
||||
pub fn trim(&mut self) {
|
||||
self.storage.trim();
|
||||
|
||||
self.prepare_layer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
antialiasing: Option<Antialiasing>,
|
||||
) -> Pipeline {
|
||||
Pipeline {
|
||||
msaa: antialiasing.map(|a| msaa::Pipeline::new(device, format, a)),
|
||||
solid: solid::Pipeline::new(device, format, antialiasing),
|
||||
gradient: gradient::Pipeline::new(device, format, antialiasing),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render<'a>(
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
target: &wgpu::TextureView,
|
||||
mut msaa: Option<(&msaa::State, &msaa::Pipeline)>,
|
||||
solid: &solid::Pipeline,
|
||||
gradient: &gradient::Pipeline,
|
||||
bounds: Rectangle,
|
||||
group: impl Iterator<Item = (&'a Layer, &'a [Mesh], Transformation)>,
|
||||
) {
|
||||
{
|
||||
let mut render_pass = if let Some((_state, pipeline)) = &mut msaa {
|
||||
pipeline.render_pass(encoder)
|
||||
} else {
|
||||
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("iced_wgpu.triangle.render_pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: target,
|
||||
depth_slice: None,
|
||||
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,
|
||||
})
|
||||
};
|
||||
|
||||
for (layer, meshes, transformation) in group {
|
||||
layer.render(
|
||||
solid,
|
||||
gradient,
|
||||
meshes,
|
||||
bounds,
|
||||
transformation,
|
||||
&mut render_pass,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((state, pipeline)) = msaa {
|
||||
state.render(pipeline, encoder, target);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Layer {
|
||||
index_buffer: Buffer<u32>,
|
||||
solid: solid::Layer,
|
||||
gradient: gradient::Layer,
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
fn new(
|
||||
device: &wgpu::Device,
|
||||
solid: &solid::Pipeline,
|
||||
gradient: &gradient::Pipeline,
|
||||
) -> Self {
|
||||
Self {
|
||||
index_buffer: Buffer::new(
|
||||
device,
|
||||
"iced_wgpu.triangle.index_buffer",
|
||||
INITIAL_INDEX_COUNT,
|
||||
wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
|
||||
),
|
||||
solid: solid::Layer::new(device, &solid.constants_layout),
|
||||
gradient: gradient::Layer::new(device, &gradient.constants_layout),
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
solid: &solid::Pipeline,
|
||||
gradient: &gradient::Pipeline,
|
||||
meshes: &[Mesh],
|
||||
transformation: Transformation,
|
||||
) {
|
||||
// Count the total amount of vertices & indices we need to handle
|
||||
let count = mesh::attribute_count_of(meshes);
|
||||
|
||||
// Then we ensure the current attribute buffers are big enough, resizing if necessary.
|
||||
// We are not currently using the return value of these functions as we have no system in
|
||||
// place to calculate mesh diff, or to know whether or not that would be more performant for
|
||||
// the majority of use cases. Therefore we will write GPU data every frame (for now).
|
||||
let _ = self.index_buffer.resize(device, count.indices);
|
||||
let _ = self.solid.vertices.resize(device, count.solid_vertices);
|
||||
let _ = self
|
||||
.gradient
|
||||
.vertices
|
||||
.resize(device, count.gradient_vertices);
|
||||
|
||||
if self.solid.uniforms.resize(device, count.solids) {
|
||||
self.solid.constants = solid::Layer::bind_group(
|
||||
device,
|
||||
&self.solid.uniforms.raw,
|
||||
&solid.constants_layout,
|
||||
);
|
||||
}
|
||||
|
||||
if self.gradient.uniforms.resize(device, count.gradients) {
|
||||
self.gradient.constants = gradient::Layer::bind_group(
|
||||
device,
|
||||
&self.gradient.uniforms.raw,
|
||||
&gradient.constants_layout,
|
||||
);
|
||||
}
|
||||
|
||||
let mut solid_vertex_offset = 0;
|
||||
let mut solid_uniform_offset = 0;
|
||||
let mut gradient_vertex_offset = 0;
|
||||
let mut gradient_uniform_offset = 0;
|
||||
let mut index_offset = 0;
|
||||
|
||||
for mesh in meshes {
|
||||
let clip_bounds = mesh.clip_bounds() * transformation;
|
||||
let snap_distance = clip_bounds
|
||||
.snap()
|
||||
.map(|snapped_bounds| {
|
||||
Point::new(snapped_bounds.x as f32, snapped_bounds.y as f32)
|
||||
- clip_bounds.position()
|
||||
})
|
||||
.unwrap_or(Vector::ZERO);
|
||||
|
||||
let uniforms = Uniforms::new(
|
||||
transformation
|
||||
* mesh.transformation()
|
||||
* Transformation::translate(
|
||||
snap_distance.x,
|
||||
snap_distance.y,
|
||||
),
|
||||
);
|
||||
|
||||
let indices = mesh.indices();
|
||||
|
||||
index_offset += self.index_buffer.write(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
index_offset,
|
||||
indices,
|
||||
);
|
||||
|
||||
match mesh {
|
||||
Mesh::Solid { buffers, .. } => {
|
||||
solid_vertex_offset += self.solid.vertices.write(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
solid_vertex_offset,
|
||||
&buffers.vertices,
|
||||
);
|
||||
|
||||
solid_uniform_offset += self.solid.uniforms.write(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
solid_uniform_offset,
|
||||
&[uniforms],
|
||||
);
|
||||
}
|
||||
Mesh::Gradient { buffers, .. } => {
|
||||
gradient_vertex_offset += self.gradient.vertices.write(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
gradient_vertex_offset,
|
||||
&buffers.vertices,
|
||||
);
|
||||
|
||||
gradient_uniform_offset += self.gradient.uniforms.write(
|
||||
device,
|
||||
encoder,
|
||||
belt,
|
||||
gradient_uniform_offset,
|
||||
&[uniforms],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render<'a>(
|
||||
&'a self,
|
||||
solid: &'a solid::Pipeline,
|
||||
gradient: &'a gradient::Pipeline,
|
||||
meshes: &[Mesh],
|
||||
bounds: Rectangle,
|
||||
transformation: Transformation,
|
||||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
) {
|
||||
let mut num_solids = 0;
|
||||
let mut num_gradients = 0;
|
||||
let mut solid_offset = 0;
|
||||
let mut gradient_offset = 0;
|
||||
let mut index_offset = 0;
|
||||
let mut last_is_solid = None;
|
||||
|
||||
for mesh in meshes {
|
||||
let Some(clip_bounds) = bounds
|
||||
.intersection(&(mesh.clip_bounds() * transformation))
|
||||
.and_then(Rectangle::snap)
|
||||
else {
|
||||
match mesh {
|
||||
Mesh::Solid { buffers, .. } => {
|
||||
solid_offset += buffers.vertices.len();
|
||||
num_solids += 1;
|
||||
}
|
||||
Mesh::Gradient { buffers, .. } => {
|
||||
gradient_offset += buffers.vertices.len();
|
||||
num_gradients += 1;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
};
|
||||
|
||||
render_pass.set_scissor_rect(
|
||||
clip_bounds.x,
|
||||
clip_bounds.y,
|
||||
clip_bounds.width,
|
||||
clip_bounds.height,
|
||||
);
|
||||
|
||||
match mesh {
|
||||
Mesh::Solid { buffers, .. } => {
|
||||
if !last_is_solid.unwrap_or(false) {
|
||||
render_pass.set_pipeline(&solid.pipeline);
|
||||
|
||||
last_is_solid = Some(true);
|
||||
}
|
||||
|
||||
render_pass.set_bind_group(
|
||||
0,
|
||||
&self.solid.constants,
|
||||
&[(num_solids * std::mem::size_of::<Uniforms>())
|
||||
as u32],
|
||||
);
|
||||
|
||||
render_pass.set_vertex_buffer(
|
||||
0,
|
||||
self.solid.vertices.range(
|
||||
solid_offset,
|
||||
solid_offset + buffers.vertices.len(),
|
||||
),
|
||||
);
|
||||
|
||||
num_solids += 1;
|
||||
solid_offset += buffers.vertices.len();
|
||||
}
|
||||
Mesh::Gradient { buffers, .. } => {
|
||||
if last_is_solid.unwrap_or(true) {
|
||||
render_pass.set_pipeline(&gradient.pipeline);
|
||||
|
||||
last_is_solid = Some(false);
|
||||
}
|
||||
|
||||
render_pass.set_bind_group(
|
||||
0,
|
||||
&self.gradient.constants,
|
||||
&[(num_gradients * std::mem::size_of::<Uniforms>())
|
||||
as u32],
|
||||
);
|
||||
|
||||
render_pass.set_vertex_buffer(
|
||||
0,
|
||||
self.gradient.vertices.range(
|
||||
gradient_offset,
|
||||
gradient_offset + buffers.vertices.len(),
|
||||
),
|
||||
);
|
||||
|
||||
num_gradients += 1;
|
||||
gradient_offset += buffers.vertices.len();
|
||||
}
|
||||
};
|
||||
|
||||
render_pass.set_index_buffer(
|
||||
self.index_buffer
|
||||
.range(index_offset, index_offset + mesh.indices().len()),
|
||||
wgpu::IndexFormat::Uint32,
|
||||
);
|
||||
|
||||
render_pass.draw_indexed(0..mesh.indices().len() as u32, 0, 0..1);
|
||||
|
||||
index_offset += mesh.indices().len();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fragment_target(
|
||||
texture_format: wgpu::TextureFormat,
|
||||
) -> wgpu::ColorTargetState {
|
||||
wgpu::ColorTargetState {
|
||||
format: texture_format,
|
||||
blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
}
|
||||
}
|
||||
|
||||
fn primitive_state() -> wgpu::PrimitiveState {
|
||||
wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
front_face: wgpu::FrontFace::Cw,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn multisample_state(
|
||||
antialiasing: Option<Antialiasing>,
|
||||
) -> wgpu::MultisampleState {
|
||||
wgpu::MultisampleState {
|
||||
count: antialiasing.map(Antialiasing::sample_count).unwrap_or(1),
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct Uniforms {
|
||||
transform: [f32; 16],
|
||||
/// Uniform values must be 256-aligned;
|
||||
/// see: [`wgpu::Limits`] `min_uniform_buffer_offset_alignment`.
|
||||
_padding: [f32; 48],
|
||||
}
|
||||
|
||||
impl Uniforms {
|
||||
pub fn new(transform: Transformation) -> Self {
|
||||
Self {
|
||||
transform: transform.into(),
|
||||
_padding: [0.0; 48],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn entry() -> wgpu::BindGroupLayoutEntry {
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: true,
|
||||
min_binding_size: wgpu::BufferSize::new(
|
||||
std::mem::size_of::<Self>() as u64,
|
||||
),
|
||||
},
|
||||
count: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn min_size() -> Option<wgpu::BufferSize> {
|
||||
wgpu::BufferSize::new(std::mem::size_of::<Self>() as u64)
|
||||
}
|
||||
}
|
||||
|
||||
mod solid {
|
||||
use crate::Buffer;
|
||||
use crate::graphics::Antialiasing;
|
||||
use crate::graphics::mesh;
|
||||
use crate::triangle;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Pipeline {
|
||||
pub pipeline: wgpu::RenderPipeline,
|
||||
pub constants_layout: wgpu::BindGroupLayout,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Layer {
|
||||
pub vertices: Buffer<mesh::SolidVertex2D>,
|
||||
pub uniforms: Buffer<triangle::Uniforms>,
|
||||
pub constants: wgpu::BindGroup,
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
constants_layout: &wgpu::BindGroupLayout,
|
||||
) -> Self {
|
||||
let vertices = Buffer::new(
|
||||
device,
|
||||
"iced_wgpu.triangle.solid.vertex_buffer",
|
||||
triangle::INITIAL_VERTEX_COUNT,
|
||||
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
);
|
||||
|
||||
let uniforms = Buffer::new(
|
||||
device,
|
||||
"iced_wgpu.triangle.solid.uniforms",
|
||||
1,
|
||||
wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
);
|
||||
|
||||
let constants =
|
||||
Self::bind_group(device, &uniforms.raw, constants_layout);
|
||||
|
||||
Self {
|
||||
vertices,
|
||||
uniforms,
|
||||
constants,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bind_group(
|
||||
device: &wgpu::Device,
|
||||
buffer: &wgpu::Buffer,
|
||||
layout: &wgpu::BindGroupLayout,
|
||||
) -> wgpu::BindGroup {
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu.triangle.solid.bind_group"),
|
||||
layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(
|
||||
wgpu::BufferBinding {
|
||||
buffer,
|
||||
offset: 0,
|
||||
size: triangle::Uniforms::min_size(),
|
||||
},
|
||||
),
|
||||
}],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
antialiasing: Option<Antialiasing>,
|
||||
) -> Self {
|
||||
let constants_layout = device.create_bind_group_layout(
|
||||
&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("iced_wgpu.triangle.solid.bind_group_layout"),
|
||||
entries: &[triangle::Uniforms::entry()],
|
||||
},
|
||||
);
|
||||
|
||||
let layout = device.create_pipeline_layout(
|
||||
&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("iced_wgpu.triangle.solid.pipeline_layout"),
|
||||
bind_group_layouts: &[&constants_layout],
|
||||
push_constant_ranges: &[],
|
||||
},
|
||||
);
|
||||
|
||||
let shader =
|
||||
device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("iced_wgpu.triangle.solid.shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(
|
||||
std::borrow::Cow::Borrowed(concat!(
|
||||
include_str!("shader/triangle.wgsl"),
|
||||
"\n",
|
||||
include_str!("shader/triangle/solid.wgsl"),
|
||||
"\n",
|
||||
include_str!("shader/color.wgsl"),
|
||||
)),
|
||||
),
|
||||
});
|
||||
|
||||
let pipeline =
|
||||
device.create_render_pipeline(
|
||||
&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("iced_wgpu::triangle::solid pipeline"),
|
||||
layout: Some(&layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("solid_vs_main"),
|
||||
buffers: &[wgpu::VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<
|
||||
mesh::SolidVertex2D,
|
||||
>(
|
||||
)
|
||||
as u64,
|
||||
step_mode: wgpu::VertexStepMode::Vertex,
|
||||
attributes: &wgpu::vertex_attr_array!(
|
||||
// Position
|
||||
0 => Float32x2,
|
||||
// Color
|
||||
1 => Float32x4,
|
||||
),
|
||||
}],
|
||||
compilation_options:
|
||||
wgpu::PipelineCompilationOptions::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("solid_fs_main"),
|
||||
targets: &[Some(triangle::fragment_target(format))],
|
||||
compilation_options:
|
||||
wgpu::PipelineCompilationOptions::default(),
|
||||
}),
|
||||
primitive: triangle::primitive_state(),
|
||||
depth_stencil: None,
|
||||
multisample: triangle::multisample_state(antialiasing),
|
||||
multiview: None,
|
||||
cache: None,
|
||||
},
|
||||
);
|
||||
|
||||
Self {
|
||||
pipeline,
|
||||
constants_layout,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod gradient {
|
||||
use crate::Buffer;
|
||||
use crate::graphics::Antialiasing;
|
||||
use crate::graphics::mesh;
|
||||
use crate::triangle;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Pipeline {
|
||||
pub pipeline: wgpu::RenderPipeline,
|
||||
pub constants_layout: wgpu::BindGroupLayout,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Layer {
|
||||
pub vertices: Buffer<mesh::GradientVertex2D>,
|
||||
pub uniforms: Buffer<triangle::Uniforms>,
|
||||
pub constants: wgpu::BindGroup,
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
constants_layout: &wgpu::BindGroupLayout,
|
||||
) -> Self {
|
||||
let vertices = Buffer::new(
|
||||
device,
|
||||
"iced_wgpu.triangle.gradient.vertex_buffer",
|
||||
triangle::INITIAL_VERTEX_COUNT,
|
||||
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
);
|
||||
|
||||
let uniforms = Buffer::new(
|
||||
device,
|
||||
"iced_wgpu.triangle.gradient.uniforms",
|
||||
1,
|
||||
wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
);
|
||||
|
||||
let constants =
|
||||
Self::bind_group(device, &uniforms.raw, constants_layout);
|
||||
|
||||
Self {
|
||||
vertices,
|
||||
uniforms,
|
||||
constants,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bind_group(
|
||||
device: &wgpu::Device,
|
||||
uniform_buffer: &wgpu::Buffer,
|
||||
layout: &wgpu::BindGroupLayout,
|
||||
) -> wgpu::BindGroup {
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu.triangle.gradient.bind_group"),
|
||||
layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(
|
||||
wgpu::BufferBinding {
|
||||
buffer: uniform_buffer,
|
||||
offset: 0,
|
||||
size: triangle::Uniforms::min_size(),
|
||||
},
|
||||
),
|
||||
}],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
antialiasing: Option<Antialiasing>,
|
||||
) -> Self {
|
||||
let constants_layout = device.create_bind_group_layout(
|
||||
&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some(
|
||||
"iced_wgpu.triangle.gradient.bind_group_layout",
|
||||
),
|
||||
entries: &[triangle::Uniforms::entry()],
|
||||
},
|
||||
);
|
||||
|
||||
let layout = device.create_pipeline_layout(
|
||||
&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("iced_wgpu.triangle.gradient.pipeline_layout"),
|
||||
bind_group_layouts: &[&constants_layout],
|
||||
push_constant_ranges: &[],
|
||||
},
|
||||
);
|
||||
|
||||
let shader =
|
||||
device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("iced_wgpu.triangle.gradient.shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(
|
||||
std::borrow::Cow::Borrowed(concat!(
|
||||
include_str!("shader/triangle.wgsl"),
|
||||
"\n",
|
||||
include_str!("shader/triangle/gradient.wgsl"),
|
||||
"\n",
|
||||
include_str!("shader/color.wgsl"),
|
||||
"\n",
|
||||
include_str!("shader/color/linear_rgb.wgsl")
|
||||
)),
|
||||
),
|
||||
});
|
||||
|
||||
let pipeline = device.create_render_pipeline(
|
||||
&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("iced_wgpu.triangle.gradient.pipeline"),
|
||||
layout: Some(&layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("gradient_vs_main"),
|
||||
buffers: &[wgpu::VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<
|
||||
mesh::GradientVertex2D,
|
||||
>()
|
||||
as u64,
|
||||
step_mode: wgpu::VertexStepMode::Vertex,
|
||||
attributes: &wgpu::vertex_attr_array!(
|
||||
// Position
|
||||
0 => Float32x2,
|
||||
// Colors 1-2
|
||||
1 => Uint32x4,
|
||||
// Colors 3-4
|
||||
2 => Uint32x4,
|
||||
// Colors 5-6
|
||||
3 => Uint32x4,
|
||||
// Colors 7-8
|
||||
4 => Uint32x4,
|
||||
// Offsets
|
||||
5 => Uint32x4,
|
||||
// Direction
|
||||
6 => Float32x4
|
||||
),
|
||||
}],
|
||||
compilation_options:
|
||||
wgpu::PipelineCompilationOptions::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("gradient_fs_main"),
|
||||
targets: &[Some(triangle::fragment_target(format))],
|
||||
compilation_options:
|
||||
wgpu::PipelineCompilationOptions::default(),
|
||||
}),
|
||||
primitive: triangle::primitive_state(),
|
||||
depth_stencil: None,
|
||||
multisample: triangle::multisample_state(antialiasing),
|
||||
multiview: None,
|
||||
cache: None,
|
||||
},
|
||||
);
|
||||
|
||||
Self {
|
||||
pipeline,
|
||||
constants_layout,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,371 +0,0 @@
|
|||
use crate::core::{Size, Transformation};
|
||||
use crate::graphics;
|
||||
|
||||
use std::num::NonZeroU64;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Pipeline {
|
||||
format: wgpu::TextureFormat,
|
||||
sampler: wgpu::Sampler,
|
||||
raw: wgpu::RenderPipeline,
|
||||
constant_layout: wgpu::BindGroupLayout,
|
||||
texture_layout: wgpu::BindGroupLayout,
|
||||
sample_count: u32,
|
||||
targets: Arc<RwLock<Option<Targets>>>,
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
antialiasing: graphics::Antialiasing,
|
||||
) -> Pipeline {
|
||||
let sampler =
|
||||
device.create_sampler(&wgpu::SamplerDescriptor::default());
|
||||
|
||||
let constant_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("iced_wgpu::triangle:msaa uniforms layout"),
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(
|
||||
wgpu::SamplerBindingType::NonFiltering,
|
||||
),
|
||||
count: None,
|
||||
},
|
||||
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 texture_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("iced_wgpu::triangle::msaa texture layout"),
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float {
|
||||
filterable: false,
|
||||
},
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
});
|
||||
|
||||
let layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("iced_wgpu::triangle::msaa pipeline layout"),
|
||||
push_constant_ranges: &[],
|
||||
bind_group_layouts: &[&constant_layout, &texture_layout],
|
||||
});
|
||||
|
||||
let shader =
|
||||
device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("iced_wgpu triangle blit_shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
|
||||
include_str!("../shader/blit.wgsl"),
|
||||
)),
|
||||
});
|
||||
|
||||
let pipeline =
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("iced_wgpu::triangle::msaa pipeline"),
|
||||
layout: Some(&layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[],
|
||||
compilation_options:
|
||||
wgpu::PipelineCompilationOptions::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("fs_main"),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format,
|
||||
blend: Some(
|
||||
wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING,
|
||||
),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
compilation_options:
|
||||
wgpu::PipelineCompilationOptions::default(),
|
||||
}),
|
||||
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,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
Self {
|
||||
format,
|
||||
sampler,
|
||||
raw: pipeline,
|
||||
constant_layout,
|
||||
texture_layout,
|
||||
sample_count: antialiasing.sample_count(),
|
||||
targets: Arc::new(RwLock::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
fn targets(
|
||||
&self,
|
||||
device: &wgpu::Device,
|
||||
region_size: Size<u32>,
|
||||
) -> Targets {
|
||||
let mut targets = self.targets.write().expect("Write MSAA targets");
|
||||
|
||||
match targets.as_mut() {
|
||||
Some(targets)
|
||||
if region_size.width <= targets.size.width
|
||||
&& region_size.height <= targets.size.height => {}
|
||||
_ => {
|
||||
*targets = Some(Targets::new(
|
||||
device,
|
||||
self.format,
|
||||
&self.texture_layout,
|
||||
self.sample_count,
|
||||
region_size,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
targets.as_ref().unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn render_pass<'a>(
|
||||
&self,
|
||||
encoder: &'a mut wgpu::CommandEncoder,
|
||||
) -> wgpu::RenderPass<'a> {
|
||||
let targets = self.targets.read().expect("Read MSAA targets");
|
||||
let targets = targets.as_ref().unwrap();
|
||||
|
||||
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("iced_wgpu.triangle.render_pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &targets.attachment,
|
||||
depth_slice: None,
|
||||
resolve_target: Some(&targets.resolve),
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Targets {
|
||||
attachment: wgpu::TextureView,
|
||||
resolve: wgpu::TextureView,
|
||||
bind_group: wgpu::BindGroup,
|
||||
size: Size<u32>,
|
||||
}
|
||||
|
||||
impl Targets {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
texture_layout: &wgpu::BindGroupLayout,
|
||||
sample_count: u32,
|
||||
size: Size<u32>,
|
||||
) -> Targets {
|
||||
let extent = wgpu::Extent3d {
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
|
||||
let attachment = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("iced_wgpu::triangle::msaa attachment"),
|
||||
size: extent,
|
||||
mip_level_count: 1,
|
||||
sample_count,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
let resolve = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("iced_wgpu::triangle::msaa resolve target"),
|
||||
size: extent,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
|
||||
| wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
let attachment =
|
||||
attachment.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let resolve =
|
||||
resolve.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::triangle::msaa texture bind group"),
|
||||
layout: texture_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(&resolve),
|
||||
}],
|
||||
});
|
||||
|
||||
Targets {
|
||||
attachment,
|
||||
resolve,
|
||||
bind_group,
|
||||
size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
#[repr(C)]
|
||||
struct Ratio {
|
||||
u: f32,
|
||||
v: f32,
|
||||
// Padding field for 16-byte alignment.
|
||||
// See https://docs.rs/wgpu/latest/wgpu/struct.DownlevelFlags.html#associatedconstant.BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED
|
||||
_padding: [f32; 2],
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
ratio: wgpu::Buffer,
|
||||
constants: wgpu::BindGroup,
|
||||
last_ratio: Option<Ratio>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new(device: &wgpu::Device, pipeline: &Pipeline) -> Self {
|
||||
let ratio = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("iced_wgpu::triangle::msaa ratio"),
|
||||
size: std::mem::size_of::<Ratio>() as u64,
|
||||
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::triangle::msaa uniforms bind group"),
|
||||
layout: &pipeline.constant_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Sampler(&pipeline.sampler),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: ratio.as_entire_binding(),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
Self {
|
||||
ratio,
|
||||
constants,
|
||||
last_ratio: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
belt: &mut wgpu::util::StagingBelt,
|
||||
pipeline: &Pipeline,
|
||||
region_size: Size<u32>,
|
||||
) -> Transformation {
|
||||
let targets = pipeline.targets(device, region_size);
|
||||
|
||||
let ratio = Ratio {
|
||||
u: region_size.width as f32 / targets.size.width as f32,
|
||||
v: region_size.height as f32 / targets.size.height as f32,
|
||||
_padding: [0.0; 2],
|
||||
};
|
||||
|
||||
if Some(ratio) != self.last_ratio {
|
||||
belt.write_buffer(
|
||||
encoder,
|
||||
&self.ratio,
|
||||
0,
|
||||
NonZeroU64::new(std::mem::size_of::<Ratio>() as u64)
|
||||
.expect("non-empty ratio"),
|
||||
device,
|
||||
)
|
||||
.copy_from_slice(bytemuck::bytes_of(&ratio));
|
||||
|
||||
self.last_ratio = Some(ratio);
|
||||
}
|
||||
|
||||
Transformation::orthographic(targets.size.width, targets.size.height)
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
&self,
|
||||
pipeline: &Pipeline,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
target: &wgpu::TextureView,
|
||||
) {
|
||||
let mut render_pass =
|
||||
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("iced_wgpu::triangle::msaa render pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: target,
|
||||
depth_slice: None,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
|
||||
render_pass.set_pipeline(&pipeline.raw);
|
||||
render_pass.set_bind_group(0, &self.constants, &[]);
|
||||
render_pass.set_bind_group(
|
||||
1,
|
||||
&pipeline
|
||||
.targets
|
||||
.read()
|
||||
.expect("Read MSAA targets")
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.bind_group,
|
||||
&[],
|
||||
);
|
||||
render_pass.draw(0..6, 0..1);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
//! Display rendering results on windows.
|
||||
pub mod compositor;
|
||||
|
||||
pub use compositor::Compositor;
|
||||
pub use wgpu::Surface;
|
||||
|
|
@ -1,358 +0,0 @@
|
|||
//! Connect a window with a renderer.
|
||||
use crate::core::Color;
|
||||
use crate::graphics::color;
|
||||
use crate::graphics::compositor;
|
||||
use crate::graphics::error;
|
||||
use crate::graphics::{self, Shell, Viewport};
|
||||
use crate::settings::{self, Settings};
|
||||
use crate::{Engine, Renderer};
|
||||
|
||||
/// A window graphics backend for iced powered by `wgpu`.
|
||||
pub struct Compositor {
|
||||
instance: wgpu::Instance,
|
||||
adapter: wgpu::Adapter,
|
||||
format: wgpu::TextureFormat,
|
||||
alpha_mode: wgpu::CompositeAlphaMode,
|
||||
engine: Engine,
|
||||
settings: Settings,
|
||||
}
|
||||
|
||||
/// A compositor error.
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// The surface creation failed.
|
||||
#[error("the surface creation failed: {0}")]
|
||||
SurfaceCreationFailed(#[from] wgpu::CreateSurfaceError),
|
||||
/// The surface is not compatible.
|
||||
#[error("the surface is not compatible")]
|
||||
IncompatibleSurface,
|
||||
/// No adapter was found for the options requested.
|
||||
#[error("no adapter was found for the options requested: {0:?}")]
|
||||
NoAdapterFound(String),
|
||||
/// No device request succeeded.
|
||||
#[error("no device request succeeded: {0:?}")]
|
||||
RequestDeviceFailed(Vec<(wgpu::Limits, wgpu::RequestDeviceError)>),
|
||||
}
|
||||
|
||||
impl From<Error> for graphics::Error {
|
||||
fn from(error: Error) -> Self {
|
||||
Self::GraphicsAdapterNotFound {
|
||||
backend: "wgpu",
|
||||
reason: error::Reason::RequestFailed(error.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Compositor {
|
||||
/// Requests a new [`Compositor`] with the given [`Settings`].
|
||||
///
|
||||
/// Returns `None` if no compatible graphics adapter could be found.
|
||||
pub async fn request<W: compositor::Window>(
|
||||
settings: Settings,
|
||||
compatible_window: Option<W>,
|
||||
shell: Shell,
|
||||
) -> Result<Self, Error> {
|
||||
let instance = wgpu::util::new_instance_with_webgpu_detection(&wgpu::InstanceDescriptor {
|
||||
backends: settings.backends,
|
||||
flags: if cfg!(feature = "strict-assertions") {
|
||||
wgpu::InstanceFlags::debugging()
|
||||
} else {
|
||||
wgpu::InstanceFlags::empty()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.await;
|
||||
|
||||
log::info!("{settings:#?}");
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if log::max_level() >= log::LevelFilter::Info {
|
||||
let available_adapters: Vec<_> = instance
|
||||
.enumerate_adapters(settings.backends)
|
||||
.iter()
|
||||
.map(wgpu::Adapter::get_info)
|
||||
.collect();
|
||||
log::info!("Available adapters: {available_adapters:#?}");
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
let compatible_surface =
|
||||
compatible_window.and_then(|window| instance.create_surface(window).ok());
|
||||
|
||||
let adapter_options = wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::from_env()
|
||||
.unwrap_or(wgpu::PowerPreference::HighPerformance),
|
||||
compatible_surface: compatible_surface.as_ref(),
|
||||
force_fallback_adapter: false,
|
||||
};
|
||||
|
||||
let adapter = instance
|
||||
.request_adapter(&adapter_options)
|
||||
.await
|
||||
.map_err(|_error| Error::NoAdapterFound(format!("{adapter_options:?}")))?;
|
||||
|
||||
log::info!("Selected: {:#?}", adapter.get_info());
|
||||
|
||||
let (format, alpha_mode) = compatible_surface
|
||||
.as_ref()
|
||||
.and_then(|surface| {
|
||||
let capabilities = surface.get_capabilities(&adapter);
|
||||
|
||||
let formats = capabilities.formats.iter().copied();
|
||||
|
||||
log::info!("Available formats: {formats:#?}");
|
||||
|
||||
let mut formats =
|
||||
formats.filter(|format| format.required_features() == wgpu::Features::empty());
|
||||
|
||||
let format = if color::GAMMA_CORRECTION {
|
||||
formats.find(wgpu::TextureFormat::is_srgb)
|
||||
} else {
|
||||
formats.find(|format| !wgpu::TextureFormat::is_srgb(format))
|
||||
};
|
||||
|
||||
let format = format.or_else(|| {
|
||||
log::warn!("No format found!");
|
||||
|
||||
capabilities.formats.first().copied()
|
||||
});
|
||||
|
||||
let alpha_modes = capabilities.alpha_modes;
|
||||
|
||||
log::info!("Available alpha modes: {alpha_modes:#?}");
|
||||
|
||||
let preferred_alpha =
|
||||
if alpha_modes.contains(&wgpu::CompositeAlphaMode::PostMultiplied) {
|
||||
wgpu::CompositeAlphaMode::PostMultiplied
|
||||
} else if alpha_modes.contains(&wgpu::CompositeAlphaMode::PreMultiplied) {
|
||||
wgpu::CompositeAlphaMode::PreMultiplied
|
||||
} else {
|
||||
wgpu::CompositeAlphaMode::Auto
|
||||
};
|
||||
|
||||
format.zip(Some(preferred_alpha))
|
||||
})
|
||||
.ok_or(Error::IncompatibleSurface)?;
|
||||
|
||||
log::info!("Selected format: {format:?} with alpha mode: {alpha_mode:?}");
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let limits = if adapter.get_info().backend == wgpu::Backend::BrowserWebGpu {
|
||||
vec![
|
||||
wgpu::Limits::default().using_resolution(adapter.limits()),
|
||||
wgpu::Limits::downlevel_webgl2_defaults().using_resolution(adapter.limits()),
|
||||
]
|
||||
} else {
|
||||
vec![wgpu::Limits::downlevel_webgl2_defaults().using_resolution(adapter.limits())]
|
||||
};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let limits = vec![wgpu::Limits::default(), wgpu::Limits::downlevel_defaults()];
|
||||
|
||||
let limits = limits.into_iter().map(|limits| wgpu::Limits {
|
||||
max_bind_groups: 2,
|
||||
max_non_sampler_bindings: 2048,
|
||||
..limits
|
||||
});
|
||||
|
||||
let mut errors = Vec::new();
|
||||
|
||||
for required_limits in limits {
|
||||
let result = adapter
|
||||
.request_device(&wgpu::DeviceDescriptor {
|
||||
label: Some("iced_wgpu::window::compositor device descriptor"),
|
||||
required_features: wgpu::Features::empty(),
|
||||
required_limits: required_limits.clone(),
|
||||
memory_hints: wgpu::MemoryHints::MemoryUsage,
|
||||
trace: wgpu::Trace::Off,
|
||||
experimental_features: wgpu::ExperimentalFeatures::disabled(),
|
||||
})
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok((device, queue)) => {
|
||||
let engine = Engine::new(
|
||||
&adapter,
|
||||
device,
|
||||
queue,
|
||||
format,
|
||||
settings.antialiasing,
|
||||
shell,
|
||||
);
|
||||
|
||||
return Ok(Compositor {
|
||||
instance,
|
||||
adapter,
|
||||
format,
|
||||
alpha_mode,
|
||||
engine,
|
||||
settings,
|
||||
});
|
||||
}
|
||||
Err(error) => {
|
||||
errors.push((required_limits, error));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::RequestDeviceFailed(errors))
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [`Compositor`] with the given [`Settings`] and window.
|
||||
pub async fn new<W: compositor::Window>(
|
||||
settings: Settings,
|
||||
compatible_window: W,
|
||||
shell: Shell,
|
||||
) -> Result<Compositor, Error> {
|
||||
Compositor::request(settings, Some(compatible_window), shell).await
|
||||
}
|
||||
|
||||
/// Presents the given primitives with the given [`Compositor`].
|
||||
pub fn present(
|
||||
renderer: &mut Renderer,
|
||||
surface: &mut wgpu::Surface<'static>,
|
||||
viewport: &Viewport,
|
||||
background_color: Color,
|
||||
on_pre_present: impl FnOnce(),
|
||||
) -> Result<(), compositor::SurfaceError> {
|
||||
match surface.get_current_texture() {
|
||||
Ok(frame) => {
|
||||
let view = &frame
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let _submission = renderer.present(
|
||||
Some(background_color),
|
||||
frame.texture.format(),
|
||||
view,
|
||||
viewport,
|
||||
);
|
||||
|
||||
// Present the frame
|
||||
on_pre_present();
|
||||
frame.present();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Err(error) => match error {
|
||||
wgpu::SurfaceError::Timeout => Err(compositor::SurfaceError::Timeout),
|
||||
wgpu::SurfaceError::Outdated => Err(compositor::SurfaceError::Outdated),
|
||||
wgpu::SurfaceError::Lost => Err(compositor::SurfaceError::Lost),
|
||||
wgpu::SurfaceError::OutOfMemory => Err(compositor::SurfaceError::OutOfMemory),
|
||||
wgpu::SurfaceError::Other => Err(compositor::SurfaceError::Other),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
impl graphics::Compositor for Compositor {
|
||||
type Renderer = Renderer;
|
||||
type Surface = wgpu::Surface<'static>;
|
||||
|
||||
async fn with_backend(
|
||||
settings: graphics::Settings,
|
||||
_display: impl compositor::Display,
|
||||
compatible_window: impl compositor::Window,
|
||||
shell: Shell,
|
||||
backend: Option<&str>,
|
||||
) -> Result<Self, graphics::Error> {
|
||||
match backend {
|
||||
None | Some("wgpu") => {
|
||||
let mut settings = Settings::from(settings);
|
||||
|
||||
if let Some(backends) = wgpu::Backends::from_env() {
|
||||
settings.backends = backends;
|
||||
}
|
||||
|
||||
if let Some(present_mode) = settings::present_mode_from_env() {
|
||||
settings.present_mode = present_mode;
|
||||
}
|
||||
|
||||
Ok(new(settings, compatible_window, shell).await?)
|
||||
}
|
||||
Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound {
|
||||
backend: "wgpu",
|
||||
reason: error::Reason::DidNotMatch {
|
||||
preferred_backend: backend.to_owned(),
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_renderer(&self) -> Self::Renderer {
|
||||
Renderer::new(
|
||||
self.engine.clone(),
|
||||
self.settings.default_font,
|
||||
self.settings.default_text_size,
|
||||
)
|
||||
}
|
||||
|
||||
fn create_surface<W: compositor::Window>(
|
||||
&mut self,
|
||||
window: W,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Self::Surface {
|
||||
let mut surface = self
|
||||
.instance
|
||||
.create_surface(window)
|
||||
.expect("Create surface");
|
||||
|
||||
if width > 0 && height > 0 {
|
||||
self.configure_surface(&mut surface, width, height);
|
||||
}
|
||||
|
||||
surface
|
||||
}
|
||||
|
||||
fn configure_surface(&mut self, surface: &mut Self::Surface, width: u32, height: u32) {
|
||||
surface.configure(
|
||||
&self.engine.device,
|
||||
&wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: self.format,
|
||||
present_mode: self.settings.present_mode,
|
||||
width,
|
||||
height,
|
||||
alpha_mode: self.alpha_mode,
|
||||
view_formats: vec![],
|
||||
desired_maximum_frame_latency: 1,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn information(&self) -> compositor::Information {
|
||||
let information = self.adapter.get_info();
|
||||
|
||||
compositor::Information {
|
||||
adapter: information.name,
|
||||
backend: format!("{:?}", information.backend),
|
||||
}
|
||||
}
|
||||
|
||||
fn present(
|
||||
&mut self,
|
||||
renderer: &mut Self::Renderer,
|
||||
surface: &mut Self::Surface,
|
||||
viewport: &Viewport,
|
||||
background_color: Color,
|
||||
on_pre_present: impl FnOnce(),
|
||||
) -> Result<(), compositor::SurfaceError> {
|
||||
present(
|
||||
renderer,
|
||||
surface,
|
||||
viewport,
|
||||
background_color,
|
||||
on_pre_present,
|
||||
)
|
||||
}
|
||||
|
||||
fn screenshot(
|
||||
&mut self,
|
||||
renderer: &mut Self::Renderer,
|
||||
viewport: &Viewport,
|
||||
background_color: Color,
|
||||
) -> Vec<u8> {
|
||||
renderer.screenshot(viewport, background_color)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue