#![deny(unsafe_op_in_unsafe_fn)]
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_metal::{
MTLCommandBuffer as _, MTLComputeCommandEncoder as _, MTLComputePipelineState, MTLDevice as _,
MTLLibrary as _, MTLLoadAction, MTLPixelFormat, MTLPrimitiveType, MTLRenderCommandEncoder as _,
MTLRenderPassDescriptor, MTLRenderPipelineState, MTLSize, MTLStoreAction, MTLTexture,
MTLTextureType, MTLTextureUsage,
};
use crate::gfx::render_graph::{FOG_FROXEL_X, FOG_FROXEL_Y, FOG_FROXEL_Z};
use crate::gfx::render_types::{FogFroxelParams, FogParams};
use crate::gfx::volumetric_fog::FogSettings;
use super::context::MtlContext;
use super::descriptors::TextureDesc;
use super::encode::{ComputeEncode, RenderEncode};
use super::pipeline::ns_str;
use super::post::fullscreen::{
FullscreenBlend, build_slang_fullscreen_pipeline, set_fragment_sampler_range,
};
use super::scoped_encoder::ScopedEncoder;
use super::slang_shaders::{FOG_FRAG, FOG_FROXEL};
pub(crate) struct FogState {
pub settings: Option<FogSettings>,
pub pipeline: Option<Retained<ProtocolObject<dyn MTLRenderPipelineState>>>,
pub froxel_pipeline: Option<Retained<ProtocolObject<dyn MTLComputePipelineState>>>,
pub froxel_volume: Option<Retained<ProtocolObject<dyn MTLTexture>>>,
}
impl MtlContext {
pub(crate) fn update_fog_settings(&mut self, settings: Option<FogSettings>) {
if settings.is_some() && self.fog.pipeline.is_none() {
tracing::warn!(
"VolumetricFog hot-reload: world started without fog, so the fog \
pipeline was never built: re-enabling fog mid-run is not \
supported (relaunch required). Ignoring update."
);
return;
}
self.fog.settings = settings;
}
pub(in crate::metal) fn encode_fog(
&self,
cmd_buf: &objc2::runtime::ProtocolObject<dyn objc2_metal::MTLCommandBuffer>,
params: &FogParams,
froxel_params: &FogFroxelParams,
) -> Result<u32, String> {
let pipeline = match &self.fog.pipeline {
Some(p) => p,
None => return Ok(0),
};
let volume = match &self.fog.froxel_volume {
Some(v) => v,
None => return Ok(0),
};
let pass_desc = MTLRenderPassDescriptor::new();
unsafe {
let ca = pass_desc.colorAttachments().objectAtIndexedSubscript(0);
ca.setTexture(Some(self.hdr_targets.hdr_resolve.as_ref()));
ca.setLoadAction(MTLLoadAction::Load);
ca.setStoreAction(MTLStoreAction::Store);
}
if let Some(t) = &self.diagnostics.pass_timing {
t.attach_render(&pass_desc, super::pass_timing::PassId::Fog);
}
let enc = ScopedEncoder::new(
cmd_buf
.renderCommandEncoderWithDescriptor(&pass_desc)
.ok_or("failed to get fog render encoder")?,
"volumetric fog",
);
enc.set_pipeline(pipeline);
enc.set_fragment_value(params, 0);
enc.set_fragment_value(froxel_params, 1);
enc.set_fragment_texture(self.hdr_targets.depth_resolve.as_ref(), 0);
enc.set_fragment_texture(volume.as_ref(), 1);
set_fragment_sampler_range(&enc, &self.post_sampler, 0, 1);
unsafe {
enc.drawPrimitives_vertexStart_vertexCount(MTLPrimitiveType::Triangle, 0, 3);
}
Ok(1)
}
pub(in crate::metal) fn encode_fog_froxel(
&self,
cmd_buf: &objc2::runtime::ProtocolObject<dyn objc2_metal::MTLCommandBuffer>,
params: &FogParams,
froxel_params: &FogFroxelParams,
) -> Result<u32, String> {
let pipeline = match &self.fog.froxel_pipeline {
Some(p) => p,
None => return Ok(0),
};
let volume = match &self.fog.froxel_volume {
Some(v) => v,
None => return Ok(0),
};
let cmd_buf_dyn: &ProtocolObject<dyn objc2_metal::MTLCommandBuffer> = cmd_buf;
let desc = objc2_metal::MTLComputePassDescriptor::computePassDescriptor();
if let Some(t) = &self.diagnostics.pass_timing {
t.attach_compute(&desc, super::pass_timing::PassId::FogFroxel);
}
let enc = ScopedEncoder::new(
cmd_buf_dyn
.computeCommandEncoderWithDescriptor(&desc)
.ok_or("failed to get fog froxel compute encoder")?,
"fog froxel volume",
);
enc.set_pipeline(pipeline);
enc.set_value(params, 0);
enc.set_value(froxel_params, 1);
enc.set_value(&self.shadow.uniforms, 2);
enc.set_texture(self.shadow.map.as_ref(), 0);
enc.set_texture(volume.as_ref(), 1);
enc.set_sampler(self.shadow.sampler.as_ref(), 0);
let tg = MTLSize {
width: 8,
height: 8,
depth: 1,
};
let grid = MTLSize {
width: FOG_FROXEL_X as usize,
height: FOG_FROXEL_Y as usize,
depth: 1,
};
enc.dispatchThreads_threadsPerThreadgroup(grid, tg);
Ok(0)
}
}
pub(super) fn build_fog_pipeline(
device: &ProtocolObject<dyn objc2_metal::MTLDevice>,
hot_reload: bool,
) -> Result<Retained<ProtocolObject<dyn MTLRenderPipelineState>>, String> {
build_slang_fullscreen_pipeline(
device,
&FOG_FRAG,
MTLPixelFormat::RGBA16Float,
FullscreenBlend::PremultipliedOver,
hot_reload,
)
}
pub(super) fn build_fog_froxel_pipeline(
device: &ProtocolObject<dyn objc2_metal::MTLDevice>,
hot_reload: bool,
) -> Result<Retained<ProtocolObject<dyn MTLComputePipelineState>>, String> {
let library = FOG_FROXEL.library(device, hot_reload)?;
let func = library
.newFunctionWithName(&ns_str("fog_froxel_kernel"))
.ok_or("fog_froxel_kernel not found")?;
device
.newComputePipelineStateWithFunction_error(&func)
.map_err(|e| format!("failed to create fog froxel pipeline: {:?}", e))
}
pub(super) fn build_fog_froxel_volume(
device: &ProtocolObject<dyn objc2_metal::MTLDevice>,
) -> Result<Retained<ProtocolObject<dyn MTLTexture>>, String> {
let desc = TextureDesc {
kind: MTLTextureType::Type3D,
format: MTLPixelFormat::RGBA16Float,
width: FOG_FROXEL_X as usize,
height: FOG_FROXEL_Y as usize,
depth: FOG_FROXEL_Z as usize,
usage: MTLTextureUsage::ShaderRead | MTLTextureUsage::ShaderWrite,
..Default::default()
}
.build();
device
.newTextureWithDescriptor(&desc)
.ok_or_else(|| "failed to allocate fog froxel volume texture".to_string())
}