vk: reallow usage of render passes for environments where dynamic rendering is not available

This implementation is greatly simplified compared to the older implementation where framebuffers were attached to output targets. Instead, the graphics pipeline object will create new framebuffers on the fly. The suggestion is still to use dynamic rendering for best performance.
This commit is contained in:
chyyran 2023-01-25 23:45:10 -05:00
parent 9397233a0c
commit fb827b7c24
9 changed files with 219 additions and 42 deletions

44
Cargo.lock generated
View file

@ -765,9 +765,9 @@ dependencies = [
[[package]]
name = "librashader-spirv-cross"
version = "0.23.3"
version = "0.23.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36e8754296f21f37016240d34ceb26d941190fc4c8345691a9025b4127bfcb8d"
checksum = "bc8e651a93a3bec5cec3264949f8ca9a8c4cbd09f03911af789000e3cebf98f6"
dependencies = [
"cc",
"js-sys",
@ -964,6 +964,15 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "nom8"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
dependencies = [
"memchr",
]
[[package]]
name = "nom_locate"
version = "4.1.0"
@ -1053,18 +1062,18 @@ dependencies = [
[[package]]
name = "num_enum"
version = "0.5.7"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9"
checksum = "8d829733185c1ca374f17e52b762f24f535ec625d2cc1f070e34c8a9068f341b"
dependencies = [
"num_enum_derive",
]
[[package]]
name = "num_enum_derive"
version = "0.5.7"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce"
checksum = "2be1598bf1c313dcdd12092e3f1920f463462525a21b7b4e11b4168353d0123e"
dependencies = [
"proc-macro-crate",
"proc-macro2",
@ -1161,13 +1170,12 @@ dependencies = [
[[package]]
name = "proc-macro-crate"
version = "1.2.1"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9"
checksum = "66618389e4ec1c7afe67d51a9bf34ff9236480f8d51e7489b7d5ab0303c13f34"
dependencies = [
"once_cell",
"thiserror",
"toml",
"toml_edit",
]
[[package]]
@ -1452,12 +1460,20 @@ dependencies = [
]
[[package]]
name = "toml"
version = "0.5.11"
name = "toml_datetime"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5"
[[package]]
name = "toml_edit"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "729bfd096e40da9c001f778f5cdecbd2957929a24e10e5883d9392220a751581"
dependencies = [
"serde",
"indexmap",
"nom8",
"toml_datetime",
]
[[package]]

View file

@ -101,7 +101,9 @@ Please report an issue if you run into a shader that works in RetroArch, but not
* Framebuffer copies are done via `ID3D11DeviceContext::CopySubresourceRegion` rather than a CPU conversion + copy.
* HDR10 support is not part of the shader runtime and is not supported.
* Vulkan 1.3+
* The Vulkan runtime uses [`VK_KHR_dynamic_rendering`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_dynamic_rendering.html). This extension must be enabled at device creation to use librashader.
* The Vulkan runtime uses [`VK_KHR_dynamic_rendering`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_dynamic_rendering.html) by default.
This extension must be enabled at device creation. Explicit render passes can be used by configuring filter chain options, but may have reduced performance
compared to dynamic rendering.
* UBOs use multiple discontiguous buffers. This may be improved in the future by switching to VMA rather than manually handling allocations.
Most, if not all shader presets should work fine on librashader. The runtime specific differences should not affect the output,

View file

@ -149,6 +149,7 @@ struct FrameResiduals {
device: ash::Device,
image_views: Vec<vk::ImageView>,
owned: Vec<OwnedImage>,
framebuffers: Vec<Option<vk::Framebuffer>>
}
impl FrameResiduals {
@ -157,6 +158,7 @@ impl FrameResiduals {
device: device.clone(),
image_views: Vec::new(),
owned: Vec::new(),
framebuffers: Vec::new(),
}
}
@ -168,6 +170,10 @@ impl FrameResiduals {
self.owned.push(owned)
}
pub(crate) fn dispose_framebuffers(&mut self, fb: Option<vk::Framebuffer>) {
self.framebuffers.push(fb)
}
/// Dispose of the intermediate objects created during a frame.
pub fn dispose(&mut self) {
for image_view in self.image_views.drain(0..) {
@ -177,6 +183,14 @@ impl FrameResiduals {
}
}
}
for framebuffer in self.framebuffers.drain(0..) {
if let Some(framebuffer) = framebuffer
&& framebuffer != vk::Framebuffer::null() {
unsafe {
self.device.destroy_framebuffer(framebuffer, None);
}
}
}
self.owned.clear()
}
}
@ -218,7 +232,8 @@ impl FilterChainVulkan {
}
// initialize passes
let filters = Self::init_passes(&device, passes, &semantics, frames_in_flight)?;
let filters = Self::init_passes(&device, passes, &semantics, frames_in_flight, options
.map(|o| o.render_pass_format).unwrap_or(vk::Format::UNDEFINED))?;
let luts = FilterChainVulkan::load_luts(&device, &preset.textures)?;
let samplers = SamplerSet::new(&device.device)?;
@ -284,6 +299,7 @@ impl FilterChainVulkan {
passes: Vec<ShaderPassMeta>,
semantics: &ShaderSemantics,
frames_in_flight: u32,
render_pass_format: vk::Format,
) -> error::Result<Box<[FilterPass]>> {
let mut filters = Vec::new();
let frames_in_flight = std::cmp::max(1, frames_in_flight);
@ -332,6 +348,7 @@ impl FilterChainVulkan {
&spirv_words,
&reflection,
frames_in_flight,
render_pass_format
)?;
// let ubo_ring = VkUboRing::new(
@ -685,7 +702,7 @@ impl FilterChainVulkan {
output: OutputImage::new(&self.vulkan, target.image.clone())?,
};
pass.draw(
let residual_fb = pass.draw(
cmd,
index,
&self.common,
@ -709,6 +726,7 @@ impl FilterChainVulkan {
source = self.common.output_inputs[index].as_ref().unwrap();
intermediates.dispose_outputs(out.output);
intermediates.dispose_framebuffers(residual_fb);
}
// try to hint the optimizer
@ -724,7 +742,7 @@ impl FilterChainVulkan {
output: OutputImage::new(&self.vulkan, viewport.output.clone())?,
};
pass.draw(
let residual_fb = pass.draw(
cmd,
passes_len - 1,
&self.common,
@ -737,6 +755,7 @@ impl FilterChainVulkan {
)?;
intermediates.dispose_outputs(out.output);
intermediates.dispose_framebuffers(residual_fb);
}
self.push_history(input, cmd, count)?;

View file

@ -95,7 +95,7 @@ impl FilterPass {
original: &InputImage,
source: &InputImage,
output: &RenderTarget,
) -> error::Result<()> {
) -> error::Result<Option<vk::Framebuffer>> {
let mut descriptor = self.graphics_pipeline.layout.descriptor_sets
[(frame_count % self.frames_in_flight) as usize];
@ -132,23 +132,9 @@ impl FilterPass {
output.output.begin_pass(cmd);
let attachments = [vk::RenderingAttachmentInfo::builder()
.load_op(vk::AttachmentLoadOp::DONT_CARE)
.store_op(vk::AttachmentStoreOp::STORE)
.image_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
.image_view(output.output.image_view)
.build()];
let rendering_info = vk::RenderingInfo::builder()
.layer_count(1)
.render_area(vk::Rect2D {
offset: vk::Offset2D { x: 0, y: 0 },
extent: output.output.size.into(),
})
.color_attachments(&attachments);
let residual = self.graphics_pipeline.begin_rendering(&parent.device, output, cmd)?;
unsafe {
parent.device.cmd_begin_rendering(cmd, &rendering_info);
parent.device.cmd_bind_pipeline(
cmd,
vk::PipelineBindPoint::GRAPHICS,
@ -200,9 +186,9 @@ impl FilterPass {
.device
.cmd_set_viewport(cmd, 0, &[output.output.size.into()]);
parent.device.cmd_draw(cmd, 4, 1, 0, 0);
parent.device.cmd_end_rendering(cmd);
self.graphics_pipeline.end_rendering(&parent.device, cmd);
}
Ok(())
Ok(residual)
}
fn build_semantics(

View file

@ -30,12 +30,14 @@ pub use texture::VulkanImage;
pub mod error;
pub mod options;
mod render_pass;
#[cfg(test)]
mod tests {
use ash::vk;
use crate::filter_chain::FilterChainVulkan;
use crate::hello_triangle::vulkan_base::VulkanBase;
use crate::options::FilterChainOptionsVulkan;
#[test]
fn triangle_vk() {
@ -47,7 +49,11 @@ mod tests {
// "../test/slang-shaders/border/gameboy-player/gameboy-player-crt-royale.slangp",
"../test/slang-shaders/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp",
// "../test/basic.slangp",
None,
Some(&FilterChainOptionsVulkan {
frames_in_flight: 3,
force_no_mipmaps: false,
render_pass_format: vk::Format::R8G8B8A8_UNORM,
}),
)
.unwrap();

View file

@ -1,5 +1,7 @@
//! Vulkan shader runtime options.
use ash::vk;
/// Options for each Vulkan shader frame.
#[repr(C)]
#[derive(Debug, Clone)]
@ -18,4 +20,9 @@ pub struct FilterChainOptionsVulkan {
pub frames_in_flight: u32,
/// Whether or not to explicitly disable mipmap generation regardless of shader preset settings.
pub force_no_mipmaps: bool,
/// The format to use for the render pass. If this is `VK_FORMAT_UNDEFINED`, dynamic rendering
/// will be used instead of a render pass. If this is set to some format, the render passes
/// will be created with such format. It is recommended if possible to use dynamic rendering,
/// because render-pass mode will create new framebuffers per pass.
pub render_pass_format: vk::Format
}

View file

@ -0,0 +1,51 @@
use crate::error;
use ash::vk;
use ash::vk::{
AttachmentLoadOp, AttachmentStoreOp, ImageLayout, PipelineBindPoint, SampleCountFlags,
};
pub struct VulkanRenderPass {
pub handle: vk::RenderPass,
pub format: vk::Format,
}
impl VulkanRenderPass {
pub fn create_render_pass(
device: &ash::Device,
mut format: vk::Format,
) -> error::Result<Self> {
// format should never be undefined.
let attachment = [vk::AttachmentDescription::builder()
.flags(vk::AttachmentDescriptionFlags::empty())
.format(format)
.samples(SampleCountFlags::TYPE_1)
.load_op(AttachmentLoadOp::DONT_CARE)
.store_op(AttachmentStoreOp::STORE)
.stencil_load_op(AttachmentLoadOp::DONT_CARE)
.stencil_store_op(AttachmentStoreOp::DONT_CARE)
.initial_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
.final_layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
.build()];
let attachment_ref = [vk::AttachmentReference::builder()
.attachment(0)
.layout(ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
.build()];
let subpass = [vk::SubpassDescription::builder()
.pipeline_bind_point(PipelineBindPoint::GRAPHICS)
.color_attachments(&attachment_ref)
.build()];
let renderpass_info = vk::RenderPassCreateInfo::builder()
.flags(vk::RenderPassCreateFlags::empty())
.attachments(&attachment)
.subpasses(&subpass)
.build();
unsafe {
let rp = device.create_render_pass(&renderpass_info, None)?;
Ok(Self { handle: rp, format })
}
}
}

View file

@ -5,6 +5,10 @@ use librashader_reflect::back::ShaderCompilerOutput;
use librashader_reflect::reflect::semantics::{TextureBinding, UboReflection};
use librashader_reflect::reflect::ShaderReflection;
use std::ffi::CStr;
use crate::framebuffer::OutputImage;
use crate::render_target::RenderTarget;
use crate::render_pass::VulkanRenderPass;
const ENTRY_POINT: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"main\0") };
@ -173,6 +177,7 @@ impl Drop for VulkanShaderModule {
pub struct VulkanGraphicsPipeline {
pub layout: PipelineLayoutObjects,
pub pipeline: vk::Pipeline,
pub render_pass: Option<VulkanRenderPass>
}
impl VulkanGraphicsPipeline {
@ -182,6 +187,7 @@ impl VulkanGraphicsPipeline {
shader_assembly: &ShaderCompilerOutput<Vec<u32>>,
reflection: &ShaderReflection,
replicas: u32,
render_pass_format: vk::Format
) -> error::Result<VulkanGraphicsPipeline> {
let pipeline_layout = PipelineLayoutObjects::new(reflection, replicas, device)?;
@ -280,7 +286,7 @@ impl VulkanGraphicsPipeline {
.build(),
];
let pipeline_info = vk::GraphicsPipelineCreateInfo::builder()
let mut pipeline_info = vk::GraphicsPipelineCreateInfo::builder()
.stages(&shader_stages)
.vertex_input_state(&pipeline_input_state)
.input_assembly_state(&input_assembly)
@ -290,8 +296,15 @@ impl VulkanGraphicsPipeline {
.viewport_state(&viewport_state)
.depth_stencil_state(&depth_stencil_state)
.dynamic_state(&dynamic_state)
.layout(pipeline_layout.layout)
.build();
.layout(pipeline_layout.layout);
let mut render_pass = None;
if render_pass_format != vk::Format::UNDEFINED {
render_pass = Some(VulkanRenderPass::create_render_pass(&device, render_pass_format)?);
pipeline_info = pipeline_info.render_pass(render_pass.as_ref().unwrap().handle)
}
let pipeline_info = pipeline_info.build();
let pipeline = unsafe {
// panic_safety: if this is successful this should return 1 pipelines.
@ -304,6 +317,81 @@ impl VulkanGraphicsPipeline {
Ok(VulkanGraphicsPipeline {
layout: pipeline_layout,
pipeline,
render_pass,
})
}
#[inline(always)]
pub(crate) fn begin_rendering(&self, device: &ash::Device, output: &RenderTarget, cmd: vk::CommandBuffer) -> error::Result<Option<vk::Framebuffer>>{
if let Some(render_pass) = &self.render_pass {
let attachments = [output.output.image_view];
let framebuffer = unsafe {
device.create_framebuffer(
&vk::FramebufferCreateInfo::builder()
.render_pass(render_pass.handle)
.attachments(&attachments)
.width(output.output.size.width)
.height(output.output.size.height)
.layers(1)
.build(),
None,
)?
};
let clear_values = [vk::ClearValue {
color: vk::ClearColorValue {
float32: [0.0, 0.0, 0.0, 0.0]
}
}];
let render_pass_info = vk::RenderPassBeginInfo::builder()
.framebuffer(framebuffer)
.render_pass(render_pass.handle)
.clear_values(&clear_values)
// always render into the full output, regardless of viewport settings.
.render_area(vk::Rect2D {
offset: vk::Offset2D {
x: 0,
y: 0,
},
extent: output.output.size.into(),
}).build();
unsafe {
device.cmd_begin_render_pass(cmd, &render_pass_info, vk::SubpassContents::INLINE);
}
Ok(Some(framebuffer))
} else {
let attachments = [vk::RenderingAttachmentInfo::builder()
.load_op(vk::AttachmentLoadOp::DONT_CARE)
.store_op(vk::AttachmentStoreOp::STORE)
.image_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
.image_view(output.output.image_view)
.build()];
let rendering_info = vk::RenderingInfo::builder()
.layer_count(1)
.render_area(vk::Rect2D {
offset: vk::Offset2D { x: 0, y: 0 },
extent: output.output.size.into(),
})
.color_attachments(&attachments)
.build();
unsafe {
device.cmd_begin_rendering(cmd, &rendering_info);
}
Ok(None)
}
}
pub(crate) fn end_rendering(&self, device: &ash::Device, cmd: vk::CommandBuffer) {
unsafe {
if self.render_pass.is_none() {
device.cmd_end_rendering(cmd);
} else {
device.cmd_end_render_pass(cmd)
}
}
}
}

View file

@ -16,7 +16,9 @@
//!
//! ## Runtimes
//! Currently available runtimes are Vulkan 1.3+, OpenGL 3.3+ and 4.6 (with DSA), and Direct3D 11.
//! Work on the Direct3D 12 runtime is in progress. The Vulkan runtime requires [`VK_KHR_dynamic_rendering`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_dynamic_rendering.html).
//! Work on the Direct3D 12 runtime is in progress. The Vulkan runtime requires [`VK_KHR_dynamic_rendering`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_dynamic_rendering.html)
//! by default, unless [`FilterChainOptions::render_pass_format`](crate::runtime::vk::FilterChainOptions) is explicitly set. Note that dynamic rendering
//! will being the best performance.
//!
//! | **API** | **Status** | **`librashader` feature** |
//! |-------------|------------|---------------------------|