use ash::vk;
use concinnity_core::render::error::RenderResult;
use concinnity_core::render::reactive_mask::ReactiveWrite;
use super::allocator::DeviceAllocator;
use super::owned::{OwnedRenderPass, VkDevice};
use super::pipeline_desc::Blend;
use super::texture::{
GpuImage, ImageSpec, LayoutTransition, SubresourceRange, create_image, create_image_view,
one_shot_submit, transition_image_layout_range,
};
pub(in crate::vulkan) const REACTIVE_MASK_FORMAT: vk::Format = vk::Format::R8_UNORM;
pub(in crate::vulkan) const REACTIVE_MASK_CLEAR: vk::ClearValue = vk::ClearValue {
color: vk::ClearColorValue { float32: [0.0; 4] },
};
const RANGE: vk::ImageSubresourceRange = vk::ImageSubresourceRange {
aspect_mask: vk::ImageAspectFlags::COLOR,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
};
pub(in crate::vulkan) fn create_reactive_masks(
alloc: &DeviceAllocator,
device: &VkDevice,
(command_pool, queue): (vk::CommandPool, vk::Queue),
(width, height): (u32, u32),
count: usize,
) -> RenderResult<Vec<GpuImage>> {
let mut masks = Vec::with_capacity(count);
for _ in 0..count {
let pooled = create_image(
alloc,
&ImageSpec {
width: width.max(1),
height: height.max(1),
format: REACTIVE_MASK_FORMAT,
tiling: vk::ImageTiling::OPTIMAL,
usage: vk::ImageUsageFlags::COLOR_ATTACHMENT
| vk::ImageUsageFlags::SAMPLED
| vk::ImageUsageFlags::TRANSFER_DST,
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_range(
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: 1,
},
);
})?;
let view = create_image_view(
device,
image,
REACTIVE_MASK_FORMAT,
vk::ImageAspectFlags::COLOR,
)?;
masks.push(GpuImage::from_pooled(pooled, view));
}
Ok(masks)
}
pub(in crate::vulkan) fn attachment(write: ReactiveWrite) -> vk::AttachmentDescription {
let (load, store, initial) = match write {
ReactiveWrite::Unstored => (
vk::AttachmentLoadOp::DONT_CARE,
vk::AttachmentStoreOp::DONT_CARE,
vk::ImageLayout::UNDEFINED,
),
ReactiveWrite::Clear => (
vk::AttachmentLoadOp::CLEAR,
vk::AttachmentStoreOp::STORE,
vk::ImageLayout::UNDEFINED,
),
ReactiveWrite::Load => (
vk::AttachmentLoadOp::LOAD,
vk::AttachmentStoreOp::STORE,
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
),
};
vk::AttachmentDescription::default()
.format(REACTIVE_MASK_FORMAT)
.samples(vk::SampleCountFlags::TYPE_1)
.load_op(load)
.store_op(store)
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
.initial_layout(initial)
.final_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(in crate::vulkan) enum WriterTargets {
SceneAndMask,
SceneOnly,
}
impl WriterTargets {
pub(in crate::vulkan) fn for_device(independent_blend: bool) -> Self {
if independent_blend {
Self::SceneAndMask
} else {
Self::SceneOnly
}
}
pub(in crate::vulkan) fn carries_mask(self) -> bool {
self == Self::SceneAndMask
}
pub(in crate::vulkan) fn blends(self) -> &'static [Blend] {
match self {
Self::SceneAndMask => &[Blend::AlphaOver, Blend::Max],
Self::SceneOnly => &[Blend::AlphaOver],
}
}
pub(in crate::vulkan) fn attachments(
self,
scene: vk::AttachmentDescription,
write: Option<ReactiveWrite>,
) -> Vec<vk::AttachmentDescription> {
let mut attachments = vec![scene];
if let Some(write) = write.filter(|_| self.carries_mask()) {
attachments.push(attachment(write));
}
attachments
}
pub(in crate::vulkan) fn views(
self,
scene: vk::ImageView,
mask: vk::ImageView,
) -> Vec<vk::ImageView> {
if self.carries_mask() {
vec![scene, mask]
} else {
vec![scene]
}
}
}
pub(in crate::vulkan) enum WriterRenderPasses {
SceneAndMask {
unstored: OwnedRenderPass,
clear: OwnedRenderPass,
load: OwnedRenderPass,
},
SceneOnly(OwnedRenderPass),
}
impl WriterRenderPasses {
pub(in crate::vulkan) fn new(
targets: WriterTargets,
mut create: impl FnMut(Option<ReactiveWrite>) -> RenderResult<OwnedRenderPass>,
) -> RenderResult<Self> {
Ok(match targets {
WriterTargets::SceneAndMask => Self::SceneAndMask {
unstored: create(Some(ReactiveWrite::Unstored))?,
clear: create(Some(ReactiveWrite::Clear))?,
load: create(Some(ReactiveWrite::Load))?,
},
WriterTargets::SceneOnly => Self::SceneOnly(create(None)?),
})
}
pub(in crate::vulkan) fn get(&self, write: ReactiveWrite) -> vk::RenderPass {
match (self, write) {
(Self::SceneAndMask { unstored, .. }, ReactiveWrite::Unstored) => unstored.handle(),
(Self::SceneAndMask { clear, .. }, ReactiveWrite::Clear) => clear.handle(),
(Self::SceneAndMask { load, .. }, ReactiveWrite::Load) => load.handle(),
(Self::SceneOnly(pass), _) => pass.handle(),
}
}
pub(in crate::vulkan) fn compatible(&self) -> vk::RenderPass {
self.get(ReactiveWrite::Load)
}
}
pub(in crate::vulkan) fn clear_outside_pass(
device: &VkDevice,
cmd: vk::CommandBuffer,
mask: vk::Image,
) {
let reads = vk::PipelineStageFlags::FRAGMENT_SHADER | vk::PipelineStageFlags::COMPUTE_SHADER;
let later = reads | vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT;
let to_transfer = vk::ImageMemoryBarrier::default()
.src_access_mask(vk::AccessFlags::SHADER_READ)
.dst_access_mask(vk::AccessFlags::TRANSFER_WRITE)
.old_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.new_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.image(mask)
.subresource_range(RANGE);
let back = vk::ImageMemoryBarrier::default()
.src_access_mask(vk::AccessFlags::TRANSFER_WRITE)
.dst_access_mask(
vk::AccessFlags::SHADER_READ
| vk::AccessFlags::COLOR_ATTACHMENT_READ
| vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
)
.old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
.new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.image(mask)
.subresource_range(RANGE);
unsafe {
device.cmd_pipeline_barrier(
cmd,
reads,
vk::PipelineStageFlags::TRANSFER,
vk::DependencyFlags::empty(),
&[],
&[],
std::slice::from_ref(&to_transfer),
);
device.cmd_clear_color_image(
cmd,
mask,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
&vk::ClearColorValue { float32: [0.0; 4] },
std::slice::from_ref(&RANGE),
);
device.cmd_pipeline_barrier(
cmd,
vk::PipelineStageFlags::TRANSFER,
later,
vk::DependencyFlags::empty(),
&[],
&[],
std::slice::from_ref(&back),
);
}
}
impl super::context::VkContext {
pub(in crate::vulkan) fn clear_reactive_mask(&self, cmd: vk::CommandBuffer, frame_idx: usize) {
if let Some(mask) = self.targets.reactive_mask_images.get(frame_idx) {
clear_outside_pass(&self.hw.device, cmd, mask.image);
}
}
pub(in crate::vulkan) fn reactive_mask(&self, frame_idx: usize) -> Option<&GpuImage> {
self.targets.reactive_mask_images.get(frame_idx)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_device_decides_whether_the_writers_carry_the_mask() {
let carried = WriterTargets::for_device(true);
assert!(carried.carries_mask());
assert_eq!(carried.blends(), [Blend::AlphaOver, Blend::Max]);
let scene = vk::AttachmentDescription::default().format(vk::Format::R16G16B16A16_SFLOAT);
assert_eq!(
carried.attachments(scene, Some(ReactiveWrite::Load)).len(),
2
);
assert_eq!(
carried
.views(vk::ImageView::null(), vk::ImageView::null())
.len(),
2
);
let alone = WriterTargets::for_device(false);
assert!(!alone.carries_mask());
assert_eq!(alone.blends(), [Blend::AlphaOver]);
assert_eq!(alone.attachments(scene, None).len(), 1);
assert_eq!(alone.attachments(scene, Some(ReactiveWrite::Load)).len(), 1);
assert_eq!(
alone
.views(vk::ImageView::null(), vk::ImageView::null())
.len(),
1
);
}
#[test]
fn the_writer_attachments_are_compatible_and_end_sampled() {
let all = [
attachment(ReactiveWrite::Unstored),
attachment(ReactiveWrite::Clear),
attachment(ReactiveWrite::Load),
];
for a in all {
assert_eq!(a.format, REACTIVE_MASK_FORMAT);
assert_eq!(a.samples, vk::SampleCountFlags::TYPE_1);
assert_eq!(a.final_layout, vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL);
}
assert_eq!(all[0].store_op, vk::AttachmentStoreOp::DONT_CARE);
assert_eq!(all[1].load_op, vk::AttachmentLoadOp::CLEAR);
assert_eq!(all[2].load_op, vk::AttachmentLoadOp::LOAD);
assert_eq!(
all[2].initial_layout,
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL
);
}
}