use ash::vk;
use concinnity_core::render::error::{RenderError, RenderResult};
use concinnity_core::render::post::device::{
PostBlend, PostDraw, PostExtent, PostLoadOp, PostPassDevice, PostSampler, check_level,
level_extent, resolved_texture,
};
use concinnity_core::render::post::program::{PostProgram, PostProgramBindings};
use concinnity_core::render::render_graph::{PixelFormat, TextureDesc};
use crate::vulkan::allocator::DeviceAllocator;
use crate::vulkan::builtin_shaders::{self, CompileProgram};
use crate::vulkan::error::map_vk_result;
use crate::vulkan::owned::{OwnedPipeline, OwnedPipelineLayout, OwnedSetLayout, VkDevice};
use crate::vulkan::pipeline_desc::{Blend, GraphicsPipelineDesc};
use crate::vulkan::post::pass_cache::{AttachmentRest, PostPassCache};
use crate::vulkan::post::set_arena::PostSetArena;
use crate::vulkan::set_writes::SetWrites;
use crate::vulkan::texture::{
GpuImage, LayoutTransition, SubresourceRange, one_shot_submit, transition_image_layout_range,
};
use crate::vulkan::transient_pool::{image_format, image_usage, sample_count};
const PROBE_SET_INDEX: u32 = 1;
pub(in crate::vulkan) struct PostPipeline {
pipeline: OwnedPipeline,
layout: OwnedPipelineLayout,
set_layout: OwnedSetLayout,
bindings: PostProgramBindings,
}
pub(in crate::vulkan) struct PostTarget {
label: &'static str,
image: GpuImage,
extent: vk::Extent2D,
format: PixelFormat,
levels: u32,
}
impl PostTarget {
pub(in crate::vulkan) fn view(&self) -> vk::ImageView {
self.image.view
}
pub(in crate::vulkan) fn image(&self) -> &GpuImage {
&self.image
}
fn level_view(&self, level: u32) -> RenderResult<vk::ImageView> {
check_level(self.label, level, self.levels)?;
Ok(self
.image
.aux_views
.get(level as usize)
.copied()
.unwrap_or(self.image.view))
}
}
fn level_range_view(
device: &VkDevice,
image: vk::Image,
format: vk::Format,
base: u32,
count: u32,
) -> RenderResult<vk::ImageView> {
let info = vk::ImageViewCreateInfo::default()
.image(image)
.view_type(vk::ImageViewType::TYPE_2D)
.format(format)
.subresource_range(
vk::ImageSubresourceRange::default()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.base_mip_level(base)
.level_count(count)
.base_array_layer(0)
.layer_count(1),
);
unsafe { device.create_image_view(&info, None) }
.map_err(|e| map_vk_result(e, "post target view"))
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct VkAttachment {
pub view: vk::ImageView,
pub extent: vk::Extent2D,
pub format: PixelFormat,
pub rest: AttachmentRest,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct VkPostProbes<'a> {
pub layout: vk::DescriptorSetLayout,
pub sets: &'a [vk::DescriptorSet],
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct PostQueue {
pub command_pool: vk::CommandPool,
pub queue: vk::Queue,
}
pub(in crate::vulkan) struct VkPostDevice<'a> {
pub device: &'a VkDevice,
pub alloc: &'a DeviceAllocator,
pub queue: PostQueue,
pub cache: &'a PostPassCache,
pub arena: &'a PostSetArena,
pub sampler: vk::Sampler,
pub cube_sampler: vk::Sampler,
pub probes: VkPostProbes<'a>,
pub frame: usize,
pub hot_reload: bool,
}
fn compile(program: PostProgram, hot_reload: bool) -> RenderResult<(Vec<u8>, Vec<u8>)> {
let vert = builtin_shaders::FULLSCREEN_VERT.compile(hot_reload)?;
Ok((vert, program.program().compile(hot_reload)?))
}
impl VkPostDevice<'_> {
fn set_layout(&self, n: usize) -> RenderResult<OwnedSetLayout> {
crate::vulkan::resources::create_descriptor_set_layout(
self.device,
&crate::vulkan::resources::source_set_bindings(n as u32),
)
}
fn probes_for(&self, bindings: PostProgramBindings) -> Option<VkPostProbes<'_>> {
bindings.probes.then_some(self.probes)
}
fn sampler_for(&self, sampler: PostSampler) -> vk::Sampler {
match sampler {
PostSampler::LinearClamp => self.sampler,
PostSampler::LinearCube => self.cube_sampler,
}
}
}
impl PostPassDevice for VkPostDevice<'_> {
type Recorder = vk::CommandBuffer;
type Pipeline = PostPipeline;
type Target = PostTarget;
type TextureRef<'a> = vk::ImageView;
type Attachment<'a> = VkAttachment;
fn create_pipeline(
&self,
program: PostProgram,
format: PixelFormat,
blend: PostBlend,
) -> RenderResult<Self::Pipeline> {
let bindings = program.bindings();
let probes = self.probes_for(bindings);
let set_layout = self.set_layout(bindings.textures)?;
let mut set_layouts = vec![set_layout.handle()];
set_layouts.extend(probes.map(|p| p.layout));
let push = vk::PushConstantRange::default()
.stage_flags(vk::ShaderStageFlags::FRAGMENT)
.offset(0)
.size(bindings.constants as u32);
let mut layout_info = vk::PipelineLayoutCreateInfo::default().set_layouts(&set_layouts);
if bindings.constants > 0 {
layout_info = layout_info.push_constant_ranges(std::slice::from_ref(&push));
}
let layout = self
.device
.create_pipeline_layout(&layout_info)
.map_err(|e| map_vk_result(e, "post pipeline layout"))?;
let render_pass = self.cache.render_pass(
self.device,
format,
PostLoadOp::DontCare,
AttachmentRest::Sampled,
)?;
let (vert, frag) = compile(program, self.hot_reload)?;
let pipeline = GraphicsPipelineDesc::fullscreen(
&vert,
&frag,
layout.handle(),
render_pass,
&[Blend::from(blend)],
)
.build(self.device, "post")?;
Ok(PostPipeline {
pipeline,
layout,
set_layout,
bindings,
})
}
fn create_target(
&self,
label: &'static str,
desc: &TextureDesc,
extent: PostExtent,
) -> RenderResult<Self::Target> {
let spec = resolved_texture(label, desc, extent);
let format = image_format(spec.format);
let levels = spec.mip_levels.max(1);
let info = vk::ImageCreateInfo::default()
.image_type(vk::ImageType::TYPE_2D)
.extent(vk::Extent3D {
width: spec.width,
height: spec.height,
depth: 1,
})
.mip_levels(levels)
.array_layers(1)
.format(format)
.tiling(vk::ImageTiling::OPTIMAL)
.initial_layout(vk::ImageLayout::UNDEFINED)
.usage(image_usage(spec.usage))
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.samples(sample_count(spec.sample_count));
let pooled = self
.alloc
.create_image(&info, vk::MemoryPropertyFlags::DEVICE_LOCAL)
.map_err(|e| e.context(format_args!("{label} post target")))?;
let image = pooled.image();
one_shot_submit(
self.device,
self.queue.command_pool,
self.queue.queue,
|cmd| {
transition_image_layout_range(
self.device,
cmd,
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: levels,
},
);
},
)?;
let view = level_range_view(self.device, image, format, 0, levels)?;
let mut gpu = GpuImage::from_pooled(pooled, view);
if levels > 1 {
for level in 0..levels {
gpu.push_aux_view(level_range_view(self.device, image, format, level, 1)?);
}
}
Ok(PostTarget {
label,
image: gpu,
extent: vk::Extent2D {
width: spec.width,
height: spec.height,
},
format: spec.format,
levels,
})
}
fn target_ref<'a>(&self, target: &'a Self::Target) -> Self::TextureRef<'a> {
target.image.view
}
fn target_attachment<'a>(&self, target: &'a Self::Target) -> Self::Attachment<'a> {
VkAttachment {
view: target
.image
.aux_views
.first()
.copied()
.unwrap_or(target.image.view),
extent: target.extent,
format: target.format,
rest: AttachmentRest::Sampled,
}
}
fn target_level_ref<'a>(
&self,
target: &'a Self::Target,
level: u32,
) -> RenderResult<Self::TextureRef<'a>> {
target.level_view(level)
}
fn target_level_attachment<'a>(
&self,
target: &'a Self::Target,
level: u32,
) -> RenderResult<Self::Attachment<'a>> {
let extent = level_extent(post_extent(target.extent), level);
Ok(VkAttachment {
view: target.level_view(level)?,
extent: vk::Extent2D {
width: extent.width,
height: extent.height,
},
format: target.format,
rest: AttachmentRest::Sampled,
})
}
fn encode(&self, rec: &Self::Recorder, draw: &PostDraw<'_, '_, Self>) -> RenderResult<()> {
let cmd = *rec;
let pipe = draw.pipeline;
draw.check(pipe.bindings)?;
let probe_set = match self.probes_for(pipe.bindings) {
None => None,
Some(probes) => Some(*probes.sets.get(self.frame).ok_or_else(|| {
RenderError::Other(format!(
"{}: no global set for frame {}",
draw.label, self.frame
))
})?),
};
let target = draw.target;
let render_pass =
self.cache
.render_pass(self.device, target.format, draw.load, target.rest)?;
let framebuffer =
self.cache
.framebuffer(self.device, render_pass, target.view, target.extent)?;
let set = self
.arena
.alloc(self.device, self.frame, pipe.set_layout.handle())?;
let n = draw.binds.len() as u32;
draw.binds
.iter()
.zip(0..)
.fold(SetWrites::new(set), |w, (b, i)| {
w.sampled_image(i, b.texture)
.sampler(n + i, self.sampler_for(b.sampler))
})
.apply(self.device);
let extent = target.extent;
let rp_begin = vk::RenderPassBeginInfo::default()
.render_pass(render_pass)
.framebuffer(framebuffer)
.render_area(vk::Rect2D::default().extent(extent));
let vp = 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);
let device = self.device;
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, pipe.pipeline.handle());
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
pipe.layout.handle(),
0,
std::slice::from_ref(&set),
&[],
);
if let Some(global) = probe_set {
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
pipe.layout.handle(),
PROBE_SET_INDEX,
std::slice::from_ref(&global),
&[],
);
}
if !draw.constants.is_empty() {
device.cmd_push_constants(
cmd,
pipe.layout.handle(),
vk::ShaderStageFlags::FRAGMENT,
0,
draw.constants,
);
}
device.cmd_draw(cmd, 3, 1, 0, 0);
device.cmd_end_render_pass(cmd);
}
Ok(())
}
}
pub(in crate::vulkan) fn post_extent(extent: vk::Extent2D) -> PostExtent {
PostExtent {
width: extent.width,
height: extent.height,
}
}
impl crate::vulkan::context::VkContext {
pub(in crate::vulkan) fn post_device(&self, frame: usize) -> VkPostDevice<'_> {
VkPostDevice {
device: &self.hw.device,
alloc: &self.hw.alloc,
queue: PostQueue {
command_pool: self.commands.command_pool,
queue: self.hw.graphics_queue,
},
cache: &self.post.cache,
arena: &self.post.arena,
sampler: self.post.sampler.handle(),
cube_sampler: self.scene.cube_sampler.handle(),
probes: VkPostProbes {
layout: self.descriptors.global_set_layout.handle(),
sets: &self.descriptors.global_sets,
},
frame,
hot_reload: self.hot_reload.enabled,
}
}
pub(in crate::vulkan) fn hdr_scene_attachment(&self, frame: usize) -> VkAttachment {
VkAttachment {
view: self.targets.hdr_resolve_images[frame % self.targets.hdr_resolve_images.len()]
.view,
extent: self.targets.render_extent,
format: PixelFormat::Rgba16Float,
rest: AttachmentRest::Sampled,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_post_program_compiles() {
concinnity_shader::require_dxc!();
for program in PostProgram::ALL {
let (vert, frag) =
compile(program, false).unwrap_or_else(|e| panic!("{program:?}: {e}"));
assert!(crate::vulkan::pipeline::is_spirv(&vert));
assert!(crate::vulkan::pipeline::is_spirv(&frag));
}
}
}