use std::ffi::c_void;
use windows::Win32::Graphics::Direct3D::D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
use windows::Win32::Graphics::Direct3D12::*;
use windows::Win32::Graphics::Dxgi::Common::*;
use concinnity_core::components::sdf_programs::SdfPrograms;
use concinnity_core::platform::Platform;
use concinnity_core::render::slang_programs::raymarch::{self, Family};
use concinnity_slang::SlangTarget;
use super::allocator::{DeviceAllocator, PooledBuffer, PooledTexture};
use crate::components::sdf_volume::SdfVolume;
use crate::directx::com;
use crate::directx::context::{DxContext, FRAMES, align256, dump_on_err};
use crate::directx::pipeline::{main_input_layout, serialize_desc_and_create};
use crate::directx::texture::{
HDR_FORMAT, create_buffer, create_fallback_white_resource, create_hdr_resolve_target,
transition_barrier,
};
use crate::gfx::mesh_payload::Vertex;
use crate::gfx::render_types::LightUniforms;
pub(in crate::directx) use concinnity_core::render::uniforms::{
RaymarchView, RaymarchVolumeUniforms,
};
fn volume_uniforms_from(v: &SdfVolume) -> RaymarchVolumeUniforms {
RaymarchVolumeUniforms {
centre: v.centre,
_pad0: 0.0,
extent: v.extent,
_pad1: 0.0,
cone_ratio: v.cone_ratio(),
max_distance: v.max_distance,
max_steps: v.max_steps as i32,
receive_shadows: if v.receive_shadows { 1 } else { 0 },
params: v.params,
}
}
pub(in crate::directx) struct RaymarchVolumeRecord {
pub(in crate::directx) pso: ID3D12PipelineState,
pub(in crate::directx) shadow_pso: Option<ID3D12PipelineState>,
#[expect(
dead_code,
reason = "held to keep the GPU memory alive; the encoder binds through volume_cbuffer_gva"
)]
volume_cbuffer: PooledBuffer,
pub(in crate::directx) volume_cbuffer_gva: u64,
pub(in crate::directx) visible: bool,
pub(in crate::directx) cast_shadows: bool,
}
pub(in crate::directx) struct RaymarchResources {
pub(in crate::directx) root_sig: ID3D12RootSignature,
pub(in crate::directx) shadow_root_sig: ID3D12RootSignature,
#[expect(
dead_code,
reason = "held to keep the cube geometry resident; the encoder binds cube_vbv"
)]
cube_vb: PooledBuffer,
#[expect(
dead_code,
reason = "held to keep the cube geometry resident; the encoder binds cube_ibv"
)]
cube_ib: PooledBuffer,
cube_vbv: D3D12_VERTEX_BUFFER_VIEW,
cube_ibv: D3D12_INDEX_BUFFER_VIEW,
view_cbuffers: Vec<PooledBuffer>,
view_ptrs: Vec<*mut u8>,
#[expect(
dead_code,
reason = "holds the scene_color slot open during init; the live SRV is re-pointed before the first frame"
)]
scene_color_fallback: PooledTexture,
hdr_resolve_copy: ID3D12Resource,
scene_color_srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
pub(in crate::directx) srv_table_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
pub(in crate::directx) sampler_table_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
pub(in crate::directx) volumes: Vec<RaymarchVolumeRecord>,
}
fn family_dxil(
programs: &SdfPrograms,
family: Family,
hot_reload: bool,
label: &str,
) -> Result<(Vec<u8>, Vec<u8>), String> {
let mut stages = raymarch::ALL.iter().filter(|p| p.family == family);
let dxil = |entry: &str, profile: &'static str| -> Result<Vec<u8>, String> {
crate::raymarch_source::artifact(
programs,
&crate::raymarch_source::Request {
family,
platform: Platform::Hlsl,
entries: &[entry],
target: SlangTarget::Dxil(profile),
hot_reload,
label,
},
)
.map(|bytes| bytes.into_owned())
};
let vs = dxil(
stages
.next()
.expect("a family declares a vertex entry")
.entry,
"vs_6_0",
)?;
let ps = dxil(
stages
.next()
.expect("a family declares a fragment entry")
.entry,
"ps_6_0",
)?;
Ok((vs, ps))
}
fn create_raymarch_root_signature(device: &ID3D12Device) -> Result<ID3D12RootSignature, String> {
let srv_range = D3D12_DESCRIPTOR_RANGE {
RangeType: D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
NumDescriptors: 4,
BaseShaderRegister: 0, RegisterSpace: 0,
OffsetInDescriptorsFromTableStart: D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND,
};
let samp_range = D3D12_DESCRIPTOR_RANGE {
RangeType: D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER,
NumDescriptors: 3,
BaseShaderRegister: 0, RegisterSpace: 0,
OffsetInDescriptorsFromTableStart: D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND,
};
let cbv = |reg: u32, vis: D3D12_SHADER_VISIBILITY| D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_CBV,
Anonymous: D3D12_ROOT_PARAMETER_0 {
Descriptor: D3D12_ROOT_DESCRIPTOR {
ShaderRegister: reg,
RegisterSpace: 0,
},
},
ShaderVisibility: vis,
};
let params = [
cbv(0, D3D12_SHADER_VISIBILITY_ALL),
cbv(1, D3D12_SHADER_VISIBILITY_ALL),
cbv(2, D3D12_SHADER_VISIBILITY_PIXEL),
cbv(3, D3D12_SHADER_VISIBILITY_PIXEL),
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
Anonymous: D3D12_ROOT_PARAMETER_0 {
DescriptorTable: D3D12_ROOT_DESCRIPTOR_TABLE {
NumDescriptorRanges: 1,
pDescriptorRanges: &srv_range,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_PIXEL,
},
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
Anonymous: D3D12_ROOT_PARAMETER_0 {
DescriptorTable: D3D12_ROOT_DESCRIPTOR_TABLE {
NumDescriptorRanges: 1,
pDescriptorRanges: &samp_range,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_PIXEL,
},
];
let desc = D3D12_ROOT_SIGNATURE_DESC {
NumParameters: params.len() as u32,
pParameters: params.as_ptr(),
Flags: D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT,
..Default::default()
};
serialize_desc_and_create(device, &desc, "raymarch root sig")
}
fn create_raymarch_pso(
device: &ID3D12Device,
root_sig: &ID3D12RootSignature,
vs: &[u8],
ps: &[u8],
msaa_samples: u32,
) -> Result<ID3D12PipelineState, String> {
let input_layout = main_input_layout();
let mut rasterizer = D3D12_RASTERIZER_DESC {
FillMode: D3D12_FILL_MODE_SOLID,
CullMode: D3D12_CULL_MODE_FRONT,
FrontCounterClockwise: windows::core::BOOL(0),
DepthBias: 0,
DepthBiasClamp: 0.0,
SlopeScaledDepthBias: 0.0,
DepthClipEnable: windows::core::BOOL(1),
MultisampleEnable: windows::core::BOOL(if msaa_samples > 1 { 1 } else { 0 }),
AntialiasedLineEnable: windows::core::BOOL(0),
ForcedSampleCount: 0,
ConservativeRaster: D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF,
};
rasterizer.DepthBias = 0;
let mut blend = D3D12_BLEND_DESC {
AlphaToCoverageEnable: windows::core::BOOL(0),
IndependentBlendEnable: windows::core::BOOL(0),
RenderTarget: [D3D12_RENDER_TARGET_BLEND_DESC::default(); 8],
};
blend.RenderTarget[0] = D3D12_RENDER_TARGET_BLEND_DESC {
BlendEnable: windows::core::BOOL(0),
LogicOpEnable: windows::core::BOOL(0),
SrcBlend: D3D12_BLEND_ONE,
DestBlend: D3D12_BLEND_ZERO,
BlendOp: D3D12_BLEND_OP_ADD,
SrcBlendAlpha: D3D12_BLEND_ONE,
DestBlendAlpha: D3D12_BLEND_ZERO,
BlendOpAlpha: D3D12_BLEND_OP_ADD,
LogicOp: D3D12_LOGIC_OP_NOOP,
RenderTargetWriteMask: D3D12_COLOR_WRITE_ENABLE_ALL.0 as u8,
};
let depth_stencil = D3D12_DEPTH_STENCIL_DESC {
DepthEnable: windows::core::BOOL(1),
DepthWriteMask: D3D12_DEPTH_WRITE_MASK_ALL,
DepthFunc: D3D12_COMPARISON_FUNC_LESS_EQUAL,
StencilEnable: windows::core::BOOL(0),
..Default::default()
};
let mut rtv_formats = [DXGI_FORMAT_UNKNOWN; 8];
rtv_formats[0] = HDR_FORMAT;
let desc = D3D12_GRAPHICS_PIPELINE_STATE_DESC {
pRootSignature: com::borrowed(root_sig),
VS: D3D12_SHADER_BYTECODE {
pShaderBytecode: vs.as_ptr() as _,
BytecodeLength: vs.len(),
},
PS: D3D12_SHADER_BYTECODE {
pShaderBytecode: ps.as_ptr() as _,
BytecodeLength: ps.len(),
},
BlendState: blend,
SampleMask: u32::MAX,
RasterizerState: rasterizer,
DepthStencilState: depth_stencil,
InputLayout: D3D12_INPUT_LAYOUT_DESC {
pInputElementDescs: input_layout.as_ptr(),
NumElements: input_layout.len() as u32,
},
PrimitiveTopologyType: D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
NumRenderTargets: 1,
RTVFormats: rtv_formats,
DSVFormat: DXGI_FORMAT_D32_FLOAT,
SampleDesc: DXGI_SAMPLE_DESC {
Count: msaa_samples.max(1),
Quality: 0,
},
..Default::default()
};
unsafe { crate::directx::pso_library::create_graphics(device, &desc) }
.map_err(|e| format!("create raymarch PSO: {e}"))
}
fn compile_volume_pso(
device: &ID3D12Device,
root_sig: &ID3D12RootSignature,
programs: &SdfPrograms,
asset_label: &str,
msaa_samples: u32,
hot_reload: bool,
) -> Result<ID3D12PipelineState, String> {
let (vs, ps) = family_dxil(programs, Family::Surface, hot_reload, asset_label)?;
create_raymarch_pso(device, root_sig, &vs, &ps, msaa_samples)
}
fn create_raymarch_volumetric_pso(
device: &ID3D12Device,
root_sig: &ID3D12RootSignature,
vs: &[u8],
ps: &[u8],
msaa_samples: u32,
) -> Result<ID3D12PipelineState, String> {
let input_layout = main_input_layout();
let rasterizer = D3D12_RASTERIZER_DESC {
FillMode: D3D12_FILL_MODE_SOLID,
CullMode: D3D12_CULL_MODE_FRONT,
FrontCounterClockwise: windows::core::BOOL(0),
DepthBias: 0,
DepthBiasClamp: 0.0,
SlopeScaledDepthBias: 0.0,
DepthClipEnable: windows::core::BOOL(1),
MultisampleEnable: windows::core::BOOL(if msaa_samples > 1 { 1 } else { 0 }),
AntialiasedLineEnable: windows::core::BOOL(0),
ForcedSampleCount: 0,
ConservativeRaster: D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF,
};
let mut blend = D3D12_BLEND_DESC {
AlphaToCoverageEnable: windows::core::BOOL(0),
IndependentBlendEnable: windows::core::BOOL(0),
RenderTarget: [D3D12_RENDER_TARGET_BLEND_DESC::default(); 8],
};
blend.RenderTarget[0] = D3D12_RENDER_TARGET_BLEND_DESC {
BlendEnable: windows::core::BOOL(1),
LogicOpEnable: windows::core::BOOL(0),
SrcBlend: D3D12_BLEND_SRC_ALPHA,
DestBlend: D3D12_BLEND_INV_SRC_ALPHA,
BlendOp: D3D12_BLEND_OP_ADD,
SrcBlendAlpha: D3D12_BLEND_ONE,
DestBlendAlpha: D3D12_BLEND_INV_SRC_ALPHA,
BlendOpAlpha: D3D12_BLEND_OP_ADD,
LogicOp: D3D12_LOGIC_OP_NOOP,
RenderTargetWriteMask: D3D12_COLOR_WRITE_ENABLE_ALL.0 as u8,
};
let depth_stencil = D3D12_DEPTH_STENCIL_DESC {
DepthEnable: windows::core::BOOL(1),
DepthWriteMask: D3D12_DEPTH_WRITE_MASK_ZERO,
DepthFunc: D3D12_COMPARISON_FUNC_LESS_EQUAL,
StencilEnable: windows::core::BOOL(0),
..Default::default()
};
let mut rtv_formats = [DXGI_FORMAT_UNKNOWN; 8];
rtv_formats[0] = HDR_FORMAT;
let desc = D3D12_GRAPHICS_PIPELINE_STATE_DESC {
pRootSignature: com::borrowed(root_sig),
VS: D3D12_SHADER_BYTECODE {
pShaderBytecode: vs.as_ptr() as _,
BytecodeLength: vs.len(),
},
PS: D3D12_SHADER_BYTECODE {
pShaderBytecode: ps.as_ptr() as _,
BytecodeLength: ps.len(),
},
BlendState: blend,
SampleMask: u32::MAX,
RasterizerState: rasterizer,
DepthStencilState: depth_stencil,
InputLayout: D3D12_INPUT_LAYOUT_DESC {
pInputElementDescs: input_layout.as_ptr(),
NumElements: input_layout.len() as u32,
},
PrimitiveTopologyType: D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
NumRenderTargets: 1,
RTVFormats: rtv_formats,
DSVFormat: DXGI_FORMAT_D32_FLOAT,
SampleDesc: DXGI_SAMPLE_DESC {
Count: msaa_samples.max(1),
Quality: 0,
},
..Default::default()
};
unsafe { crate::directx::pso_library::create_graphics(device, &desc) }
.map_err(|e| format!("create raymarch volumetric PSO: {e}"))
}
fn compile_volume_volumetric_pso(
device: &ID3D12Device,
root_sig: &ID3D12RootSignature,
programs: &SdfPrograms,
asset_label: &str,
msaa_samples: u32,
hot_reload: bool,
) -> Result<ID3D12PipelineState, String> {
let (vs, ps) = family_dxil(programs, Family::Volumetric, hot_reload, asset_label)?;
create_raymarch_volumetric_pso(device, root_sig, &vs, &ps, msaa_samples)
}
fn create_raymarch_shadow_root_signature(
device: &ID3D12Device,
) -> Result<ID3D12RootSignature, String> {
let cbv = |reg: u32, vis: D3D12_SHADER_VISIBILITY| D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_CBV,
Anonymous: D3D12_ROOT_PARAMETER_0 {
Descriptor: D3D12_ROOT_DESCRIPTOR {
ShaderRegister: reg,
RegisterSpace: 0,
},
},
ShaderVisibility: vis,
};
let params = [
cbv(0, D3D12_SHADER_VISIBILITY_ALL),
cbv(1, D3D12_SHADER_VISIBILITY_ALL),
cbv(2, D3D12_SHADER_VISIBILITY_PIXEL),
cbv(3, D3D12_SHADER_VISIBILITY_ALL),
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS,
Anonymous: D3D12_ROOT_PARAMETER_0 {
Constants: D3D12_ROOT_CONSTANTS {
ShaderRegister: 4,
RegisterSpace: 0,
Num32BitValues: 4,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_ALL,
},
];
let desc = D3D12_ROOT_SIGNATURE_DESC {
NumParameters: params.len() as u32,
pParameters: params.as_ptr(),
Flags: D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT,
..Default::default()
};
serialize_desc_and_create(device, &desc, "raymarch shadow root sig")
}
fn create_raymarch_shadow_pso(
device: &ID3D12Device,
root_sig: &ID3D12RootSignature,
vs: &[u8],
ps: &[u8],
) -> Result<ID3D12PipelineState, String> {
let input_layout = main_input_layout();
let rasterizer = D3D12_RASTERIZER_DESC {
FillMode: D3D12_FILL_MODE_SOLID,
CullMode: D3D12_CULL_MODE_FRONT,
FrontCounterClockwise: windows::core::BOOL(0),
DepthBias: 0,
DepthBiasClamp: 0.0,
SlopeScaledDepthBias: 0.0,
DepthClipEnable: windows::core::BOOL(1),
MultisampleEnable: windows::core::BOOL(0),
AntialiasedLineEnable: windows::core::BOOL(0),
ForcedSampleCount: 0,
ConservativeRaster: D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF,
};
let depth_stencil = D3D12_DEPTH_STENCIL_DESC {
DepthEnable: windows::core::BOOL(1),
DepthWriteMask: D3D12_DEPTH_WRITE_MASK_ALL,
DepthFunc: D3D12_COMPARISON_FUNC_LESS,
StencilEnable: windows::core::BOOL(0),
..Default::default()
};
let desc = D3D12_GRAPHICS_PIPELINE_STATE_DESC {
pRootSignature: com::borrowed(root_sig),
VS: D3D12_SHADER_BYTECODE {
pShaderBytecode: vs.as_ptr() as _,
BytecodeLength: vs.len(),
},
PS: D3D12_SHADER_BYTECODE {
pShaderBytecode: ps.as_ptr() as _,
BytecodeLength: ps.len(),
},
BlendState: D3D12_BLEND_DESC::default(),
SampleMask: u32::MAX,
RasterizerState: rasterizer,
DepthStencilState: depth_stencil,
InputLayout: D3D12_INPUT_LAYOUT_DESC {
pInputElementDescs: input_layout.as_ptr(),
NumElements: input_layout.len() as u32,
},
PrimitiveTopologyType: D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
NumRenderTargets: 0,
RTVFormats: [DXGI_FORMAT_UNKNOWN; 8],
DSVFormat: DXGI_FORMAT_D32_FLOAT,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
..Default::default()
};
unsafe { crate::directx::pso_library::create_graphics(device, &desc) }
.map_err(|e| format!("create raymarch shadow PSO: {e}"))
}
fn compile_volume_shadow_pso(
device: &ID3D12Device,
root_sig: &ID3D12RootSignature,
programs: &SdfPrograms,
asset_label: &str,
hot_reload: bool,
) -> Result<ID3D12PipelineState, String> {
let (vs, ps) = family_dxil(programs, Family::Shadow, hot_reload, asset_label)?;
create_raymarch_shadow_pso(device, root_sig, &vs, &ps)
}
fn build_cube_buffers(
alloc: &DeviceAllocator,
) -> Result<
(
PooledBuffer,
PooledBuffer,
D3D12_VERTEX_BUFFER_VIEW,
D3D12_INDEX_BUFFER_VIEW,
),
String,
> {
#[rustfmt::skip]
let corners: [Vertex; 8] = [
v([-1.0, -1.0, -1.0]),
v([ 1.0, -1.0, -1.0]),
v([ 1.0, 1.0, -1.0]),
v([-1.0, 1.0, -1.0]),
v([-1.0, -1.0, 1.0]),
v([ 1.0, -1.0, 1.0]),
v([ 1.0, 1.0, 1.0]),
v([-1.0, 1.0, 1.0]),
];
#[rustfmt::skip]
let indices: [u16; 36] = [
0, 2, 1, 0, 3, 2,
4, 5, 6, 4, 6, 7,
0, 4, 7, 0, 7, 3,
1, 2, 6, 1, 6, 5,
0, 1, 5, 0, 5, 4,
3, 7, 6, 3, 6, 2,
];
let vb_bytes = std::mem::size_of_val(&corners) as u64;
let ib_bytes = std::mem::size_of_val(&indices) as u64;
let vb = create_buffer(
alloc,
vb_bytes,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)?;
let ib = create_buffer(
alloc,
ib_bytes,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)?;
unsafe {
let mut p = std::ptr::null_mut::<c_void>();
vb.Map(0, None, Some(&mut p))
.map_err(|e| format!("raymarch cube vb map: {e}"))?;
std::ptr::copy_nonoverlapping(
corners.as_ptr() as *const u8,
p as *mut u8,
vb_bytes as usize,
);
vb.Unmap(0, None);
let mut p = std::ptr::null_mut::<c_void>();
ib.Map(0, None, Some(&mut p))
.map_err(|e| format!("raymarch cube ib map: {e}"))?;
std::ptr::copy_nonoverlapping(
indices.as_ptr() as *const u8,
p as *mut u8,
ib_bytes as usize,
);
ib.Unmap(0, None);
}
let vbv = D3D12_VERTEX_BUFFER_VIEW {
BufferLocation: com::gpu_va(&vb),
SizeInBytes: vb_bytes as u32,
StrideInBytes: std::mem::size_of::<Vertex>() as u32,
};
let ibv = D3D12_INDEX_BUFFER_VIEW {
BufferLocation: com::gpu_va(&ib),
SizeInBytes: ib_bytes as u32,
Format: DXGI_FORMAT_R16_UINT,
};
Ok((vb, ib, vbv, ibv))
}
fn v(pos: [f32; 3]) -> Vertex {
Vertex {
pos,
normal: [0.0, 0.0, 0.0],
tangent: [0.0, 0.0, 0.0],
color: [0.0, 0.0, 0.0],
uv: [0.0, 0.0],
}
}
fn write_raymarch_srvs(
device: &ID3D12Device,
shadow_resource: Option<&ID3D12Resource>,
irradiance_resource: &ID3D12Resource,
prefilter_resource: &ID3D12Resource,
base_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
descriptor_size: usize,
shadow_layers: u32,
) {
let slot_cpu = |i: usize| D3D12_CPU_DESCRIPTOR_HANDLE {
ptr: base_cpu.ptr + i * descriptor_size,
};
if let Some(shadow) = shadow_resource {
let desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: DXGI_FORMAT_R32_FLOAT,
ViewDimension: D3D12_SRV_DIMENSION_TEXTURE2DARRAY,
Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
Texture2DArray: D3D12_TEX2D_ARRAY_SRV {
MostDetailedMip: 0,
MipLevels: 1,
FirstArraySlice: 0,
ArraySize: shadow_layers,
PlaneSlice: 0,
ResourceMinLODClamp: 0.0,
},
},
};
unsafe { device.CreateShaderResourceView(shadow, Some(&desc), slot_cpu(0)) };
}
for (i, res) in [irradiance_resource, prefilter_resource].iter().enumerate() {
let desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: DXGI_FORMAT_R32G32B32A32_FLOAT,
ViewDimension: D3D12_SRV_DIMENSION_TEXTURECUBE,
Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
TextureCube: D3D12_TEXCUBE_SRV {
MostDetailedMip: 0,
MipLevels: u32::MAX,
ResourceMinLODClamp: 0.0,
},
},
};
unsafe { device.CreateShaderResourceView(*res, Some(&desc), slot_cpu(1 + i)) };
}
}
fn write_scene_color_srv(
device: &ID3D12Device,
scene_resource: &ID3D12Resource,
srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
) {
let desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: HDR_FORMAT,
ViewDimension: D3D12_SRV_DIMENSION_TEXTURE2D,
Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
Texture2D: D3D12_TEX2D_SRV {
MostDetailedMip: 0,
MipLevels: 1,
PlaneSlice: 0,
ResourceMinLODClamp: 0.0,
},
},
};
unsafe { device.CreateShaderResourceView(scene_resource, Some(&desc), srv_cpu) };
}
fn write_raymarch_samplers(
device: &ID3D12Device,
base_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
descriptor_size: usize,
) {
let slot_cpu = |i: usize| D3D12_CPU_DESCRIPTOR_HANDLE {
ptr: base_cpu.ptr + i * descriptor_size,
};
let shadow = D3D12_SAMPLER_DESC {
Filter: D3D12_FILTER_COMPARISON_MIN_MAG_LINEAR_MIP_POINT,
AddressU: D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
AddressV: D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
AddressW: D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
ComparisonFunc: D3D12_COMPARISON_FUNC_LESS_EQUAL,
MinLOD: 0.0,
MaxLOD: f32::MAX,
..Default::default()
};
unsafe { device.CreateSampler(&shadow, slot_cpu(0)) };
let cube = D3D12_SAMPLER_DESC {
Filter: D3D12_FILTER_MIN_MAG_MIP_LINEAR,
AddressU: D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
AddressV: D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
AddressW: D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
MinLOD: 0.0,
MaxLOD: f32::MAX,
..Default::default()
};
unsafe { device.CreateSampler(&cube, slot_cpu(1)) };
let scene = D3D12_SAMPLER_DESC {
Filter: D3D12_FILTER_MIN_MAG_MIP_LINEAR,
AddressU: D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
AddressV: D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
AddressW: D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
MinLOD: 0.0,
MaxLOD: f32::MAX,
..Default::default()
};
unsafe { device.CreateSampler(&scene, slot_cpu(2)) };
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct RaymarchDeviceContext<'a> {
pub alloc: &'a DeviceAllocator,
pub info_queue: Option<&'a ID3D12InfoQueue>,
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct RaymarchTargetConfig {
pub width: u32,
pub height: u32,
pub msaa_samples: u32,
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct RaymarchSharedBindings<'a> {
pub shadow_resource: Option<&'a ID3D12Resource>,
pub shadow_layers: u32,
pub irradiance_resource: &'a ID3D12Resource,
pub prefilter_resource: &'a ID3D12Resource,
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct RaymarchDescriptorHandles {
pub srv_base_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
pub srv_base_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
pub srv_descriptor_size: usize,
pub sampler_base_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
pub sampler_base_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
pub sampler_descriptor_size: usize,
}
impl RaymarchResources {
pub(in crate::directx) fn try_new(
ctx: RaymarchDeviceContext,
target: RaymarchTargetConfig,
bindings: RaymarchSharedBindings,
handles: RaymarchDescriptorHandles,
sdf_volumes: &[(SdfVolume, Vec<u8>, String)],
hot_reload: bool,
) -> Result<Option<Self>, String> {
let RaymarchDeviceContext { alloc, info_queue } = ctx;
let device = alloc.device();
let RaymarchTargetConfig {
width,
height,
msaa_samples,
} = target;
let RaymarchSharedBindings {
shadow_resource,
shadow_layers,
irradiance_resource,
prefilter_resource,
} = bindings;
let RaymarchDescriptorHandles {
srv_base_cpu,
srv_base_gpu,
srv_descriptor_size,
sampler_base_cpu,
sampler_base_gpu,
sampler_descriptor_size,
} = handles;
let active: Vec<&(SdfVolume, Vec<u8>, String)> = sdf_volumes.iter().collect();
if active.is_empty() {
return Ok(None);
}
let root_sig = dump_on_err(info_queue, create_raymarch_root_signature(device))?;
let shadow_root_sig =
dump_on_err(info_queue, create_raymarch_shadow_root_signature(device))?;
let (cube_vb, cube_ib, cube_vbv, cube_ibv) = build_cube_buffers(alloc)?;
let view_size = align256(std::mem::size_of::<RaymarchView>() as u64);
let mut view_cbuffers: Vec<PooledBuffer> = Vec::with_capacity(FRAMES);
let mut view_ptrs: Vec<*mut u8> = Vec::with_capacity(FRAMES);
for _ in 0..FRAMES {
let buf = create_buffer(
alloc,
view_size,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)?;
let mut p = std::ptr::null_mut::<c_void>();
unsafe { buf.Map(0, None, Some(&mut p)) }
.map_err(|e| format!("raymarch view ubo map: {e}"))?;
view_ptrs.push(p as *mut u8);
view_cbuffers.push(buf);
}
let scene_color_fallback = create_fallback_white_resource(alloc)?;
let hdr_resolve_copy = create_hdr_resolve_target(device, width.max(1), height.max(1))?;
let mut volumes: Vec<RaymarchVolumeRecord> = Vec::with_capacity(active.len());
for (vol, payload, label) in &active {
let programs = crate::raymarch_source::decode(payload, label)?;
let pso = dump_on_err(
info_queue,
if vol.volumetric {
compile_volume_volumetric_pso(
device,
&root_sig,
&programs,
label,
msaa_samples,
hot_reload,
)
} else {
compile_volume_pso(
device,
&root_sig,
&programs,
label,
msaa_samples,
hot_reload,
)
},
)?;
let shadow_pso = if vol.cast_shadows {
Some(dump_on_err(
info_queue,
compile_volume_shadow_pso(
device,
&shadow_root_sig,
&programs,
label,
hot_reload,
),
)?)
} else {
None
};
let uniforms = volume_uniforms_from(vol);
let cb_size = align256(std::mem::size_of::<RaymarchVolumeUniforms>() as u64);
let cb = create_buffer(
alloc,
cb_size,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)?;
let mut p = std::ptr::null_mut::<c_void>();
unsafe { cb.Map(0, None, Some(&mut p)) }
.map_err(|e| format!("raymarch volume cb map: {e}"))?;
unsafe {
std::ptr::copy_nonoverlapping(
&uniforms as *const RaymarchVolumeUniforms as *const u8,
p as *mut u8,
std::mem::size_of::<RaymarchVolumeUniforms>(),
);
}
let gva = com::gpu_va(&cb);
volumes.push(RaymarchVolumeRecord {
pso,
shadow_pso,
volume_cbuffer: cb,
volume_cbuffer_gva: gva,
visible: vol.visible,
cast_shadows: vol.cast_shadows,
});
}
write_raymarch_srvs(
device,
shadow_resource,
irradiance_resource,
prefilter_resource,
srv_base_cpu,
srv_descriptor_size,
shadow_layers,
);
let scene_color_srv_cpu = D3D12_CPU_DESCRIPTOR_HANDLE {
ptr: srv_base_cpu.ptr + 3 * srv_descriptor_size,
};
write_scene_color_srv(device, &hdr_resolve_copy, scene_color_srv_cpu);
write_raymarch_samplers(device, sampler_base_cpu, sampler_descriptor_size);
Ok(Some(Self {
root_sig,
shadow_root_sig,
cube_vb,
cube_ib,
cube_vbv,
cube_ibv,
view_cbuffers,
view_ptrs,
scene_color_fallback,
hdr_resolve_copy,
scene_color_srv_cpu,
srv_table_gpu: srv_base_gpu,
sampler_table_gpu: sampler_base_gpu,
volumes,
}))
}
pub(in crate::directx) fn resize_to(
&mut self,
device: &ID3D12Device,
width: u32,
height: u32,
) -> Result<(), String> {
self.hdr_resolve_copy = create_hdr_resolve_target(device, width.max(1), height.max(1))?;
write_scene_color_srv(device, &self.hdr_resolve_copy, self.scene_color_srv_cpu);
Ok(())
}
pub(in crate::directx) fn any_visible(&self) -> bool {
self.volumes.iter().any(|v| v.visible)
}
}
unsafe impl Send for RaymarchResources {}
unsafe impl Sync for RaymarchResources {}
impl DxContext {
pub(in crate::directx) fn encode_raymarch(
&self,
cmd: &ID3D12GraphicsCommandList,
frame_idx: usize,
view: &RaymarchView,
) -> Result<(), String> {
let Some(rm) = self.raymarch.as_ref() else {
return Ok(());
};
if !rm.any_visible() {
return Ok(());
}
let view_ptr = rm
.view_ptrs
.get(frame_idx)
.copied()
.ok_or("raymarch: view_ptrs index OOB")?;
unsafe {
std::ptr::copy_nonoverlapping(
view as *const RaymarchView as *const u8,
view_ptr,
std::mem::size_of::<RaymarchView>(),
);
}
let view_gva = com::gpu_va(&rm.view_cbuffers[frame_idx]);
let light_gva = com::gpu_va(&self.uniforms.light_ubo_resources[frame_idx]);
let shadow_gva = com::gpu_va(&self.uniforms.shadow_ubo_resources[frame_idx]);
let msaa = self.hdr.resolve.is_some();
let snapshot_src_to_copy = transition_barrier(
self.hdr_scene_target(),
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_COPY_SOURCE,
);
let snapshot_dst_to_copy = transition_barrier(
&rm.hdr_resolve_copy,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
D3D12_RESOURCE_STATE_COPY_DEST,
);
unsafe { cmd.ResourceBarrier(&[snapshot_src_to_copy, snapshot_dst_to_copy]) };
unsafe { cmd.CopyResource(&rm.hdr_resolve_copy, self.hdr_scene_target()) };
let snapshot_src_back = transition_barrier(
self.hdr_scene_target(),
D3D12_RESOURCE_STATE_COPY_SOURCE,
D3D12_RESOURCE_STATE_RENDER_TARGET,
);
let snapshot_dst_to_psr = transition_barrier(
&rm.hdr_resolve_copy,
D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
);
unsafe { cmd.ResourceBarrier(&[snapshot_src_back, snapshot_dst_to_psr]) };
let w = self.extent.render_width;
let h = self.extent.render_height;
unsafe {
cmd.OMSetRenderTargets(1, Some(&self.hdr.color_rtv), false, Some(&self.depth.dsv));
let vp = D3D12_VIEWPORT {
TopLeftX: 0.0,
TopLeftY: 0.0,
Width: w as f32,
Height: h as f32,
MinDepth: 0.0,
MaxDepth: 1.0,
};
cmd.RSSetViewports(&[vp]);
let scissor = windows::Win32::Foundation::RECT {
left: 0,
top: 0,
right: w as i32,
bottom: h as i32,
};
cmd.RSSetScissorRects(&[scissor]);
cmd.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
cmd.IASetVertexBuffers(0, Some(&[rm.cube_vbv]));
cmd.IASetIndexBuffer(Some(&rm.cube_ibv));
cmd.SetGraphicsRootSignature(&rm.root_sig);
cmd.SetDescriptorHeaps(&[
Some(self.descriptors.srv_heap.clone()),
Some(self.descriptors.sampler_heap.clone()),
]);
cmd.SetGraphicsRootConstantBufferView(0, view_gva);
cmd.SetGraphicsRootConstantBufferView(2, light_gva);
cmd.SetGraphicsRootConstantBufferView(3, shadow_gva);
cmd.SetGraphicsRootDescriptorTable(4, rm.srv_table_gpu);
cmd.SetGraphicsRootDescriptorTable(5, rm.sampler_table_gpu);
}
for vol in &rm.volumes {
if !vol.visible {
continue;
}
unsafe {
cmd.SetPipelineState(&vol.pso);
cmd.SetGraphicsRootConstantBufferView(1, vol.volume_cbuffer_gva);
cmd.DrawIndexedInstanced(36, 1, 0, 0, 0);
}
self.inc_draw_calls(1);
}
if msaa {
let hdr_color_to_resolve_src = transition_barrier(
&self.hdr.color,
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_RESOLVE_SOURCE,
);
let resolve_to_dst = transition_barrier(
self.hdr_scene_target(),
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_RESOLVE_DEST,
);
unsafe {
cmd.ResourceBarrier(&[hdr_color_to_resolve_src, resolve_to_dst]);
cmd.ResolveSubresource(self.hdr_scene_target(), 0, &self.hdr.color, 0, HDR_FORMAT);
}
let resolve_back = transition_barrier(
self.hdr_scene_target(),
D3D12_RESOURCE_STATE_RESOLVE_DEST,
D3D12_RESOURCE_STATE_RENDER_TARGET,
);
let hdr_color_back_to_rt = transition_barrier(
&self.hdr.color,
D3D12_RESOURCE_STATE_RESOLVE_SOURCE,
D3D12_RESOURCE_STATE_RENDER_TARGET,
);
unsafe { cmd.ResourceBarrier(&[resolve_back, hdr_color_back_to_rt]) };
}
Ok(())
}
}
impl RaymarchResources {
pub(in crate::directx) fn any_shadow_casters(&self) -> bool {
self.volumes
.iter()
.any(|v| v.visible && v.cast_shadows && v.shadow_pso.is_some())
}
}
impl DxContext {
pub(in crate::directx) fn encode_sdf_shadow_casters(
&self,
cmd: &ID3D12GraphicsCommandList,
frame_idx: usize,
shadow_ubo_gva: u64,
view: &RaymarchView,
) -> Result<(), String> {
let Some(rm) = self.raymarch.as_ref() else {
return Ok(());
};
if !rm.any_shadow_casters() {
return Ok(());
}
if self.shadow.dsvs.is_empty() {
return Ok(());
}
let view_ptr = rm
.view_ptrs
.get(frame_idx)
.copied()
.ok_or("raymarch shadow: view_ptrs index OOB")?;
unsafe {
std::ptr::copy_nonoverlapping(
view as *const RaymarchView as *const u8,
view_ptr,
std::mem::size_of::<RaymarchView>(),
);
}
let view_gva = com::gpu_va(&rm.view_cbuffers[frame_idx]);
let light_gva = com::gpu_va(&self.uniforms.light_ubo_resources[frame_idx]);
let sm = self.shadow.map_size;
unsafe {
cmd.SetGraphicsRootSignature(&rm.shadow_root_sig);
cmd.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
cmd.IASetVertexBuffers(0, Some(&[rm.cube_vbv]));
cmd.IASetIndexBuffer(Some(&rm.cube_ibv));
let vp = D3D12_VIEWPORT {
TopLeftX: 0.0,
TopLeftY: 0.0,
Width: sm as f32,
Height: sm as f32,
MinDepth: 0.0,
MaxDepth: 1.0,
};
cmd.RSSetViewports(&[vp]);
let scissor = windows::Win32::Foundation::RECT {
left: 0,
top: 0,
right: sm as i32,
bottom: sm as i32,
};
cmd.RSSetScissorRects(&[scissor]);
cmd.SetGraphicsRootConstantBufferView(0, view_gva);
cmd.SetGraphicsRootConstantBufferView(2, light_gva);
cmd.SetGraphicsRootConstantBufferView(3, shadow_ubo_gva);
}
let all_cascades = (1u32 << crate::gfx::render_types::NUM_SHADOW_CASCADES) - 1;
let render_mask = if self.shadow.render_mask == 0 {
all_cascades
} else {
self.shadow.render_mask
};
for cascade_idx in 0..crate::gfx::render_types::NUM_SHADOW_CASCADES {
if render_mask & (1u32 << cascade_idx) == 0 {
continue;
}
let dsv = self.shadow.dsvs[cascade_idx];
unsafe {
cmd.OMSetRenderTargets(0, None, false, Some(&dsv));
let constants = [cascade_idx as u32, 0u32, 0u32, 0u32];
cmd.SetGraphicsRoot32BitConstants(
4,
4,
constants.as_ptr() as *const std::ffi::c_void,
0,
);
}
for vol in &rm.volumes {
if !vol.visible || !vol.cast_shadows {
continue;
}
let Some(pso) = vol.shadow_pso.as_ref() else {
continue;
};
unsafe {
cmd.SetPipelineState(pso);
cmd.SetGraphicsRootConstantBufferView(1, vol.volume_cbuffer_gva);
cmd.DrawIndexedInstanced(36, 1, 0, 0, 0);
}
self.inc_draw_calls(1);
}
}
Ok(())
}
}
const _LIGHT_LAYOUT_REF: usize = std::mem::size_of::<LightUniforms>();