use bytemuck::{Pod, Zeroable};
#[inline]
pub(crate) const fn pack_rgba8(rgba: [u8; 4]) -> u32 {
(rgba[0] as u32)
| ((rgba[1] as u32) << 8)
| ((rgba[2] as u32) << 16)
| ((rgba[3] as u32) << 24)
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
pub(crate) struct QuadInstance {
pub pos: [f32; 2],
pub size: [f32; 2],
pub color: u32,
pub border_color: u32,
pub corner_radius: f32,
pub border_width: f32,
pub _pad0: [f32; 2],
pub clip_rect: [f32; 4],
}
const _: () = assert!(
std::mem::size_of::<QuadInstance>() == 56,
"QuadInstance must stay 56 bytes — WGSL struct in shaders.rs mirrors this layout"
);
pub(crate) fn quad_instance_layout() -> wgpu::VertexBufferLayout<'static> {
use wgpu::VertexFormat::*;
static ATTRS: &[wgpu::VertexAttribute] = &[
wgpu::VertexAttribute { shader_location: 0, format: Float32x2, offset: 0 },
wgpu::VertexAttribute { shader_location: 1, format: Float32x2, offset: 8 },
wgpu::VertexAttribute { shader_location: 2, format: Uint32, offset: 16 },
wgpu::VertexAttribute { shader_location: 3, format: Uint32, offset: 20 },
wgpu::VertexAttribute { shader_location: 4, format: Float32, offset: 24 },
wgpu::VertexAttribute { shader_location: 5, format: Float32, offset: 28 },
wgpu::VertexAttribute { shader_location: 6, format: Float32x2, offset: 32 },
wgpu::VertexAttribute { shader_location: 7, format: Float32x4, offset: 40 },
];
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<QuadInstance>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Instance,
attributes: ATTRS,
}
}
const INITIAL_CAPACITY: usize = 1024;
fn make_instance_buffer(device: &wgpu::Device, capacity: usize) -> wgpu::Buffer {
device.create_buffer(&wgpu::BufferDescriptor {
label: Some("uzor_urx_wgpu.native_quad_buffer"),
size: (capacity * std::mem::size_of::<QuadInstance>()) as wgpu::BufferAddress,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
})
}
pub(crate) fn premultiplied_blend_state() -> wgpu::BlendState {
wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
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,
},
}
}
pub(crate) struct QuadPipeline {
pipeline_off: wgpu::RenderPipeline,
pipeline_test: wgpu::RenderPipeline,
buffer: wgpu::Buffer,
capacity: usize,
}
impl QuadPipeline {
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.quad_shader_native"),
source: wgpu::ShaderSource::Wgsl(crate::shaders::QUAD_SHADER_NATIVE.into()),
});
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("uzor_urx_wgpu.quad_pipeline_layout_native"),
bind_group_layouts: &[Some(uniform_bgl)],
immediate_size: 0,
});
let vertex_buffers = [quad_instance_layout()];
let color_targets = [Some(wgpu::ColorTargetState {
format,
blend: Some(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.quad_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: &[QuadInstance]) {
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>) {
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(..));
}
pub(crate) fn draw_range(&self, pass: &mut wgpu::RenderPass<'_>, start: u32, count: u32) {
pass.draw(0..6, start..(start + count));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn quad_instance_is_56_bytes() {
assert_eq!(std::mem::size_of::<QuadInstance>(), 56);
}
#[test]
fn pack_rgba8_round_trip_endianness() {
let p = pack_rgba8([0x11, 0x22, 0x33, 0x44]);
assert_eq!(p, 0x4433_2211);
}
}