use std::ops::Range;
use bevy::platform::collections::HashMap;
use bevy::asset::{load_internal_asset, uuid_handle};
use bevy::camera::visibility::RenderLayers;
use bevy::core_pipeline::core_2d::CORE_2D_DEPTH_FORMAT;
use bevy::core_pipeline::schedule::{Core2d, Core2dSystems};
use bevy::mesh::VertexBufferLayout;
use bevy::prelude::*;
use bevy::render::camera::ExtractedCamera;
use bevy::render::diagnostic::RecordDiagnostics;
use bevy::render::render_resource::binding_types::uniform_buffer;
use bevy::render::render_resource::{
BindGroup, BindGroupEntries, BindGroupLayoutDescriptor, BindGroupLayoutEntries,
BlendState, Buffer, BufferDescriptor, BufferInitDescriptor, BufferUsages,
CachedRenderPipelineId,
ColorTargetState, ColorWrites, CompareFunction, DepthBiasState, DepthStencilState,
FragmentState, IndexFormat, MultisampleState, PipelineCache, PrimitiveState,
RenderPassDescriptor, RenderPipelineDescriptor, ShaderStages, ShaderType, StencilState,
StoreOp, TextureFormat, UniformBuffer, VertexFormat, VertexState, VertexStepMode,
};
use bevy::render::renderer::{RenderContext, RenderDevice, RenderQueue, ViewQuery};
use bevy::render::settings::WgpuFeatures;
use bevy::render::view::{ExtractedView, Msaa, ViewDepthTexture, ViewTarget};
use bevy::render::{Extract, ExtractSchedule, Render, RenderApp, RenderSystems};
use bevy::shader::Shader;
use crate::painter::VectorPainterQueue;
use crate::tess;
use crate::{ClippedBy, HudTransform, VectorClipShape, VectorPrimitive, VectorShape};
pub const VECTOR_SHADER_HANDLE: Handle<Shader> =
uuid_handle!("7a3f1c2e-9b4d-4b8a-a2f0-5e1d3c6b9f01");
pub const VECTOR_PARAM_SHADER_HANDLE: Handle<Shader> =
uuid_handle!("2c8e5b1a-4f7d-4c3e-9a06-8b21d75c4e90");
pub const VECTOR_SDF_SHADER_HANDLE: Handle<Shader> =
uuid_handle!("5d1b7f34-0a62-4e19-8c7d-3f9e2a4b6c81");
pub struct VectorRenderPlugin;
impl Plugin for VectorRenderPlugin {
fn build(&self, app: &mut App) {
if !app.world().contains_resource::<Assets<Shader>>() {
return;
}
load_internal_asset!(app, VECTOR_SHADER_HANDLE, "vector.wgsl", Shader::from_wgsl);
load_internal_asset!(
app,
VECTOR_PARAM_SHADER_HANDLE,
"vector_param.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
VECTOR_SDF_SHADER_HANDLE,
"vector_sdf.wgsl",
Shader::from_wgsl
);
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app
.init_resource::<GeometryCache>()
.init_resource::<ExtractedShapes>()
.init_resource::<ExtractedParametrics>()
.init_resource::<ExtractedSdf>()
.init_resource::<ExtractedClips>()
.init_resource::<ExtractedShapeCache>()
.init_resource::<LayerTable>()
.init_resource::<GeometryKeys>()
.init_resource::<GradientAtlas>()
.init_resource::<VectorBuffers>()
.init_resource::<VectorViewBindGroups>()
.init_resource::<VectorPipeline>()
.add_systems(
ExtractSchedule,
(extract_clips, extract_shapes, extract_primitives).chain(),
)
.add_systems(
Render,
(
queue_vector_pipelines.in_set(RenderSystems::Queue),
prepare_vector_buffers.in_set(RenderSystems::PrepareResources),
prepare_parametrics.in_set(RenderSystems::PrepareResources),
prepare_sdf.in_set(RenderSystems::PrepareResources),
prepare_clips.in_set(RenderSystems::PrepareResources),
prepare_gradients.in_set(RenderSystems::PrepareResources),
prepare_view_bind_groups.in_set(RenderSystems::PrepareBindGroups),
),
)
.add_systems(Core2d, vector_pass.in_set(Core2dSystems::Prepass));
}
}
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
struct GpuVertex {
position: [f32; 2],
normal: [f32; 2],
coverage: f32,
}
#[repr(C)]
#[derive(Clone, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
struct GpuInstance {
linear: [f32; 4],
translation_z: [f32; 4],
color: [u8; 4],
brush_params: [f32; 4],
brush_meta: f32,
}
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
struct GpuClip {
inv_linear: [f32; 4],
inv_translation: [f32; 2],
half_extents: [f32; 2],
radius: f32,
_pad: [f32; 3],
}
const MAX_CLIPS: usize = 1024;
fn pack_clip(start: u32, count: u32) -> f32 {
(start * 8 + count.min(7)) as f32
}
#[derive(Resource, Default)]
pub struct ExtractedClips {
entries: Vec<GpuClip>,
chains: HashMap<Entity, (u32, u32)>,
}
#[derive(Resource, Default)]
pub struct GeometryKeys {
keys: HashMap<Entity, (Option<u64>, Option<u64>)>,
}
#[derive(Resource, Default)]
pub struct LayerTable {
masks: Vec<RenderLayers>,
}
impl LayerTable {
fn clear(&mut self) {
self.masks.clear();
}
fn intern(&mut self, layers: Option<&RenderLayers>) -> u16 {
if self.masks.is_empty() {
self.masks.push(RenderLayers::default());
}
match layers {
None => 0,
Some(layers) => match self.masks.iter().position(|m| m == layers) {
Some(index) => index as u16,
None => {
self.masks.push(layers.clone());
(self.masks.len() - 1) as u16
}
},
}
}
fn visible(&self, id: u16, view: &RenderLayers) -> bool {
self.masks
.get(id as usize)
.is_none_or(|mask| mask.intersects(view))
}
}
pub const GRADIENT_ATLAS_SIZE: u32 = 256;
pub const GRADIENT_ATLAS_ROWS: u32 = 1024;
#[derive(Resource, Default)]
pub struct GradientAtlas {
rows: HashMap<u64, u32>,
next_row: u32,
pending: Vec<(u32, Vec<u8>)>,
}
impl GradientAtlas {
fn ensure_row(&mut self, stops: &[crate::path::GradientStop]) -> Option<u32> {
use std::hash::{Hash, Hasher};
let mut hasher = tess::fast_hasher();
for stop in stops {
stop.offset.to_bits().hash(&mut hasher);
stop.color.red.to_bits().hash(&mut hasher);
stop.color.green.to_bits().hash(&mut hasher);
stop.color.blue.to_bits().hash(&mut hasher);
stop.color.alpha.to_bits().hash(&mut hasher);
}
let key = hasher.finish();
if let Some(&row) = self.rows.get(&key) {
return Some(row);
}
if self.next_row >= GRADIENT_ATLAS_ROWS {
return None;
}
let row = self.next_row;
self.next_row += 1;
self.rows.insert(key, row);
let mut sorted: Vec<_> = stops.to_vec();
sorted.sort_by(|a, b| a.offset.total_cmp(&b.offset));
let mut texels = Vec::with_capacity(GRADIENT_ATLAS_SIZE as usize * 4);
for i in 0..GRADIENT_ATLAS_SIZE {
let t = i as f32 / (GRADIENT_ATLAS_SIZE - 1) as f32;
let color = sample_stops(&sorted, t);
let srgba: Srgba = color.into();
texels.push((srgba.red.clamp(0.0, 1.0) * 255.0 + 0.5) as u8);
texels.push((srgba.green.clamp(0.0, 1.0) * 255.0 + 0.5) as u8);
texels.push((srgba.blue.clamp(0.0, 1.0) * 255.0 + 0.5) as u8);
texels.push((srgba.alpha.clamp(0.0, 1.0) * 255.0 + 0.5) as u8);
}
self.pending.push((row, texels));
Some(row)
}
}
fn sample_stops(sorted: &[crate::path::GradientStop], t: f32) -> LinearRgba {
match sorted {
[] => LinearRgba::WHITE,
[only] => only.color,
_ => {
if t <= sorted[0].offset {
return sorted[0].color;
}
for pair in sorted.windows(2) {
if t <= pair[1].offset {
let span = (pair[1].offset - pair[0].offset).max(1.0e-6);
let k = (t - pair[0].offset) / span;
return LinearRgba {
red: pair[0].color.red + (pair[1].color.red - pair[0].color.red) * k,
green: pair[0].color.green
+ (pair[1].color.green - pair[0].color.green) * k,
blue: pair[0].color.blue + (pair[1].color.blue - pair[0].color.blue) * k,
alpha: pair[0].color.alpha
+ (pair[1].color.alpha - pair[0].color.alpha) * k,
};
}
}
sorted.last().unwrap().color
}
}
}
fn resolve_brush(brush: &crate::path::Brush, atlas: &mut GradientAtlas) -> ([u8; 4], [f32; 4], f32) {
use crate::path::Brush;
match brush {
Brush::Solid(color) => (pack_color(*color), [0.0; 4], 0.0),
Brush::Linear { start, end, stops } => match atlas.ensure_row(stops) {
Some(row) => (
[255; 4],
[start.x, start.y, end.x, end.y],
(row * 4 + 1) as f32,
),
None => (
pack_color(stops.first().map(|s| s.color).unwrap_or(LinearRgba::WHITE)),
[0.0; 4],
0.0,
),
},
Brush::Radial { center, radius, stops } => match atlas.ensure_row(stops) {
Some(row) => (
[255; 4],
[center.x, center.y, *radius, 0.0],
(row * 4 + 2) as f32,
),
None => (
pack_color(stops.first().map(|s| s.color).unwrap_or(LinearRgba::WHITE)),
[0.0; 4],
0.0,
),
},
}
}
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
struct GpuParamInstance {
linear: [f32; 4],
translation_z: [f32; 4],
color: [u8; 4],
params: [f32; 4],
}
fn param_instance_buffer_layout() -> VertexBufferLayout {
let mut layout = VertexBufferLayout::from_vertex_formats(
VertexStepMode::Instance,
[
VertexFormat::Float32x4,
VertexFormat::Float32x4,
VertexFormat::Unorm8x4,
VertexFormat::Float32x4,
],
);
for (i, attribute) in layout.attributes.iter_mut().enumerate() {
attribute.shader_location = 3 + i as u32;
}
layout
}
fn canonical_arc_mesh() -> (Vec<GpuVertex>, Vec<u32>) {
const SEGMENTS: u32 = 64;
let n = SEGMENTS;
let mut vertices = Vec::with_capacity((4 * (n + 1) + 4) as usize);
for i in 0..=n {
let t = i as f32 / n as f32;
let tangential = if i == 0 {
-1.0
} else if i == n {
1.0
} else {
0.0
};
for side in [0.0f32, 1.0] {
let radial = if side == 0.0 { -1.0 } else { 1.0 };
vertices.push(GpuVertex {
position: [t, side],
normal: [radial, tangential],
coverage: 1.0,
});
}
}
let outer_ring = vertices.len() as u32;
for i in 0..=n {
let t = i as f32 / n as f32;
vertices.push(GpuVertex { position: [t, 1.0], normal: [1.0, 0.0], coverage: 0.0 });
}
let inner_ring = vertices.len() as u32;
for i in 0..=n {
let t = i as f32 / n as f32;
vertices.push(GpuVertex { position: [t, 0.0], normal: [-1.0, 0.0], coverage: 0.0 });
}
let caps = vertices.len() as u32;
vertices.push(GpuVertex { position: [0.0, 0.0], normal: [-1.0, -1.0], coverage: 0.0 });
vertices.push(GpuVertex { position: [0.0, 1.0], normal: [1.0, -1.0], coverage: 0.0 });
vertices.push(GpuVertex { position: [1.0, 0.0], normal: [-1.0, 1.0], coverage: 0.0 });
vertices.push(GpuVertex { position: [1.0, 1.0], normal: [1.0, 1.0], coverage: 0.0 });
let (inner_at, outer_at) = (|i: u32| i * 2, |i: u32| i * 2 + 1);
let mut indices = Vec::new();
for i in 0..n {
indices.extend_from_slice(&[
inner_at(i),
outer_at(i),
inner_at(i + 1),
inner_at(i + 1),
outer_at(i),
outer_at(i + 1),
]);
}
for i in 0..n {
indices.extend_from_slice(&[
outer_at(i),
outer_ring + i,
outer_at(i + 1),
outer_at(i + 1),
outer_ring + i,
outer_ring + i + 1,
]);
indices.extend_from_slice(&[
inner_ring + i,
inner_at(i),
inner_ring + i + 1,
inner_ring + i + 1,
inner_at(i),
inner_at(i + 1),
]);
}
indices.extend_from_slice(&[
inner_at(0), caps, outer_at(0), outer_at(0), caps, caps + 1,
]);
indices.extend_from_slice(&[
inner_at(n), caps + 2, outer_at(n), outer_at(n), caps + 2, caps + 3,
]);
(vertices, indices)
}
const ARC_INTERIOR_INDEX_COUNT: u32 = 64 * 6;
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
struct GpuDrawIndexedIndirect {
index_count: u32,
instance_count: u32,
first_index: u32,
base_vertex: i32,
first_instance: u32,
}
#[derive(ShaderType, Clone, Copy)]
struct VectorViewUniform {
clip_from_world: Mat4,
viewport: Vec4,
}
fn vertex_buffer_layout() -> VertexBufferLayout {
VertexBufferLayout::from_vertex_formats(
VertexStepMode::Vertex,
[
VertexFormat::Float32x2,
VertexFormat::Float32x2,
VertexFormat::Float32,
],
)
}
fn instance_buffer_layout() -> VertexBufferLayout {
let mut layout = VertexBufferLayout::from_vertex_formats(
VertexStepMode::Instance,
[
VertexFormat::Float32x4,
VertexFormat::Float32x4,
VertexFormat::Unorm8x4,
VertexFormat::Float32x4,
VertexFormat::Float32,
],
);
for (i, attribute) in layout.attributes.iter_mut().enumerate() {
attribute.shader_location = 3 + i as u32;
}
layout
}
#[derive(Clone, Copy)]
struct GeometryRange {
base_vertex: i32,
interior_first: u32,
interior_count: u32,
fringe_first: u32,
fringe_count: u32,
}
#[derive(Resource, Default)]
pub struct GeometryCache {
ranges: HashMap<u64, Option<GeometryRange>>,
vertices: Vec<GpuVertex>,
indices: Vec<u32>,
dirty: bool,
}
impl GeometryCache {
fn ensure(&mut self, key: u64, tessellate: impl FnOnce() -> Option<tess::TessellatedGeometry>) {
if self.ranges.contains_key(&key) {
return;
}
let range = tessellate().map(|geometry| {
let base_vertex = self.vertices.len() as i32;
self.vertices.extend(geometry.vertices.iter().map(|v| GpuVertex {
position: v.position,
normal: v.normal,
coverage: v.coverage,
}));
let interior_first = self.indices.len() as u32;
self.indices.extend_from_slice(&geometry.interior_indices);
let fringe_first = self.indices.len() as u32;
self.indices.extend_from_slice(&geometry.fringe_indices);
self.dirty = true;
GeometryRange {
base_vertex,
interior_first,
interior_count: geometry.interior_indices.len() as u32,
fringe_first,
fringe_count: geometry.fringe_indices.len() as u32,
}
});
self.ranges.insert(key, range);
}
}
#[derive(Clone, Copy)]
#[allow(dead_code)] enum GeometryRef {
Cached(u64),
Dynamic(GeometryRange),
}
#[derive(Clone)]
struct ExtractedInstance {
geometry: GeometryRef,
z: f32,
linear: [f32; 4],
translation: [f32; 2],
color: [u8; 4],
brush_params: [f32; 4],
brush_meta: f32,
opaque: bool,
clip: f32,
layer: u16,
}
#[derive(Resource, Default)]
pub struct ExtractedShapes {
items: Vec<ExtractedInstance>,
dynamic_vertices: Vec<GpuVertex>,
dynamic_indices: Vec<u32>,
}
#[derive(Resource, Default)]
struct ExtractedShapeCache {
items: HashMap<Entity, CachedShapeInstances>,
}
struct CachedShapeInstances {
clipped: bool,
items: Vec<ExtractedInstance>,
}
impl ExtractedShapes {
#[allow(dead_code)]
fn append_dynamic(&mut self, geometry: tess::TessellatedGeometry) -> GeometryRange {
let base_vertex = self.dynamic_vertices.len() as i32;
self.dynamic_vertices.extend(geometry.vertices.iter().map(|v| GpuVertex {
position: v.position,
normal: v.normal,
coverage: v.coverage,
}));
let interior_first = self.dynamic_indices.len() as u32;
self.dynamic_indices.extend_from_slice(&geometry.interior_indices);
let fringe_first = self.dynamic_indices.len() as u32;
self.dynamic_indices.extend_from_slice(&geometry.fringe_indices);
GeometryRange {
base_vertex,
interior_first,
interior_count: geometry.interior_indices.len() as u32,
fringe_first,
fringe_count: geometry.fringe_indices.len() as u32,
}
}
}
struct VectorBatch {
indices: Range<u32>,
base_vertex: i32,
instances: Range<u32>,
layer: u16,
}
struct ParamItem {
z: f32,
linear: [f32; 4],
translation: [f32; 2],
color: [u8; 4],
params: [f32; 4],
opaque: bool,
clip: f32,
layer: u16,
}
#[derive(Resource, Default)]
pub struct ExtractedParametrics(Vec<ParamItem>);
#[derive(Resource, Default)]
pub struct ExtractedSdf(Vec<ParamItem>);
#[derive(Resource, Default)]
pub struct VectorBuffers {
vertex: Option<Buffer>,
index: Option<Buffer>,
instance: Option<Buffer>,
instance_capacity: usize,
indirect: Option<Buffer>,
indirect_capacity: usize,
static_vertex_count: usize,
static_index_count: usize,
vertex_capacity: usize,
index_capacity: usize,
opaque_batches: Vec<VectorBatch>,
blend_batches: Vec<VectorBatch>,
use_multi_draw: bool,
layout_fingerprint: u64,
permutation: Vec<u32>,
prepared_instances: Vec<GpuInstance>,
param_vertex: Option<Buffer>,
param_index: Option<Buffer>,
param_instance: Option<Buffer>,
param_instance_capacity: usize,
param_opaque_count: u32,
param_total_count: u32,
param_layer_runs: Vec<(Range<u32>, u16)>,
sdf_vertex: Option<Buffer>,
sdf_index: Option<Buffer>,
sdf_instance: Option<Buffer>,
sdf_instance_capacity: usize,
sdf_opaque_count: u32,
sdf_total_count: u32,
sdf_layer_runs: Vec<(Range<u32>, u16)>,
clip: Option<Buffer>,
gradient_texture: Option<bevy::render::render_resource::Texture>,
gradient_view: Option<bevy::render::render_resource::TextureView>,
gradient_sampler: Option<bevy::render::render_resource::Sampler>,
}
#[derive(Default)]
struct ViewEntry {
uniform: UniformBuffer<VectorViewUniform>,
bind_group: Option<BindGroup>,
}
impl Default for VectorViewUniform {
fn default() -> Self {
Self { clip_from_world: Mat4::IDENTITY, viewport: Vec4::ONE }
}
}
#[derive(Resource, Default)]
pub struct VectorViewBindGroups {
per_view: HashMap<Entity, ViewEntry>,
}
#[derive(Resource)]
pub struct VectorPipeline {
view_layout: BindGroupLayoutDescriptor,
variants: HashMap<(TextureFormat, u32, bool), CachedRenderPipelineId>,
param_variants: HashMap<(TextureFormat, u32, bool), CachedRenderPipelineId>,
sdf_variants: HashMap<(TextureFormat, u32, bool), CachedRenderPipelineId>,
}
impl Default for VectorPipeline {
fn default() -> Self {
Self {
view_layout: BindGroupLayoutDescriptor::new(
"pf_vector_view_layout",
&BindGroupLayoutEntries::with_indices(
ShaderStages::VERTEX_FRAGMENT,
(
(0, uniform_buffer::<VectorViewUniform>(false)),
(1, bevy::render::render_resource::binding_types::storage_buffer_read_only_sized(false, None)),
(2, bevy::render::render_resource::binding_types::texture_2d(bevy::render::render_resource::TextureSampleType::Float { filterable: true })),
(3, bevy::render::render_resource::binding_types::sampler(bevy::render::render_resource::SamplerBindingType::Filtering)),
),
),
),
variants: HashMap::new(),
param_variants: HashMap::new(),
sdf_variants: HashMap::new(),
}
}
}
impl VectorPipeline {
fn ensure(
&mut self,
cache: &PipelineCache,
format: TextureFormat,
samples: u32,
opaque: bool,
) -> CachedRenderPipelineId {
*self
.variants
.entry((format, samples, opaque))
.or_insert_with(|| {
cache.queue_render_pipeline(RenderPipelineDescriptor {
label: Some(
if opaque { "pf_vector_opaque_pipeline" } else { "pf_vector_blend_pipeline" }
.into(),
),
layout: vec![self.view_layout.clone()],
vertex: VertexState {
shader: VECTOR_SHADER_HANDLE,
entry_point: Some("vertex".into()),
shader_defs: Vec::new(),
buffers: vec![vertex_buffer_layout(), instance_buffer_layout()],
},
fragment: Some(FragmentState {
shader: VECTOR_SHADER_HANDLE,
entry_point: Some("fragment".into()),
shader_defs: Vec::new(),
targets: vec![Some(ColorTargetState {
format,
blend: (!opaque).then_some(BlendState::ALPHA_BLENDING),
write_mask: ColorWrites::ALL,
})],
}),
primitive: PrimitiveState::default(),
depth_stencil: Some(DepthStencilState {
format: CORE_2D_DEPTH_FORMAT,
depth_write_enabled: Some(opaque),
depth_compare: Some(CompareFunction::GreaterEqual),
stencil: StencilState::default(),
bias: DepthBiasState::default(),
}),
multisample: MultisampleState {
count: samples,
mask: !0,
alpha_to_coverage_enabled: false,
},
immediate_size: 0,
zero_initialize_workgroup_memory: false,
})
})
}
fn ensure_param(
&mut self,
cache: &PipelineCache,
format: TextureFormat,
samples: u32,
opaque: bool,
) -> CachedRenderPipelineId {
let view_layout = self.view_layout.clone();
*self
.param_variants
.entry((format, samples, opaque))
.or_insert_with(|| {
cache.queue_render_pipeline(RenderPipelineDescriptor {
label: Some(
if opaque {
"pf_vector_param_opaque_pipeline"
} else {
"pf_vector_param_blend_pipeline"
}
.into(),
),
layout: vec![view_layout],
vertex: VertexState {
shader: VECTOR_PARAM_SHADER_HANDLE,
entry_point: Some("vertex".into()),
shader_defs: Vec::new(),
buffers: vec![vertex_buffer_layout(), param_instance_buffer_layout()],
},
fragment: Some(FragmentState {
shader: VECTOR_PARAM_SHADER_HANDLE,
entry_point: Some("fragment".into()),
shader_defs: Vec::new(),
targets: vec![Some(ColorTargetState {
format,
blend: (!opaque).then_some(BlendState::ALPHA_BLENDING),
write_mask: ColorWrites::ALL,
})],
}),
primitive: PrimitiveState::default(),
depth_stencil: Some(DepthStencilState {
format: CORE_2D_DEPTH_FORMAT,
depth_write_enabled: Some(opaque),
depth_compare: Some(CompareFunction::GreaterEqual),
stencil: StencilState::default(),
bias: DepthBiasState::default(),
}),
multisample: MultisampleState {
count: samples,
mask: !0,
alpha_to_coverage_enabled: false,
},
immediate_size: 0,
zero_initialize_workgroup_memory: false,
})
})
}
fn ensure_sdf(
&mut self,
cache: &PipelineCache,
format: TextureFormat,
samples: u32,
opaque: bool,
) -> CachedRenderPipelineId {
let view_layout = self.view_layout.clone();
*self
.sdf_variants
.entry((format, samples, opaque))
.or_insert_with(|| {
cache.queue_render_pipeline(RenderPipelineDescriptor {
label: Some(
if opaque { "pf_vector_sdf_opaque_pipeline" } else { "pf_vector_sdf_blend_pipeline" }
.into(),
),
layout: vec![view_layout],
vertex: VertexState {
shader: VECTOR_SDF_SHADER_HANDLE,
entry_point: Some("vertex".into()),
shader_defs: Vec::new(),
buffers: vec![vertex_buffer_layout(), param_instance_buffer_layout()],
},
fragment: Some(FragmentState {
shader: VECTOR_SDF_SHADER_HANDLE,
entry_point: Some("fragment".into()),
shader_defs: Vec::new(),
targets: vec![Some(ColorTargetState {
format,
blend: (!opaque).then_some(BlendState::ALPHA_BLENDING),
write_mask: ColorWrites::ALL,
})],
}),
primitive: PrimitiveState::default(),
depth_stencil: Some(DepthStencilState {
format: CORE_2D_DEPTH_FORMAT,
depth_write_enabled: Some(opaque),
depth_compare: Some(CompareFunction::GreaterEqual),
stencil: StencilState::default(),
bias: DepthBiasState::default(),
}),
multisample: MultisampleState {
count: samples,
mask: !0,
alpha_to_coverage_enabled: false,
},
immediate_size: 0,
zero_initialize_workgroup_memory: false,
})
})
}
fn get_sdf(
&self,
format: TextureFormat,
samples: u32,
opaque: bool,
) -> Option<CachedRenderPipelineId> {
self.sdf_variants.get(&(format, samples, opaque)).copied()
}
fn get(
&self,
format: TextureFormat,
samples: u32,
opaque: bool,
) -> Option<CachedRenderPipelineId> {
self.variants.get(&(format, samples, opaque)).copied()
}
fn get_param(
&self,
format: TextureFormat,
samples: u32,
opaque: bool,
) -> Option<CachedRenderPipelineId> {
self.param_variants.get(&(format, samples, opaque)).copied()
}
}
fn pack_color(color: LinearRgba) -> [u8; 4] {
let quantize = |v: f32| (v.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
[
quantize(color.red),
quantize(color.green),
quantize(color.blue),
quantize(color.alpha),
]
}
#[allow(clippy::too_many_arguments)]
fn extract_shapes(
shapes: Extract<
Query<
(
Entity,
Ref<VectorShape>,
Ref<GlobalTransform>,
Option<Ref<ClippedBy>>,
Option<Ref<RenderLayers>>,
),
Without<HudTransform>,
>,
>,
flat_shapes: Extract<
Query<(
Entity,
Ref<VectorShape>,
Ref<HudTransform>,
Option<Ref<ClippedBy>>,
Option<Ref<RenderLayers>>,
)>,
>,
painted: Extract<Res<VectorPainterQueue>>,
clips: Res<ExtractedClips>,
mut cache: ResMut<GeometryCache>,
mut extracted: ResMut<ExtractedShapes>,
mut extracted_cache: ResMut<ExtractedShapeCache>,
mut atlas: ResMut<GradientAtlas>,
mut layers: ResMut<LayerTable>,
mut keys: ResMut<GeometryKeys>,
) {
let live_last = extracted.items.len();
extracted.items.clear();
extracted.dynamic_vertices.clear();
extracted.dynamic_indices.clear();
layers.clear();
let flush_at = 8192.max(live_last.saturating_mul(4));
if cache.ranges.len() > flush_at {
*cache = GeometryCache::default();
keys.keys.clear();
extracted_cache.items.clear();
}
if keys.keys.len() > 65536 {
keys.keys.clear();
}
if extracted_cache.items.len() > 8192.max(live_last.saturating_mul(4)) {
extracted_cache.items.clear();
}
for (entity, shape, transform, clipped, shape_layers) in &shapes {
let layer = layers.intern(shape_layers.as_deref());
let changed = shape.is_changed()
|| transform.is_changed()
|| clipped.is_some()
|| shape_layers
.as_ref()
.is_some_and(|layers| layers.is_changed())
|| extracted_cache
.items
.get(&entity)
.is_none_or(|cached| {
cached.clipped != clipped.is_some()
|| cached.items.iter().any(|item| item.layer != layer)
});
if !changed {
extracted
.items
.extend_from_slice(&extracted_cache.items[&entity].items);
continue;
}
let model = transform.to_matrix();
let linear = [
model.x_axis.x,
model.x_axis.y,
model.y_axis.x,
model.y_axis.y,
];
let translation = [model.w_axis.x, model.w_axis.y];
let first = extracted.items.len();
push_shape(
&mut cache,
&mut extracted,
&mut atlas,
&clips,
&shape.commands,
&shape.style,
entity,
&mut keys,
shape.is_changed(),
linear,
translation,
model.w_axis.z,
clipped.as_deref(),
layer,
);
extracted_cache.items.insert(
entity,
CachedShapeInstances {
clipped: clipped.is_some(),
items: extracted.items[first..].to_vec(),
},
);
}
for (entity, shape, hud, clipped, shape_layers) in &flat_shapes {
let layer = layers.intern(shape_layers.as_deref());
let changed = shape.is_changed()
|| hud.is_changed()
|| clipped.is_some()
|| shape_layers
.as_ref()
.is_some_and(|layers| layers.is_changed())
|| extracted_cache
.items
.get(&entity)
.is_none_or(|cached| {
cached.clipped != clipped.is_some()
|| cached.items.iter().any(|item| item.layer != layer)
});
if !changed {
extracted
.items
.extend_from_slice(&extracted_cache.items[&entity].items);
continue;
}
let (linear, translation, z) = hud.decompose();
let first = extracted.items.len();
push_shape(
&mut cache,
&mut extracted,
&mut atlas,
&clips,
&shape.commands,
&shape.style,
entity,
&mut keys,
shape.is_changed(),
linear,
translation,
z,
clipped.as_deref(),
layer,
);
extracted_cache.items.insert(
entity,
CachedShapeInstances {
clipped: clipped.is_some(),
items: extracted.items[first..].to_vec(),
},
);
}
for item in &painted.shapes {
let layer = layers.intern(item.layers.as_ref());
push_shape(
&mut cache,
&mut extracted,
&mut atlas,
&clips,
&item.commands,
&item.style,
Entity::PLACEHOLDER,
&mut keys,
true,
item.linear,
item.translation,
item.z,
None,
layer,
);
}
}
#[allow(clippy::too_many_arguments)]
fn push_shape(
cache: &mut GeometryCache,
extracted: &mut ExtractedShapes,
atlas: &mut GradientAtlas,
clips: &ExtractedClips,
commands: &[crate::path::PathCommand],
style: &crate::path::PathStyle,
entity: Entity,
keys: &mut GeometryKeys,
changed: bool,
linear: [f32; 4],
translation: [f32; 2],
z: f32,
clipped: Option<&ClippedBy>,
layer: u16,
) {
let clip = clipped
.and_then(|c| clips.chains.get(&c.0))
.map(|&(start, count)| pack_clip(start, count))
.unwrap_or(0.0);
{
if let Some(brush) = &style.fill {
let rule = style.fill_rule;
let cached = (!changed)
.then(|| keys.keys.get(&entity).and_then(|k| k.0))
.flatten();
let key = match cached {
Some(key) => key,
None => {
let key = tess::fill_key(commands, rule);
keys.keys.entry(entity).or_default().0 = Some(key);
cache.ensure(key, || tess::tessellate_fill(commands, rule));
key
}
};
let geometry = Some(GeometryRef::Cached(key));
if let Some(geometry) = geometry {
let (color, brush_params, brush_meta) = resolve_brush(brush, atlas);
extracted.items.push(ExtractedInstance {
geometry,
z,
linear,
translation,
color,
brush_params,
brush_meta,
opaque: brush.is_opaque() && clip == 0.0,
clip,
layer,
});
}
}
if let Some(stroke) = &style.stroke {
let cached = (!changed)
.then(|| keys.keys.get(&entity).and_then(|k| k.1))
.flatten();
let key = match cached {
Some(key) => key,
None => {
let key = tess::stroke_key(commands, stroke);
keys.keys.entry(entity).or_default().1 = Some(key);
cache.ensure(key, || tess::tessellate_stroke(commands, stroke));
key
}
};
let geometry = Some(GeometryRef::Cached(key));
if let Some(geometry) = geometry {
let (color, brush_params, brush_meta) = resolve_brush(&stroke.brush, atlas);
extracted.items.push(ExtractedInstance {
geometry,
z: z + 1.0e-4,
linear,
translation,
color,
brush_params,
brush_meta,
opaque: stroke.brush.is_opaque() && clip == 0.0,
clip,
layer,
});
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn push_primitive(
extracted: &mut ExtractedParametrics,
sdf: &mut ExtractedSdf,
clips: &ExtractedClips,
primitive: &VectorPrimitive,
linear: [f32; 4],
translation: [f32; 2],
z: f32,
clipped: Option<&ClippedBy>,
layer: u16,
) {
let clip = clipped
.and_then(|c| clips.chains.get(&c.0))
.map(|&(start, count)| pack_clip(start, count))
.unwrap_or(0.0);
let (target, params, color) = match *primitive {
VectorPrimitive::Arc { inner, outer, start, sweep, color } => {
(&mut extracted.0, [start, sweep, inner, outer], color)
}
VectorPrimitive::Rect { size, radius, thickness, color } => (
&mut sdf.0,
[size.x, size.y, radius, thickness],
color,
),
};
target.push(ParamItem {
z,
linear,
translation,
color: pack_color(color),
params,
opaque: color.alpha >= 1.0
&& clip == 0.0
&& !matches!(*primitive, VectorPrimitive::Rect { thickness, .. } if thickness > 0.0),
clip,
layer,
});
}
fn queue_vector_pipelines(
mut pipeline: ResMut<VectorPipeline>,
cache: Res<PipelineCache>,
views: Query<(&ExtractedView, &Msaa)>,
) {
for (view, msaa) in &views {
for opaque in [true, false] {
pipeline.ensure(&cache, view.target_format, msaa.samples(), opaque);
pipeline.ensure_param(&cache, view.target_format, msaa.samples(), opaque);
pipeline.ensure_sdf(&cache, view.target_format, msaa.samples(), opaque);
}
}
}
fn extract_clips(
clips: Extract<
Query<
(Entity, &VectorClipShape, &GlobalTransform, Option<&ClippedBy>),
Without<HudTransform>,
>,
>,
flat_clips: Extract<Query<(Entity, &VectorClipShape, &HudTransform, Option<&ClippedBy>)>>,
mut extracted: ResMut<ExtractedClips>,
) {
extracted.entries.clear();
extracted.chains.clear();
struct ClipNode {
entry: GpuClip,
parent: Option<Entity>,
}
fn clip_node(
shape: &VectorClipShape,
linear: Mat2,
translation: Vec2,
parent: Option<&ClippedBy>,
) -> ClipNode {
let inverse = linear.inverse();
let inv_translation = -(inverse * translation);
let (half_extents, radius) = match *shape {
VectorClipShape::RoundedRect { half_extents, radius } => {
([half_extents.x - radius, half_extents.y - radius], radius)
}
VectorClipShape::Circle { radius } => ([0.0, 0.0], radius),
};
ClipNode {
entry: GpuClip {
inv_linear: inverse.to_cols_array(),
inv_translation: inv_translation.to_array(),
half_extents,
radius,
_pad: [0.0; 3],
},
parent: parent.map(|p| p.0),
}
}
let mut nodes: HashMap<Entity, ClipNode> = HashMap::default();
for (entity, shape, transform, parent) in &clips {
let model = transform.to_matrix();
let linear = Mat2::from_cols_array(&[
model.x_axis.x,
model.x_axis.y,
model.y_axis.x,
model.y_axis.y,
]);
let translation = Vec2::new(model.w_axis.x, model.w_axis.y);
nodes.insert(entity, clip_node(shape, linear, translation, parent));
}
for (entity, shape, hud, parent) in &flat_clips {
let (linear, translation, _z) = hud.decompose();
nodes.insert(
entity,
clip_node(
shape,
Mat2::from_cols_array(&linear),
Vec2::new(translation[0], translation[1]),
parent,
),
);
}
let heads: Vec<Entity> = nodes.keys().copied().collect();
for head in heads {
let start = extracted.entries.len() as u32;
if start as usize >= MAX_CLIPS {
break;
}
let mut count = 0u32;
let mut cursor = Some(head);
while let (Some(entity), true) = (cursor, count < 4) {
let Some(node) = nodes.get(&entity) else { break };
if extracted.entries.len() >= MAX_CLIPS {
break;
}
extracted.entries.push(node.entry);
count += 1;
cursor = node.parent;
}
extracted.chains.insert(head, (start, count));
}
}
fn extract_primitives(
primitives: Extract<
Query<
(&VectorPrimitive, &GlobalTransform, Option<&ClippedBy>, Option<&RenderLayers>),
Without<HudTransform>,
>,
>,
flat_primitives: Extract<
Query<(&VectorPrimitive, &HudTransform, Option<&ClippedBy>, Option<&RenderLayers>)>,
>,
painted: Extract<Res<VectorPainterQueue>>,
clips: Res<ExtractedClips>,
mut extracted: ResMut<ExtractedParametrics>,
mut sdf: ResMut<ExtractedSdf>,
mut layers: ResMut<LayerTable>,
) {
extracted.0.clear();
sdf.0.clear();
for (primitive, transform, clipped, item_layers) in &primitives {
let model = transform.to_matrix();
push_primitive(
&mut extracted,
&mut sdf,
&clips,
primitive,
[model.x_axis.x, model.x_axis.y, model.y_axis.x, model.y_axis.y],
[model.w_axis.x, model.w_axis.y],
model.w_axis.z,
clipped,
layers.intern(item_layers),
);
}
for (primitive, hud, clipped, item_layers) in &flat_primitives {
let (linear, translation, z) = hud.decompose();
push_primitive(
&mut extracted,
&mut sdf,
&clips,
primitive,
linear,
translation,
z,
clipped,
layers.intern(item_layers),
);
}
for arc in &painted.arcs {
let layer = layers.intern(arc.layers.as_ref());
extracted.0.push(ParamItem {
z: arc.z,
linear: arc.linear,
translation: arc.translation,
color: pack_color(arc.color),
params: [arc.start, arc.sweep, arc.inner, arc.outer],
opaque: arc.color.alpha >= 1.0,
clip: 0.0,
layer,
});
}
}
fn prepare_vector_buffers(
device: Res<RenderDevice>,
queue: Res<RenderQueue>,
mut cache: ResMut<GeometryCache>,
extracted: Res<ExtractedShapes>,
mut buffers: ResMut<VectorBuffers>,
) {
let dyn_vertex_count = extracted.dynamic_vertices.len();
let dyn_index_count = extracted.dynamic_indices.len();
let need_vertices = cache.vertices.len() + dyn_vertex_count;
let need_indices = cache.indices.len() + dyn_index_count;
let recreate = need_vertices > 0
&& (cache.dirty
|| buffers.vertex.is_none()
|| buffers.index.is_none()
|| buffers.vertex_capacity < need_vertices
|| buffers.index_capacity < need_indices
|| buffers.static_vertex_count != cache.vertices.len()
|| buffers.static_index_count != cache.indices.len());
if recreate {
let vertex_capacity = need_vertices + dyn_vertex_count.max(256);
let index_capacity = need_indices + dyn_index_count.max(1024);
let vertex = device.create_buffer(&BufferDescriptor {
label: Some("pf_vector_vertices"),
size: (vertex_capacity * size_of::<GpuVertex>()) as u64,
usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let index = device.create_buffer(&BufferDescriptor {
label: Some("pf_vector_indices"),
size: (index_capacity * size_of::<u32>()) as u64,
usage: BufferUsages::INDEX | BufferUsages::COPY_DST,
mapped_at_creation: false,
});
if !cache.vertices.is_empty() {
queue.write_buffer(&vertex, 0, bytemuck::cast_slice(&cache.vertices));
queue.write_buffer(&index, 0, bytemuck::cast_slice(&cache.indices));
}
buffers.vertex = Some(vertex);
buffers.index = Some(index);
buffers.vertex_capacity = vertex_capacity;
buffers.index_capacity = index_capacity;
buffers.static_vertex_count = cache.vertices.len();
buffers.static_index_count = cache.indices.len();
cache.dirty = false;
}
if dyn_vertex_count > 0 {
if let (Some(vertex), Some(index)) = (&buffers.vertex, &buffers.index) {
queue.write_buffer(
vertex,
(buffers.static_vertex_count * size_of::<GpuVertex>()) as u64,
bytemuck::cast_slice(&extracted.dynamic_vertices),
);
queue.write_buffer(
index,
(buffers.static_index_count * size_of::<u32>()) as u64,
bytemuck::cast_slice(&extracted.dynamic_indices),
);
}
}
let static_vertex_count = buffers.static_vertex_count as i32;
let static_index_count = buffers.static_index_count as u32;
let resolve = |geometry: &GeometryRef| -> Option<GeometryRange> {
match geometry {
GeometryRef::Cached(key) => cache.ranges.get(key).copied().flatten(),
GeometryRef::Dynamic(range) => Some(GeometryRange {
base_vertex: range.base_vertex + static_vertex_count,
interior_first: range.interior_first + static_index_count,
interior_count: range.interior_count,
fringe_first: range.fringe_first + static_index_count,
fringe_count: range.fringe_count,
}),
}
};
let group_key = |item: &ExtractedInstance| -> (u8, u64, u16) {
match &item.geometry {
GeometryRef::Cached(key) => (0, *key, item.layer),
GeometryRef::Dynamic(range) => (
1,
(u64::from(range.interior_first) << 32) | u64::from(range.base_vertex as u32),
item.layer,
),
}
};
let fingerprint = {
use std::hash::{Hash, Hasher};
let mut hasher = tess::fast_hasher();
extracted.items.len().hash(&mut hasher);
for item in &extracted.items {
group_key(item).hash(&mut hasher);
item.z.to_bits().hash(&mut hasher);
item.opaque.hash(&mut hasher);
item.clip.to_bits().hash(&mut hasher);
}
hasher.finish()
};
let gpu_instance = |item: &ExtractedInstance| GpuInstance {
linear: item.linear,
translation_z: [item.translation[0], item.translation[1], item.z, item.clip],
color: item.color,
brush_params: item.brush_params,
brush_meta: item.brush_meta,
};
if fingerprint == buffers.layout_fingerprint && !buffers.permutation.is_empty() {
if buffers.prepared_instances.len() == buffers.permutation.len() {
let mut dirty_start = None;
let mut dirty_end = 0;
for slot in 0..buffers.permutation.len() {
let item_index = buffers.permutation[slot];
let instance = gpu_instance(&extracted.items[item_index as usize]);
if buffers.prepared_instances[slot] != instance {
buffers.prepared_instances[slot] = instance;
dirty_start.get_or_insert(slot);
dirty_end = slot + 1;
}
}
if let Some(start) = dirty_start {
if let Some(buffer) = &buffers.instance {
let bytes = bytemuck::cast_slice(&buffers.prepared_instances[start..dirty_end]);
queue.write_buffer(
buffer,
(start * size_of::<GpuInstance>()) as u64,
bytes,
);
}
}
return;
}
}
buffers.layout_fingerprint = fingerprint;
buffers.permutation.clear();
buffers.opaque_batches.clear();
buffers.blend_batches.clear();
let mut instances: Vec<GpuInstance> = Vec::with_capacity(extracted.items.len() * 2);
let push_instance = |instances: &mut Vec<GpuInstance>,
permutation: &mut Vec<u32>,
item_index: usize,
item: &ExtractedInstance|
-> u32 {
let index = instances.len() as u32;
instances.push(gpu_instance(item));
permutation.push(item_index as u32);
index
};
let mut opaque_order: Vec<usize> = (0..extracted.items.len())
.filter(|&i| extracted.items[i].opaque)
.collect();
let mut group_front: HashMap<(u8, u64, u16), f32> = HashMap::default();
for &i in &opaque_order {
let item = &extracted.items[i];
let entry = group_front.entry(group_key(item)).or_insert(item.z);
*entry = entry.max(item.z);
}
opaque_order.sort_unstable_by(|&a, &b| {
let (ia, ib) = (&extracted.items[a], &extracted.items[b]);
let (ka, kb) = (group_key(ia), group_key(ib));
group_front[&kb]
.total_cmp(&group_front[&ka])
.then(ka.cmp(&kb))
.then(ib.z.total_cmp(&ia.z))
});
let mut last_geometry: Option<(u8, u64, u16)> = None;
for &item_index in &opaque_order {
let item = &extracted.items[item_index];
let Some(range) = resolve(&item.geometry) else {
continue;
};
if range.interior_count == 0 {
continue;
}
let index = push_instance(&mut instances, &mut buffers.permutation, item_index, item);
let key = group_key(item);
if last_geometry == Some(key) {
buffers.opaque_batches.last_mut().unwrap().instances.end = index + 1;
} else {
buffers.opaque_batches.push(VectorBatch {
indices: range.interior_first..range.interior_first + range.interior_count,
base_vertex: range.base_vertex,
instances: index..index + 1,
layer: item.layer,
});
last_geometry = Some(key);
}
}
let mut blend_order: Vec<(usize, bool)> = Vec::new();
let mut z_sorted: Vec<usize> = (0..extracted.items.len()).collect();
z_sorted.sort_by(|&a, &b| extracted.items[a].z.total_cmp(&extracted.items[b].z));
for &item_index in &z_sorted {
if !extracted.items[item_index].opaque {
blend_order.push((item_index, false));
}
blend_order.push((item_index, true));
}
let mut last_key: Option<((u8, u64, u16), bool)> = None;
for &(item_index, is_fringe) in &blend_order {
let item = &extracted.items[item_index];
let Some(range) = resolve(&item.geometry) else {
continue;
};
let (first, count) = if is_fringe {
(range.fringe_first, range.fringe_count)
} else {
(range.interior_first, range.interior_count)
};
if count == 0 {
continue;
}
let index = push_instance(&mut instances, &mut buffers.permutation, item_index, item);
let key = (group_key(item), is_fringe);
if last_key == Some(key) {
buffers.blend_batches.last_mut().unwrap().instances.end = index + 1;
} else {
buffers.blend_batches.push(VectorBatch {
indices: first..first + count,
base_vertex: range.base_vertex,
instances: index..index + 1,
layer: item.layer,
});
last_key = Some(key);
}
}
if instances.is_empty() {
buffers.prepared_instances.clear();
return;
}
let bytes: &[u8] = bytemuck::cast_slice(&instances);
if buffers.instance.is_none() || buffers.instance_capacity < bytes.len() {
buffers.instance = Some(device.create_buffer_with_data(&BufferInitDescriptor {
label: Some("pf_vector_instances"),
contents: bytes,
usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
}));
buffers.instance_capacity = bytes.len();
} else if let Some(buffer) = &buffers.instance {
queue.write_buffer(buffer, 0, bytes);
}
buffers.prepared_instances = instances;
buffers.use_multi_draw = device
.features()
.contains(WgpuFeatures::INDIRECT_FIRST_INSTANCE);
if buffers.use_multi_draw {
let args: Vec<GpuDrawIndexedIndirect> = buffers
.opaque_batches
.iter()
.chain(buffers.blend_batches.iter())
.map(|batch| GpuDrawIndexedIndirect {
index_count: batch.indices.end - batch.indices.start,
instance_count: batch.instances.end - batch.instances.start,
first_index: batch.indices.start,
base_vertex: batch.base_vertex,
first_instance: batch.instances.start,
})
.collect();
let bytes: &[u8] = bytemuck::cast_slice(&args);
if buffers.indirect.is_none() || buffers.indirect_capacity < bytes.len() {
buffers.indirect = Some(device.create_buffer_with_data(&BufferInitDescriptor {
label: Some("pf_vector_indirect"),
contents: bytes,
usage: BufferUsages::INDIRECT | BufferUsages::COPY_DST,
}));
buffers.indirect_capacity = bytes.len();
} else if let Some(buffer) = &buffers.indirect {
queue.write_buffer(buffer, 0, bytes);
}
}
}
fn prepare_parametrics(
device: Res<RenderDevice>,
queue: Res<RenderQueue>,
mut extracted: ResMut<ExtractedParametrics>,
mut buffers: ResMut<VectorBuffers>,
) {
if extracted.0.is_empty() {
buffers.param_total_count = 0;
buffers.param_opaque_count = 0;
buffers.param_layer_runs.clear();
return;
}
if buffers.param_vertex.is_none() {
let (vertices, indices) = canonical_arc_mesh();
buffers.param_vertex = Some(device.create_buffer_with_data(&BufferInitDescriptor {
label: Some("pf_vector_param_vertices"),
contents: bytemuck::cast_slice(&vertices),
usage: BufferUsages::VERTEX,
}));
buffers.param_index = Some(device.create_buffer_with_data(&BufferInitDescriptor {
label: Some("pf_vector_param_indices"),
contents: bytemuck::cast_slice(&indices),
usage: BufferUsages::INDEX,
}));
}
extracted.0.sort_unstable_by(|a, b| {
b.opaque
.cmp(&a.opaque)
.then_with(|| if a.opaque { a.layer.cmp(&b.layer) } else { std::cmp::Ordering::Equal })
.then(a.z.total_cmp(&b.z))
});
buffers.param_layer_runs.clear();
for (i, item) in extracted.0.iter().enumerate() {
match buffers.param_layer_runs.last_mut() {
Some((range, layer)) if *layer == item.layer => range.end = i as u32 + 1,
_ => buffers.param_layer_runs.push((i as u32..i as u32 + 1, item.layer)),
}
}
let instances: Vec<GpuParamInstance> = extracted
.0
.iter()
.map(|item| GpuParamInstance {
linear: item.linear,
translation_z: [item.translation[0], item.translation[1], item.z, item.clip],
color: item.color,
params: item.params,
})
.collect();
buffers.param_total_count = instances.len() as u32;
buffers.param_opaque_count = extracted.0.iter().filter(|i| i.opaque).count() as u32;
let bytes: &[u8] = bytemuck::cast_slice(&instances);
if buffers.param_instance.is_none() || buffers.param_instance_capacity < bytes.len() {
buffers.param_instance = Some(device.create_buffer_with_data(&BufferInitDescriptor {
label: Some("pf_vector_param_instances"),
contents: bytes,
usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
}));
buffers.param_instance_capacity = bytes.len();
} else if let Some(buffer) = &buffers.param_instance {
queue.write_buffer(buffer, 0, bytes);
}
}
fn prepare_sdf(
device: Res<RenderDevice>,
queue: Res<RenderQueue>,
mut extracted: ResMut<ExtractedSdf>,
mut buffers: ResMut<VectorBuffers>,
) {
if extracted.0.is_empty() {
buffers.sdf_total_count = 0;
buffers.sdf_opaque_count = 0;
buffers.sdf_layer_runs.clear();
return;
}
if buffers.sdf_vertex.is_none() {
let corners = [[-1.0f32, -1.0], [1.0, -1.0], [1.0, 1.0], [-1.0, 1.0]];
let vertices: Vec<GpuVertex> = corners
.iter()
.map(|&position| GpuVertex { position, normal: [0.0, 0.0], coverage: 1.0 })
.collect();
buffers.sdf_vertex = Some(device.create_buffer_with_data(&BufferInitDescriptor {
label: Some("pf_vector_sdf_vertices"),
contents: bytemuck::cast_slice(&vertices),
usage: BufferUsages::VERTEX,
}));
let indices: [u32; 6] = [0, 1, 2, 0, 2, 3];
buffers.sdf_index = Some(device.create_buffer_with_data(&BufferInitDescriptor {
label: Some("pf_vector_sdf_indices"),
contents: bytemuck::cast_slice(&indices),
usage: BufferUsages::INDEX,
}));
}
extracted.0.sort_unstable_by(|a, b| {
b.opaque
.cmp(&a.opaque)
.then_with(|| if a.opaque { a.layer.cmp(&b.layer) } else { std::cmp::Ordering::Equal })
.then(a.z.total_cmp(&b.z))
});
buffers.sdf_layer_runs.clear();
for (i, item) in extracted.0.iter().enumerate() {
match buffers.sdf_layer_runs.last_mut() {
Some((range, layer)) if *layer == item.layer => range.end = i as u32 + 1,
_ => buffers.sdf_layer_runs.push((i as u32..i as u32 + 1, item.layer)),
}
}
let instances: Vec<GpuParamInstance> = extracted
.0
.iter()
.map(|item| GpuParamInstance {
linear: item.linear,
translation_z: [item.translation[0], item.translation[1], item.z, item.clip],
color: item.color,
params: item.params,
})
.collect();
buffers.sdf_total_count = instances.len() as u32;
buffers.sdf_opaque_count = extracted.0.iter().filter(|i| i.opaque).count() as u32;
let bytes: &[u8] = bytemuck::cast_slice(&instances);
if buffers.sdf_instance.is_none() || buffers.sdf_instance_capacity < bytes.len() {
buffers.sdf_instance = Some(device.create_buffer_with_data(&BufferInitDescriptor {
label: Some("pf_vector_sdf_instances"),
contents: bytes,
usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
}));
buffers.sdf_instance_capacity = bytes.len();
} else if let Some(buffer) = &buffers.sdf_instance {
queue.write_buffer(buffer, 0, bytes);
}
}
fn prepare_clips(
device: Res<RenderDevice>,
queue: Res<RenderQueue>,
extracted: Res<ExtractedClips>,
mut buffers: ResMut<VectorBuffers>,
) {
let buffer = buffers.clip.get_or_insert_with(|| {
device.create_buffer(&BufferDescriptor {
label: Some("pf_vector_clips"),
size: (MAX_CLIPS * size_of::<GpuClip>()) as u64,
usage: BufferUsages::STORAGE | BufferUsages::COPY_DST,
mapped_at_creation: false,
})
});
if !extracted.entries.is_empty() {
queue.write_buffer(buffer, 0, bytemuck::cast_slice(&extracted.entries));
}
if std::env::var("PF_DEBUG_CLIPS").is_ok() {
eprintln!(
"clips: {} entries, {} chains",
extracted.entries.len(),
extracted.chains.len()
);
if let Some(e) = extracted.entries.first() {
eprintln!(
"first entry: inv={:?} t={:?} half={:?} r={}",
e.inv_linear, e.inv_translation, e.half_extents, e.radius
);
}
}
}
fn prepare_gradients(
device: Res<RenderDevice>,
queue: Res<RenderQueue>,
mut atlas: ResMut<GradientAtlas>,
mut buffers: ResMut<VectorBuffers>,
) {
use bevy::render::render_resource::{
AddressMode, Extent3d, FilterMode, Origin3d, SamplerDescriptor, TexelCopyBufferLayout,
TexelCopyTextureInfo, TextureAspect, TextureDescriptor, TextureDimension, TextureUsages,
};
if buffers.gradient_texture.is_none() {
let texture = device.create_texture(&TextureDescriptor {
label: Some("pf_vector_gradients"),
size: Extent3d {
width: GRADIENT_ATLAS_SIZE,
height: GRADIENT_ATLAS_ROWS,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: TextureFormat::Rgba8UnormSrgb,
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
view_formats: &[],
});
buffers.gradient_view = Some(texture.create_view(&Default::default()));
buffers.gradient_texture = Some(texture);
buffers.gradient_sampler = Some(device.create_sampler(&SamplerDescriptor {
label: Some("pf_vector_gradient_sampler"),
address_mode_u: AddressMode::ClampToEdge,
address_mode_v: AddressMode::ClampToEdge,
mag_filter: FilterMode::Linear,
min_filter: FilterMode::Linear,
..Default::default()
}));
}
if let Some(texture) = &buffers.gradient_texture {
for (row, texels) in atlas.pending.drain(..) {
queue.write_texture(
TexelCopyTextureInfo {
texture,
mip_level: 0,
origin: Origin3d { x: 0, y: row, z: 0 },
aspect: TextureAspect::All,
},
&texels,
TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(GRADIENT_ATLAS_SIZE * 4),
rows_per_image: None,
},
Extent3d { width: GRADIENT_ATLAS_SIZE, height: 1, depth_or_array_layers: 1 },
);
}
}
}
fn prepare_view_bind_groups(
device: Res<RenderDevice>,
queue: Res<RenderQueue>,
pipeline: Res<VectorPipeline>,
buffers: Res<VectorBuffers>,
views: Query<(Entity, &ExtractedView)>,
mut bind_groups: ResMut<VectorViewBindGroups>,
) {
let (Some(clip_buffer), Some(gradient_view), Some(gradient_sampler)) =
(&buffers.clip, &buffers.gradient_view, &buffers.gradient_sampler)
else {
return;
};
for (entity, view) in &views {
let clip_from_world = view.clip_from_world.unwrap_or_else(|| {
view.clip_from_view * view.world_from_view.to_matrix().inverse()
});
let viewport = view.viewport.as_vec4();
let entry = bind_groups.per_view.entry(entity).or_default();
entry.uniform.set(VectorViewUniform { clip_from_world, viewport });
entry.uniform.write_buffer(&device, &queue);
if entry.bind_group.is_none() {
let layout = device
.create_bind_group_layout("pf_vector_view_layout", &pipeline.view_layout.entries);
entry.bind_group = Some(device.create_bind_group(
"pf_vector_view_bind_group",
&layout,
&BindGroupEntries::with_indices((
(0, entry.uniform.binding().unwrap()),
(1, clip_buffer.as_entire_binding()),
(2, gradient_view),
(3, gradient_sampler),
)),
));
}
}
}
fn vector_pass(
pipeline: Res<VectorPipeline>,
pipeline_cache: Res<PipelineCache>,
buffers: Res<VectorBuffers>,
bind_groups: Res<VectorViewBindGroups>,
layer_table: Res<LayerTable>,
view: ViewQuery<(
&ExtractedCamera,
&ExtractedView,
&ViewTarget,
&ViewDepthTexture,
&Msaa,
Option<&RenderLayers>,
)>,
mut ctx: RenderContext,
) {
let has_tessellated = (!buffers.opaque_batches.is_empty()
|| !buffers.blend_batches.is_empty())
&& buffers.vertex.is_some()
&& buffers.index.is_some()
&& buffers.instance.is_some();
let has_sdf = buffers.sdf_total_count > 0
&& buffers.sdf_vertex.is_some()
&& buffers.sdf_index.is_some()
&& buffers.sdf_instance.is_some();
let has_params = buffers.param_total_count > 0
&& buffers.param_vertex.is_some()
&& buffers.param_index.is_some()
&& buffers.param_instance.is_some();
if !has_tessellated && !has_params && !has_sdf {
return;
}
let view_entity = view.entity();
let (camera, extracted_view, target, depth, msaa, view_layers) = view.into_inner();
let view_mask = view_layers.cloned().unwrap_or_default();
let get = |id: Option<CachedRenderPipelineId>| {
id.and_then(|id| pipeline_cache.get_render_pipeline(id))
};
let (format, samples) = (extracted_view.target_format, msaa.samples());
let opaque_pipeline = get(pipeline.get(format, samples, true));
let blend_pipeline = get(pipeline.get(format, samples, false));
let param_opaque_pipeline = get(pipeline.get_param(format, samples, true));
let param_blend_pipeline = get(pipeline.get_param(format, samples, false));
let sdf_opaque_pipeline = get(pipeline.get_sdf(format, samples, true));
let sdf_blend_pipeline = get(pipeline.get_sdf(format, samples, false));
let sdf_ready = has_sdf && sdf_opaque_pipeline.is_some() && sdf_blend_pipeline.is_some();
let tess_ready = has_tessellated && opaque_pipeline.is_some() && blend_pipeline.is_some();
let params_ready =
has_params && param_opaque_pipeline.is_some() && param_blend_pipeline.is_some();
if !tess_ready && !params_ready && !sdf_ready {
return;
}
let Some(bind_group) = bind_groups
.per_view
.get(&view_entity)
.and_then(|entry| entry.bind_group.as_ref())
else {
return;
};
let diagnostics = ctx.diagnostic_recorder();
let diagnostics = diagnostics.as_deref();
let mut pass = ctx.begin_tracked_render_pass(RenderPassDescriptor {
label: Some("pf_vector_pass"),
color_attachments: &[Some(target.get_color_attachment())],
depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)),
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
let pass_span = diagnostics.pass_span(&mut pass, "vector_pass");
if let Some(viewport) = camera.viewport.as_ref() {
pass.set_camera_viewport(viewport);
}
pass.set_bind_group(0, bind_group, &[]);
let indirect = buffers.use_multi_draw.then(|| buffers.indirect.as_ref()).flatten();
let args_size = size_of::<GpuDrawIndexedIndirect>() as u64;
macro_rules! bind_tessellated {
() => {{
pass.set_vertex_buffer(0, buffers.vertex.as_ref().unwrap().slice(..));
pass.set_vertex_buffer(1, buffers.instance.as_ref().unwrap().slice(..));
pass.set_index_buffer(
buffers.index.as_ref().unwrap().slice(..),
IndexFormat::Uint32,
);
}};
}
macro_rules! bind_sdf {
() => {{
pass.set_vertex_buffer(0, buffers.sdf_vertex.as_ref().unwrap().slice(..));
pass.set_vertex_buffer(1, buffers.sdf_instance.as_ref().unwrap().slice(..));
pass.set_index_buffer(
buffers.sdf_index.as_ref().unwrap().slice(..),
IndexFormat::Uint32,
);
}};
}
macro_rules! bind_params {
() => {{
pass.set_vertex_buffer(0, buffers.param_vertex.as_ref().unwrap().slice(..));
pass.set_vertex_buffer(1, buffers.param_instance.as_ref().unwrap().slice(..));
pass.set_index_buffer(
buffers.param_index.as_ref().unwrap().slice(..),
IndexFormat::Uint32,
);
}};
}
macro_rules! draw_filtered {
($batches:expr, $args_base:expr) => {{
let batches = $batches;
match indirect {
Some(indirect) => {
let mut i = 0usize;
while i < batches.len() {
if !layer_table.visible(batches[i].layer, &view_mask) {
i += 1;
continue;
}
let start = i;
while i < batches.len()
&& layer_table.visible(batches[i].layer, &view_mask)
{
i += 1;
}
pass.multi_draw_indexed_indirect(
indirect,
($args_base + start) as u64 * args_size,
(i - start) as u32,
);
}
}
None => {
for batch in batches {
if !layer_table.visible(batch.layer, &view_mask) {
continue;
}
pass.draw_indexed(
batch.indices.clone(),
batch.base_vertex,
batch.instances.clone(),
);
}
}
}
}};
}
if tess_ready && !buffers.opaque_batches.is_empty() {
bind_tessellated!();
pass.set_render_pipeline(opaque_pipeline.unwrap());
draw_filtered!(&buffers.opaque_batches, 0);
}
if sdf_ready && buffers.sdf_opaque_count > 0 {
bind_sdf!();
pass.set_render_pipeline(sdf_opaque_pipeline.unwrap());
for (range, layer) in &buffers.sdf_layer_runs {
let run = range.start..range.end.min(buffers.sdf_opaque_count);
if run.start < run.end && layer_table.visible(*layer, &view_mask) {
pass.draw_indexed(0..6, 0, run);
}
}
}
if params_ready && buffers.param_opaque_count > 0 {
bind_params!();
pass.set_render_pipeline(param_opaque_pipeline.unwrap());
for (range, layer) in &buffers.param_layer_runs {
let run = range.start..range.end.min(buffers.param_opaque_count);
if run.start < run.end && layer_table.visible(*layer, &view_mask) {
pass.draw_indexed(0..ARC_INTERIOR_INDEX_COUNT, 0, run);
}
}
}
if tess_ready && !buffers.blend_batches.is_empty() {
bind_tessellated!();
pass.set_render_pipeline(blend_pipeline.unwrap());
draw_filtered!(&buffers.blend_batches, buffers.opaque_batches.len());
}
if params_ready {
let total = buffers.param_total_count;
bind_params!();
pass.set_render_pipeline(param_blend_pipeline.unwrap());
for (range, layer) in &buffers.param_layer_runs {
let run = range.start.max(buffers.param_opaque_count)..range.end.min(total);
if run.start < run.end && layer_table.visible(*layer, &view_mask) {
pass.draw_indexed(0..ARC_INTERIOR_INDEX_COUNT, 0, run);
}
}
let index_count: u32 = (64 * 6) + (64 * 12) + 12;
for (range, layer) in &buffers.param_layer_runs {
if layer_table.visible(*layer, &view_mask) {
pass.draw_indexed(ARC_INTERIOR_INDEX_COUNT..index_count, 0, range.clone());
}
}
}
if sdf_ready {
bind_sdf!();
pass.set_render_pipeline(sdf_blend_pipeline.unwrap());
for (range, layer) in &buffers.sdf_layer_runs {
let run = range.start.max(buffers.sdf_opaque_count)..range.end;
if run.start < run.end && layer_table.visible(*layer, &view_mask) {
pass.draw_indexed(0..6, 0, run);
}
}
}
pass_span.end(&mut pass);
}