use ash::vk;
use concinnity_core::gfx::render_types::{FogFroxelParams, FogParams, ShadowUniforms};
use concinnity_core::render::error::RenderResult;
use concinnity_core::render::render_graph::{FOG_FROXEL_X, FOG_FROXEL_Y, FOG_FROXEL_Z};
use concinnity_core::render::volumetric_fog;
use concinnity_core::transform::mat4_inverse;
use super::allocator::{DeviceAllocator, PooledBuffer, PooledImage};
use super::context::VkContext;
use super::descriptor_layout::{Binding, PoolSizes};
use super::pipeline_desc::{Blend, GraphicsPipelineDesc, compute_pipeline};
use super::resources::{alloc_descriptor_sets, create_descriptor_set_layout};
use super::set_writes::SetWrites;
use super::texture::{
LayoutTransition, SubresourceRange, one_shot_submit, transition_image_layout_range,
};
use crate::vulkan::builtin_shaders::CompileProgram;
use crate::vulkan::owned::{
OwnedDescriptorPool, OwnedFramebuffer, OwnedPipeline, OwnedPipelineLayout, OwnedRenderPass,
OwnedSampler, OwnedSetLayout, VkDevice,
};
use crate::vulkan::record::Recorder;
const FROXEL_TILE: u32 = 8;
const VOLUME_FORMAT: vk::Format = vk::Format::R16G16B16A16_SFLOAT;
pub(in crate::vulkan) struct FogResources {
pub(in crate::vulkan) render_pass: OwnedRenderPass,
pub(in crate::vulkan) pipeline: OwnedPipeline,
pub(in crate::vulkan) pipeline_layout: OwnedPipelineLayout,
pub(in crate::vulkan) _view_set_layout: OwnedSetLayout,
pub(in crate::vulkan) _descriptor_pool: OwnedDescriptorPool,
pub(in crate::vulkan) params_ubos: Vec<PooledBuffer>,
pub(in crate::vulkan) froxel_ubos: Vec<PooledBuffer>,
pub(in crate::vulkan) view_sets: Vec<vk::DescriptorSet>,
pub(in crate::vulkan) froxel_pipeline: OwnedPipeline,
pub(in crate::vulkan) froxel_pipeline_layout: OwnedPipelineLayout,
pub(in crate::vulkan) _froxel_set_layout: OwnedSetLayout,
pub(in crate::vulkan) froxel_sets: Vec<vk::DescriptorSet>,
pub(in crate::vulkan) volume: PooledImage,
pub(in crate::vulkan) framebuffers: Vec<OwnedFramebuffer>,
pub(in crate::vulkan) _volume_sampler: OwnedSampler,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct FogDeviceContext<'a> {
pub(in crate::vulkan) alloc: &'a DeviceAllocator,
pub(in crate::vulkan) device: &'a VkDevice,
pub(in crate::vulkan) command_pool: vk::CommandPool,
pub(in crate::vulkan) queue: vk::Queue,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct FogFrameTargets<'a> {
pub(in crate::vulkan) frames: usize,
pub(in crate::vulkan) msaa: bool,
pub(in crate::vulkan) hdr_format: vk::Format,
pub(in crate::vulkan) hdr_resolve_views: &'a [vk::ImageView],
pub(in crate::vulkan) depth_views: &'a [vk::ImageView],
pub(in crate::vulkan) extent: vk::Extent2D,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct FogShadowResources<'a> {
pub(in crate::vulkan) ubos: &'a [PooledBuffer],
pub(in crate::vulkan) map_view: vk::ImageView,
pub(in crate::vulkan) sampler: vk::Sampler,
}
impl FogResources {
pub(in crate::vulkan) fn new(
ctx: FogDeviceContext,
targets: FogFrameTargets,
shadow: FogShadowResources<'_>,
hot_reload: bool,
) -> RenderResult<Self> {
let FogDeviceContext {
alloc,
device,
command_pool,
queue,
} = ctx;
let FogFrameTargets {
frames,
msaa,
hdr_format,
hdr_resolve_views,
depth_views,
extent,
} = targets;
let FogShadowResources {
ubos: shadow_ubos,
map_view: shadow_map_view,
sampler: shadow_sampler,
} = shadow;
let render_pass = create_fog_render_pass(device, hdr_format)?;
let view_set_layout = create_descriptor_set_layout(device, &fog_set_bindings())?;
let pipeline_layout = create_fog_pipeline_layout(device, view_set_layout.handle())?;
let (vert_spv, frag_spv) = compile_fog_shaders(hot_reload, msaa)?;
let pipeline = create_fog_pipeline(
device,
render_pass.handle(),
pipeline_layout.handle(),
&vert_spv,
&frag_spv,
)?;
let froxel_set_layout = create_descriptor_set_layout(device, &froxel_set_bindings())?;
let froxel_pipeline_layout =
create_froxel_pipeline_layout(device, froxel_set_layout.handle())?;
let froxel_spv = compile_fog_froxel_shader(hot_reload)?;
let froxel_pipeline = compute_pipeline(
device,
froxel_pipeline_layout.handle(),
&froxel_spv,
"fog froxel",
)?;
let volume = create_volume_image(alloc)?;
one_shot_submit(device, command_pool, queue, |cmd| {
transition_image_layout_range(
device,
cmd,
volume.image(),
LayoutTransition {
old_layout: vk::ImageLayout::UNDEFINED,
new_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
aspect: vk::ImageAspectFlags::COLOR,
},
SubresourceRange {
base_layer: 0,
layer_count: 1,
base_mip: 0,
mip_count: 1,
},
);
})?;
let volume_storage_view = create_volume_view(device, volume.image())?;
volume.attach_view(volume_storage_view);
let volume_sampled_view = create_volume_view(device, volume.image())?;
volume.attach_view(volume_sampled_view);
let volume_sampler = create_volume_sampler(device)?;
let params_ubos = alloc_ubo_ring(alloc, frames, std::mem::size_of::<FogParams>() as u64)?;
let froxel_ubos =
alloc_ubo_ring(alloc, frames, std::mem::size_of::<FogFroxelParams>() as u64)?;
let descriptor_pool = create_fog_descriptor_pool(device, frames)?;
let view_layouts: Vec<_> = (0..frames).map(|_| view_set_layout.handle()).collect();
let view_sets = alloc_descriptor_sets(device, descriptor_pool.handle(), &view_layouts)?;
let froxel_layouts: Vec<_> = (0..frames).map(|_| froxel_set_layout.handle()).collect();
let froxel_sets = alloc_descriptor_sets(device, descriptor_pool.handle(), &froxel_layouts)?;
let last_depth = depth_views.len().saturating_sub(1);
for (i, &set) in view_sets.iter().enumerate() {
write_view_set(
device,
set,
FogViewBindings {
params_ubo: params_ubos[i].buffer(),
depth_view: depth_views[i.min(last_depth)],
froxel_ubo: froxel_ubos[i].buffer(),
volume_view: volume_sampled_view,
volume_sampler: volume_sampler.handle(),
},
);
}
for (i, &set) in froxel_sets.iter().enumerate() {
write_froxel_set(
device,
set,
FogFroxelBindings {
params_ubo: params_ubos[i].buffer(),
froxel_ubo: froxel_ubos[i].buffer(),
shadow_ubo: shadow_ubos[i].buffer(),
shadow_map_view,
shadow_sampler,
volume_storage_view,
},
);
}
let mut framebuffers = Vec::with_capacity(frames);
for &view in hdr_resolve_views.iter().take(frames) {
let attachments = [view];
let fb_info = vk::FramebufferCreateInfo::default()
.render_pass(render_pass.handle())
.attachments(&attachments)
.width(extent.width.max(1))
.height(extent.height.max(1))
.layers(1);
let fb = device
.create_framebuffer(&fb_info)
.map_err(|e| super::error::map_vk_result(e, "fog framebuffer"))?;
framebuffers.push(fb);
}
Ok(Self {
render_pass,
pipeline,
pipeline_layout,
_view_set_layout: view_set_layout,
_descriptor_pool: descriptor_pool,
params_ubos,
froxel_ubos,
view_sets,
froxel_pipeline,
froxel_pipeline_layout,
_froxel_set_layout: froxel_set_layout,
froxel_sets,
volume,
framebuffers,
_volume_sampler: volume_sampler,
})
}
pub(in crate::vulkan) fn rebuild(
&mut self,
device: &VkDevice,
hdr_resolve_views: &[vk::ImageView],
depth_views: &[vk::ImageView],
extent: vk::Extent2D,
) -> RenderResult<()> {
self.framebuffers.clear();
for &view in hdr_resolve_views.iter().take(self.params_ubos.len()) {
let attachments = [view];
let fb_info = vk::FramebufferCreateInfo::default()
.render_pass(self.render_pass.handle())
.attachments(&attachments)
.width(extent.width.max(1))
.height(extent.height.max(1))
.layers(1);
let fb = device
.create_framebuffer(&fb_info)
.map_err(|e| super::error::map_vk_result(e, "fog framebuffer (rebuild)"))?;
self.framebuffers.push(fb);
}
let last_depth = depth_views.len().saturating_sub(1);
for (i, &set) in self.view_sets.iter().enumerate() {
SetWrites::new(set)
.sampled_image(1, depth_views[i.min(last_depth)])
.apply(device);
}
Ok(())
}
pub(in crate::vulkan) fn destroy(&mut self, _device: &VkDevice) {
self.framebuffers.clear();
self.params_ubos.clear();
self.froxel_ubos.clear();
self.volume = PooledImage::null();
}
}
fn alloc_ubo_ring(
alloc: &DeviceAllocator,
count: usize,
size: u64,
) -> RenderResult<Vec<PooledBuffer>> {
(0..count)
.map(|_| {
alloc.create_buffer(
size,
vk::BufferUsageFlags::UNIFORM_BUFFER,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)
})
.collect()
}
fn create_fog_render_pass(device: &VkDevice, format: vk::Format) -> RenderResult<OwnedRenderPass> {
let attachment = vk::AttachmentDescription::default()
.format(format)
.samples(vk::SampleCountFlags::TYPE_1)
.load_op(vk::AttachmentLoadOp::LOAD)
.store_op(vk::AttachmentStoreOp::STORE)
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
.initial_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.final_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL);
let color_ref = vk::AttachmentReference::default()
.attachment(0)
.layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
let subpass = vk::SubpassDescription::default()
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
.color_attachments(std::slice::from_ref(&color_ref));
let dep_in = vk::SubpassDependency::default()
.src_subpass(vk::SUBPASS_EXTERNAL)
.dst_subpass(0)
.src_stage_mask(
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
| vk::PipelineStageFlags::FRAGMENT_SHADER,
)
.src_access_mask(vk::AccessFlags::SHADER_READ | vk::AccessFlags::COLOR_ATTACHMENT_WRITE)
.dst_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
.dst_access_mask(
vk::AccessFlags::COLOR_ATTACHMENT_WRITE | vk::AccessFlags::COLOR_ATTACHMENT_READ,
);
let dep_out = vk::SubpassDependency::default()
.src_subpass(0)
.dst_subpass(vk::SUBPASS_EXTERNAL)
.src_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
.src_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE)
.dst_stage_mask(vk::PipelineStageFlags::FRAGMENT_SHADER)
.dst_access_mask(vk::AccessFlags::SHADER_READ);
let deps = [dep_in, dep_out];
let info = vk::RenderPassCreateInfo::default()
.attachments(std::slice::from_ref(&attachment))
.subpasses(std::slice::from_ref(&subpass))
.dependencies(&deps);
device
.create_render_pass(&info)
.map_err(|e| super::error::map_vk_result(e, "fog render pass"))
}
fn fog_set_bindings() -> [Binding; 5] {
use vk::DescriptorType as T;
let frag = vk::ShaderStageFlags::FRAGMENT;
[
(0, T::UNIFORM_BUFFER, frag),
(1, T::SAMPLED_IMAGE, frag),
(2, T::UNIFORM_BUFFER, frag),
(3, T::SAMPLED_IMAGE, frag),
(4, T::SAMPLER, frag),
]
}
fn froxel_set_bindings() -> [Binding; 6] {
use vk::DescriptorType as T;
let compute = vk::ShaderStageFlags::COMPUTE;
[
(0, T::UNIFORM_BUFFER, compute),
(1, T::UNIFORM_BUFFER, compute),
(2, T::UNIFORM_BUFFER, compute),
(3, T::SAMPLED_IMAGE, compute),
(4, T::STORAGE_IMAGE, compute),
(5, T::SAMPLER, compute),
]
}
fn create_fog_pipeline_layout(
device: &VkDevice,
view_set_layout: vk::DescriptorSetLayout,
) -> RenderResult<OwnedPipelineLayout> {
let set_layouts = [view_set_layout];
let info = vk::PipelineLayoutCreateInfo::default().set_layouts(&set_layouts);
device
.create_pipeline_layout(&info)
.map_err(|e| super::error::map_vk_result(e, "fog pipeline layout"))
}
fn create_froxel_pipeline_layout(
device: &VkDevice,
froxel_set_layout: vk::DescriptorSetLayout,
) -> RenderResult<OwnedPipelineLayout> {
let set_layouts = [froxel_set_layout];
let info = vk::PipelineLayoutCreateInfo::default().set_layouts(&set_layouts);
device
.create_pipeline_layout(&info)
.map_err(|e| super::error::map_vk_result(e, "fog froxel pipeline layout"))
}
fn create_fog_descriptor_pool(
device: &VkDevice,
frames: usize,
) -> RenderResult<OwnedDescriptorPool> {
let f = frames as u32;
let sizes = PoolSizes::default()
.sets(&fog_set_bindings(), f)
.sets(&froxel_set_bindings(), f)
.build();
let info = vk::DescriptorPoolCreateInfo::default()
.max_sets(2 * f)
.pool_sizes(&sizes);
device
.create_descriptor_pool(&info)
.map_err(|e| super::error::map_vk_result(e, "fog descriptor pool"))
}
#[derive(Clone, Copy)]
struct FogViewBindings {
params_ubo: vk::Buffer,
depth_view: vk::ImageView,
froxel_ubo: vk::Buffer,
volume_view: vk::ImageView,
volume_sampler: vk::Sampler,
}
fn write_view_set(device: &VkDevice, set: vk::DescriptorSet, bindings: FogViewBindings) {
let FogViewBindings {
params_ubo,
depth_view,
froxel_ubo,
volume_view,
volume_sampler,
} = bindings;
SetWrites::new(set)
.uniform_buffer(0, params_ubo, std::mem::size_of::<FogParams>() as u64)
.sampled_image(1, depth_view)
.uniform_buffer(2, froxel_ubo, std::mem::size_of::<FogFroxelParams>() as u64)
.sampled_image(3, volume_view)
.sampler(4, volume_sampler)
.apply(device);
}
#[derive(Clone, Copy)]
struct FogFroxelBindings {
params_ubo: vk::Buffer,
froxel_ubo: vk::Buffer,
shadow_ubo: vk::Buffer,
shadow_map_view: vk::ImageView,
shadow_sampler: vk::Sampler,
volume_storage_view: vk::ImageView,
}
fn write_froxel_set(device: &VkDevice, set: vk::DescriptorSet, bindings: FogFroxelBindings) {
let FogFroxelBindings {
params_ubo,
froxel_ubo,
shadow_ubo,
shadow_map_view,
shadow_sampler,
volume_storage_view,
} = bindings;
SetWrites::new(set)
.uniform_buffer(0, params_ubo, std::mem::size_of::<FogParams>() as u64)
.uniform_buffer(1, froxel_ubo, std::mem::size_of::<FogFroxelParams>() as u64)
.uniform_buffer(2, shadow_ubo, std::mem::size_of::<ShadowUniforms>() as u64)
.sampled_image(3, shadow_map_view)
.storage_image(4, volume_storage_view)
.sampler(5, shadow_sampler)
.apply(device);
}
fn create_volume_image(alloc: &DeviceAllocator) -> RenderResult<PooledImage> {
let img_info = vk::ImageCreateInfo::default()
.image_type(vk::ImageType::TYPE_3D)
.extent(vk::Extent3D {
width: FOG_FROXEL_X,
height: FOG_FROXEL_Y,
depth: FOG_FROXEL_Z,
})
.mip_levels(1)
.array_layers(1)
.format(VOLUME_FORMAT)
.tiling(vk::ImageTiling::OPTIMAL)
.initial_layout(vk::ImageLayout::UNDEFINED)
.usage(vk::ImageUsageFlags::STORAGE | vk::ImageUsageFlags::SAMPLED)
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.samples(vk::SampleCountFlags::TYPE_1);
alloc.create_image(&img_info, vk::MemoryPropertyFlags::DEVICE_LOCAL)
}
fn create_volume_view(device: &VkDevice, image: vk::Image) -> RenderResult<vk::ImageView> {
let info = vk::ImageViewCreateInfo::default()
.image(image)
.view_type(vk::ImageViewType::TYPE_3D)
.format(VOLUME_FORMAT)
.subresource_range(vk::ImageSubresourceRange {
aspect_mask: vk::ImageAspectFlags::COLOR,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
});
unsafe { device.create_image_view(&info, None) }
.map_err(|e| super::error::map_vk_result(e, "fog volume view"))
}
fn create_volume_sampler(device: &VkDevice) -> RenderResult<OwnedSampler> {
let info = vk::SamplerCreateInfo::default()
.mag_filter(vk::Filter::LINEAR)
.min_filter(vk::Filter::LINEAR)
.mipmap_mode(vk::SamplerMipmapMode::NEAREST)
.address_mode_u(vk::SamplerAddressMode::CLAMP_TO_EDGE)
.address_mode_v(vk::SamplerAddressMode::CLAMP_TO_EDGE)
.address_mode_w(vk::SamplerAddressMode::CLAMP_TO_EDGE);
device
.create_sampler(&info)
.map_err(|e| super::error::map_vk_result(e, "fog volume sampler"))
}
fn compile_fog_shaders(hot_reload: bool, msaa: bool) -> RenderResult<(Vec<u8>, Vec<u8>)> {
let vert = super::builtin_shaders::FULLSCREEN_VERT.compile(hot_reload)?;
let frag = super::builtin_shaders::FOG_FRAG
.at(msaa)
.compile(hot_reload)?;
Ok((vert, frag))
}
fn compile_fog_froxel_shader(hot_reload: bool) -> RenderResult<Vec<u8>> {
super::builtin_shaders::FOG_FROXEL.compile(hot_reload)
}
pub(in crate::vulkan) fn rebuild_fog_pipeline(
device: &VkDevice,
fog: &FogResources,
msaa: bool,
hot_reload: bool,
) -> RenderResult<OwnedPipeline> {
let (vert_spv, frag_spv) = compile_fog_shaders(hot_reload, msaa)?;
create_fog_pipeline(
device,
fog.render_pass.handle(),
fog.pipeline_layout.handle(),
&vert_spv,
&frag_spv,
)
}
pub(in crate::vulkan) fn rebuild_fog_froxel_pipeline(
device: &VkDevice,
fog: &FogResources,
hot_reload: bool,
) -> RenderResult<OwnedPipeline> {
let spv = compile_fog_froxel_shader(hot_reload)?;
compute_pipeline(
device,
fog.froxel_pipeline_layout.handle(),
&spv,
"fog froxel",
)
}
fn create_fog_pipeline(
device: &VkDevice,
render_pass: vk::RenderPass,
layout: vk::PipelineLayout,
vert_spv: &[u8],
frag_spv: &[u8],
) -> RenderResult<OwnedPipeline> {
GraphicsPipelineDesc::fullscreen(
vert_spv,
frag_spv,
layout,
render_pass,
&[Blend::PremultipliedOver],
)
.build(device, "fog")
}
impl VkContext {
pub(in crate::vulkan) fn apply_fog_settings(
&mut self,
settings: Option<volumetric_fog::FogSettings>,
) {
if settings.is_some() && self.fog.resources.is_none() {
tracing::warn!(
"VolumetricFog hot-reload: world started without fog, so the fog \
pipeline + froxel volume were never built: re-enabling fog mid-run \
is not supported (relaunch required). Ignoring update."
);
return;
}
self.fog.settings = settings;
}
pub(in crate::vulkan) fn encode_fog_froxel(
&self,
rec: &Recorder<'_>,
frame_idx: usize,
near: f32,
vp: [[f32; 4]; 4],
cam_pos: [f32; 3],
) {
let fog_settings = match &self.fog.settings {
Some(s) => *s,
None => return,
};
let fog = match &self.fog.resources {
Some(f) => f,
None => return,
};
let inv_vp = mat4_inverse(vp);
let viewport_pix = [
self.targets.render_extent.width as f32,
self.targets.render_extent.height as f32,
];
let params = fog_settings.params(
inv_vp,
cam_pos,
self.fog.sun_dir,
self.fog.sun_color,
viewport_pix,
);
let froxel_params = FogFroxelParams {
view: self.state.view.matrix,
froxel_dims: [FOG_FROXEL_X, FOG_FROXEL_Y, FOG_FROXEL_Z],
_pad_align: 0,
z_near: near.max(1e-3),
z_far: fog_settings.max_distance,
_pad: [0.0; 2],
};
fog.params_ubos[frame_idx].write_val(0, ¶ms);
fog.froxel_ubos[frame_idx].write_val(0, &froxel_params);
rec.bind_pipeline(vk::PipelineBindPoint::COMPUTE, &fog.froxel_pipeline);
rec.bind_descriptor_sets(
vk::PipelineBindPoint::COMPUTE,
&fog.froxel_pipeline_layout,
0,
std::slice::from_ref(&fog.froxel_sets[frame_idx]),
&[],
);
rec.dispatch(
FOG_FROXEL_X.div_ceil(FROXEL_TILE),
FOG_FROXEL_Y.div_ceil(FROXEL_TILE),
1,
);
}
pub(in crate::vulkan) fn encode_fog(
&self,
rec: &Recorder<'_>,
frame_idx: usize,
_vp: [[f32; 4]; 4],
_cam_pos: [f32; 3],
) {
if self.fog.settings.is_none() {
return;
}
let fog = match &self.fog.resources {
Some(f) => f,
None => return,
};
let extent = self.targets.render_extent;
let vp_state = vk::Viewport {
x: 0.0,
y: 0.0,
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);
rec.begin_render_pass(
&fog.render_pass,
&fog.framebuffers[frame_idx],
vk::Rect2D::default().extent(extent),
&[],
);
rec.set_viewport(&vp_state);
rec.set_scissor(&scissor);
rec.bind_pipeline(vk::PipelineBindPoint::GRAPHICS, &fog.pipeline);
rec.bind_descriptor_sets(
vk::PipelineBindPoint::GRAPHICS,
&fog.pipeline_layout,
0,
std::slice::from_ref(&fog.view_sets[frame_idx]),
&[],
);
rec.draw(3, 1, 0, 0);
rec.end_render_pass();
}
}
#[cfg(test)]
mod tests {
use concinnity_core::gfx::render_types::{FogFroxelParams, FogParams};
use std::mem::size_of;
#[test]
fn fog_params_ubo_size_matches_glsl() {
assert_eq!(size_of::<FogParams>(), 176);
}
#[test]
fn fog_froxel_params_ubo_size_matches_glsl() {
assert_eq!(size_of::<FogFroxelParams>(), 96);
}
#[test]
fn fog_shaders_compile() {
concinnity_shader::require_dxc!();
super::compile_fog_shaders(false, false).expect("fog shaders (no MSAA) compile");
super::compile_fog_shaders(false, true).expect("fog shaders (MSAA) compile");
super::compile_fog_froxel_shader(false).expect("fog froxel kernel compiles");
}
}