use ash::vk;
use concinnity_core::gfx::transform::IDENTITY;
use crate::vulkan::owned::{
OwnedDescriptorPool, OwnedFramebuffer, OwnedPipeline, OwnedPipelineLayout, OwnedRenderPass,
OwnedSetLayout, VkDevice,
};
use crate::vulkan::uniforms::GBUFFER_PREPASS_PUSH_BYTES;
use crate::vulkan::uniforms::GbModelPush;
use concinnity_render::uniforms::GBufferView;
use super::super::allocator::{DeviceAllocator, PooledBuffer};
use super::super::context::VkContext;
use super::super::pipeline::*;
use super::super::resources::{alloc_descriptor_sets, create_descriptor_set_layout};
use super::super::texture::*;
pub(in crate::vulkan) const GBUFFER_NORMAL_DEPTH_FORMAT: vk::Format =
vk::Format::R16G16B16A16_SFLOAT;
pub(in crate::vulkan) const GBUFFER_ROUGHNESS_FORMAT: vk::Format = vk::Format::R8_UNORM;
pub(in crate::vulkan) const GBUFFER_VELOCITY_FORMAT: vk::Format = vk::Format::R16G16_SFLOAT;
pub(in crate::vulkan) const GBUFFER_VIEW_UBO_SIZE: vk::DeviceSize = 256;
pub(in crate::vulkan) struct GbufferShaders {
pub prepass_vs: Vec<u8>,
pub prepass_instanced_vs: Vec<u8>,
pub prepass_skinned_vs: Vec<u8>,
pub prepass_fs: Vec<u8>,
}
pub(in crate::vulkan) fn compile_gbuffer_shaders(
hot_reload: bool,
) -> Result<GbufferShaders, String> {
use super::super::{builtins, slang_builtins};
let ctx = builtins::Ctx::plain(hot_reload);
Ok(GbufferShaders {
prepass_vs: slang_builtins::GBUFFER_PREPASS_VERT.compile(&ctx)?,
prepass_instanced_vs: slang_builtins::GBUFFER_PREPASS_VERT_INSTANCED.compile(&ctx)?,
prepass_skinned_vs: slang_builtins::GBUFFER_PREPASS_VERT_SKINNED.compile(&ctx)?,
prepass_fs: slang_builtins::GBUFFER_PREPASS_FRAG.compile(&ctx)?,
})
}
fn create_prepass_render_pass(device: &VkDevice) -> Result<OwnedRenderPass, String> {
let attachments = [
vk::AttachmentDescription::default()
.format(GBUFFER_NORMAL_DEPTH_FORMAT)
.samples(vk::SampleCountFlags::TYPE_1)
.load_op(vk::AttachmentLoadOp::CLEAR)
.store_op(vk::AttachmentStoreOp::STORE)
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
.initial_layout(vk::ImageLayout::UNDEFINED)
.final_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL),
vk::AttachmentDescription::default()
.format(GBUFFER_ROUGHNESS_FORMAT)
.samples(vk::SampleCountFlags::TYPE_1)
.load_op(vk::AttachmentLoadOp::CLEAR)
.store_op(vk::AttachmentStoreOp::STORE)
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
.initial_layout(vk::ImageLayout::UNDEFINED)
.final_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL),
vk::AttachmentDescription::default()
.format(GBUFFER_VELOCITY_FORMAT)
.samples(vk::SampleCountFlags::TYPE_1)
.load_op(vk::AttachmentLoadOp::CLEAR)
.store_op(vk::AttachmentStoreOp::STORE)
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
.initial_layout(vk::ImageLayout::UNDEFINED)
.final_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL),
vk::AttachmentDescription::default()
.format(vk::Format::D32_SFLOAT)
.samples(vk::SampleCountFlags::TYPE_1)
.load_op(vk::AttachmentLoadOp::CLEAR)
.store_op(vk::AttachmentStoreOp::STORE)
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
.initial_layout(vk::ImageLayout::UNDEFINED)
.final_layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL),
];
let color_refs = [
vk::AttachmentReference::default()
.attachment(0)
.layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL),
vk::AttachmentReference::default()
.attachment(1)
.layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL),
vk::AttachmentReference::default()
.attachment(2)
.layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL),
];
let depth_ref = vk::AttachmentReference::default()
.attachment(3)
.layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
let subpass = vk::SubpassDescription::default()
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
.color_attachments(&color_refs)
.depth_stencil_attachment(&depth_ref);
let dep = vk::SubpassDependency::default()
.src_subpass(vk::SUBPASS_EXTERNAL)
.dst_subpass(0)
.src_stage_mask(
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
| vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS
| vk::PipelineStageFlags::FRAGMENT_SHADER,
)
.src_access_mask(vk::AccessFlags::SHADER_READ)
.dst_stage_mask(
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
| vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS,
)
.dst_access_mask(
vk::AccessFlags::COLOR_ATTACHMENT_WRITE
| vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE,
);
let info = vk::RenderPassCreateInfo::default()
.attachments(&attachments)
.subpasses(std::slice::from_ref(&subpass))
.dependencies(std::slice::from_ref(&dep));
device
.create_render_pass(&info)
.map_err(|e| format!("gbuffer prepass render pass: {e}"))
}
#[derive(Clone, Copy)]
struct PrepassPipelineTargets {
render_pass: vk::RenderPass,
layout: vk::PipelineLayout,
}
struct PrepassPipelineShaders<'a> {
vert_spv: &'a [u8],
frag_spv: &'a [u8],
bindings: &'a [vk::VertexInputBindingDescription],
attrs: &'a [vk::VertexInputAttributeDescription],
}
fn create_prepass_pipeline(
device: &VkDevice,
targets: PrepassPipelineTargets,
shaders: PrepassPipelineShaders,
) -> Result<OwnedPipeline, String> {
let PrepassPipelineTargets {
render_pass,
layout,
} = targets;
let PrepassPipelineShaders {
vert_spv,
frag_spv,
bindings,
attrs,
} = shaders;
let vert_mod = spv_module(device, vert_spv)?;
let frag_mod = spv_module(device, frag_spv)?;
let entry = std::ffi::CString::new("main").unwrap();
let stages = [
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::VERTEX)
.module(vert_mod.handle())
.name(&entry),
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::FRAGMENT)
.module(frag_mod.handle())
.name(&entry),
];
let vert_input = vk::PipelineVertexInputStateCreateInfo::default()
.vertex_binding_descriptions(bindings)
.vertex_attribute_descriptions(attrs);
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
.topology(vk::PrimitiveTopology::TRIANGLE_LIST);
let viewport_state = vk::PipelineViewportStateCreateInfo::default()
.viewport_count(1)
.scissor_count(1);
let raster = vk::PipelineRasterizationStateCreateInfo::default()
.polygon_mode(vk::PolygonMode::FILL)
.line_width(1.0)
.cull_mode(vk::CullModeFlags::NONE)
.front_face(vk::FrontFace::COUNTER_CLOCKWISE);
let multisample = vk::PipelineMultisampleStateCreateInfo::default()
.rasterization_samples(vk::SampleCountFlags::TYPE_1);
let depth = vk::PipelineDepthStencilStateCreateInfo::default()
.depth_test_enable(true)
.depth_write_enable(true)
.depth_compare_op(vk::CompareOp::LESS);
let blend_attaches = [
vk::PipelineColorBlendAttachmentState::default()
.color_write_mask(vk::ColorComponentFlags::RGBA)
.blend_enable(false),
vk::PipelineColorBlendAttachmentState::default()
.color_write_mask(vk::ColorComponentFlags::RGBA)
.blend_enable(false),
vk::PipelineColorBlendAttachmentState::default()
.color_write_mask(vk::ColorComponentFlags::RGBA)
.blend_enable(false),
];
let blend = vk::PipelineColorBlendStateCreateInfo::default().attachments(&blend_attaches);
let dyn_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
let dynamic = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dyn_states);
let info = vk::GraphicsPipelineCreateInfo::default()
.stages(&stages)
.vertex_input_state(&vert_input)
.input_assembly_state(&input_assembly)
.viewport_state(&viewport_state)
.rasterization_state(&raster)
.multisample_state(&multisample)
.depth_stencil_state(&depth)
.color_blend_state(&blend)
.dynamic_state(&dynamic)
.layout(layout)
.render_pass(render_pass)
.subpass(0);
let pipeline = crate::vulkan::pipeline_cache::create_graphics_pipeline(device, &info)
.map_err(|e| format!("create gbuffer prepass pso: {e}"))?;
Ok(pipeline)
}
fn vertex_56_input() -> (
[vk::VertexInputBindingDescription; 1],
[vk::VertexInputAttributeDescription; 3],
) {
let binding = vk::VertexInputBindingDescription::default()
.binding(0)
.stride(56)
.input_rate(vk::VertexInputRate::VERTEX);
let attrs = [
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(0)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(0),
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(1)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(12),
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(3)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(36),
];
([binding], attrs)
}
fn skinned_vertex_input() -> (
[vk::VertexInputBindingDescription; 1],
[vk::VertexInputAttributeDescription; 4],
) {
let binding = vk::VertexInputBindingDescription::default()
.binding(0)
.stride(80)
.input_rate(vk::VertexInputRate::VERTEX);
let attrs = [
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(0)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(0),
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(1)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(12),
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(5)
.format(vk::Format::R16G16B16A16_UINT)
.offset(56),
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(6)
.format(vk::Format::R32G32B32A32_SFLOAT)
.offset(64),
];
([binding], attrs)
}
fn vertex_56_dual_input() -> (
[vk::VertexInputBindingDescription; 2],
[vk::VertexInputAttributeDescription; 4],
) {
let bindings = [
vk::VertexInputBindingDescription::default()
.binding(0)
.stride(56)
.input_rate(vk::VertexInputRate::VERTEX),
vk::VertexInputBindingDescription::default()
.binding(1)
.stride(56)
.input_rate(vk::VertexInputRate::VERTEX),
];
let attrs = [
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(0)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(0),
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(1)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(12),
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(3)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(36),
vk::VertexInputAttributeDescription::default()
.binding(1)
.location(5)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(0),
];
(bindings, attrs)
}
pub(in crate::vulkan) struct GbufferBindless {
pub(in crate::vulkan) pipeline: OwnedPipeline,
pub(in crate::vulkan) pipeline_layout: OwnedPipelineLayout,
pub(in crate::vulkan) set_layout: OwnedSetLayout,
pub(in crate::vulkan) sets: Vec<vk::DescriptorSet>,
pub(in crate::vulkan) prev_model_buffers: Vec<PooledBuffer>,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct GbufferDeviceCtx<'a> {
pub alloc: &'a DeviceAllocator,
pub device: &'a VkDevice,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct GbufferBindlessDescriptors {
pub descriptor_pool: vk::DescriptorPool,
pub bindless_set_layout: vk::DescriptorSetLayout,
}
pub(in crate::vulkan) struct GbufferBindlessScene<'a> {
pub instance_models: &'a [[[f32; 4]; 4]],
pub n_objects: usize,
pub n_cull: usize,
pub frames: usize,
}
pub(in crate::vulkan) fn build_gbuffer_bindless(
ctx: GbufferDeviceCtx,
descriptors: GbufferBindlessDescriptors,
gb: &GbufferResources,
scene: GbufferBindlessScene,
hot_reload: bool,
) -> Result<GbufferBindless, String> {
use super::super::builtins;
let GbufferDeviceCtx { alloc, device } = ctx;
let GbufferBindlessDescriptors {
descriptor_pool,
bindless_set_layout,
} = descriptors;
let GbufferBindlessScene {
instance_models,
n_objects,
n_cull,
frames,
} = scene;
let compile_ctx = builtins::Ctx::plain(hot_reload);
let vs = super::super::slang_builtins::GBUFFER_BINDLESS_VERT.compile(&compile_ctx)?;
let fs = super::super::slang_builtins::GBUFFER_BINDLESS_FRAG.compile(&compile_ctx)?;
let set_layout = create_descriptor_set_layout(
device,
&[
(
0,
vk::DescriptorType::UNIFORM_BUFFER,
vk::ShaderStageFlags::VERTEX,
),
(
1,
vk::DescriptorType::STORAGE_BUFFER,
vk::ShaderStageFlags::VERTEX,
),
],
)?;
let layouts = [set_layout.handle(), bindless_set_layout];
let pipeline_layout = device
.create_pipeline_layout(&vk::PipelineLayoutCreateInfo::default().set_layouts(&layouts))
.map_err(|e| format!("gbuffer bindless pipeline layout: {e}"))?;
let (bindings, attrs) = vertex_56_dual_input();
let pipeline = create_prepass_pipeline(
device,
PrepassPipelineTargets {
render_pass: gb.prepass_render_pass.handle(),
layout: pipeline_layout.handle(),
},
PrepassPipelineShaders {
vert_spv: &vs,
frag_spv: &fs,
bindings: &bindings,
attrs: &attrs,
},
)?;
let buf_size = (n_cull * std::mem::size_of::<[[f32; 4]; 4]>()) as u64;
let mut prev_model_buffers = Vec::with_capacity(frames);
for _ in 0..frames {
let buf = alloc.create_buffer(
buf_size,
vk::BufferUsageFlags::STORAGE_BUFFER,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?;
if !instance_models.is_empty() {
let stride = std::mem::size_of::<[[f32; 4]; 4]>();
buf.write_slice(n_objects * stride, instance_models);
}
prev_model_buffers.push(buf);
}
let set_layouts: Vec<_> = (0..frames).map(|_| set_layout.handle()).collect();
let sets = alloc_descriptor_sets(device, descriptor_pool, &set_layouts)?;
for (f, &set) in sets.iter().enumerate() {
let view_info = vk::DescriptorBufferInfo::default()
.buffer(gb.view_ubo_buffers[f].buffer())
.offset(0)
.range(GBUFFER_VIEW_UBO_SIZE);
let pm_info = vk::DescriptorBufferInfo::default()
.buffer(prev_model_buffers[f].buffer())
.offset(0)
.range(buf_size);
let writes = [
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(0)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
.buffer_info(std::slice::from_ref(&view_info)),
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(1)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.buffer_info(std::slice::from_ref(&pm_info)),
];
unsafe { device.update_descriptor_sets(&writes, &[]) };
}
Ok(GbufferBindless {
pipeline,
pipeline_layout,
set_layout,
sets,
prev_model_buffers,
})
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct PooledTarget {
pub image: vk::Image,
pub view: vk::ImageView,
}
#[derive(Clone, Default)]
pub(in crate::vulkan) struct GbufferPooled {
pub normal_depth: Vec<PooledTarget>,
pub roughness: Vec<PooledTarget>,
pub velocity: Vec<PooledTarget>,
}
pub(in crate::vulkan) struct GbufferResources {
pub(in crate::vulkan) prepass_render_pass: OwnedRenderPass,
pub(in crate::vulkan) prepass_set_layout: OwnedSetLayout,
pub(in crate::vulkan) prepass_layout_static: OwnedPipelineLayout,
pub(in crate::vulkan) prepass_layout_instanced: Option<OwnedPipelineLayout>,
pub(in crate::vulkan) prepass_layout_skinned: Option<OwnedPipelineLayout>,
pub(in crate::vulkan) prepass_pso_static: OwnedPipeline,
pub(in crate::vulkan) prepass_pso_instanced: Option<OwnedPipeline>,
pub(in crate::vulkan) prepass_pso_skinned: Option<OwnedPipeline>,
pub(in crate::vulkan) view_ubo_buffers: Vec<PooledBuffer>,
pub(in crate::vulkan) prepass_sets: Vec<vk::DescriptorSet>,
pub(in crate::vulkan) _descriptor_pool: OwnedDescriptorPool,
pub(in crate::vulkan) normal_depth_images: Vec<PooledTarget>,
pub(in crate::vulkan) roughness_images: Vec<PooledTarget>,
pub(in crate::vulkan) velocity_images: Vec<PooledTarget>,
pub(in crate::vulkan) depth_images: Vec<GpuImage>,
pub(in crate::vulkan) framebuffers: Vec<OwnedFramebuffer>,
pub(in crate::vulkan) prev_view_proj: [[f32; 4]; 4],
pub(in crate::vulkan) prev_models: Vec<[[f32; 4]; 4]>,
pub(in crate::vulkan) hot_reload: bool,
}
pub(in crate::vulkan) struct RebuiltGbufferPipelines {
pub prepass_static: OwnedPipeline,
pub prepass_instanced: Option<OwnedPipeline>,
pub prepass_skinned: Option<OwnedPipeline>,
}
pub(in crate::vulkan) fn rebuild_gbuffer_pipelines(
device: &VkDevice,
gbuffer: &GbufferResources,
hot_reload: bool,
) -> Result<RebuiltGbufferPipelines, String> {
let shaders = compile_gbuffer_shaders(hot_reload)?;
let (vbindings, vattrs) = vertex_56_input();
let prepass_static = create_prepass_pipeline(
device,
PrepassPipelineTargets {
render_pass: gbuffer.prepass_render_pass.handle(),
layout: gbuffer.prepass_layout_static.handle(),
},
PrepassPipelineShaders {
vert_spv: &shaders.prepass_vs,
frag_spv: &shaders.prepass_fs,
bindings: &vbindings,
attrs: &vattrs,
},
)?;
let prepass_instanced = if let (Some(layout), Some(_)) = (
gbuffer.prepass_layout_instanced.as_ref(),
gbuffer.prepass_pso_instanced.as_ref(),
) {
Some(create_prepass_pipeline(
device,
PrepassPipelineTargets {
render_pass: gbuffer.prepass_render_pass.handle(),
layout: layout.handle(),
},
PrepassPipelineShaders {
vert_spv: &shaders.prepass_instanced_vs,
frag_spv: &shaders.prepass_fs,
bindings: &vbindings,
attrs: &vattrs[..2],
},
)?)
} else {
None
};
let prepass_skinned = if let (Some(layout), Some(_)) = (
gbuffer.prepass_layout_skinned.as_ref(),
gbuffer.prepass_pso_skinned.as_ref(),
) {
let (sbindings, sattrs) = skinned_vertex_input();
Some(create_prepass_pipeline(
device,
PrepassPipelineTargets {
render_pass: gbuffer.prepass_render_pass.handle(),
layout: layout.handle(),
},
PrepassPipelineShaders {
vert_spv: &shaders.prepass_skinned_vs,
frag_spv: &shaders.prepass_fs,
bindings: &sbindings,
attrs: &sattrs,
},
)?)
} else {
None
};
Ok(RebuiltGbufferPipelines {
prepass_static,
prepass_instanced,
prepass_skinned,
})
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct GbufferQueueCtx {
pub command_pool: vk::CommandPool,
pub queue: vk::Queue,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct GbufferExtent {
pub width: u32,
pub height: u32,
pub frames: usize,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct GbufferSsboLayouts {
pub instance: Option<vk::DescriptorSetLayout>,
pub skinned: Option<vk::DescriptorSetLayout>,
}
impl GbufferResources {
pub(in crate::vulkan) fn new(
ctx: GbufferDeviceCtx,
queue: GbufferQueueCtx,
extent: GbufferExtent,
ssbo_layouts: GbufferSsboLayouts,
object_count: usize,
hot_reload: bool,
pooled: &GbufferPooled,
) -> Result<Self, String> {
let GbufferDeviceCtx { alloc, device } = ctx;
let GbufferExtent { frames, .. } = extent;
let GbufferSsboLayouts {
instance: instance_ssbo_set_layout,
skinned: skinned_ssbo_set_layout,
} = ssbo_layouts;
let prepass_render_pass = create_prepass_render_pass(device)?;
let prepass_set_layout = create_descriptor_set_layout(
device,
&[(
0,
vk::DescriptorType::UNIFORM_BUFFER,
vk::ShaderStageFlags::VERTEX,
)],
)?;
let prepass_push = vk::PushConstantRange::default()
.stage_flags(vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT)
.offset(0)
.size(GBUFFER_PREPASS_PUSH_BYTES);
let static_layouts = [prepass_set_layout.handle()];
let prepass_layout_static = device
.create_pipeline_layout(
&vk::PipelineLayoutCreateInfo::default()
.set_layouts(&static_layouts)
.push_constant_ranges(std::slice::from_ref(&prepass_push)),
)
.map_err(|e| format!("gbuffer prepass static layout: {e}"))?;
let prepass_layout_instanced = if let Some(isl) = instance_ssbo_set_layout {
let layouts = [prepass_set_layout.handle(), isl];
Some(
device
.create_pipeline_layout(
&vk::PipelineLayoutCreateInfo::default()
.set_layouts(&layouts)
.push_constant_ranges(std::slice::from_ref(&prepass_push)),
)
.map_err(|e| format!("gbuffer prepass instanced layout: {e}"))?,
)
} else {
None
};
let prepass_layout_skinned = if let Some(jsl) = skinned_ssbo_set_layout {
let layouts = [prepass_set_layout.handle(), jsl, jsl];
Some(
device
.create_pipeline_layout(
&vk::PipelineLayoutCreateInfo::default()
.set_layouts(&layouts)
.push_constant_ranges(std::slice::from_ref(&prepass_push)),
)
.map_err(|e| format!("gbuffer prepass skinned layout: {e}"))?,
)
} else {
None
};
let shaders = compile_gbuffer_shaders(hot_reload)?;
let (vbindings, vattrs) = vertex_56_input();
let prepass_pso_static = create_prepass_pipeline(
device,
PrepassPipelineTargets {
render_pass: prepass_render_pass.handle(),
layout: prepass_layout_static.handle(),
},
PrepassPipelineShaders {
vert_spv: &shaders.prepass_vs,
frag_spv: &shaders.prepass_fs,
bindings: &vbindings,
attrs: &vattrs,
},
)?;
let prepass_pso_instanced = if let Some(layout) = prepass_layout_instanced.as_ref() {
Some(create_prepass_pipeline(
device,
PrepassPipelineTargets {
render_pass: prepass_render_pass.handle(),
layout: layout.handle(),
},
PrepassPipelineShaders {
vert_spv: &shaders.prepass_instanced_vs,
frag_spv: &shaders.prepass_fs,
bindings: &vbindings,
attrs: &vattrs[..2],
},
)?)
} else {
None
};
let prepass_pso_skinned = if let Some(layout) = prepass_layout_skinned.as_ref() {
let (sbindings, sattrs) = skinned_vertex_input();
Some(create_prepass_pipeline(
device,
PrepassPipelineTargets {
render_pass: prepass_render_pass.handle(),
layout: layout.handle(),
},
PrepassPipelineShaders {
vert_spv: &shaders.prepass_skinned_vs,
frag_spv: &shaders.prepass_fs,
bindings: &sbindings,
attrs: &sattrs,
},
)?)
} else {
None
};
let mut view_ubo_buffers = Vec::with_capacity(frames);
for _ in 0..frames {
let buf = alloc.create_buffer(
GBUFFER_VIEW_UBO_SIZE,
vk::BufferUsageFlags::UNIFORM_BUFFER,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?;
view_ubo_buffers.push(buf);
}
let pool_sizes = [vk::DescriptorPoolSize::default()
.ty(vk::DescriptorType::UNIFORM_BUFFER)
.descriptor_count(frames as u32)];
let descriptor_pool = device
.create_descriptor_pool(
&vk::DescriptorPoolCreateInfo::default()
.pool_sizes(&pool_sizes)
.max_sets(frames as u32),
)
.map_err(|e| format!("gbuffer descriptor pool: {e}"))?;
let prepass_layouts: Vec<_> = (0..frames).map(|_| prepass_set_layout.handle()).collect();
let prepass_sets =
alloc_descriptor_sets(device, descriptor_pool.handle(), &prepass_layouts)?;
for (i, &set) in prepass_sets.iter().enumerate() {
let buf_info = vk::DescriptorBufferInfo::default()
.buffer(view_ubo_buffers[i].buffer())
.offset(0)
.range(GBUFFER_VIEW_UBO_SIZE);
let write = vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(0)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
.buffer_info(std::slice::from_ref(&buf_info));
unsafe { device.update_descriptor_sets(std::slice::from_ref(&write), &[]) };
}
let mut me = Self {
prepass_render_pass,
prepass_set_layout,
prepass_layout_static,
prepass_layout_instanced,
prepass_layout_skinned,
prepass_pso_static,
prepass_pso_instanced,
prepass_pso_skinned,
view_ubo_buffers,
prepass_sets,
_descriptor_pool: descriptor_pool,
normal_depth_images: Vec::new(),
roughness_images: Vec::new(),
velocity_images: Vec::new(),
depth_images: Vec::new(),
framebuffers: Vec::new(),
prev_view_proj: IDENTITY,
prev_models: vec![IDENTITY; object_count],
hot_reload,
};
me.build_targets(ctx, queue, extent, pooled)?;
Ok(me)
}
fn build_targets(
&mut self,
ctx: GbufferDeviceCtx,
queue: GbufferQueueCtx,
extent: GbufferExtent,
pooled: &GbufferPooled,
) -> Result<(), String> {
let GbufferDeviceCtx { alloc, device } = ctx;
let GbufferQueueCtx {
command_pool,
queue,
} = queue;
let GbufferExtent {
width,
height,
frames,
} = extent;
let w = width.max(1);
let h = height.max(1);
for f in 0..frames {
let normal_depth = *pooled
.normal_depth
.get(f)
.ok_or("gbuffer: pooled normal_depth slot out of range")?;
let roughness = *pooled
.roughness
.get(f)
.ok_or("gbuffer: pooled roughness slot out of range")?;
let velocity = *pooled
.velocity
.get(f)
.ok_or("gbuffer: pooled velocity slot out of range")?;
let depth = create_depth_image(
&GpuUploadContext {
alloc,
device,
command_pool,
queue,
},
w,
h,
vk::SampleCountFlags::TYPE_1,
)?;
let attachments = [normal_depth.view, roughness.view, velocity.view, depth.view];
let framebuffer = device
.create_framebuffer(
&vk::FramebufferCreateInfo::default()
.render_pass(self.prepass_render_pass.handle())
.attachments(&attachments)
.width(w)
.height(h)
.layers(1),
)
.map_err(|e| format!("gbuffer prepass framebuffer: {e}"))?;
self.normal_depth_images.push(normal_depth);
self.roughness_images.push(roughness);
self.velocity_images.push(velocity);
self.depth_images.push(depth);
self.framebuffers.push(framebuffer);
}
Ok(())
}
pub(in crate::vulkan) fn normal_depth_view(&self, frame: usize) -> vk::ImageView {
self.normal_depth_images[frame].view
}
pub(in crate::vulkan) fn roughness_view(&self, frame: usize) -> vk::ImageView {
self.roughness_images[frame].view
}
pub(in crate::vulkan) fn velocity_view(&self, frame: usize) -> vk::ImageView {
self.velocity_images[frame].view
}
pub(in crate::vulkan) fn normal_depth_views(&self) -> Vec<vk::ImageView> {
(0..self.normal_depth_images.len())
.map(|f| self.normal_depth_view(f))
.collect()
}
pub(in crate::vulkan) fn roughness_views(&self) -> Vec<vk::ImageView> {
(0..self.roughness_images.len())
.map(|f| self.roughness_view(f))
.collect()
}
pub(in crate::vulkan) fn velocity_views(&self) -> Vec<vk::ImageView> {
(0..self.velocity_images.len())
.map(|f| self.velocity_view(f))
.collect()
}
fn destroy_targets(&mut self, _device: &VkDevice) {
self.framebuffers.clear();
self.normal_depth_images.clear();
self.roughness_images.clear();
self.velocity_images.clear();
self.depth_images.clear();
}
pub(in crate::vulkan) fn rebuild(
&mut self,
ctx: GbufferDeviceCtx,
queue: GbufferQueueCtx,
extent: GbufferExtent,
pooled: &GbufferPooled,
) -> Result<(), String> {
self.destroy_targets(ctx.device);
self.build_targets(ctx, queue, extent, pooled)?;
Ok(())
}
pub(in crate::vulkan) fn ensure_skinned_gbuffer_pso(
&mut self,
device: &VkDevice,
joint_set_layout: vk::DescriptorSetLayout,
) -> Result<(), String> {
if let Some(_p) = self.prepass_pso_skinned.take() {
}
if let Some(_l) = self.prepass_layout_skinned.take() {
}
let prepass_push = vk::PushConstantRange::default()
.stage_flags(vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT)
.offset(0)
.size(GBUFFER_PREPASS_PUSH_BYTES);
let layouts = [
self.prepass_set_layout.handle(),
joint_set_layout,
joint_set_layout,
];
let layout = device
.create_pipeline_layout(
&vk::PipelineLayoutCreateInfo::default()
.set_layouts(&layouts)
.push_constant_ranges(std::slice::from_ref(&prepass_push)),
)
.map_err(|e| format!("gbuffer prepass skinned layout: {e}"))?;
use super::super::builtins;
let compile_ctx = builtins::Ctx::plain(self.hot_reload);
let sk_vs =
super::super::slang_builtins::GBUFFER_PREPASS_VERT_SKINNED.compile(&compile_ctx)?;
let prepass_fs =
super::super::slang_builtins::GBUFFER_PREPASS_FRAG.compile(&compile_ctx)?;
let (sbindings, sattrs) = skinned_vertex_input();
let pso = create_prepass_pipeline(
device,
PrepassPipelineTargets {
render_pass: self.prepass_render_pass.handle(),
layout: layout.handle(),
},
PrepassPipelineShaders {
vert_spv: &sk_vs,
frag_spv: &prepass_fs,
bindings: &sbindings,
attrs: &sattrs,
},
)?;
self.prepass_layout_skinned = Some(layout);
self.prepass_pso_skinned = Some(pso);
Ok(())
}
pub(in crate::vulkan) fn swap_pipelines(&mut self, rebuilt: RebuiltGbufferPipelines) {
self.prepass_pso_static = rebuilt.prepass_static;
self.prepass_pso_instanced = rebuilt.prepass_instanced;
self.prepass_pso_skinned = rebuilt.prepass_skinned;
}
pub(in crate::vulkan) fn destroy(&mut self, device: &VkDevice) {
self.destroy_targets(device);
}
}
pub(in crate::vulkan) struct GbufferPrepassView<'a> {
pub jittered_vp: [[f32; 4]; 4],
pub cur_vp: [[f32; 4]; 4],
pub cam_pos: [f32; 3],
pub frustum: &'a crate::gfx::frustum::Frustum,
}
impl VkContext {
pub(in crate::vulkan) fn encode_gbuffer_prepass(
&self,
gb: &GbufferResources,
cmd: vk::CommandBuffer,
frame_idx: usize,
view: GbufferPrepassView,
visible: &[u32],
velocity_active: bool,
) {
let GbufferPrepassView {
jittered_vp,
cur_vp,
cam_pos,
frustum,
} = view;
let device = &self.device;
let extent = self.render_extent;
let prev_vp = if velocity_active {
gb.prev_view_proj
} else {
cur_vp
};
let view_uni = GBufferView {
jittered_vp,
cur_vp,
prev_vp,
view: self.view.matrix,
};
gb.view_ubo_buffers[frame_idx].write_val(0, &view_uni);
let clears = [
vk::ClearValue {
color: vk::ClearColorValue {
float32: [0.0, 0.0, 0.0, 0.0],
},
},
vk::ClearValue {
color: vk::ClearColorValue {
float32: [1.0, 0.0, 0.0, 0.0],
},
},
vk::ClearValue {
color: vk::ClearColorValue { float32: [0.0; 4] },
},
vk::ClearValue {
depth_stencil: vk::ClearDepthStencilValue {
depth: 1.0,
stencil: 0,
},
},
];
let rp_begin = vk::RenderPassBeginInfo::default()
.render_pass(gb.prepass_render_pass.handle())
.framebuffer(gb.framebuffers[frame_idx].handle())
.render_area(vk::Rect2D::default().extent(extent))
.clear_values(&clears);
unsafe { device.cmd_begin_render_pass(cmd, &rp_begin, vk::SubpassContents::INLINE) };
let vp = vk::Viewport {
x: 0.0,
y: extent.height as f32,
width: extent.width as f32,
height: -(extent.height as f32),
min_depth: 0.0,
max_depth: 1.0,
};
let scissor = vk::Rect2D::default().extent(extent);
unsafe {
device.cmd_set_viewport(cmd, 0, std::slice::from_ref(&vp));
device.cmd_set_scissor(cmd, 0, std::slice::from_ref(&scissor));
}
if self.cull.gbuffer_bindless_pipeline.is_some() && self.cull_count() > 0 {
self.encode_gbuffer_prepass_gpu_driven(
gb,
cmd,
frame_idx,
visible,
cam_pos,
velocity_active,
);
unsafe { device.cmd_end_render_pass(cmd) };
return;
}
unsafe {
device.cmd_bind_vertex_buffers(cmd, 0, &[self.geometry.vertex_buffer.buffer()], &[0]);
device.cmd_bind_index_buffer(
cmd,
self.geometry.index_buffer.buffer(),
0,
vk::IndexType::UINT32,
);
device.cmd_bind_pipeline(
cmd,
vk::PipelineBindPoint::GRAPHICS,
gb.prepass_pso_static.handle(),
);
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
gb.prepass_layout_static.handle(),
0,
std::slice::from_ref(&gb.prepass_sets[frame_idx]),
&[],
);
}
let last_obj = self.draw.objects.len().saturating_sub(1);
let skip_seethrough = self.mesh_glass_active();
for &draw_idx in visible {
let i = (draw_idx as usize).min(last_obj);
let obj = match self.draw.objects.get(i) {
Some(o) => o,
None => continue,
};
if !obj.visible || !obj.resident {
continue;
}
if skip_seethrough && obj.material.see_through != 0 {
continue;
}
let d = crate::gfx::lod::camera_distance(obj, cam_pos);
let (index_offset, index_count) = obj.active_lod(d);
let prev_model = if velocity_active {
gb.prev_models.get(i).copied().unwrap_or(obj.model)
} else {
obj.model
};
let push = GbModelPush {
cur_model: obj.model,
prev_model,
roughness: obj.material.roughness,
_pad: [0.0; 3],
};
unsafe {
device.cmd_push_constants(
cmd,
gb.prepass_layout_static.handle(),
vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT,
0,
std::slice::from_raw_parts(
&push as *const GbModelPush as *const u8,
std::mem::size_of::<GbModelPush>(),
),
);
device.cmd_draw_indexed(
cmd,
index_count as u32,
1,
index_offset as u32,
obj.base_vertex,
0,
);
}
}
if let (Some(inst_pso), Some(inst_layout)) = (
gb.prepass_pso_instanced.as_ref(),
gb.prepass_layout_instanced.as_ref(),
) && !self.instanced.clusters.is_empty()
&& !self.instanced.sets.is_empty()
{
unsafe {
device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, inst_pso.handle());
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
inst_layout.handle(),
0,
std::slice::from_ref(&gb.prepass_sets[frame_idx]),
&[],
);
}
for (cluster_idx, cluster) in self.instanced.clusters.iter().enumerate() {
if cluster.instances.is_empty() {
continue;
}
if cluster.cullable() {
if !frustum.intersects_aabb(cluster.cluster_bb_min, cluster.cluster_bb_max) {
continue;
}
if cluster.cull_distance > 0.0 {
let d2 = crate::gfx::frustum::aabb_distance_sq(
cam_pos,
cluster.cluster_bb_min,
cluster.cluster_bb_max,
);
if d2 > cluster.cull_distance * cluster.cull_distance {
continue;
}
}
}
let Some(buckets) = self.instanced.lod_buckets.get(cluster_idx) else {
continue;
};
let inst_set = self.instanced.sets[frame_idx][cluster_idx];
let push = GbModelPush {
cur_model: [[0.0; 4]; 4], prev_model: [[0.0; 4]; 4], roughness: cluster.material.roughness,
_pad: [0.0; 3],
};
unsafe {
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
inst_layout.handle(),
1,
std::slice::from_ref(&inst_set),
&[],
);
device.cmd_push_constants(
cmd,
inst_layout.handle(),
vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT,
0,
std::slice::from_raw_parts(
&push as *const GbModelPush as *const u8,
std::mem::size_of::<GbModelPush>(),
),
);
let mut first_instance: u32 = 0;
for bucket in buckets {
let count = bucket.instances.len() as u32;
device.cmd_draw_indexed(
cmd,
bucket.index_count as u32,
count,
bucket.index_offset as u32,
0,
first_instance,
);
first_instance += count;
}
}
}
}
if let (Some(sk_pso), Some(sk_layout)) = (
gb.prepass_pso_skinned.as_ref(),
gb.prepass_layout_skinned.as_ref(),
) && !self.skinned.draw_objects.is_empty()
{
let frames = self.frames_in_flight.max(1);
let prev_frame_idx = if velocity_active && frames >= 2 {
(frame_idx + frames - 1) % frames
} else {
frame_idx
};
let (sk_vbuf, sk_ibuf) = self.skinned_geometry();
unsafe {
device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, sk_pso.handle());
device.cmd_bind_vertex_buffers(cmd, 0, std::slice::from_ref(&sk_vbuf), &[0]);
device.cmd_bind_index_buffer(cmd, sk_ibuf, 0, vk::IndexType::UINT32);
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
sk_layout.handle(),
0,
std::slice::from_ref(&gb.prepass_sets[frame_idx]),
&[],
);
}
for (i, obj) in self.skinned.draw_objects.iter().enumerate() {
if !obj.visible {
continue;
}
let d = crate::gfx::lod::skinned_camera_distance(obj, cam_pos);
let (index_offset, index_count) = obj.active_lod(d);
let push = GbModelPush {
cur_model: obj.model,
prev_model: obj.model,
roughness: obj.material.roughness,
_pad: [0.0; 3],
};
unsafe {
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
sk_layout.handle(),
1,
std::slice::from_ref(&self.skinned.joint_sets[frame_idx][i]),
&[],
);
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
sk_layout.handle(),
2,
std::slice::from_ref(&self.skinned.joint_sets[prev_frame_idx][i]),
&[],
);
device.cmd_push_constants(
cmd,
sk_layout.handle(),
vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT,
0,
std::slice::from_raw_parts(
&push as *const GbModelPush as *const u8,
std::mem::size_of::<GbModelPush>(),
),
);
device.cmd_draw_indexed(cmd, index_count as u32, 1, index_offset as u32, 0, 0);
}
}
unsafe {
device.cmd_bind_vertex_buffers(
cmd,
0,
&[self.geometry.vertex_buffer.buffer()],
&[0],
);
device.cmd_bind_index_buffer(
cmd,
self.geometry.index_buffer.buffer(),
0,
vk::IndexType::UINT32,
);
}
}
unsafe { device.cmd_end_render_pass(cmd) };
}
fn encode_gbuffer_prepass_gpu_driven(
&self,
gb: &GbufferResources,
cmd: vk::CommandBuffer,
frame_idx: usize,
visible: &[u32],
cam_pos: [f32; 3],
velocity_active: bool,
) {
let device = &self.device;
let (Some(pipeline), Some(layout)) = (
self.cull.gbuffer_bindless_pipeline.as_ref(),
self.cull.gbuffer_bindless_pipeline_layout.as_ref(),
) else {
return;
};
let Some(indirect) = self
.cull
.indirect_buffers
.get(frame_idx)
.map(|b| b.buffer())
else {
return;
};
let Some(&gset) = self.cull.gbuffer_sets.get(frame_idx) else {
return;
};
let stride = std::mem::size_of::<vk::DrawIndexedIndirectCommand>() as u32;
let prefix = self.skinned_record_base() as u32;
self.build_gbuffer_prev_models(gb, frame_idx, velocity_active);
unsafe {
device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, pipeline.handle());
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
layout.handle(),
0,
&[gset, self.cull.bindless_sets[frame_idx]],
&[],
);
device.cmd_bind_vertex_buffers(
cmd,
0,
&[
self.geometry.vertex_buffer.buffer(),
self.geometry.vertex_buffer.buffer(),
],
&[0, 0],
);
device.cmd_bind_index_buffer(
cmd,
self.geometry.index_buffer.buffer(),
0,
vk::IndexType::UINT32,
);
if prefix > 0 {
device.cmd_draw_indexed_indirect(cmd, indirect, 0, prefix, stride);
self.inc_draw_calls(1);
}
}
if prefix > 0 {
self.inc_draw_calls(self.draw_bucket_regions_shared_pipeline(cmd, indirect, prefix));
}
if self.draw.n_skinned > 0
&& let Some(cur) = self.skinned.deformed.get(frame_idx)
{
let frames = self.frames_in_flight.max(1);
let use_prev = velocity_active
&& frames >= 2
&& self
.skinned
.deformed_primed
.load(std::sync::atomic::Ordering::Relaxed);
let prev_idx = if use_prev {
(frame_idx + frames - 1) % frames
} else {
frame_idx
};
let prev = self.skinned.deformed.get(prev_idx).unwrap_or(cur);
unsafe {
device.cmd_bind_vertex_buffers(cmd, 0, &[cur.buffer, prev.buffer], &[0, 0]);
device.cmd_bind_index_buffer(
cmd,
self.skinned.index_buffer.buffer(),
0,
vk::IndexType::UINT32,
);
device.cmd_draw_indexed_indirect(
cmd,
indirect,
(self.skinned_record_base() * stride as usize) as u64,
self.draw.n_skinned as u32,
stride,
);
}
self.inc_draw_calls(1);
self.skinned
.deformed_primed
.store(true, std::sync::atomic::Ordering::Relaxed);
}
self.encode_gbuffer_legacy_extra(gb, cmd, frame_idx, visible, cam_pos, velocity_active);
}
fn encode_gbuffer_legacy_extra(
&self,
gb: &GbufferResources,
cmd: vk::CommandBuffer,
frame_idx: usize,
visible: &[u32],
cam_pos: [f32; 3],
velocity_active: bool,
) {
if self.clone.slot_by_draw_idx.is_empty() {
return;
}
let device = &self.device;
unsafe {
device.cmd_bind_pipeline(
cmd,
vk::PipelineBindPoint::GRAPHICS,
gb.prepass_pso_static.handle(),
);
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
gb.prepass_layout_static.handle(),
0,
std::slice::from_ref(&gb.prepass_sets[frame_idx]),
&[],
);
device.cmd_bind_vertex_buffers(cmd, 0, &[self.geometry.vertex_buffer.buffer()], &[0]);
device.cmd_bind_index_buffer(
cmd,
self.geometry.index_buffer.buffer(),
0,
vk::IndexType::UINT32,
);
}
let skip_seethrough = self.mesh_glass_active();
for &draw_idx in visible {
let i = draw_idx as usize;
if i < self.draw.n_objects {
continue; }
if !self.clone.slot_by_draw_idx.contains_key(&i) {
continue; }
let Some(obj) = self.draw.objects.get(i) else {
continue;
};
if !obj.visible || !obj.resident {
continue;
}
if skip_seethrough && obj.material.see_through != 0 {
continue;
}
let d = crate::gfx::lod::camera_distance(obj, cam_pos);
let (index_offset, index_count) = obj.active_lod(d);
let prev_model = if velocity_active {
gb.prev_models.get(i).copied().unwrap_or(obj.model)
} else {
obj.model
};
let push = GbModelPush {
cur_model: obj.model,
prev_model,
roughness: obj.material.roughness,
_pad: [0.0; 3],
};
unsafe {
device.cmd_push_constants(
cmd,
gb.prepass_layout_static.handle(),
vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT,
0,
std::slice::from_raw_parts(
&push as *const GbModelPush as *const u8,
std::mem::size_of::<GbModelPush>(),
),
);
device.cmd_draw_indexed(
cmd,
index_count as u32,
1,
index_offset as u32,
obj.base_vertex,
0,
);
}
}
}
fn build_gbuffer_prev_models(
&self,
gb: &GbufferResources,
frame_idx: usize,
velocity_active: bool,
) {
let Some(buf) = self.cull.prev_model_buffers.get(frame_idx) else {
return;
};
let stride = std::mem::size_of::<[[f32; 4]; 4]>();
for (i, obj) in self
.draw
.objects
.iter()
.take(self.draw.n_objects)
.enumerate()
{
let prev = if velocity_active {
gb.prev_models.get(i).copied().unwrap_or(obj.model)
} else {
obj.model
};
buf.write_val(i * stride, &prev);
}
let chunk_base = self.chunk_record_base();
self.for_each_chunk_record(|k, obj| {
buf.write_val((chunk_base + k) * stride, &obj.model);
});
let base = self.skinned_record_base();
for (k, obj) in self
.skinned
.draw_objects
.iter()
.take(self.draw.n_skinned)
.enumerate()
{
buf.write_val((base + k) * stride, &obj.model);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gb_view_uniforms_fits_ubo_allocation() {
assert!(std::mem::size_of::<GBufferView>() as u64 <= GBUFFER_VIEW_UBO_SIZE);
}
#[test]
fn gbuffer_shaders_compile() {
if !crate::slangc_gate::slangc_available() {
return;
}
compile_gbuffer_shaders(false).expect("gbuffer shaders compile");
}
}