use bytemuck::{Pod, Zeroable};
#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
pub(crate) struct BlendCompositeInstance {
pub alpha: f32,
pub _pad: [f32; 3],
pub clip_rect: [f32; 4],
}
const _: () = assert!(
std::mem::size_of::<BlendCompositeInstance>() == 32,
"BlendCompositeInstance must stay 32 bytes — WGSL struct in shaders.rs mirrors this layout"
);
pub(crate) fn blend_composite_instance_layout() -> wgpu::VertexBufferLayout<'static> {
use wgpu::VertexFormat::*;
static ATTRS: &[wgpu::VertexAttribute] = &[
wgpu::VertexAttribute { shader_location: 0, format: Float32, offset: 0 },
wgpu::VertexAttribute { shader_location: 1, format: Float32x3, offset: 4 },
wgpu::VertexAttribute { shader_location: 2, format: Float32x4, offset: 16 },
];
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<BlendCompositeInstance>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Instance,
attributes: ATTRS,
}
}
const INITIAL_CAPACITY: usize = 16;
fn make_instance_buffer(device: &wgpu::Device, capacity: usize) -> wgpu::Buffer {
device.create_buffer(&wgpu::BufferDescriptor {
label: Some("uzor_urx_wgpu.native_blend_composite_buffer"),
size: (capacity * std::mem::size_of::<BlendCompositeInstance>()) as wgpu::BufferAddress,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
})
}
pub(crate) struct BlendCompositePipeline {
pipeline_off: wgpu::RenderPipeline,
pipeline_test: wgpu::RenderPipeline,
buffer: wgpu::Buffer,
capacity: usize,
}
impl BlendCompositePipeline {
pub(crate) fn new(
device: &wgpu::Device,
format: wgpu::TextureFormat,
sample_count: u32,
uniform_bgl: &wgpu::BindGroupLayout,
layer_resolve_bgl: &wgpu::BindGroupLayout,
) -> Self {
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("uzor_urx_wgpu.blend_composite_shader_native"),
source: wgpu::ShaderSource::Wgsl(crate::shaders::BLEND_COMPOSITE_SHADER_NATIVE.into()),
});
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("uzor_urx_wgpu.blend_composite_pipeline_layout_native"),
bind_group_layouts: &[Some(uniform_bgl), Some(layer_resolve_bgl)],
immediate_size: 0,
});
let vertex_buffers = [blend_composite_instance_layout()];
let color_targets = [Some(wgpu::ColorTargetState {
format,
blend: Some(crate::pipelines::quad::premultiplied_blend_state()),
write_mask: wgpu::ColorWrites::ALL,
})];
let (pipeline_off, pipeline_test) =
crate::pipelines::build_off_test_pair(device, |depth_stencil| wgpu::RenderPipelineDescriptor {
label: Some("uzor_urx_wgpu.blend_composite_pipeline_native"),
layout: Some(&layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &vertex_buffers,
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &color_targets,
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
..Default::default()
},
depth_stencil,
multisample: wgpu::MultisampleState { count: sample_count, ..Default::default() },
multiview_mask: None,
cache: None,
});
let buffer = make_instance_buffer(device, INITIAL_CAPACITY);
Self { pipeline_off, pipeline_test, buffer, capacity: INITIAL_CAPACITY }
}
pub(crate) fn upload(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, data: &[BlendCompositeInstance]) {
if data.is_empty() {
return;
}
let needed = data.len();
if needed > self.capacity {
while self.capacity < needed {
self.capacity *= 2;
}
self.buffer = make_instance_buffer(device, self.capacity);
}
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(data));
}
pub(crate) fn bind(
&self,
pass: &mut wgpu::RenderPass<'_>,
stencil_ref: Option<u32>,
layer_resolve_bind_group: &wgpu::BindGroup,
) {
match stencil_ref {
None => pass.set_pipeline(&self.pipeline_off),
Some(r) => {
pass.set_pipeline(&self.pipeline_test);
pass.set_stencil_reference(r);
}
}
pass.set_vertex_buffer(0, self.buffer.slice(..));
pass.set_bind_group(1, layer_resolve_bind_group, &[]);
}
pub(crate) fn draw_one(&self, pass: &mut wgpu::RenderPass<'_>, index: u32) {
pass.draw(0..6, index..(index + 1));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn blend_composite_instance_is_32_bytes() {
assert_eq!(std::mem::size_of::<BlendCompositeInstance>(), 32);
}
}