use crate::pipelines::path::{
bounded_geometry_capacity, tri_instance_layout, GeometryCapacityError,
GeometryUploadProfile, TriInstance,
};
fn mask_stencil_state(op: wgpu::StencilOperation) -> wgpu::DepthStencilState {
wgpu::DepthStencilState {
format: wgpu::TextureFormat::Stencil8,
depth_write_enabled: None,
depth_compare: None,
stencil: wgpu::StencilState {
front: wgpu::StencilFaceState {
compare: wgpu::CompareFunction::Equal,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: op,
},
back: wgpu::StencilFaceState {
compare: wgpu::CompareFunction::Equal,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: op,
},
read_mask: 0xFF,
write_mask: 0xFF,
},
bias: wgpu::DepthBiasState::default(),
}
}
const INITIAL_CAPACITY: usize = 64;
fn make_instance_buffer(device: &wgpu::Device, capacity: usize) -> wgpu::Buffer {
device.create_buffer(&wgpu::BufferDescriptor {
label: Some("uzor_urx_wgpu.native_stencil_mask_buffer"),
size: (capacity * std::mem::size_of::<TriInstance>()) as wgpu::BufferAddress,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
})
}
pub(crate) struct StencilMaskPipeline {
increment: wgpu::RenderPipeline,
decrement: wgpu::RenderPipeline,
buffer: wgpu::Buffer,
capacity: usize,
}
impl StencilMaskPipeline {
pub(crate) fn new(
device: &wgpu::Device,
format: wgpu::TextureFormat,
sample_count: u32,
uniform_bgl: &wgpu::BindGroupLayout,
) -> Self {
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("uzor_urx_wgpu.stencil_mask_shader_native"),
source: wgpu::ShaderSource::Wgsl(crate::shaders::STENCIL_MASK_SHADER_NATIVE.into()),
});
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("uzor_urx_wgpu.stencil_mask_pipeline_layout_native"),
bind_group_layouts: &[Some(uniform_bgl)],
immediate_size: 0,
});
let make = |op: wgpu::StencilOperation| {
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("uzor_urx_wgpu.stencil_mask_pipeline_native"),
layout: Some(&layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[tri_instance_layout()],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format,
blend: None,
write_mask: wgpu::ColorWrites::empty(),
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
..Default::default()
},
depth_stencil: Some(mask_stencil_state(op)),
multisample: wgpu::MultisampleState { count: sample_count, ..Default::default() },
multiview_mask: None,
cache: None,
})
};
let increment = make(wgpu::StencilOperation::IncrementClamp);
let decrement = make(wgpu::StencilOperation::DecrementClamp);
let buffer = make_instance_buffer(device, INITIAL_CAPACITY);
Self { increment, decrement, buffer, capacity: INITIAL_CAPACITY }
}
pub(crate) fn upload(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
data: &[TriInstance],
) -> Result<GeometryUploadProfile, GeometryCapacityError> {
if data.is_empty() {
return Ok(GeometryUploadProfile {
capacity_before_instances: self.capacity,
capacity_after_instances: self.capacity,
..GeometryUploadProfile::default()
});
}
let needed = data.len();
let profile = crate::profile::enabled();
crate::profile::stage(
"gpu_buffer_reserve_begin",
format_args!(
"buffer=stencil_mask requested_instances={} requested_bytes={} current_capacity_instances={}",
needed,
needed.saturating_mul(std::mem::size_of::<TriInstance>()),
self.capacity,
),
);
let capacity_before = self.capacity;
let reserve_t0 = profile.then(std::time::Instant::now);
let capacity =
bounded_geometry_capacity(self.capacity, needed, INITIAL_CAPACITY, "stencil_mask")?;
if capacity != self.capacity {
self.buffer = make_instance_buffer(device, capacity);
self.capacity = capacity;
}
let reserve_us = reserve_t0.map_or(0, |started| started.elapsed().as_micros());
let upload_bytes = needed.saturating_mul(std::mem::size_of::<TriInstance>());
crate::profile::stage(
"gpu_buffer_upload_begin",
format_args!(
"buffer=stencil_mask upload_bytes={} capacity_instances={}",
upload_bytes,
self.capacity,
),
);
let upload_t0 = profile.then(std::time::Instant::now);
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(data));
let upload_us = upload_t0.map_or(0, |started| started.elapsed().as_micros());
Ok(GeometryUploadProfile {
upload_bytes,
capacity_before_instances: capacity_before,
capacity_after_instances: self.capacity,
growth_bytes: self
.capacity
.saturating_sub(capacity_before)
.saturating_mul(std::mem::size_of::<TriInstance>()),
reserve_us,
upload_us,
})
}
pub(crate) fn bind_increment(&self, pass: &mut wgpu::RenderPass<'_>, gate_ref: u32) {
pass.set_pipeline(&self.increment);
pass.set_stencil_reference(gate_ref);
pass.set_vertex_buffer(0, self.buffer.slice(..));
}
pub(crate) fn bind_decrement(&self, pass: &mut wgpu::RenderPass<'_>, gate_ref: u32) {
pass.set_pipeline(&self.decrement);
pass.set_stencil_reference(gate_ref);
pass.set_vertex_buffer(0, self.buffer.slice(..));
}
pub(crate) fn draw_range(&self, pass: &mut wgpu::RenderPass<'_>, start: u32, count: u32) {
pass.draw(0..3, start..(start + count));
}
}