use ash::vk;
use concinnity_core::render::error::RenderResult;
use concinnity_core::render::fullscreen;
use super::super::allocator::DeviceAllocator;
use super::super::context::*;
use super::super::pipeline::GraphicsStages;
use super::super::resources::{alloc_descriptor_sets, write_source_set};
use super::super::texture::*;
use crate::vulkan::builtin_shaders::CompileProgram;
use crate::vulkan::owned::{OwnedFramebuffer, OwnedPipeline, VkDevice};
pub(in crate::vulkan) const MAX_BLOOM_MIPS: u32 = 6;
pub(in crate::vulkan) struct BloomShaders {
pub vert: Vec<u8>,
pub prefilter: Vec<u8>,
pub downsample: Vec<u8>,
pub upsample: Vec<u8>,
}
pub(in crate::vulkan) fn compile_bloom_shaders(hot_reload: bool) -> RenderResult<BloomShaders> {
use super::super::builtin_shaders;
Ok(BloomShaders {
vert: builtin_shaders::FULLSCREEN_VERT.compile(hot_reload)?,
prefilter: builtin_shaders::BLOOM_PREFILTER.compile(hot_reload)?,
downsample: builtin_shaders::BLOOM_DOWNSAMPLE.compile(hot_reload)?,
upsample: builtin_shaders::BLOOM_UPSAMPLE.compile(hot_reload)?,
})
}
pub(in crate::vulkan) fn create_bloom_pipeline(
device: &VkDevice,
render_pass: vk::RenderPass,
layout: vk::PipelineLayout,
vert_spv: &[u8],
frag_spv: &[u8],
additive: bool,
) -> RenderResult<OwnedPipeline> {
let modules = GraphicsStages::new(device, vert_spv, frag_spv)?;
let stages = modules.infos();
let vert_input = vk::PipelineVertexInputStateCreateInfo::default();
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
.topology(vk::PrimitiveTopology::TRIANGLE_LIST)
.primitive_restart_enable(false);
let viewport_state = vk::PipelineViewportStateCreateInfo::default()
.viewport_count(1)
.scissor_count(1);
let raster = vk::PipelineRasterizationStateCreateInfo::default()
.depth_clamp_enable(false)
.rasterizer_discard_enable(false)
.polygon_mode(vk::PolygonMode::FILL)
.line_width(1.0)
.cull_mode(vk::CullModeFlags::NONE)
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
.depth_bias_enable(false);
let multisample = vk::PipelineMultisampleStateCreateInfo::default()
.sample_shading_enable(false)
.rasterization_samples(vk::SampleCountFlags::TYPE_1);
let depth_stencil = vk::PipelineDepthStencilStateCreateInfo::default()
.depth_test_enable(false)
.depth_write_enable(false)
.depth_compare_op(vk::CompareOp::ALWAYS);
let color_blend_attach = if additive {
vk::PipelineColorBlendAttachmentState::default()
.color_write_mask(vk::ColorComponentFlags::RGBA)
.blend_enable(true)
.src_color_blend_factor(vk::BlendFactor::ONE)
.dst_color_blend_factor(vk::BlendFactor::ONE)
.color_blend_op(vk::BlendOp::ADD)
.src_alpha_blend_factor(vk::BlendFactor::ONE)
.dst_alpha_blend_factor(vk::BlendFactor::ONE)
.alpha_blend_op(vk::BlendOp::ADD)
} else {
vk::PipelineColorBlendAttachmentState::default()
.color_write_mask(vk::ColorComponentFlags::RGBA)
.blend_enable(false)
};
let color_blend = vk::PipelineColorBlendStateCreateInfo::default()
.logic_op_enable(false)
.attachments(std::slice::from_ref(&color_blend_attach));
let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
let dynamic = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
let pipeline_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_stencil)
.color_blend_state(&color_blend)
.dynamic_state(&dynamic)
.layout(layout)
.render_pass(render_pass)
.subpass(0);
let pipeline = crate::vulkan::pipeline_cache::create_graphics_pipeline(device, &pipeline_info)
.map_err(|e| crate::vulkan::error::map_vk_result(e, "create bloom pipeline"))?;
Ok(pipeline)
}
pub(in crate::vulkan) fn bloom_mip_count(width: u32, height: u32) -> u32 {
let min_dim = width.min(height).max(1);
let levels = (min_dim as f32).log2().floor() as i32 - 1;
levels.clamp(4, 6) as u32
}
pub(in crate::vulkan) struct BloomDeviceContext<'a> {
pub alloc: &'a DeviceAllocator,
pub device: &'a VkDevice,
pub command_pool: vk::CommandPool,
pub queue: vk::Queue,
}
pub(in crate::vulkan) fn create_bloom_mips(
ctx: &BloomDeviceContext,
width: u32,
height: u32,
format: vk::Format,
mip0_override: Option<(vk::Image, vk::ImageView)>,
) -> RenderResult<(Vec<GpuImage>, Vec<vk::Extent2D>)> {
let &BloomDeviceContext {
alloc,
device,
command_pool,
queue,
} = ctx;
let full_w = width.max(1);
let full_h = height.max(1);
let count = bloom_mip_count(full_w, full_h);
let mut mips = Vec::with_capacity(count as usize);
let mut extents = Vec::with_capacity(count as usize);
for i in 0..count {
let mw = (full_w >> (i + 1)).max(1);
let mh = (full_h >> (i + 1)).max(1);
let gpu_image = if i == 0
&& let Some((image, view)) = mip0_override
{
GpuImage::borrowed(image, view)
} else {
let pooled = create_image(
alloc,
&ImageSpec {
width: mw,
height: mh,
format,
tiling: vk::ImageTiling::OPTIMAL,
usage: vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::SAMPLED,
mem_props: vk::MemoryPropertyFlags::DEVICE_LOCAL,
samples: vk::SampleCountFlags::TYPE_1,
},
)?;
let image = pooled.image();
one_shot_submit(device, command_pool, queue, |cmd| {
transition_image_layout(
device,
cmd,
image,
vk::ImageLayout::UNDEFINED,
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
vk::ImageAspectFlags::COLOR,
);
})?;
let view = create_image_view(device, image, format, vk::ImageAspectFlags::COLOR)?;
GpuImage::from_pooled(pooled, view)
};
mips.push(gpu_image);
extents.push(vk::Extent2D {
width: mw,
height: mh,
});
}
Ok((mips, extents))
}
pub(in crate::vulkan) fn create_bloom_chain(
ctx: &BloomDeviceContext,
extent: vk::Extent2D,
frames: usize,
bloom_top: &[(vk::Image, vk::ImageView)],
) -> RenderResult<(Vec<Vec<GpuImage>>, Vec<vk::Extent2D>)> {
let mut mips = Vec::with_capacity(frames);
let mut extents = Vec::new();
for f in 0..frames {
let (m, e) = create_bloom_mips(
ctx,
extent.width,
extent.height,
HDR_FORMAT,
bloom_top.get(f).copied(),
)?;
if extents.is_empty() {
extents = e;
}
mips.push(m);
}
Ok((mips, extents))
}
type BloomFramebuffers = (Vec<Vec<OwnedFramebuffer>>, Vec<Vec<OwnedFramebuffer>>);
pub(in crate::vulkan) fn create_bloom_framebuffers(
device: &VkDevice,
write_pass: vk::RenderPass,
blend_pass: vk::RenderPass,
bloom_mips: &[Vec<GpuImage>],
extents: &[vk::Extent2D],
) -> RenderResult<BloomFramebuffers> {
let make_fb = |rp: vk::RenderPass, view: vk::ImageView, ext: vk::Extent2D| {
let fb_info = vk::FramebufferCreateInfo::default()
.render_pass(rp)
.attachments(std::slice::from_ref(&view))
.width(ext.width)
.height(ext.height)
.layers(1);
device
.create_framebuffer(&fb_info)
.map_err(|e| crate::vulkan::error::map_vk_result(e, "bloom framebuffer"))
};
let mut write = Vec::with_capacity(bloom_mips.len());
let mut blend = Vec::with_capacity(bloom_mips.len());
for mips in bloom_mips {
let mut w = Vec::with_capacity(mips.len());
let mut b = Vec::with_capacity(mips.len().saturating_sub(1));
for (i, mip) in mips.iter().enumerate() {
w.push(make_fb(write_pass, mip.view, extents[i])?);
if i + 1 < mips.len() {
b.push(make_fb(blend_pass, mip.view, extents[i])?);
}
}
write.push(w);
blend.push(b);
}
Ok((write, blend))
}
pub(in crate::vulkan) fn rebind_bloom_input0(
device: &VkDevice,
set: vk::DescriptorSet,
view: vk::ImageView,
) {
write_bloom_input(device, set, view);
}
fn write_bloom_input(device: &VkDevice, set: vk::DescriptorSet, view: vk::ImageView) {
let img_info = vk::DescriptorImageInfo::default()
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.image_view(view);
let write = vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(0)
.descriptor_type(vk::DescriptorType::SAMPLED_IMAGE)
.image_info(std::slice::from_ref(&img_info));
unsafe { device.update_descriptor_sets(std::slice::from_ref(&write), &[]) };
}
pub(in crate::vulkan) fn alloc_bloom_input_sets(
device: &VkDevice,
pool: vk::DescriptorPool,
layout: vk::DescriptorSetLayout,
sampler: vk::Sampler,
hdr_resolve_images: &[GpuImage],
bloom_mips: &[Vec<GpuImage>],
) -> RenderResult<Vec<Vec<vk::DescriptorSet>>> {
let mut out = Vec::with_capacity(bloom_mips.len());
for (frame, mips) in bloom_mips.iter().enumerate() {
let layouts: Vec<_> = (0..mips.len() + 1).map(|_| layout).collect();
let sets = alloc_descriptor_sets(device, pool, &layouts)?;
for (idx, &set) in sets.iter().enumerate() {
let view = if idx == 0 {
hdr_resolve_images[frame].view
} else {
mips[idx - 1].view
};
write_source_set(device, set, &[(view, sampler)]);
}
out.push(sets);
}
Ok(out)
}
impl fullscreen::BloomEncoder for VkContext {
type Rec = vk::CommandBuffer;
type Args = usize;
fn bloom_mip_count(&self) -> usize {
self.bloom.mip_extents.len()
}
fn begin_bloom(&self, cmd: &Self::Rec, _frame_idx: &Self::Args) -> RenderResult<()> {
unsafe {
self.hw.device.cmd_push_constants(
*cmd,
self.bloom.pipeline_layout.handle(),
vk::ShaderStageFlags::FRAGMENT,
0,
bytemuck::bytes_of(&self.post_process),
);
}
Ok(())
}
fn bloom_prefilter(&self, cmd: &Self::Rec, frame_idx: &Self::Args) -> RenderResult<()> {
let f = *frame_idx;
self.bloom_run_pass(
*cmd,
self.bloom.write_pass.handle(),
self.bloom.write_framebuffers[f][0].handle(),
self.bloom.mip_extents[0],
&self.bloom.pipeline_prefilter,
self.bloom.input_sets[f][0],
);
Ok(())
}
fn bloom_downsample(
&self,
cmd: &Self::Rec,
frame_idx: &Self::Args,
dst: usize,
) -> RenderResult<()> {
let f = *frame_idx;
self.bloom_run_pass(
*cmd,
self.bloom.write_pass.handle(),
self.bloom.write_framebuffers[f][dst].handle(),
self.bloom.mip_extents[dst],
&self.bloom.pipeline_downsample,
self.bloom.input_sets[f][dst],
);
Ok(())
}
fn bloom_upsample(
&self,
cmd: &Self::Rec,
frame_idx: &Self::Args,
dst: usize,
) -> RenderResult<()> {
let f = *frame_idx;
self.bloom_run_pass(
*cmd,
self.bloom.blend_pass.handle(),
self.bloom.blend_framebuffers[f][dst].handle(),
self.bloom.mip_extents[dst],
&self.bloom.pipeline_upsample,
self.bloom.input_sets[f][dst + 2],
);
Ok(())
}
}
impl VkContext {
pub(in crate::vulkan) fn encode_bloom(&self, cmd: vk::CommandBuffer, frame_idx: usize) {
let _ = fullscreen::encode_bloom_chain(self, &cmd, frame_idx);
}
fn bloom_run_pass(
&self,
cmd: vk::CommandBuffer,
render_pass: vk::RenderPass,
framebuffer: vk::Framebuffer,
ext: vk::Extent2D,
pipeline: &OwnedPipeline,
input_set: vk::DescriptorSet,
) {
let device = &self.hw.device;
let rp_begin = vk::RenderPassBeginInfo::default()
.render_pass(render_pass)
.framebuffer(framebuffer)
.render_area(vk::Rect2D::default().extent(ext));
let vp = vk::Viewport {
x: 0.0,
y: 0.0,
width: ext.width as f32,
height: ext.height as f32,
min_depth: 0.0,
max_depth: 1.0,
};
let scissor = vk::Rect2D::default().extent(ext);
unsafe {
device.cmd_begin_render_pass(cmd, &rp_begin, vk::SubpassContents::INLINE);
device.cmd_set_viewport(cmd, 0, std::slice::from_ref(&vp));
device.cmd_set_scissor(cmd, 0, std::slice::from_ref(&scissor));
device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, pipeline.handle());
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
self.bloom.pipeline_layout.handle(),
0,
std::slice::from_ref(&input_set),
&[],
);
device.cmd_draw(cmd, 3, 1, 0, 0);
device.cmd_end_render_pass(cmd);
}
}
}