use std::collections::HashMap;
use std::ops::Range;
use std::sync::Arc;
use damascene_core::color::ColorSpace;
use damascene_core::paint::PhysicalScissor;
use damascene_core::scene::{
LineDraw, MeshDraw, PointDraw, ResolvedCamera, Scene3DData, SceneDepthMap, gpu,
};
use damascene_core::shader::stock_wgsl;
use damascene_core::tree::Rect;
use smallvec::smallvec;
use vulkano::{
buffer::{
Buffer, BufferCreateInfo, BufferUsage, Subbuffer,
allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo},
},
command_buffer::{
AutoCommandBufferBuilder, CopyImageToBufferInfo, PrimaryAutoCommandBuffer,
RenderPassBeginInfo, SubpassBeginInfo, SubpassContents, SubpassEndInfo,
},
descriptor_set::{
DescriptorSet, WriteDescriptorSet, allocator::StandardDescriptorSetAllocator,
layout::DescriptorSetLayout,
},
device::Device,
format::{ClearValue, Format},
image::{
Image, ImageCreateInfo, ImageType, ImageUsage, SampleCount,
sampler::{Filter, Sampler, SamplerAddressMode, SamplerCreateInfo, SamplerMipmapMode},
view::ImageView,
},
memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator},
pipeline::{
DynamicState, GraphicsPipeline, Pipeline, PipelineLayout, PipelineShaderStageCreateInfo,
graphics::{
GraphicsPipelineCreateInfo,
color_blend::{
AttachmentBlend, BlendFactor, BlendOp, ColorBlendAttachmentState, ColorBlendState,
},
depth_stencil::{CompareOp, DepthState, DepthStencilState},
input_assembly::{InputAssemblyState, PrimitiveTopology},
rasterization::{CullMode, FrontFace, RasterizationState},
subpass::PipelineSubpassType,
vertex_input::{
VertexInputAttributeDescription, VertexInputBindingDescription, VertexInputRate,
VertexInputState,
},
viewport::{Scissor, Viewport, ViewportState},
},
layout::PipelineDescriptorSetLayoutCreateInfo,
},
render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass, Subpass},
shader::{ShaderModule, ShaderModuleCreateInfo},
};
use crate::naga_compile::wgsl_to_spirv;
use crate::pipeline::{build_shared_pipeline_layout, multisample_state};
const SCENE_COLOR_FORMAT: Format = Format::R16G16B16A16_SFLOAT;
const SCENE_DEPTH_FORMAT: Format = Format::D32_SFLOAT;
const SCENE_OCC_FORMAT: Format = Format::R32_SFLOAT;
enum DrawCmd {
Mesh {
geo: u64,
slot: usize,
translucent: bool,
},
Points {
geo: u64,
slot: usize,
},
Lines {
geo: u64,
slot: usize,
},
Grid {
slot: usize,
first: u32,
count: u32,
},
}
pub(crate) struct Scene3DRun {
target_id: String,
pub scissor: Option<PhysicalScissor>,
sample_count: u32,
clear: [f32; 4],
cmds: Vec<DrawCmd>,
pub composite_instance: u32,
capture_depth: bool,
camera: ResolvedCamera,
rect: Rect,
}
enum GeoBuffers {
Mesh {
vbuf: Subbuffer<[gpu::MeshVertexGpu]>,
ibuf: Option<Subbuffer<[u32]>>,
vcount: u32,
icount: u32,
},
Points {
ibuf: Subbuffer<[gpu::PointInstance]>,
count: u32,
},
Lines {
ibuf: Subbuffer<[gpu::LineInstance]>,
count: u32,
},
}
struct CachedGeometry {
buffers: GeoBuffers,
revision: u64,
space: ColorSpace,
used_frame: u64,
}
struct OffscreenTarget {
size: (u32, u32),
sample_count: u32,
framebuffer: Arc<Framebuffer>,
depth_view: Arc<ImageView>,
composite_set: Arc<DescriptorSet>,
occlusion: Option<OcclusionResources>,
used_frame: u64,
}
struct OcclusionResources {
framebuffer: Arc<Framebuffer>,
color: Arc<Image>,
readback: Subbuffer<[f32]>,
depth_set: Arc<DescriptorSet>,
width: u32,
height: u32,
state: ReadbackState,
last_captured: Option<(ResolvedCamera, Rect)>,
}
enum ReadbackState {
Free,
Pending { camera: ResolvedCamera, rect: Rect },
}
struct ScenePass {
render_pass: Arc<RenderPass>,
point: Arc<GraphicsPipeline>,
line: Arc<GraphicsPipeline>,
mesh: Arc<GraphicsPipeline>,
mesh_back: Arc<GraphicsPipeline>,
mesh_front: Arc<GraphicsPipeline>,
}
pub(crate) struct Scene3DPaint {
device: Arc<Device>,
memory_alloc: Arc<StandardMemoryAllocator>,
descriptor_alloc: Arc<StandardDescriptorSetAllocator>,
working: ColorSpace,
point_quad_vbo: Subbuffer<[f32]>,
line_quad_vbo: Subbuffer<[f32]>,
passes: HashMap<u32, ScenePass>,
uniform_set_layout: Option<Arc<DescriptorSetLayout>>,
resolve_pass: Arc<RenderPass>,
resolve_pipelines: HashMap<u32, Arc<GraphicsPipeline>>,
composite_pipeline: Arc<GraphicsPipeline>,
sampler: Arc<Sampler>,
uniform_alloc: SubbufferAllocator,
instance_alloc: SubbufferAllocator,
uniform_sets: Vec<Arc<DescriptorSet>>,
grid_instances: Vec<gpu::LineInstance>,
grid_buf: Option<Subbuffer<[gpu::LineInstance]>>,
composite_instances: Vec<gpu::CompositeInstance>,
composite_instance_buf: Option<Subbuffer<[gpu::CompositeInstance]>>,
runs: Vec<Scene3DRun>,
geometry: HashMap<u64, CachedGeometry>,
targets: HashMap<String, OffscreenTarget>,
frame_counter: u64,
}
const SUBALLOC_ARENA_SIZE: u64 = 256 * 1024;
impl Scene3DPaint {
pub(crate) fn new(
device: Arc<Device>,
memory_alloc: Arc<StandardMemoryAllocator>,
descriptor_alloc: Arc<StandardDescriptorSetAllocator>,
composite_subpass: Subpass,
composite_sample_count: u32,
working: ColorSpace,
) -> Self {
let point_quad_vbo = Buffer::from_iter(
memory_alloc.clone(),
BufferCreateInfo {
usage: BufferUsage::VERTEX_BUFFER,
..Default::default()
},
host_alloc(),
[
-1.0_f32, -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, ],
)
.expect("damascene-vulkano: scene point quad VBO");
let line_quad_vbo = Buffer::from_iter(
memory_alloc.clone(),
BufferCreateInfo {
usage: BufferUsage::VERTEX_BUFFER,
..Default::default()
},
host_alloc(),
[
0.0_f32, -1.0, 1.0, -1.0, 0.0, 1.0, 1.0, 1.0, ],
)
.expect("damascene-vulkano: scene line quad VBO");
let composite_pipeline =
build_composite_pipeline(device.clone(), composite_subpass, composite_sample_count);
let resolve_pass = build_resolve_render_pass(device.clone());
let sampler = Sampler::new(
device.clone(),
SamplerCreateInfo {
mag_filter: Filter::Linear,
min_filter: Filter::Linear,
mipmap_mode: SamplerMipmapMode::Linear,
address_mode: [SamplerAddressMode::ClampToEdge; 3],
..Default::default()
},
)
.expect("damascene-vulkano: scene composite sampler");
let uniform_alloc = SubbufferAllocator::new(
memory_alloc.clone(),
SubbufferAllocatorCreateInfo {
arena_size: SUBALLOC_ARENA_SIZE,
buffer_usage: BufferUsage::UNIFORM_BUFFER,
memory_type_filter: MemoryTypeFilter::PREFER_HOST
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
},
);
let instance_alloc = SubbufferAllocator::new(
memory_alloc.clone(),
SubbufferAllocatorCreateInfo {
arena_size: SUBALLOC_ARENA_SIZE,
buffer_usage: BufferUsage::VERTEX_BUFFER,
memory_type_filter: MemoryTypeFilter::PREFER_HOST
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
},
);
Self {
device,
memory_alloc,
descriptor_alloc,
working,
point_quad_vbo,
line_quad_vbo,
passes: HashMap::new(),
uniform_set_layout: None,
resolve_pass,
resolve_pipelines: HashMap::new(),
composite_pipeline,
sampler,
uniform_alloc,
instance_alloc,
uniform_sets: Vec::new(),
grid_instances: Vec::new(),
grid_buf: None,
composite_instances: Vec::new(),
composite_instance_buf: None,
runs: Vec::new(),
geometry: HashMap::new(),
targets: HashMap::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.uniform_sets.clear();
self.grid_instances.clear();
self.composite_instances.clear();
self.runs.clear();
self.frame_counter = self.frame_counter.wrapping_add(1);
}
pub(crate) fn has_runs(&self) -> bool {
!self.runs.is_empty()
}
pub(crate) fn run(&self, index: usize) -> &Scene3DRun {
&self.runs[index]
}
pub(crate) fn composite_pipeline(&self) -> &Arc<GraphicsPipeline> {
&self.composite_pipeline
}
pub(crate) fn composite_descriptor(&self, run: &Scene3DRun) -> &Arc<DescriptorSet> {
&self
.targets
.get(&run.target_id)
.expect("scene target alive for the frame")
.composite_set
}
pub(crate) fn composite_instance_buf(&self) -> &Subbuffer<[gpu::CompositeInstance]> {
self.composite_instance_buf
.as_ref()
.expect("damascene-vulkano: scene composite_instance_buf accessed with no draws")
}
pub(crate) fn record(
&mut self,
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_pass(sample_count);
if scene.capture_depth {
self.ensure_resolve_pipeline(sample_count);
}
self.ensure_target(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 mut translucent_cmds = Vec::new();
for (i, translucent) in gpu::mesh_draw_order(scene) {
let m = &scene.meshes[i];
self.ensure_mesh_geometry(m);
let slot = self.push_uniform(gpu::mesh_uniform(view_proj, m, scene, working));
let cmd = DrawCmd::Mesh {
geo: m.geometry.id().0,
slot,
translucent,
};
if translucent {
translucent_cmds.push(cmd);
} else {
cmds.push(cmd);
}
}
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_uniform(gpu::grid_uniform(view_proj, screen));
cmds.push(DrawCmd::Grid { slot, first, count });
}
cmds.append(&mut translucent_cmds);
for p in &scene.points {
self.ensure_point_geometry(p, working);
let slot = self.push_uniform(gpu::point_uniform(view_proj * p.transform, screen, p));
cmds.push(DrawCmd::Points {
geo: p.geometry.id().0,
slot,
});
}
for l in &scene.lines {
self.ensure_line_geometry(l, working);
let slot = self.push_uniform(gpu::line_uniform(view_proj * l.transform, screen, l));
cmds.push(DrawCmd::Lines {
geo: l.geometry.id().0,
slot,
});
}
let clear = scene
.style
.background
.map(|c| {
let [r, g, b, a] = damascene_core::paint::rgba_f32_in(c, working);
[r * a, g * a, b * a, a]
})
.unwrap_or([0.0, 0.0, 0.0, 0.0]);
let composite_instance = self.composite_instances.len() as u32;
self.composite_instances.push(gpu::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_uniform<T>(&mut self, u: T) -> usize
where
T: vulkano::buffer::BufferContents,
{
let buf = self
.uniform_alloc
.allocate_sized::<T>()
.expect("damascene-vulkano: scene uniform suballocate");
*buf.write()
.expect("damascene-vulkano: scene uniform suballocation write") = u;
let layout = self
.uniform_set_layout
.clone()
.expect("uniform_set_layout captured by ensure_pass");
let set = DescriptorSet::new(
self.descriptor_alloc.clone(),
layout,
[WriteDescriptorSet::buffer(0, buf)],
[],
)
.expect("damascene-vulkano: scene uniform descriptor set");
let slot = self.uniform_sets.len();
self.uniform_sets.push(set);
slot
}
fn ensure_pass(&mut self, sample_count: u32) {
if self.passes.contains_key(&sample_count) {
return;
}
let render_pass = build_scene_render_pass(self.device.clone(), sample_count);
let subpass = Subpass::from(render_pass.clone(), 0)
.expect("damascene-vulkano: scene offscreen subpass 0");
let point = build_scene_pipeline(
self.device.clone(),
subpass.clone(),
sample_count,
"stock::scene_point",
stock_wgsl::SCENE_POINT,
point_vertex_input(),
PrimitiveTopology::TriangleStrip,
false,
CullMode::None,
);
let line = build_scene_pipeline(
self.device.clone(),
subpass.clone(),
sample_count,
"stock::scene_line",
stock_wgsl::SCENE_LINE,
line_vertex_input(),
PrimitiveTopology::TriangleStrip,
false,
CullMode::None,
);
let mesh_pipeline = |subpass: Subpass, name, depth_write, cull| {
build_scene_pipeline(
self.device.clone(),
subpass,
sample_count,
name,
stock_wgsl::SCENE_MESH,
mesh_vertex_input(),
PrimitiveTopology::TriangleList,
depth_write,
cull,
)
};
let mesh = mesh_pipeline(subpass.clone(), "stock::scene_mesh", true, CullMode::Back);
let mesh_back = mesh_pipeline(
subpass.clone(),
"stock::scene_mesh_back",
false,
CullMode::Front,
);
let mesh_front = mesh_pipeline(subpass, "stock::scene_mesh_front", false, CullMode::Back);
if self.uniform_set_layout.is_none() {
self.uniform_set_layout = Some(point.layout().set_layouts()[0].clone());
}
self.passes.insert(
sample_count,
ScenePass {
render_pass,
point,
line,
mesh,
mesh_back,
mesh_front,
},
);
}
fn ensure_target(&mut self, id: &str, px: (u32, u32), sample_count: u32, capture_depth: bool) {
if let Some(t) = self.targets.get(id)
&& t.size == px
&& t.sample_count == sample_count
{
let need_occ = capture_depth && t.occlusion.is_none();
let depth_view = t.depth_view.clone();
if need_occ {
let occ = self.build_occlusion_resources(depth_view, px, sample_count);
self.targets
.get_mut(id)
.expect("target just matched")
.occlusion = Some(occ);
}
self.targets
.get_mut(id)
.expect("target just matched")
.used_frame = self.frame_counter;
return;
}
let pass = self.passes.get(&sample_count).expect("pass ensured");
let extent = [px.0, px.1, 1];
let resolve_image = Image::new(
self.memory_alloc.clone(),
ImageCreateInfo {
image_type: ImageType::Dim2d,
format: SCENE_COLOR_FORMAT,
extent,
usage: ImageUsage::COLOR_ATTACHMENT
| ImageUsage::SAMPLED
| ImageUsage::TRANSFER_DST,
..Default::default()
},
device_alloc(),
)
.expect("damascene-vulkano: scene resolve image");
let resolve_view = ImageView::new_default(resolve_image).expect("scene resolve view");
let depth_image = Image::new(
self.memory_alloc.clone(),
ImageCreateInfo {
image_type: ImageType::Dim2d,
format: SCENE_DEPTH_FORMAT,
extent,
samples: SampleCount::try_from(sample_count).unwrap_or(SampleCount::Sample1),
usage: ImageUsage::DEPTH_STENCIL_ATTACHMENT | ImageUsage::SAMPLED,
..Default::default()
},
device_alloc(),
)
.expect("damascene-vulkano: scene depth image");
let depth_view = ImageView::new_default(depth_image).expect("scene depth view");
let attachments = if sample_count > 1 {
let msaa_image = Image::new(
self.memory_alloc.clone(),
ImageCreateInfo {
image_type: ImageType::Dim2d,
format: SCENE_COLOR_FORMAT,
extent,
samples: SampleCount::try_from(sample_count).unwrap_or(SampleCount::Sample1),
usage: ImageUsage::COLOR_ATTACHMENT,
..Default::default()
},
device_alloc(),
)
.expect("damascene-vulkano: scene msaa colour image");
let msaa_view = ImageView::new_default(msaa_image).expect("scene msaa view");
vec![msaa_view, resolve_view.clone(), depth_view.clone()]
} else {
vec![resolve_view.clone(), depth_view.clone()]
};
let framebuffer = Framebuffer::new(
pass.render_pass.clone(),
FramebufferCreateInfo {
attachments,
..Default::default()
},
)
.expect("damascene-vulkano: scene framebuffer");
let composite_set = DescriptorSet::new(
self.descriptor_alloc.clone(),
self.composite_pipeline.layout().set_layouts()[1].clone(),
[
WriteDescriptorSet::image_view(0, resolve_view),
WriteDescriptorSet::sampler(1, self.sampler.clone()),
],
[],
)
.expect("damascene-vulkano: scene composite descriptor set");
let occlusion = capture_depth
.then(|| self.build_occlusion_resources(depth_view.clone(), px, sample_count));
self.targets.insert(
id.to_string(),
OffscreenTarget {
size: px,
sample_count,
framebuffer,
depth_view,
composite_set,
occlusion,
used_frame: self.frame_counter,
},
);
}
fn ensure_resolve_pipeline(&mut self, sample_count: u32) {
if self.resolve_pipelines.contains_key(&sample_count) {
return;
}
let subpass = Subpass::from(self.resolve_pass.clone(), 0)
.expect("damascene-vulkano: scene resolve subpass 0");
let pipeline = build_depth_resolve_pipeline(self.device.clone(), subpass, sample_count);
self.resolve_pipelines.insert(sample_count, pipeline);
}
fn build_occlusion_resources(
&self,
depth_view: Arc<ImageView>,
px: (u32, u32),
sample_count: u32,
) -> OcclusionResources {
let (width, height) = px;
let color = Image::new(
self.memory_alloc.clone(),
ImageCreateInfo {
image_type: ImageType::Dim2d,
format: SCENE_OCC_FORMAT,
extent: [width, height, 1],
usage: ImageUsage::COLOR_ATTACHMENT | ImageUsage::TRANSFER_SRC,
..Default::default()
},
device_alloc(),
)
.expect("damascene-vulkano: scene occlusion image");
let color_view = ImageView::new_default(color.clone()).expect("scene occlusion view");
let framebuffer = Framebuffer::new(
self.resolve_pass.clone(),
FramebufferCreateInfo {
attachments: vec![color_view],
..Default::default()
},
)
.expect("damascene-vulkano: scene occlusion framebuffer");
let readback = Buffer::new_slice::<f32>(
self.memory_alloc.clone(),
BufferCreateInfo {
usage: BufferUsage::TRANSFER_DST,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_HOST
| MemoryTypeFilter::HOST_RANDOM_ACCESS,
..Default::default()
},
(width * height) as u64,
)
.expect("damascene-vulkano: scene occlusion readback buffer");
let resolve = self
.resolve_pipelines
.get(&sample_count)
.expect("resolve pipeline ensured before occlusion resources");
let depth_set = DescriptorSet::new(
self.descriptor_alloc.clone(),
resolve.layout().set_layouts()[0].clone(),
[WriteDescriptorSet::image_view(0, depth_view)],
[],
)
.expect("damascene-vulkano: scene occlusion depth descriptor set");
OcclusionResources {
framebuffer,
color,
readback,
depth_set,
width,
height,
state: ReadbackState::Free,
last_captured: None,
}
}
fn ensure_mesh_geometry(&mut self, 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 = Buffer::from_iter(
self.memory_alloc.clone(),
BufferCreateInfo {
usage: BufferUsage::VERTEX_BUFFER,
..Default::default()
},
host_alloc(),
verts.iter().copied(),
)
.expect("damascene-vulkano: scene mesh vbuf");
let vcount = verts.len() as u32;
let (ibuf, icount) = match &data.indices {
Some(indices) if !indices.is_empty() => {
let ibuf = Buffer::from_iter(
self.memory_alloc.clone(),
BufferCreateInfo {
usage: BufferUsage::INDEX_BUFFER,
..Default::default()
},
host_alloc(),
indices.iter().copied(),
)
.expect("damascene-vulkano: scene mesh ibuf");
(Some(ibuf), indices.len() as u32)
}
_ => (None, 0),
};
self.geometry.insert(
id,
CachedGeometry {
buffers: GeoBuffers::Mesh {
vbuf,
ibuf,
vcount,
icount,
},
revision: rev,
space: self.working,
used_frame: self.frame_counter,
},
);
}
fn ensure_point_geometry(&mut self, 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 count = instances.len() as u32;
let ibuf = Buffer::from_iter(
self.memory_alloc.clone(),
BufferCreateInfo {
usage: BufferUsage::VERTEX_BUFFER,
..Default::default()
},
host_alloc(),
instances,
)
.expect("damascene-vulkano: scene point ibuf");
self.geometry.insert(
id,
CachedGeometry {
buffers: GeoBuffers::Points { ibuf, count },
revision: rev,
space: working,
used_frame: self.frame_counter,
},
);
}
fn ensure_line_geometry(&mut self, 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 count = instances.len() as u32;
let ibuf = Buffer::from_iter(
self.memory_alloc.clone(),
BufferCreateInfo {
usage: BufferUsage::VERTEX_BUFFER,
..Default::default()
},
host_alloc(),
instances,
)
.expect("damascene-vulkano: scene line ibuf");
self.geometry.insert(
id,
CachedGeometry {
buffers: GeoBuffers::Lines { ibuf, count },
revision: rev,
space: working,
used_frame: self.frame_counter,
},
);
}
pub(crate) fn flush(&mut self) {
let frame = self.frame_counter;
self.geometry.retain(|_, c| c.used_frame == frame);
self.targets.retain(|_, t| t.used_frame == frame);
self.grid_buf = (!self.grid_instances.is_empty()).then(|| {
let buf = self
.instance_alloc
.allocate_slice::<gpu::LineInstance>(self.grid_instances.len() as u64)
.expect("damascene-vulkano: scene grid suballocate");
buf.write()
.expect("damascene-vulkano: scene grid write")
.copy_from_slice(&self.grid_instances);
buf
});
self.composite_instance_buf = (!self.composite_instances.is_empty()).then(|| {
let buf = self
.instance_alloc
.allocate_slice::<gpu::CompositeInstance>(self.composite_instances.len() as u64)
.expect("damascene-vulkano: scene composite suballocate");
buf.write()
.expect("damascene-vulkano: scene composite write")
.copy_from_slice(&self.composite_instances);
buf
});
}
pub(crate) fn encode_offscreen(
&self,
builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
) {
for run in &self.runs {
let Some(target) = self.targets.get(&run.target_id) else {
continue;
};
let pass = self
.passes
.get(&run.sample_count)
.expect("pass ensured at record time");
let clear_values: Vec<Option<ClearValue>> = if run.sample_count > 1 {
vec![
Some(ClearValue::Float(run.clear)),
None,
Some(ClearValue::Depth(1.0)),
]
} else {
vec![
Some(ClearValue::Float(run.clear)),
Some(ClearValue::Depth(1.0)),
]
};
builder
.begin_render_pass(
RenderPassBeginInfo {
clear_values,
..RenderPassBeginInfo::framebuffer(target.framebuffer.clone())
},
SubpassBeginInfo {
contents: SubpassContents::Inline,
..Default::default()
},
)
.expect("damascene-vulkano: scene begin_render_pass");
let (w, h) = target.size;
builder
.set_viewport(
0,
smallvec![Viewport {
offset: [0.0, 0.0],
extent: [w as f32, h as f32],
depth_range: 0.0..=1.0,
}],
)
.expect("scene set_viewport");
builder
.set_scissor(
0,
smallvec![Scissor {
offset: [0, 0],
extent: [w, h],
}],
)
.expect("scene set_scissor");
for cmd in &run.cmds {
match *cmd {
DrawCmd::Grid { slot, first, count } => {
let Some(grid) = &self.grid_buf else { continue };
bind_set0(builder, &pass.line, &self.uniform_sets[slot]);
builder
.bind_pipeline_graphics(pass.line.clone())
.expect("bind line pipeline");
builder
.bind_vertex_buffers(0, (self.line_quad_vbo.clone(), grid.clone()))
.expect("bind grid buffers");
unsafe {
builder.draw(4, count, 0, first).expect("draw grid");
}
}
DrawCmd::Points { geo, slot } => {
let Some(CachedGeometry {
buffers: GeoBuffers::Points { ibuf, count },
..
}) = self.geometry.get(&geo)
else {
continue;
};
builder
.bind_pipeline_graphics(pass.point.clone())
.expect("bind point pipeline");
bind_set0(builder, &pass.point, &self.uniform_sets[slot]);
builder
.bind_vertex_buffers(0, (self.point_quad_vbo.clone(), ibuf.clone()))
.expect("bind point buffers");
unsafe {
builder.draw(4, *count, 0, 0).expect("draw points");
}
}
DrawCmd::Lines { geo, slot } => {
let Some(CachedGeometry {
buffers: GeoBuffers::Lines { ibuf, count },
..
}) = self.geometry.get(&geo)
else {
continue;
};
builder
.bind_pipeline_graphics(pass.line.clone())
.expect("bind line pipeline");
bind_set0(builder, &pass.line, &self.uniform_sets[slot]);
builder
.bind_vertex_buffers(0, (self.line_quad_vbo.clone(), ibuf.clone()))
.expect("bind line buffers");
unsafe {
builder.draw(4, *count, 0, 0).expect("draw lines");
}
}
DrawCmd::Mesh {
geo,
slot,
translucent,
} => {
let Some(CachedGeometry {
buffers:
GeoBuffers::Mesh {
vbuf,
ibuf,
vcount,
icount,
},
..
}) = self.geometry.get(&geo)
else {
continue;
};
let pipelines: &[&Arc<GraphicsPipeline>] = if translucent {
&[&pass.mesh_back, &pass.mesh_front]
} else {
&[&pass.mesh]
};
for pipeline in pipelines {
builder
.bind_pipeline_graphics((*pipeline).clone())
.expect("bind mesh pipeline");
bind_set0(builder, pipeline, &self.uniform_sets[slot]);
builder
.bind_vertex_buffers(0, vbuf.clone())
.expect("bind mesh vbuf");
match ibuf {
Some(ibuf) => {
builder
.bind_index_buffer(ibuf.clone())
.expect("bind mesh ibuf");
unsafe {
builder
.draw_indexed(*icount, 1, 0, 0, 0)
.expect("draw_indexed mesh");
}
}
None => unsafe {
builder.draw(*vcount, 1, 0, 0).expect("draw mesh");
},
}
}
}
}
}
builder
.end_render_pass(SubpassEndInfo::default())
.expect("damascene-vulkano: scene end_render_pass");
}
}
pub(crate) fn encode_depth_capture(
&mut self,
builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
) {
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(resolve) = self.resolve_pipelines.get(&sample_count).cloned() else {
continue;
};
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; }
builder
.begin_render_pass(
RenderPassBeginInfo {
clear_values: vec![Some(ClearValue::Float([1.0, 0.0, 0.0, 0.0]))],
..RenderPassBeginInfo::framebuffer(occ.framebuffer.clone())
},
SubpassBeginInfo {
contents: SubpassContents::Inline,
..Default::default()
},
)
.expect("damascene-vulkano: occlusion begin_render_pass");
builder
.set_viewport(
0,
smallvec![Viewport {
offset: [0.0, 0.0],
extent: [occ.width as f32, occ.height as f32],
depth_range: 0.0..=1.0,
}],
)
.expect("occlusion set_viewport");
builder
.set_scissor(
0,
smallvec![Scissor {
offset: [0, 0],
extent: [occ.width, occ.height],
}],
)
.expect("occlusion set_scissor");
builder
.bind_pipeline_graphics(resolve.clone())
.expect("bind occlusion pipeline");
builder
.bind_descriptor_sets(
vulkano::pipeline::PipelineBindPoint::Graphics,
resolve.layout().clone(),
0,
occ.depth_set.clone(),
)
.expect("bind occlusion depth set");
unsafe {
builder.draw(3, 1, 0, 0).expect("draw occlusion resolve");
}
builder
.end_render_pass(SubpassEndInfo::default())
.expect("damascene-vulkano: occlusion end_render_pass");
builder
.copy_image_to_buffer(CopyImageToBufferInfo::image_buffer(
occ.color.clone(),
occ.readback.clone(),
))
.expect("damascene-vulkano: occlusion copy image → buffer");
occ.state = ReadbackState::Pending { camera, rect };
occ.last_captured = Some((camera, rect));
}
}
pub(crate) fn collect_depth_maps(&mut self) -> Vec<(String, SceneDepthMap)> {
let mut ready = Vec::new();
for (id, target) in self.targets.iter_mut() {
let Some(occ) = target.occlusion.as_mut() else {
continue;
};
let ReadbackState::Pending { camera, rect } = occ.state else {
continue;
};
let Ok(guard) = occ.readback.read() else {
continue;
};
let depth: Vec<f32> = guard.to_vec();
drop(guard);
occ.state = ReadbackState::Free;
ready.push((
id.clone(),
SceneDepthMap {
camera,
rect,
width: occ.width,
height: occ.height,
depth: Arc::from(depth),
},
));
}
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))
}
}
})
}
}
fn bind_set0(
builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
pipeline: &Arc<GraphicsPipeline>,
set: &Arc<DescriptorSet>,
) {
builder
.bind_descriptor_sets(
vulkano::pipeline::PipelineBindPoint::Graphics,
pipeline.layout().clone(),
0,
set.clone(),
)
.expect("damascene-vulkano: scene bind set 0");
}
fn host_alloc() -> AllocationCreateInfo {
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
}
}
fn device_alloc() -> AllocationCreateInfo {
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE,
..Default::default()
}
}
fn attr(binding: u32, offset: u32, format: Format) -> VertexInputAttributeDescription {
VertexInputAttributeDescription {
binding,
offset,
format,
..Default::default()
}
}
fn point_vertex_input() -> VertexInputState {
VertexInputState::new()
.binding(
0,
VertexInputBindingDescription {
stride: (4 * std::mem::size_of::<f32>()) as u32,
input_rate: VertexInputRate::Vertex,
..Default::default()
},
)
.binding(
1,
VertexInputBindingDescription {
stride: std::mem::size_of::<gpu::PointInstance>() as u32,
input_rate: VertexInputRate::Instance { divisor: 1 },
..Default::default()
},
)
.attribute(0, attr(0, 0, Format::R32G32_SFLOAT)) .attribute(1, attr(0, 8, Format::R32G32_SFLOAT)) .attribute(2, attr(1, 0, Format::R32G32B32_SFLOAT)) .attribute(3, attr(1, 12, Format::R32G32B32A32_SFLOAT)) }
fn line_vertex_input() -> VertexInputState {
VertexInputState::new()
.binding(
0,
VertexInputBindingDescription {
stride: (2 * std::mem::size_of::<f32>()) as u32,
input_rate: VertexInputRate::Vertex,
..Default::default()
},
)
.binding(
1,
VertexInputBindingDescription {
stride: std::mem::size_of::<gpu::LineInstance>() as u32,
input_rate: VertexInputRate::Instance { divisor: 1 },
..Default::default()
},
)
.attribute(0, attr(0, 0, Format::R32G32_SFLOAT)) .attribute(1, attr(1, 0, Format::R32G32B32_SFLOAT)) .attribute(2, attr(1, 12, Format::R32G32B32_SFLOAT)) .attribute(3, attr(1, 24, Format::R32G32B32A32_SFLOAT)) .attribute(4, attr(1, 40, Format::R32_SFLOAT)) }
fn mesh_vertex_input() -> VertexInputState {
VertexInputState::new()
.binding(
0,
VertexInputBindingDescription {
stride: std::mem::size_of::<gpu::MeshVertexGpu>() as u32,
input_rate: VertexInputRate::Vertex,
..Default::default()
},
)
.attribute(0, attr(0, 0, Format::R32G32B32_SFLOAT)) .attribute(1, attr(0, 12, Format::R32G32B32_SFLOAT)) }
#[allow(clippy::too_many_arguments)]
fn build_scene_pipeline(
device: Arc<Device>,
subpass: Subpass,
sample_count: u32,
name: &str,
wgsl: &str,
vertex_input_state: VertexInputState,
topology: PrimitiveTopology,
depth_write: bool,
cull: CullMode,
) -> Arc<GraphicsPipeline> {
let module = compile(device.clone(), name, wgsl);
let vs = module
.entry_point("vs_main")
.unwrap_or_else(|| panic!("`{name}` has no vs_main"));
let fs = module
.entry_point("fs_main")
.unwrap_or_else(|| panic!("`{name}` has no fs_main"));
let stages = [
PipelineShaderStageCreateInfo::new(vs),
PipelineShaderStageCreateInfo::new(fs),
];
let layout = build_shared_pipeline_layout(device.clone(), &stages);
let rasterization_state = RasterizationState {
cull_mode: cull,
front_face: FrontFace::CounterClockwise,
..Default::default()
};
GraphicsPipeline::new(
device,
None,
GraphicsPipelineCreateInfo {
stages: stages.into_iter().collect(),
vertex_input_state: Some(vertex_input_state),
input_assembly_state: Some(InputAssemblyState {
topology,
..Default::default()
}),
viewport_state: Some(ViewportState::default()),
rasterization_state: Some(rasterization_state),
multisample_state: Some(multisample_state(sample_count)),
depth_stencil_state: Some(DepthStencilState {
depth: Some(DepthState {
write_enable: depth_write,
compare_op: CompareOp::LessOrEqual,
}),
..Default::default()
}),
color_blend_state: Some(ColorBlendState::with_attachment_states(
subpass.num_color_attachments(),
ColorBlendAttachmentState {
blend: Some(premultiplied_blend()),
..Default::default()
},
)),
dynamic_state: [DynamicState::Viewport, DynamicState::Scissor]
.into_iter()
.collect(),
subpass: Some(PipelineSubpassType::BeginRenderPass(subpass)),
..GraphicsPipelineCreateInfo::layout(layout)
},
)
.unwrap_or_else(|e| panic!("damascene-vulkano: scene pipeline `{name}`: {e:?}"))
}
fn build_composite_pipeline(
device: Arc<Device>,
subpass: Subpass,
sample_count: u32,
) -> Arc<GraphicsPipeline> {
let module = compile(device.clone(), "stock::surface", stock_wgsl::SURFACE);
let vs = module.entry_point("vs_main").expect("surface vs_main");
let fs = module.entry_point("fs_premul").expect("surface fs_premul");
let stages = [
PipelineShaderStageCreateInfo::new(vs),
PipelineShaderStageCreateInfo::new(fs),
];
let layout = build_shared_pipeline_layout(device.clone(), &stages);
let vertex_input_state = VertexInputState::new()
.binding(
0,
VertexInputBindingDescription {
stride: (2 * std::mem::size_of::<f32>()) as u32,
input_rate: VertexInputRate::Vertex,
..Default::default()
},
)
.binding(
1,
VertexInputBindingDescription {
stride: std::mem::size_of::<gpu::CompositeInstance>() as u32,
input_rate: VertexInputRate::Instance { divisor: 1 },
..Default::default()
},
)
.attribute(0, attr(0, 0, Format::R32G32_SFLOAT))
.attribute(1, attr(1, 0, Format::R32G32B32A32_SFLOAT)) .attribute(2, attr(1, 16, Format::R32G32B32A32_SFLOAT)) .attribute(3, attr(1, 32, Format::R32G32_SFLOAT));
GraphicsPipeline::new(
device,
None,
GraphicsPipelineCreateInfo {
stages: stages.into_iter().collect(),
vertex_input_state: Some(vertex_input_state),
input_assembly_state: Some(InputAssemblyState {
topology: PrimitiveTopology::TriangleStrip,
..Default::default()
}),
viewport_state: Some(ViewportState::default()),
rasterization_state: Some(RasterizationState::default()),
multisample_state: Some(multisample_state(sample_count)),
color_blend_state: Some(ColorBlendState::with_attachment_states(
subpass.num_color_attachments(),
ColorBlendAttachmentState {
blend: Some(premultiplied_blend()),
..Default::default()
},
)),
dynamic_state: [DynamicState::Viewport, DynamicState::Scissor]
.into_iter()
.collect(),
subpass: Some(PipelineSubpassType::BeginRenderPass(subpass)),
..GraphicsPipelineCreateInfo::layout(layout)
},
)
.expect("damascene-vulkano: scene composite pipeline")
}
fn compile(device: Arc<Device>, name: &str, wgsl: &str) -> Arc<ShaderModule> {
let words = wgsl_to_spirv(name, wgsl).unwrap_or_else(|e| panic!("WGSL compile `{name}`: {e}"));
unsafe {
ShaderModule::new(device, ShaderModuleCreateInfo::new(&words))
.unwrap_or_else(|e| panic!("ShaderModule::new `{name}`: {e}"))
}
}
fn premultiplied_blend() -> AttachmentBlend {
AttachmentBlend {
src_color_blend_factor: BlendFactor::One,
dst_color_blend_factor: BlendFactor::OneMinusSrcAlpha,
color_blend_op: BlendOp::Add,
src_alpha_blend_factor: BlendFactor::One,
dst_alpha_blend_factor: BlendFactor::OneMinusSrcAlpha,
alpha_blend_op: BlendOp::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_render_pass(device: Arc<Device>) -> Arc<RenderPass> {
vulkano::single_pass_renderpass!(
device,
attachments: {
occ: {
format: SCENE_OCC_FORMAT,
samples: 1,
load_op: Clear,
store_op: Store,
},
},
pass: {
color: [occ],
depth_stencil: {},
},
)
.expect("damascene-vulkano: scene resolve render pass")
}
fn build_depth_resolve_pipeline(
device: Arc<Device>,
subpass: Subpass,
sample_count: u32,
) -> Arc<GraphicsPipeline> {
let module = compile(
device.clone(),
"stock::scene_depth_resolve",
&resolve_wgsl(sample_count > 1),
);
let vs = module.entry_point("vs_main").expect("resolve vs_main");
let fs = module.entry_point("fs_main").expect("resolve fs_main");
let stages = [
PipelineShaderStageCreateInfo::new(vs),
PipelineShaderStageCreateInfo::new(fs),
];
let layout = PipelineLayout::new(
device.clone(),
PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages)
.into_pipeline_layout_create_info(device.clone())
.expect("damascene-vulkano: resolve layout from stages"),
)
.expect("damascene-vulkano: resolve pipeline layout");
GraphicsPipeline::new(
device,
None,
GraphicsPipelineCreateInfo {
stages: stages.into_iter().collect(),
vertex_input_state: Some(VertexInputState::new()),
input_assembly_state: Some(InputAssemblyState {
topology: PrimitiveTopology::TriangleList,
..Default::default()
}),
viewport_state: Some(ViewportState::default()),
rasterization_state: Some(RasterizationState::default()),
multisample_state: Some(multisample_state(1)),
color_blend_state: Some(ColorBlendState::with_attachment_states(
subpass.num_color_attachments(),
ColorBlendAttachmentState::default(),
)),
dynamic_state: [DynamicState::Viewport, DynamicState::Scissor]
.into_iter()
.collect(),
subpass: Some(PipelineSubpassType::BeginRenderPass(subpass)),
..GraphicsPipelineCreateInfo::layout(layout)
},
)
.unwrap_or_else(|e| panic!("damascene-vulkano: depth-resolve pipeline: {e:?}"))
}
fn build_scene_render_pass(device: Arc<Device>, sample_count: u32) -> Arc<RenderPass> {
if sample_count <= 1 {
vulkano::single_pass_renderpass!(
device,
attachments: {
color: {
format: SCENE_COLOR_FORMAT,
samples: 1,
load_op: Clear,
store_op: Store,
},
depth: {
format: SCENE_DEPTH_FORMAT,
samples: 1,
load_op: Clear,
store_op: Store,
},
},
pass: {
color: [color],
depth_stencil: { depth },
},
)
.expect("damascene-vulkano: scene single-sample render pass")
} else {
vulkano::single_pass_renderpass!(
device,
attachments: {
color_msaa: {
format: SCENE_COLOR_FORMAT,
samples: sample_count,
load_op: Clear,
store_op: Store,
},
color_resolve: {
format: SCENE_COLOR_FORMAT,
samples: 1,
load_op: DontCare,
store_op: Store,
},
depth: {
format: SCENE_DEPTH_FORMAT,
samples: sample_count,
load_op: Clear,
store_op: Store,
},
},
pass: {
color: [color_msaa],
color_resolve: [color_resolve],
depth_stencil: { depth },
},
)
.expect("damascene-vulkano: scene multisample render pass")
}
}