use std::borrow::Cow;
use std::collections::HashMap;
use std::ops::Range;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use damascene_core::color::ColorSpace;
use damascene_core::paint::{PhysicalScissor, rgba_f32_in};
use damascene_core::scene::gpu::{
self, CompositeInstance, LineInstance, LineUniform, MeshUniform, MeshVertexGpu, PointInstance,
PointUniform,
};
use damascene_core::scene::{
LineDraw, MeshDraw, PointDraw, ResolvedCamera, Scene3DData, SceneDepthMap,
};
use damascene_core::shader::stock_wgsl;
use damascene_core::tree::Rect;
use wgpu::util::DeviceExt;
const SCENE_COLOR_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Float;
const SCENE_DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
const SCENE_OCC_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::R32Float;
const COPY_ROW_ALIGN: u32 = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
const UNIFORM_STRIDE: u64 = 256;
const POINT_QUAD_ATTRS: [wgpu::VertexAttribute; 2] =
wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2]; const POINT_INSTANCE_ATTRS: [wgpu::VertexAttribute; 2] =
wgpu::vertex_attr_array![2 => Float32x3, 3 => Float32x4]; const LINE_QUAD_ATTRS: [wgpu::VertexAttribute; 1] = wgpu::vertex_attr_array![0 => Float32x2];
const LINE_INSTANCE_ATTRS: [wgpu::VertexAttribute; 4] =
wgpu::vertex_attr_array![1 => Float32x3, 2 => Float32x3, 3 => Float32x4, 4 => Float32];
const MESH_VERTEX_ATTRS: [wgpu::VertexAttribute; 2] =
wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3];
const COMPOSITE_INSTANCE_ATTRS: [wgpu::VertexAttribute; 3] =
wgpu::vertex_attr_array![1 => Float32x4, 2 => Float32x4, 3 => Float32x2];
struct MeshBuffers {
vbuf: wgpu::Buffer,
ibuf: Option<wgpu::Buffer>,
vcount: u32,
icount: u32,
}
struct PointBuffers {
ibuf: wgpu::Buffer,
count: u32,
}
struct LineBuffers {
ibuf: wgpu::Buffer,
count: u32,
}
enum GeoBuffers {
Mesh(MeshBuffers),
Points(PointBuffers),
Lines(LineBuffers),
}
struct CachedGeometry {
buffers: GeoBuffers,
revision: u64,
space: ColorSpace,
used_frame: u64,
}
struct OffscreenTarget {
size: (u32, u32),
sample_count: u32,
msaa_color: Option<wgpu::TextureView>,
depth: wgpu::TextureView,
resolve_view: wgpu::TextureView,
composite_bind_group: wgpu::BindGroup,
occlusion: Option<OcclusionResources>,
used_frame: u64,
}
struct OcclusionResources {
color_view: wgpu::TextureView,
color: wgpu::Texture,
readback: wgpu::Buffer,
padded_bytes_per_row: u32,
width: u32,
height: u32,
state: ReadbackState,
last_captured: Option<(ResolvedCamera, Rect)>,
}
enum ReadbackState {
Free,
Pending { camera: ResolvedCamera, rect: Rect },
Mapping {
camera: ResolvedCamera,
rect: Rect,
done: Arc<AtomicBool>,
},
}
struct ResolvePipeline {
pipeline: wgpu::RenderPipeline,
bind_layout: wgpu::BindGroupLayout,
}
enum DrawCmd {
Mesh {
geo: u64,
uniform_slot: u32,
},
Points {
geo: u64,
uniform_slot: u32,
},
Lines {
geo: u64,
uniform_slot: u32,
},
Grid {
uniform_slot: u32,
first: u32,
count: u32,
},
}
pub(crate) struct Scene3DRun {
target_id: String,
pub scissor: Option<PhysicalScissor>,
sample_count: u32,
clear: wgpu::Color,
cmds: Vec<DrawCmd>,
pub composite_instance: u32,
capture_depth: bool,
camera: ResolvedCamera,
rect: Rect,
}
struct ScenePipelines {
point: wgpu::RenderPipeline,
line: wgpu::RenderPipeline,
mesh: wgpu::RenderPipeline,
}
pub(crate) struct Scene3DPaint {
working: ColorSpace,
point_quad_vbo: wgpu::Buffer,
line_quad_vbo: wgpu::Buffer,
uniform_layout: wgpu::BindGroupLayout,
point_shader: wgpu::ShaderModule,
line_shader: wgpu::ShaderModule,
mesh_shader: wgpu::ShaderModule,
scene_pipeline_layout: wgpu::PipelineLayout,
pipelines: HashMap<u32, ScenePipelines>,
resolve_pipelines: HashMap<u32, ResolvePipeline>,
point_uniforms: Vec<PointUniform>,
line_uniforms: Vec<LineUniform>,
mesh_uniforms: Vec<MeshUniform>,
point_ubo: wgpu::Buffer,
line_ubo: wgpu::Buffer,
mesh_ubo: wgpu::Buffer,
point_uniform_cap: usize,
line_uniform_cap: usize,
mesh_uniform_cap: usize,
point_bind_group: wgpu::BindGroup,
line_bind_group: wgpu::BindGroup,
mesh_bind_group: wgpu::BindGroup,
composite_pipeline: wgpu::RenderPipeline,
composite_bind_layout: wgpu::BindGroupLayout,
sampler: wgpu::Sampler,
composite_instances: Vec<CompositeInstance>,
composite_instance_buf: wgpu::Buffer,
composite_instance_cap: usize,
grid_instances: Vec<LineInstance>,
grid_buf: wgpu::Buffer,
grid_cap: usize,
geometry: HashMap<u64, CachedGeometry>,
targets: HashMap<String, OffscreenTarget>,
runs: Vec<Scene3DRun>,
frame_counter: u64,
}
const INITIAL_UNIFORM_CAP: usize = 16;
const INITIAL_COMPOSITE_CAP: usize = 8;
const INITIAL_GRID_CAP: usize = 256;
impl Scene3DPaint {
pub(crate) fn new(
device: &wgpu::Device,
target_format: wgpu::TextureFormat,
sample_count: u32,
frame_bind_layout: &wgpu::BindGroupLayout,
working: ColorSpace,
) -> Self {
let point_quad_vbo = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("damascene_wgpu::scene::point_quad"),
contents: bytemuck::cast_slice::<f32, u8>(&[
-1.0, -1.0, 0.0, 0.0, 1.0, -1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, ]),
usage: wgpu::BufferUsages::VERTEX,
});
let line_quad_vbo = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("damascene_wgpu::scene::line_quad"),
contents: bytemuck::cast_slice::<f32, u8>(&[
0.0, -1.0, 1.0, -1.0, 0.0, 1.0, 1.0, 1.0, ]),
usage: wgpu::BufferUsages::VERTEX,
});
let uniform_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("damascene_wgpu::scene::uniform_layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: None,
},
count: None,
}],
});
let point_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("scene::point"),
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(stock_wgsl::SCENE_POINT)),
});
let line_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("scene::line"),
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(stock_wgsl::SCENE_LINE)),
});
let mesh_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("scene::mesh"),
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(stock_wgsl::SCENE_MESH)),
});
let scene_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("damascene_wgpu::scene::pipeline_layout"),
bind_group_layouts: &[Some(&uniform_layout)],
immediate_size: 0,
});
let (point_ubo, point_bind_group) =
make_uniform_buffer(device, &uniform_layout, INITIAL_UNIFORM_CAP, "point");
let (line_ubo, line_bind_group) =
make_uniform_buffer(device, &uniform_layout, INITIAL_UNIFORM_CAP, "line");
let (mesh_ubo, mesh_bind_group) =
make_uniform_buffer(device, &uniform_layout, INITIAL_UNIFORM_CAP, "mesh");
let composite_bind_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("damascene_wgpu::scene::composite_tex_layout"),
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 composite_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("damascene_wgpu::scene::composite_pipeline_layout"),
bind_group_layouts: &[Some(frame_bind_layout), Some(&composite_bind_layout)],
immediate_size: 0,
});
let surface_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("scene::composite (stock surface)"),
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(stock_wgsl::SURFACE)),
});
let composite_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("damascene_wgpu::scene::composite_pipeline"),
layout: Some(&composite_pipeline_layout),
vertex: wgpu::VertexState {
module: &surface_shader,
entry_point: Some("vs_main"),
compilation_options: Default::default(),
buffers: &[
wgpu::VertexBufferLayout {
array_stride: (2 * std::mem::size_of::<f32>()) as u64,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[wgpu::VertexAttribute {
shader_location: 0,
format: wgpu::VertexFormat::Float32x2,
offset: 0,
}],
},
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<CompositeInstance>() as u64,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &COMPOSITE_INSTANCE_ATTRS,
},
],
},
fragment: Some(wgpu::FragmentState {
module: &surface_shader,
entry_point: Some("fs_premul"),
compilation_options: Default::default(),
targets: &[Some(wgpu::ColorTargetState {
format: target_format,
blend: Some(premultiplied_blend()),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleStrip,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: sample_count,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview_mask: None,
cache: None,
});
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("damascene_wgpu::scene::sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
..Default::default()
});
let composite_instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("damascene_wgpu::scene::composite_instances"),
size: (INITIAL_COMPOSITE_CAP * std::mem::size_of::<CompositeInstance>()) as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let grid_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("damascene_wgpu::scene::grid_lines"),
size: (INITIAL_GRID_CAP * std::mem::size_of::<LineInstance>()) as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
Self {
working,
point_quad_vbo,
line_quad_vbo,
uniform_layout,
point_shader,
line_shader,
mesh_shader,
scene_pipeline_layout,
pipelines: HashMap::new(),
resolve_pipelines: HashMap::new(),
point_uniforms: Vec::new(),
line_uniforms: Vec::new(),
mesh_uniforms: Vec::new(),
point_ubo,
line_ubo,
mesh_ubo,
point_uniform_cap: INITIAL_UNIFORM_CAP,
line_uniform_cap: INITIAL_UNIFORM_CAP,
mesh_uniform_cap: INITIAL_UNIFORM_CAP,
point_bind_group,
line_bind_group,
mesh_bind_group,
composite_pipeline,
composite_bind_layout,
sampler,
composite_instances: Vec::new(),
composite_instance_buf,
composite_instance_cap: INITIAL_COMPOSITE_CAP,
grid_instances: Vec::new(),
grid_buf,
grid_cap: INITIAL_GRID_CAP,
geometry: HashMap::new(),
targets: HashMap::new(),
runs: Vec::new(),
frame_counter: 0,
}
}
pub(crate) fn set_working_color_space(&mut self, space: ColorSpace) {
self.working = space;
}
pub(crate) fn frame_begin(&mut self) {
self.runs.clear();
self.point_uniforms.clear();
self.line_uniforms.clear();
self.mesh_uniforms.clear();
self.composite_instances.clear();
self.grid_instances.clear();
self.frame_counter = self.frame_counter.wrapping_add(1);
}
pub(crate) fn record(
&mut self,
device: &wgpu::Device,
rect: Rect,
scissor: Option<PhysicalScissor>,
id: &str,
scene: &Scene3DData,
scale_factor: f32,
) -> Range<usize> {
let start = self.runs.len();
if rect.w <= 0.0 || rect.h <= 0.0 {
return start..start;
}
let px = (
(rect.w * scale_factor).round().max(1.0) as u32,
(rect.h * scale_factor).round().max(1.0) as u32,
);
let sample_count = scene.style.msaa_samples.max(1);
self.ensure_pipelines(device, sample_count);
self.ensure_target(device, id, px, sample_count, scene.capture_depth);
let aspect = px.0 as f32 / px.1 as f32;
let view_proj = scene.camera.view_proj(aspect);
let screen = [px.0 as f32, px.1 as f32];
let working = self.working;
let mut cmds = Vec::new();
let first = self.grid_instances.len() as u32;
gpu::build_grid_lines(&scene.style, working, &mut self.grid_instances);
let count = self.grid_instances.len() as u32 - first;
if count > 0 {
let slot = self.push_line_uniform(gpu::grid_uniform(view_proj, screen));
cmds.push(DrawCmd::Grid {
uniform_slot: slot,
first,
count,
});
}
for m in &scene.meshes {
self.ensure_mesh_geometry(device, m);
let slot = self.push_mesh_uniform(gpu::mesh_uniform(view_proj, m, scene, working));
cmds.push(DrawCmd::Mesh {
geo: m.geometry.id().0,
uniform_slot: slot,
});
}
for p in &scene.points {
self.ensure_point_geometry(device, p, working);
let slot =
self.push_point_uniform(gpu::point_uniform(view_proj * p.transform, screen, p));
cmds.push(DrawCmd::Points {
geo: p.geometry.id().0,
uniform_slot: slot,
});
}
for l in &scene.lines {
self.ensure_line_geometry(device, l, working);
let slot =
self.push_line_uniform(gpu::line_uniform(view_proj * l.transform, screen, l));
cmds.push(DrawCmd::Lines {
geo: l.geometry.id().0,
uniform_slot: slot,
});
}
let clear = match scene.style.background {
Some(c) => {
let [r, g, b, a] = rgba_f32_in(c, working);
wgpu::Color {
r: (r * a) as f64,
g: (g * a) as f64,
b: (b * a) as f64,
a: a as f64,
}
}
None => wgpu::Color::TRANSPARENT,
};
let composite_instance = self.composite_instances.len() as u32;
self.composite_instances.push(CompositeInstance::new(
[rect.x, rect.y, rect.w, rect.h],
[1.0, 0.0, 0.0, 1.0],
[0.0, 0.0],
));
self.runs.push(Scene3DRun {
target_id: id.to_string(),
scissor,
sample_count,
clear,
cmds,
composite_instance,
capture_depth: scene.capture_depth,
camera: scene.camera,
rect,
});
start..self.runs.len()
}
fn push_point_uniform(&mut self, u: PointUniform) -> u32 {
let slot = self.point_uniforms.len() as u32;
self.point_uniforms.push(u);
slot
}
fn push_line_uniform(&mut self, u: LineUniform) -> u32 {
let slot = self.line_uniforms.len() as u32;
self.line_uniforms.push(u);
slot
}
fn push_mesh_uniform(&mut self, u: MeshUniform) -> u32 {
let slot = self.mesh_uniforms.len() as u32;
self.mesh_uniforms.push(u);
slot
}
fn ensure_pipelines(&mut self, device: &wgpu::Device, sample_count: u32) {
self.resolve_pipelines
.entry(sample_count)
.or_insert_with(|| build_resolve_pipeline(device, sample_count));
if self.pipelines.contains_key(&sample_count) {
return;
}
let pipelines = build_scene_pipelines(
device,
&self.scene_pipeline_layout,
&self.point_shader,
&self.line_shader,
&self.mesh_shader,
sample_count,
);
self.pipelines.insert(sample_count, pipelines);
}
fn ensure_target(
&mut self,
device: &wgpu::Device,
id: &str,
px: (u32, u32),
sample_count: u32,
capture_depth: bool,
) {
if let Some(t) = self.targets.get_mut(id)
&& t.size == px
&& t.sample_count == sample_count
{
t.used_frame = self.frame_counter;
if capture_depth && t.occlusion.is_none() {
t.occlusion = Some(build_occlusion_resources(device, px));
}
return;
}
let extent = wgpu::Extent3d {
width: px.0,
height: px.1,
depth_or_array_layers: 1,
};
let resolve = device.create_texture(&wgpu::TextureDescriptor {
label: Some("damascene_wgpu::scene::resolve"),
size: extent,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: SCENE_COLOR_FORMAT,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let resolve_view = resolve.create_view(&wgpu::TextureViewDescriptor::default());
let msaa_color = (sample_count > 1).then(|| {
device
.create_texture(&wgpu::TextureDescriptor {
label: Some("damascene_wgpu::scene::msaa_color"),
size: extent,
mip_level_count: 1,
sample_count,
dimension: wgpu::TextureDimension::D2,
format: SCENE_COLOR_FORMAT,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
})
.create_view(&wgpu::TextureViewDescriptor::default())
});
let depth = device
.create_texture(&wgpu::TextureDescriptor {
label: Some("damascene_wgpu::scene::depth"),
size: extent,
mip_level_count: 1,
sample_count,
dimension: wgpu::TextureDimension::D2,
format: SCENE_DEPTH_FORMAT,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
})
.create_view(&wgpu::TextureViewDescriptor::default());
let composite_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("damascene_wgpu::scene::composite_bind_group"),
layout: &self.composite_bind_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&resolve_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&self.sampler),
},
],
});
self.targets.insert(
id.to_string(),
OffscreenTarget {
size: px,
sample_count,
msaa_color,
depth,
resolve_view,
composite_bind_group,
occlusion: capture_depth.then(|| build_occlusion_resources(device, px)),
used_frame: self.frame_counter,
},
);
}
fn ensure_mesh_geometry(&mut self, device: &wgpu::Device, draw: &MeshDraw) {
let id = draw.geometry.id().0;
let (data, rev) = draw.geometry.snapshot();
if let Some(c) = self.geometry.get_mut(&id)
&& c.revision == rev
&& matches!(c.buffers, GeoBuffers::Mesh(_))
{
c.used_frame = self.frame_counter;
return;
}
let verts = gpu::mesh_vertices(&data);
let vbuf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("damascene_wgpu::scene::mesh_vbuf"),
contents: bytemuck::cast_slice(&verts),
usage: wgpu::BufferUsages::VERTEX,
});
let (ibuf, icount) = match &data.indices {
Some(indices) => (
Some(
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("damascene_wgpu::scene::mesh_ibuf"),
contents: bytemuck::cast_slice(indices),
usage: wgpu::BufferUsages::INDEX,
}),
),
indices.len() as u32,
),
None => (None, 0),
};
self.geometry.insert(
id,
CachedGeometry {
buffers: GeoBuffers::Mesh(MeshBuffers {
vbuf,
ibuf,
vcount: verts.len() as u32,
icount,
}),
revision: rev,
space: self.working,
used_frame: self.frame_counter,
},
);
}
fn ensure_point_geometry(
&mut self,
device: &wgpu::Device,
draw: &PointDraw,
working: ColorSpace,
) {
let id = draw.geometry.id().0;
let (data, rev) = draw.geometry.snapshot();
if let Some(c) = self.geometry.get_mut(&id)
&& c.revision == rev
&& c.space == working
&& matches!(c.buffers, GeoBuffers::Points(_))
{
c.used_frame = self.frame_counter;
return;
}
let instances = gpu::point_instances(&data, working);
let ibuf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("damascene_wgpu::scene::point_ibuf"),
contents: bytemuck::cast_slice(&instances),
usage: wgpu::BufferUsages::VERTEX,
});
self.geometry.insert(
id,
CachedGeometry {
buffers: GeoBuffers::Points(PointBuffers {
ibuf,
count: instances.len() as u32,
}),
revision: rev,
space: working,
used_frame: self.frame_counter,
},
);
}
fn ensure_line_geometry(
&mut self,
device: &wgpu::Device,
draw: &LineDraw,
working: ColorSpace,
) {
let id = draw.geometry.id().0;
let (data, rev) = draw.geometry.snapshot();
if let Some(c) = self.geometry.get_mut(&id)
&& c.revision == rev
&& c.space == working
&& matches!(c.buffers, GeoBuffers::Lines(_))
{
c.used_frame = self.frame_counter;
return;
}
let instances = gpu::line_instances(&data, working);
let ibuf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("damascene_wgpu::scene::line_ibuf"),
contents: bytemuck::cast_slice(&instances),
usage: wgpu::BufferUsages::VERTEX,
});
self.geometry.insert(
id,
CachedGeometry {
buffers: GeoBuffers::Lines(LineBuffers {
ibuf,
count: instances.len() as u32,
}),
revision: rev,
space: working,
used_frame: self.frame_counter,
},
);
}
pub(crate) fn flush(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) {
let frame = self.frame_counter;
self.geometry.retain(|_, c| c.used_frame == frame);
self.targets.retain(|_, t| t.used_frame == frame);
if self.point_uniforms.len() > self.point_uniform_cap {
let cap = self.point_uniforms.len().next_power_of_two();
let (buf, bg) = make_uniform_buffer(device, &self.uniform_layout, cap, "point");
self.point_ubo = buf;
self.point_bind_group = bg;
self.point_uniform_cap = cap;
}
if self.line_uniforms.len() > self.line_uniform_cap {
let cap = self.line_uniforms.len().next_power_of_two();
let (buf, bg) = make_uniform_buffer(device, &self.uniform_layout, cap, "line");
self.line_ubo = buf;
self.line_bind_group = bg;
self.line_uniform_cap = cap;
}
if self.mesh_uniforms.len() > self.mesh_uniform_cap {
let cap = self.mesh_uniforms.len().next_power_of_two();
let (buf, bg) = make_uniform_buffer(device, &self.uniform_layout, cap, "mesh");
self.mesh_ubo = buf;
self.mesh_bind_group = bg;
self.mesh_uniform_cap = cap;
}
for (i, u) in self.point_uniforms.iter().enumerate() {
queue.write_buffer(
&self.point_ubo,
i as u64 * UNIFORM_STRIDE,
bytemuck::bytes_of(u),
);
}
for (i, u) in self.line_uniforms.iter().enumerate() {
queue.write_buffer(
&self.line_ubo,
i as u64 * UNIFORM_STRIDE,
bytemuck::bytes_of(u),
);
}
for (i, u) in self.mesh_uniforms.iter().enumerate() {
queue.write_buffer(
&self.mesh_ubo,
i as u64 * UNIFORM_STRIDE,
bytemuck::bytes_of(u),
);
}
if self.composite_instances.len() > self.composite_instance_cap {
let cap = self.composite_instances.len().next_power_of_two();
self.composite_instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("damascene_wgpu::scene::composite_instances (resized)"),
size: (cap * std::mem::size_of::<CompositeInstance>()) as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.composite_instance_cap = cap;
}
if !self.composite_instances.is_empty() {
queue.write_buffer(
&self.composite_instance_buf,
0,
bytemuck::cast_slice(&self.composite_instances),
);
}
if self.grid_instances.len() > self.grid_cap {
let cap = self.grid_instances.len().next_power_of_two();
self.grid_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("damascene_wgpu::scene::grid_lines (resized)"),
size: (cap * std::mem::size_of::<LineInstance>()) as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.grid_cap = cap;
}
if !self.grid_instances.is_empty() {
queue.write_buffer(
&self.grid_buf,
0,
bytemuck::cast_slice(&self.grid_instances),
);
}
}
pub(crate) fn encode_offscreen(&self, encoder: &mut wgpu::CommandEncoder) {
for run in &self.runs {
let Some(target) = self.targets.get(&run.target_id) else {
continue;
};
let pipelines = self
.pipelines
.get(&run.sample_count)
.expect("pipelines ensured at record time");
let (view, resolve_target) = match &target.msaa_color {
Some(ms) => (ms, Some(&target.resolve_view)),
None => (&target.resolve_view, None),
};
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("damascene_wgpu::scene::offscreen"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view,
resolve_target,
depth_slice: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(run.clear),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: &target.depth,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0),
store: if run.capture_depth {
wgpu::StoreOp::Store
} else {
wgpu::StoreOp::Discard
},
}),
stencil_ops: None,
}),
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
for cmd in &run.cmds {
match *cmd {
DrawCmd::Mesh { geo, uniform_slot } => {
let Some(CachedGeometry {
buffers: GeoBuffers::Mesh(m),
..
}) = self.geometry.get(&geo)
else {
continue;
};
pass.set_pipeline(&pipelines.mesh);
pass.set_bind_group(
0,
&self.mesh_bind_group,
&[uniform_slot * UNIFORM_STRIDE as u32],
);
pass.set_vertex_buffer(0, m.vbuf.slice(..));
match &m.ibuf {
Some(ibuf) => {
pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
pass.draw_indexed(0..m.icount, 0, 0..1);
}
None => pass.draw(0..m.vcount, 0..1),
}
}
DrawCmd::Points { geo, uniform_slot } => {
let Some(CachedGeometry {
buffers: GeoBuffers::Points(p),
..
}) = self.geometry.get(&geo)
else {
continue;
};
pass.set_pipeline(&pipelines.point);
pass.set_bind_group(
0,
&self.point_bind_group,
&[uniform_slot * UNIFORM_STRIDE as u32],
);
pass.set_vertex_buffer(0, self.point_quad_vbo.slice(..));
pass.set_vertex_buffer(1, p.ibuf.slice(..));
pass.draw(0..4, 0..p.count);
}
DrawCmd::Lines { geo, uniform_slot } => {
let Some(CachedGeometry {
buffers: GeoBuffers::Lines(l),
..
}) = self.geometry.get(&geo)
else {
continue;
};
pass.set_pipeline(&pipelines.line);
pass.set_bind_group(
0,
&self.line_bind_group,
&[uniform_slot * UNIFORM_STRIDE as u32],
);
pass.set_vertex_buffer(0, self.line_quad_vbo.slice(..));
pass.set_vertex_buffer(1, l.ibuf.slice(..));
pass.draw(0..4, 0..l.count);
}
DrawCmd::Grid {
uniform_slot,
first,
count,
} => {
pass.set_pipeline(&pipelines.line);
pass.set_bind_group(
0,
&self.line_bind_group,
&[uniform_slot * UNIFORM_STRIDE as u32],
);
pass.set_vertex_buffer(0, self.line_quad_vbo.slice(..));
pass.set_vertex_buffer(1, self.grid_buf.slice(..));
pass.draw(0..4, first..first + count);
}
}
}
}
}
pub(crate) fn encode_depth_capture(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
) {
let jobs: Vec<(String, ResolvedCamera, Rect, u32)> = self
.runs
.iter()
.filter(|r| r.capture_depth)
.map(|r| (r.target_id.clone(), r.camera, r.rect, r.sample_count))
.collect();
for (id, camera, rect, sample_count) in jobs {
let Some(target) = self.targets.get_mut(&id) else {
continue;
};
let Some(occ) = target.occlusion.as_mut() else {
continue;
};
if !matches!(occ.state, ReadbackState::Free) {
continue; }
if occ.last_captured == Some((camera, rect)) {
continue; }
let Some(resolve) = self.resolve_pipelines.get(&sample_count) else {
continue;
};
let bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("damascene_wgpu::scene::depth_resolve_bind"),
layout: &resolve.bind_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&target.depth),
}],
});
{
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("damascene_wgpu::scene::depth_resolve"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &occ.color_view,
resolve_target: None,
depth_slice: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::WHITE), store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
pass.set_pipeline(&resolve.pipeline);
pass.set_bind_group(0, &bind, &[]);
pass.draw(0..3, 0..1);
}
encoder.copy_texture_to_buffer(
wgpu::TexelCopyTextureInfo {
texture: &occ.color,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
wgpu::TexelCopyBufferInfo {
buffer: &occ.readback,
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(occ.padded_bytes_per_row),
rows_per_image: Some(occ.height),
},
},
wgpu::Extent3d {
width: occ.width,
height: occ.height,
depth_or_array_layers: 1,
},
);
occ.state = ReadbackState::Pending { camera, rect };
occ.last_captured = Some((camera, rect));
}
}
pub(crate) fn collect_depth_maps(
&mut self,
device: &wgpu::Device,
) -> Vec<(String, SceneDepthMap)> {
let _ = device.poll(wgpu::PollType::Poll);
let mut ready = Vec::new();
for (id, target) in self.targets.iter_mut() {
let Some(occ) = target.occlusion.as_mut() else {
continue;
};
enum Step {
Map(ResolvedCamera, Rect),
Read(ResolvedCamera, Rect),
Idle,
}
let step = match &occ.state {
ReadbackState::Pending { camera, rect } => Step::Map(*camera, *rect),
ReadbackState::Mapping { camera, rect, done } if done.load(Ordering::Acquire) => {
Step::Read(*camera, *rect)
}
_ => Step::Idle,
};
match step {
Step::Map(camera, rect) => {
let done = Arc::new(AtomicBool::new(false));
let flag = done.clone();
occ.readback
.slice(..)
.map_async(wgpu::MapMode::Read, move |res| {
if res.is_ok() {
flag.store(true, Ordering::Release);
}
});
occ.state = ReadbackState::Mapping { camera, rect, done };
}
Step::Read(camera, rect) => {
let depth = {
let view = occ.readback.slice(..).get_mapped_range();
depad_r32(&view, occ.width, occ.height, occ.padded_bytes_per_row)
};
occ.readback.unmap();
occ.state = ReadbackState::Free;
ready.push((
id.clone(),
SceneDepthMap {
camera,
rect,
width: occ.width,
height: occ.height,
depth: Arc::from(depth),
},
));
}
Step::Idle => {}
}
}
ready
}
pub(crate) fn has_target(&self, id: &str) -> bool {
self.targets.contains_key(id)
}
pub(crate) fn occlusion_unsettled(&self) -> bool {
self.runs.iter().filter(|r| r.capture_depth).any(|r| {
match self
.targets
.get(&r.target_id)
.and_then(|t| t.occlusion.as_ref())
{
None => true,
Some(occ) => {
!matches!(occ.state, ReadbackState::Free)
|| occ.last_captured != Some((r.camera, r.rect))
}
}
})
}
pub(crate) fn run(&self, index: usize) -> &Scene3DRun {
&self.runs[index]
}
pub(crate) fn composite_pipeline(&self) -> &wgpu::RenderPipeline {
&self.composite_pipeline
}
pub(crate) fn composite_instance_buf(&self) -> &wgpu::Buffer {
&self.composite_instance_buf
}
pub(crate) fn composite_bind_group(&self, run: &Scene3DRun) -> &wgpu::BindGroup {
&self
.targets
.get(&run.target_id)
.expect("target alive for the frame")
.composite_bind_group
}
pub(crate) fn has_runs(&self) -> bool {
!self.runs.is_empty()
}
}
fn make_uniform_buffer(
device: &wgpu::Device,
layout: &wgpu::BindGroupLayout,
capacity: usize,
tag: &str,
) -> (wgpu::Buffer, wgpu::BindGroup) {
let buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("damascene_wgpu::scene::uniform_buf"),
size: capacity as u64 * UNIFORM_STRIDE,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let _ = tag;
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("damascene_wgpu::scene::uniform_bind_group"),
layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: &buf,
offset: 0,
size: wgpu::BufferSize::new(UNIFORM_STRIDE),
}),
}],
});
(buf, bind_group)
}
fn premultiplied_blend() -> 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,
},
}
}
fn resolve_wgsl(multisampled: bool) -> String {
let binding = if multisampled {
"texture_depth_multisampled_2d"
} else {
"texture_depth_2d"
};
format!(
"@vertex
fn vs_main(@builtin(vertex_index) vid: u32) -> @builtin(position) vec4<f32> {{
var p = array<vec2<f32>, 3>(
vec2<f32>(-1.0, -1.0), vec2<f32>(3.0, -1.0), vec2<f32>(-1.0, 3.0));
return vec4<f32>(p[vid], 0.0, 1.0);
}}
@group(0) @binding(0) var depth_tex: {binding};
@fragment
fn fs_main(@builtin(position) frag: vec4<f32>) -> @location(0) f32 {{
return textureLoad(depth_tex, vec2<i32>(i32(frag.x), i32(frag.y)), 0);
}}
"
)
}
fn build_resolve_pipeline(device: &wgpu::Device, sample_count: u32) -> ResolvePipeline {
let multisampled = sample_count > 1;
let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("damascene_wgpu::scene::depth_resolve"),
source: wgpu::ShaderSource::Wgsl(Cow::Owned(resolve_wgsl(multisampled))),
});
let bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("damascene_wgpu::scene::depth_resolve_bind_layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Depth,
view_dimension: wgpu::TextureViewDimension::D2,
multisampled,
},
count: None,
}],
});
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("damascene_wgpu::scene::depth_resolve_layout"),
bind_group_layouts: &[Some(&bind_layout)],
immediate_size: 0,
});
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("damascene_wgpu::scene::depth_resolve_pipeline"),
layout: Some(&layout),
vertex: wgpu::VertexState {
module: &module,
entry_point: Some("vs_main"),
compilation_options: Default::default(),
buffers: &[],
},
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
fragment: Some(wgpu::FragmentState {
module: &module,
entry_point: Some("fs_main"),
compilation_options: Default::default(),
targets: &[Some(wgpu::ColorTargetState {
format: SCENE_OCC_FORMAT,
blend: None,
write_mask: wgpu::ColorWrites::ALL,
})],
}),
multiview_mask: None,
cache: None,
});
ResolvePipeline {
pipeline,
bind_layout,
}
}
fn build_occlusion_resources(device: &wgpu::Device, px: (u32, u32)) -> OcclusionResources {
let (width, height) = px;
let color = device.create_texture(&wgpu::TextureDescriptor {
label: Some("damascene_wgpu::scene::occlusion_depth"),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: SCENE_OCC_FORMAT,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
let color_view = color.create_view(&wgpu::TextureViewDescriptor::default());
let unpadded = width * 4; let padded_bytes_per_row = unpadded.div_ceil(COPY_ROW_ALIGN) * COPY_ROW_ALIGN;
let readback = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("damascene_wgpu::scene::occlusion_readback"),
size: (padded_bytes_per_row * height) as u64,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
});
OcclusionResources {
color_view,
color,
readback,
padded_bytes_per_row,
width,
height,
state: ReadbackState::Free,
last_captured: None,
}
}
fn depad_r32(bytes: &[u8], width: u32, height: u32, padded_bytes_per_row: u32) -> Vec<f32> {
let mut out = Vec::with_capacity((width * height) as usize);
let row_bytes = (width * 4) as usize;
for y in 0..height as usize {
let start = y * padded_bytes_per_row as usize;
let row = &bytes[start..start + row_bytes];
for px in row.chunks_exact(4) {
out.push(f32::from_le_bytes([px[0], px[1], px[2], px[3]]));
}
}
out
}
fn build_scene_pipelines(
device: &wgpu::Device,
layout: &wgpu::PipelineLayout,
point_shader: &wgpu::ShaderModule,
line_shader: &wgpu::ShaderModule,
mesh_shader: &wgpu::ShaderModule,
sample_count: u32,
) -> ScenePipelines {
let color_target = |blend| {
Some(wgpu::ColorTargetState {
format: SCENE_COLOR_FORMAT,
blend: Some(blend),
write_mask: wgpu::ColorWrites::ALL,
})
};
let multisample = wgpu::MultisampleState {
count: sample_count,
mask: !0,
alpha_to_coverage_enabled: false,
};
let depth_no_write = wgpu::DepthStencilState {
format: SCENE_DEPTH_FORMAT,
depth_write_enabled: Some(false),
depth_compare: Some(wgpu::CompareFunction::LessEqual),
stencil: Default::default(),
bias: Default::default(),
};
let depth_write = wgpu::DepthStencilState {
format: SCENE_DEPTH_FORMAT,
depth_write_enabled: Some(true),
depth_compare: Some(wgpu::CompareFunction::LessEqual),
stencil: Default::default(),
bias: Default::default(),
};
let point = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("damascene_wgpu::scene::point_pipeline"),
layout: Some(layout),
vertex: wgpu::VertexState {
module: point_shader,
entry_point: Some("vs_main"),
compilation_options: Default::default(),
buffers: &[
wgpu::VertexBufferLayout {
array_stride: (4 * std::mem::size_of::<f32>()) as u64,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &POINT_QUAD_ATTRS,
},
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<PointInstance>() as u64,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &POINT_INSTANCE_ATTRS,
},
],
},
fragment: Some(wgpu::FragmentState {
module: point_shader,
entry_point: Some("fs_main"),
compilation_options: Default::default(),
targets: &[color_target(premultiplied_blend())],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleStrip,
..Default::default()
},
depth_stencil: Some(depth_no_write.clone()),
multisample,
multiview_mask: None,
cache: None,
});
let line = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("damascene_wgpu::scene::line_pipeline"),
layout: Some(layout),
vertex: wgpu::VertexState {
module: line_shader,
entry_point: Some("vs_main"),
compilation_options: Default::default(),
buffers: &[
wgpu::VertexBufferLayout {
array_stride: (2 * std::mem::size_of::<f32>()) as u64,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &LINE_QUAD_ATTRS,
},
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<LineInstance>() as u64,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &LINE_INSTANCE_ATTRS,
},
],
},
fragment: Some(wgpu::FragmentState {
module: line_shader,
entry_point: Some("fs_main"),
compilation_options: Default::default(),
targets: &[color_target(premultiplied_blend())],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleStrip,
..Default::default()
},
depth_stencil: Some(depth_no_write),
multisample,
multiview_mask: None,
cache: None,
});
let mesh = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("damascene_wgpu::scene::mesh_pipeline"),
layout: Some(layout),
vertex: wgpu::VertexState {
module: mesh_shader,
entry_point: Some("vs_main"),
compilation_options: Default::default(),
buffers: &[wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<MeshVertexGpu>() as u64,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &MESH_VERTEX_ATTRS,
}],
},
fragment: Some(wgpu::FragmentState {
module: mesh_shader,
entry_point: Some("fs_main"),
compilation_options: Default::default(),
targets: &[color_target(premultiplied_blend())],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
..Default::default()
},
depth_stencil: Some(depth_write),
multisample,
multiview_mask: None,
cache: None,
});
ScenePipelines { point, line, mesh }
}