use valo_dl::{GradientStop, Image, RuntimeShader, Sampling, TileMode};
use valo_geometry::{FillRule, Matrix, Rect};
use crate::frame::{Bindings, Draw, Geometry, Mesh, Stencil};
use crate::glyphs::{GlyphStore, PageRef};
use crate::host_buffer::{HostBuffer, UniformSlot};
use crate::images::ImageStore;
use crate::pipelines::{
Frag, PipelineBlend, PipelineCache, PipelineKey, PipelineKind, GLYPH_VERTEX_FLOATS,
MESH_VERTEX_FLOATS,
};
use crate::ramps::RampCache;
use crate::shader_abi::{slot, UniformRecord};
use super::draw_context::DrawContext;
use super::gaussian::MAX_KERNEL_SAMPLES;
use super::shading::Shading;
pub(crate) struct EmitterServices {
pub host: HostBuffer,
pub pipelines: PipelineCache,
pub images: ImageStore,
pub ramps: RampCache,
pub samplers: LinearSamplers,
}
pub(crate) struct LinearSamplers {
clamp: wgpu::Sampler,
repeat: wgpu::Sampler,
mirror: wgpu::Sampler,
levels: wgpu::Sampler,
}
impl LinearSamplers {
pub fn new(device: &wgpu::Device) -> Self {
let linear = |label, address_mode| {
device.create_sampler(&wgpu::SamplerDescriptor {
label: Some(label),
address_mode_u: address_mode,
address_mode_v: address_mode,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
..Default::default()
})
};
Self {
clamp: linear("valo.linear", wgpu::AddressMode::ClampToEdge),
repeat: linear("valo.linear.repeat", wgpu::AddressMode::Repeat),
mirror: linear("valo.linear.mirror", wgpu::AddressMode::MirrorRepeat),
levels: device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("valo.linear.levels"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::MipmapFilterMode::Linear,
..Default::default()
}),
}
}
fn for_tile_mode(&self, tile_mode: TileMode) -> &wgpu::Sampler {
match tile_mode {
TileMode::Clamp | TileMode::Decal => &self.clamp,
TileMode::Repeat => &self.repeat,
TileMode::Mirror => &self.mirror,
}
}
}
pub(super) struct Entity {
pub stencil: Option<StencilWrite>,
pub cover: Cover,
pub role: Role,
pub shading: Shading,
pub blend: PipelineBlend,
pub z: f32,
}
pub(super) struct StencilWrite {
pub transform: Matrix,
pub mesh: Mesh,
pub marking: Marking,
}
pub(super) enum Marking {
Fan(FillRule),
Strip,
}
pub(super) enum Cover {
Quad { transform: Matrix, rect: Rect },
Mesh { transform: Matrix, mesh: Mesh },
Texels,
}
#[derive(Clone, Copy)]
pub(super) enum Role {
Fill,
StencilledFill,
Stroke,
Glyphs,
Clip { difference: bool },
}
impl Role {
fn kind(self, frag: Frag, opaque: bool) -> PipelineKind {
match (self, opaque) {
(Role::Fill, false) => PipelineKind::Draw(frag),
(Role::Fill, true) => PipelineKind::OpaqueDraw(frag),
(Role::StencilledFill, false) => PipelineKind::Cover(frag),
(Role::StencilledFill, true) => PipelineKind::OpaqueCover(frag),
(Role::Stroke, _) => PipelineKind::Strip(frag),
(Role::Glyphs, _) => PipelineKind::Glyphs(frag),
(Role::Clip { difference }, _) => PipelineKind::ClipCover { difference },
}
}
}
pub(super) struct StepEmitter<'a> {
host: &'a mut HostBuffer,
device: &'a wgpu::Device,
pipelines: &'a mut PipelineCache,
images: &'a mut ImageStore,
ramps: &'a mut RampCache,
samplers: &'a LinearSamplers,
format: wgpu::TextureFormat,
}
impl<'a> StepEmitter<'a> {
pub fn new(
services: &'a mut EmitterServices,
device: &'a wgpu::Device,
format: wgpu::TextureFormat,
) -> Self {
let EmitterServices {
host,
pipelines,
images,
ramps,
samplers,
} = services;
Self {
host,
device,
pipelines,
images,
ramps,
samplers,
format,
}
}
pub fn push(&mut self, context: &mut DrawContext, entity: Entity) {
let draw = self.place(context, entity);
context.draws.push(draw);
}
pub fn push_clip(&mut self, context: &mut DrawContext, entity: Entity) {
let draw = self.place(context, entity);
context.clips.push(draw.clone());
context.draws.push(draw);
}
fn place(&mut self, context: &DrawContext, entity: Entity) -> Draw {
let Entity {
stencil,
cover,
role,
mut shading,
blend,
z,
} = entity;
let stencil = stencil.map(|stencil| self.stencil(context, stencil));
let geometry = match cover {
Cover::Quad { transform, rect } => {
shading.record.set(slot::RECT, rect_payload(&rect));
let model = transform.then(&rect_to_unit(&rect));
shading.record.set_mvp(ortho(context, &model, z));
Geometry::Quad
}
Cover::Mesh { transform, mesh } => {
shading.record.set_mvp(ortho(context, &transform, z));
Geometry::Mesh(mesh)
}
Cover::Texels => {
let size = context.area.size();
let texels = Rect::new(0.0, 0.0, size[0] as f32, size[1] as f32);
shading
.record
.set_mvp(ortho_mvp(&rect_to_unit(&texels), size, z));
Geometry::Quad
}
};
let opaque = shading.opaque && matches!(blend, PipelineBlend::SrcOver | PipelineBlend::Src);
let kind = role.kind(shading.frag, opaque);
Draw {
stencil,
key: PipelineKey::new(self.format, blend, kind),
uniforms: self.host.alloc_uniform(shading.record.bytes()),
bindings: shading.bindings,
geometry,
z,
}
}
fn stencil(&mut self, context: &DrawContext, write: StencilWrite) -> Stencil {
let mut record = UniformRecord::tinted([0.0; 4]);
record.set_mvp(ortho(context, &write.transform, 0.0));
let kind = match write.marking {
Marking::Fan(rule) => PipelineKind::StencilFan {
even_odd: rule == FillRule::EvenOdd,
},
Marking::Strip => PipelineKind::StencilStrip,
};
Stencil {
key: PipelineKey::new(self.format, PipelineBlend::SrcOver, kind),
uniforms: self.host.alloc_uniform(record.bytes()),
mesh: write.mesh,
}
}
pub fn filter_draw(
&mut self,
format: wgpu::TextureFormat,
quad: &Rect,
extent: [u32; 2],
mut shading: Shading,
) -> Draw {
shading.record.set(slot::RECT, rect_payload(quad));
shading
.record
.set_mvp(ortho_mvp(&rect_to_unit(quad), extent, 0.0));
Draw {
stencil: None,
key: PipelineKey::new(
format,
PipelineBlend::SrcOver,
PipelineKind::Filter(shading.frag),
),
uniforms: self.host.alloc_uniform(shading.record.bytes()),
bindings: shading.bindings,
geometry: Geometry::Quad,
z: 0.0,
}
}
pub fn alloc_mesh(&mut self, vertices: &[f32]) -> Mesh {
self.alloc_vertices(vertices, MESH_VERTEX_FLOATS)
}
pub fn alloc_glyph_mesh(&mut self, vertices: &[f32]) -> Mesh {
self.alloc_vertices(vertices, GLYPH_VERTEX_FLOATS)
}
fn alloc_vertices(&mut self, vertices: &[f32], floats_per_vertex: usize) -> Mesh {
Mesh {
slot: self.host.alloc_vertices(bytemuck::cast_slice(vertices)),
vertices: (vertices.len() / floats_per_vertex) as u32,
}
}
pub fn alloc_kernel(&mut self, kernel: &[[f32; 4]; MAX_KERNEL_SAMPLES]) -> UniformSlot {
self.host.alloc_kernel(bytemuck::cast_slice(kernel))
}
pub fn texture_bind(&self, view: &wgpu::TextureView) -> wgpu::BindGroup {
self.tiled_texture_bind(view, TileMode::Clamp)
}
pub fn tiled_texture_bind(
&self,
view: &wgpu::TextureView,
tile_mode: TileMode,
) -> wgpu::BindGroup {
self.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("valo.step.texture"),
layout: self.pipelines.texture_bind_layout(),
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(
self.samplers.for_tile_mode(tile_mode),
),
},
],
})
}
pub fn blend_bind(&self, dst: &wgpu::TextureView, src: &wgpu::TextureView) -> wgpu::BindGroup {
self.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("valo.step.blend"),
layout: self.pipelines.blend_bind_layout(),
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(dst),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&self.samplers.clamp),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(src),
},
],
})
}
pub fn image_bind(&mut self, image: &Image, sampling: Sampling) -> wgpu::BindGroup {
self.images
.bind_group(self.pipelines.texture_bind_layout(), image, sampling)
}
pub fn ramp(&mut self, stops: &[GradientStop]) -> (wgpu::BindGroup, u32) {
let (view, texels) = self.ramps.ensure(self.device, stops);
(self.texture_bind(&view), texels)
}
pub fn runtime(&mut self, shader: &RuntimeShader) -> (Frag, Bindings) {
let frag = self.pipelines.runtime(self.device, shader);
(frag, self.runtime_bindings(shader, None))
}
pub fn runtime_filter(
&mut self,
shader: &RuntimeShader,
source: &wgpu::TextureView,
) -> (Frag, Bindings) {
let frag = self.pipelines.runtime(self.device, shader);
let source =
self.pipelines
.runtime_source_bind(self.device, shader, source, &self.samplers.levels);
(frag, self.runtime_bindings(shader, Some(source)))
}
fn runtime_bindings(
&mut self,
shader: &RuntimeShader,
source: Option<wgpu::BindGroup>,
) -> Bindings {
let images = self
.pipelines
.runtime_image_bind(self.device, self.images, shader);
let uniforms = (!shader.uniforms.is_empty())
.then(|| self.host.alloc_effect_uniforms(&shader.uniforms));
Bindings::Runtime {
images,
uniforms,
source,
}
}
pub fn atlas_bind(&self, glyphs: &mut GlyphStore, page: PageRef) -> wgpu::BindGroup {
glyphs.bind_group(self.pipelines.texture_bind_layout(), page)
}
pub fn filtered_image(
&mut self,
source: &Image,
filter: valo_dl::ColorFilter,
) -> (Image, bool) {
self.images.filtered_image(source, filter)
}
}
fn ortho(context: &DrawContext, m: &Matrix, z: f32) -> [f32; 16] {
let o = context.area.origin();
let shifted = Matrix::translation(-o.x, -o.y).then(m);
ortho_mvp(&shifted, context.area.size(), z)
}
fn rect_to_unit(r: &Rect) -> Matrix {
Matrix::from_affine(r.width, 0.0, 0.0, r.height, r.x, r.y)
}
fn rect_payload(rect: &Rect) -> [f32; 4] {
[rect.x, rect.y, rect.width, rect.height]
}
#[rustfmt::skip]
fn ortho_mvp(m: &Matrix, size: [u32; 2], z: f32) -> [f32; 16] {
let (w, h) = (size[0] as f32, size[1] as f32);
let projection = glam::Mat4::from_cols_array(&[
2.0 / w, 0.0, 0.0, 0.0,
0.0, -2.0 / h, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
-1.0, 1.0, 0.0, 1.0,
]);
let mut mvp = projection * m.to_mat4();
mvp.x_axis.z = z * mvp.x_axis.w;
mvp.y_axis.z = z * mvp.y_axis.w;
mvp.z_axis.z = z * mvp.z_axis.w;
mvp.w_axis.z = z * mvp.w_axis.w;
mvp.to_cols_array()
}