use std::any::Any;
use std::cell::Cell;
use std::collections::HashMap;
use std::num::NonZeroU64;
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct BlitUniforms {
pub uv_min: [f32; 2],
pub uv_max: [f32; 2],
}
pub const UNIFORM_RING_CAPACITY: u32 = 256;
pub struct DynamicUniformRing {
pub buffer: wgpu::Buffer,
aligned_stride: u64,
capacity: u32,
next_index: Cell<u32>,
}
impl DynamicUniformRing {
pub fn new(device: &wgpu::Device, label: &str, uniform_size: u64, min_alignment: u32) -> Self {
let aligned_stride = align_up(uniform_size, min_alignment as u64);
let capacity = UNIFORM_RING_CAPACITY;
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some(label),
size: aligned_stride * capacity as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
Self {
buffer,
aligned_stride,
capacity,
next_index: Cell::new(0),
}
}
pub fn write(&self, queue: &wgpu::Queue, data: &[u8]) -> u32 {
let idx = self.next_index.get();
debug_assert!(idx < self.capacity, "DynamicUniformRing overflow");
let offset = idx as u64 * self.aligned_stride;
queue.write_buffer(&self.buffer, offset, data);
self.next_index.set(idx + 1);
offset as u32
}
pub fn reset(&self) {
self.next_index.set(0);
}
pub fn nearly_full(&self) -> bool {
self.next_index.get() >= self.capacity - 4
}
pub fn binding_size(&self) -> NonZeroU64 {
NonZeroU64::new(self.aligned_stride).unwrap()
}
}
pub fn align_up(value: u64, alignment: u64) -> u64 {
(value + alignment - 1) & !(alignment - 1)
}
pub struct BuildContext<'a> {
pub device: &'a wgpu::Device,
pub queue: &'a wgpu::Queue,
pub uniform_bgl: &'a wgpu::BindGroupLayout,
pub selection_bgl: &'a wgpu::BindGroupLayout,
pub canvas_copy_bgl: &'a wgpu::BindGroupLayout,
pub canvas_copy_sampler: &'a wgpu::Sampler,
pub min_uniform_align: u32,
pub texture_registry: &'a crate::gpu::texture_registry::TextureRegistry,
}
impl<'a> BuildContext<'a> {
pub fn make_uniform_ring<U>(
&self,
label_ring: &str,
label_bg: &str,
) -> (DynamicUniformRing, wgpu::BindGroup) {
let ring = DynamicUniformRing::new(
self.device,
label_ring,
std::mem::size_of::<U>() as u64,
self.min_uniform_align,
);
let bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(label_bg),
layout: self.uniform_bgl,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: &ring.buffer,
offset: 0,
size: Some(ring.binding_size()),
}),
}],
});
(ring, bg)
}
}
pub trait BrushPipelineEntry: Any {
fn as_any(&self) -> &dyn Any;
fn ring(&self) -> Option<&DynamicUniformRing> {
None
}
fn rings(&self) -> Vec<&DynamicUniformRing> {
self.ring().into_iter().collect()
}
}
#[derive(Clone)]
pub struct BrushPipelineRegistration {
pub id: &'static str,
pub build: fn(&BuildContext) -> Box<dyn BrushPipelineEntry>,
}
pub fn plumbing_registrations() -> Vec<BrushPipelineRegistration> {
vec![crate::brush::composite_pipeline::composite_pipeline_registration()]
}
pub struct BrushPipelines {
uniform_bgl: wgpu::BindGroupLayout,
selection_bgl: wgpu::BindGroupLayout,
canvas_copy_bgl: wgpu::BindGroupLayout,
canvas_copy_sampler: wgpu::Sampler,
pub default_selection_bind_group: wgpu::BindGroup,
blit_pipeline: wgpu::RenderPipeline,
blit_uniform_ring: DynamicUniformRing,
pub blit_uniform_bind_group: wgpu::BindGroup,
mask_blit_pipeline: wgpu::RenderPipeline,
scratch_blit_r8_pipeline: wgpu::RenderPipeline,
entries: HashMap<&'static str, Box<dyn BrushPipelineEntry>>,
texture_registry: crate::gpu::texture_registry::TextureRegistry,
cursor_preview_pipeline_cache: CursorPreviewPipelineCache,
}
impl BrushPipelines {
pub fn new(
device: &wgpu::Device,
queue: &wgpu::Queue,
selection_bgl: &wgpu::BindGroupLayout,
) -> Self {
let min_uniform_align = device.limits().min_uniform_buffer_offset_alignment;
let selection_bgl = selection_bgl.clone();
let uniform_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("brush-uniform-bgl"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT | wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: None,
},
count: None,
}],
});
let canvas_copy_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("brush-canvas-copy-bgl"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
let sel_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("brush-default-selection"),
size: wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::R8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &sel_texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
&[255u8],
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(1),
rows_per_image: Some(1),
},
wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
);
let sel_view = sel_texture.create_view(&wgpu::TextureViewDescriptor::default());
let sel_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("brush-selection-sampler"),
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
..Default::default()
});
let default_selection_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("brush-default-selection-bg"),
layout: &selection_bgl,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&sel_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&sel_sampler),
},
],
});
let canvas_copy_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("brush-canvas-copy-sampler"),
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
..Default::default()
});
let blit_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("brush-blit"),
source: wgpu::ShaderSource::Wgsl(include_str!("../../shaders/brush/blit.wgsl").into()),
});
let blit_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("brush-blit-layout"),
bind_group_layouts: &[Some(&uniform_bgl), Some(&canvas_copy_bgl)],
immediate_size: 0,
});
let blit_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("brush-blit"),
layout: Some(&blit_layout),
vertex: wgpu::VertexState {
module: &blit_shader,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: Default::default(),
},
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
fragment: Some(wgpu::FragmentState {
module: &blit_shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::Rgba8Unorm,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
multiview_mask: None,
cache: None,
});
let blit_uniform_ring = DynamicUniformRing::new(
device,
"brush-blit-uniforms",
std::mem::size_of::<BlitUniforms>() as u64,
min_uniform_align,
);
let blit_uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("brush-blit-uniform-bg"),
layout: &uniform_bgl,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: &blit_uniform_ring.buffer,
offset: 0,
size: Some(blit_uniform_ring.binding_size()),
}),
}],
});
let mask_blit_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("brush-mask-blit"),
source: wgpu::ShaderSource::Wgsl(
include_str!("../../shaders/brush/mask_blit.wgsl").into(),
),
});
let mask_blit_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("brush-mask-blit-layout"),
bind_group_layouts: &[Some(&canvas_copy_bgl)],
immediate_size: 0,
});
let mask_blit_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("brush-mask-blit"),
layout: Some(&mask_blit_layout),
vertex: wgpu::VertexState {
module: &mask_blit_shader,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: Default::default(),
},
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
fragment: Some(wgpu::FragmentState {
module: &mask_blit_shader,
entry_point: Some("fs_broadcast_r"),
targets: &[Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::Rgba8Unorm,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
multiview_mask: None,
cache: None,
});
let scratch_blit_r8_pipeline =
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("brush-scratch-blit-r8"),
layout: Some(&mask_blit_layout),
vertex: wgpu::VertexState {
module: &mask_blit_shader,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: Default::default(),
},
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
fragment: Some(wgpu::FragmentState {
module: &mask_blit_shader,
entry_point: Some("fs_passthrough"),
targets: &[Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::R8Unorm,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
multiview_mask: None,
cache: None,
});
let texture_registry = crate::gpu::texture_registry::TextureRegistry::new(device, queue);
let build_ctx = BuildContext {
device,
queue,
uniform_bgl: &uniform_bgl,
selection_bgl: &selection_bgl,
canvas_copy_bgl: &canvas_copy_bgl,
canvas_copy_sampler: &canvas_copy_sampler,
min_uniform_align,
texture_registry: &texture_registry,
};
let mut entries: HashMap<&'static str, Box<dyn BrushPipelineEntry>> = HashMap::new();
let pipeline_regs = crate::brush::nodes::registrations()
.into_iter()
.flat_map(|node_reg| node_reg.pipelines)
.chain(plumbing_registrations());
for pl_reg in pipeline_regs {
let id = pl_reg.id;
let prev = entries.insert(id, (pl_reg.build)(&build_ctx));
debug_assert!(prev.is_none(), "duplicate brush pipeline id: {id}");
}
let cursor_preview_pipeline_cache = CursorPreviewPipelineCache::new(device);
Self {
uniform_bgl,
selection_bgl,
canvas_copy_bgl,
canvas_copy_sampler,
default_selection_bind_group,
blit_pipeline,
blit_uniform_ring,
blit_uniform_bind_group,
mask_blit_pipeline,
scratch_blit_r8_pipeline,
entries,
texture_registry,
cursor_preview_pipeline_cache,
}
}
pub fn texture_registry(&self) -> &crate::gpu::texture_registry::TextureRegistry {
&self.texture_registry
}
pub fn uniform_bind_group_layout(&self) -> &wgpu::BindGroupLayout {
&self.uniform_bgl
}
pub fn get<P: BrushPipelineEntry>(&self, id: &'static str) -> &P {
self.entries
.get(id)
.unwrap_or_else(|| panic!("brush pipeline not registered: {id}"))
.as_any()
.downcast_ref::<P>()
.unwrap_or_else(|| panic!("brush pipeline {id} downcast failed"))
}
pub fn blit_pipeline(&self) -> &wgpu::RenderPipeline {
&self.blit_pipeline
}
pub fn write_blit_uniforms(&self, queue: &wgpu::Queue, uniforms: &BlitUniforms) -> u32 {
self.blit_uniform_ring
.write(queue, bytemuck::bytes_of(uniforms))
}
pub fn mask_blit_pipeline(&self) -> &wgpu::RenderPipeline {
&self.mask_blit_pipeline
}
pub fn scratch_blit_r8_pipeline(&self) -> &wgpu::RenderPipeline {
&self.scratch_blit_r8_pipeline
}
pub fn create_blit_source_bind_group(
&self,
device: &wgpu::Device,
source_view: &wgpu::TextureView,
) -> wgpu::BindGroup {
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("brush-blit-source-bg"),
layout: &self.canvas_copy_bgl,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(source_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&self.canvas_copy_sampler),
},
],
})
}
pub fn selection_bind_group_layout(&self) -> &wgpu::BindGroupLayout {
&self.selection_bgl
}
pub fn canvas_copy_bind_group_layout(&self) -> &wgpu::BindGroupLayout {
&self.canvas_copy_bgl
}
pub fn canvas_copy_sampler(&self) -> &wgpu::Sampler {
&self.canvas_copy_sampler
}
pub fn default_selection_bind_group(&self) -> &wgpu::BindGroup {
&self.default_selection_bind_group
}
pub fn rings_nearly_full(&self) -> bool {
if self.blit_uniform_ring.nearly_full() {
return true;
}
self.entries
.values()
.flat_map(|e| e.rings())
.any(|r| r.nearly_full())
}
pub fn reset_uniform_rings(&self) {
self.blit_uniform_ring.reset();
for r in self.entries.values().flat_map(|e| e.rings()) {
r.reset();
}
}
pub fn cursor_preview_cache(&self) -> &CursorPreviewPipelineCache {
&self.cursor_preview_pipeline_cache
}
#[allow(clippy::too_many_arguments)]
pub fn render_preview(
&self,
device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
compiled: &crate::brush::wgsl::CompiledBrush,
target_view: &wgpu::TextureView,
target_size: (u32, u32),
uniform_bytes: &[u8],
dab_bytes: &[u8],
) {
let min_align = device.limits().min_uniform_buffer_offset_alignment;
self.cursor_preview_pipeline_cache.with_pipeline(
device,
&self.uniform_bgl,
min_align,
compiled,
&self.texture_registry,
|pp| {
pp.render(
queue,
encoder,
target_view,
target_size,
uniform_bytes,
dab_bytes,
)
},
);
}
}
pub struct PreviewPipeline {
pipeline: wgpu::RenderPipeline,
uniform_ring: DynamicUniformRing,
uniform_bind_group: wgpu::BindGroup,
dabs_buffer: wgpu::Buffer,
dabs_bind_group: wgpu::BindGroup,
uniform_size: usize,
graph_textures: Option<(wgpu::BindGroup, wgpu::BindGroup)>,
}
impl PreviewPipeline {
pub fn render(
&self,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
target_view: &wgpu::TextureView,
target_size: (u32, u32),
uniform_bytes: &[u8],
dab_bytes: &[u8],
) {
let mut bytes = Vec::from(uniform_bytes);
if bytes.len() < self.uniform_size {
bytes.resize(self.uniform_size, 0);
}
self.uniform_ring.reset();
let uniform_offset = self.uniform_ring.write(queue, &bytes);
queue.write_buffer(&self.dabs_buffer, 0, dab_bytes);
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("brush-preview"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: target_view,
resolve_target: None,
depth_slice: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: wgpu::StoreOp::Store,
},
})],
..Default::default()
});
pass.set_viewport(
0.0,
0.0,
target_size.0 as f32,
target_size.1 as f32,
0.0,
1.0,
);
pass.set_pipeline(&self.pipeline);
pass.set_bind_group(0, &self.uniform_bind_group, &[uniform_offset]);
pass.set_bind_group(1, &self.dabs_bind_group, &[]);
if let Some((empty_bg, graph_bg)) = self.graph_textures.as_ref() {
pass.set_bind_group(2, empty_bg, &[]);
pass.set_bind_group(3, graph_bg, &[]);
}
pass.draw(0..6, 0..1);
}
}
pub struct CursorPreviewPipelineCache {
pipelines: std::cell::RefCell<HashMap<u64, PreviewPipeline>>,
dabs_bgl: wgpu::BindGroupLayout,
empty_bgl: wgpu::BindGroupLayout,
empty_bind_group: wgpu::BindGroup,
}
impl CursorPreviewPipelineCache {
fn new(device: &wgpu::Device) -> Self {
let dabs_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("brush-preview-dabs-bgl"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
});
let empty_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("brush-preview-empty-bgl"),
entries: &[],
});
let empty_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("brush-preview-empty-bg"),
layout: &empty_bgl,
entries: &[],
});
Self {
pipelines: std::cell::RefCell::new(HashMap::new()),
dabs_bgl,
empty_bgl,
empty_bind_group,
}
}
pub fn with_pipeline<R>(
&self,
device: &wgpu::Device,
uniform_bgl: &wgpu::BindGroupLayout,
min_uniform_align: u32,
compiled: &crate::brush::wgsl::CompiledBrush,
texture_registry: &crate::gpu::texture_registry::TextureRegistry,
f: impl FnOnce(&PreviewPipeline) -> R,
) -> R {
let key = compiled.topology_hash;
let mut pipelines = self.pipelines.borrow_mut();
let entry = pipelines.entry(key).or_insert_with(|| {
build_cursor_preview_pipeline(
device,
uniform_bgl,
&self.dabs_bgl,
&self.empty_bgl,
&self.empty_bind_group,
min_uniform_align,
compiled,
texture_registry,
)
});
f(entry)
}
pub fn empty_bind_group(&self) -> &wgpu::BindGroup {
&self.empty_bind_group
}
}
#[allow(clippy::too_many_arguments)]
fn build_cursor_preview_pipeline(
device: &wgpu::Device,
uniform_bgl: &wgpu::BindGroupLayout,
dabs_bgl: &wgpu::BindGroupLayout,
empty_bgl: &wgpu::BindGroupLayout,
_empty_bind_group: &wgpu::BindGroup,
min_uniform_align: u32,
compiled: &crate::brush::wgsl::CompiledBrush,
texture_registry: &crate::gpu::texture_registry::TextureRegistry,
) -> PreviewPipeline {
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("brush-preview-shader"),
source: wgpu::ShaderSource::Wgsl(compiled.cursor_preview_wgsl.clone().into()),
});
let graph_layout = if compiled.graph_texture_names.is_empty() {
None
} else {
Some(texture_registry.layout_for_count(device, compiled.graph_texture_names.len()))
};
let layout = match &graph_layout {
None => device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("brush-preview-layout"),
bind_group_layouts: &[Some(uniform_bgl), Some(dabs_bgl)],
immediate_size: 0,
}),
Some(gl) => device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("brush-preview-layout-with-graph-textures"),
bind_group_layouts: &[
Some(uniform_bgl),
Some(dabs_bgl),
Some(empty_bgl),
Some(gl.as_ref()),
],
immediate_size: 0,
}),
};
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("brush-preview"),
layout: Some(&layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::Rgba8Unorm,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview_mask: None,
cache: None,
});
let uniform_size = (crate::brush::wgsl::INTRINSIC_UNIFORMS_SIZE + compiled.uniform_size)
.max(crate::brush::wgsl::INTRINSIC_UNIFORMS_SIZE);
let uniform_ring = DynamicUniformRing::new(
device,
"brush-preview-uniforms",
uniform_size as u64,
min_uniform_align,
);
let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("brush-preview-uniform-bg"),
layout: uniform_bgl,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: &uniform_ring.buffer,
offset: 0,
size: Some(uniform_ring.binding_size()),
}),
}],
});
let dab_record_size = compiled.dab_record_size.max(16);
let dabs_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("brush-preview-dabs-buffer"),
size: dab_record_size as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let dabs_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("brush-preview-dabs-bg"),
layout: dabs_bgl,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: dabs_buffer.as_entire_binding(),
}],
});
let graph_textures = if compiled.graph_texture_names.is_empty() {
None
} else {
let (_layout, bg) = texture_registry.make_bind_group(device, &compiled.graph_texture_names);
let empty_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("brush-preview-empty-bg"),
layout: empty_bgl,
entries: &[],
});
Some((empty_bg, bg))
};
PreviewPipeline {
pipeline,
uniform_ring,
uniform_bind_group,
dabs_buffer,
dabs_bind_group,
uniform_size: align_up(uniform_size as u64, min_uniform_align as u64) as usize,
graph_textures,
}
}