diff options
Diffstat (limited to 'src/video_core/renderer_vulkan/present')
16 files changed, 2318 insertions, 0 deletions
diff --git a/src/video_core/renderer_vulkan/present/anti_alias_pass.h b/src/video_core/renderer_vulkan/present/anti_alias_pass.h new file mode 100644 index 000000000..1f20fbd7f --- /dev/null +++ b/src/video_core/renderer_vulkan/present/anti_alias_pass.h @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Vulkan { + +class Scheduler; + +class AntiAliasPass { +public: + virtual ~AntiAliasPass() = default; + virtual void Draw(Scheduler& scheduler, size_t image_index, VkImage* inout_image, + VkImageView* inout_image_view) = 0; +}; + +class NoAA final : public AntiAliasPass { +public: + void Draw(Scheduler& scheduler, size_t image_index, VkImage* inout_image, + VkImageView* inout_image_view) override {} +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/filters.cpp b/src/video_core/renderer_vulkan/present/filters.cpp new file mode 100644 index 000000000..b5e08938e --- /dev/null +++ b/src/video_core/renderer_vulkan/present/filters.cpp @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/common_types.h" + +#include "video_core/host_shaders/present_bicubic_frag_spv.h" +#include "video_core/host_shaders/present_gaussian_frag_spv.h" +#include "video_core/host_shaders/vulkan_present_frag_spv.h" +#include "video_core/host_shaders/vulkan_present_scaleforce_fp16_frag_spv.h" +#include "video_core/host_shaders/vulkan_present_scaleforce_fp32_frag_spv.h" +#include "video_core/renderer_vulkan/present/filters.h" +#include "video_core/renderer_vulkan/present/util.h" +#include "video_core/renderer_vulkan/vk_shader_util.h" +#include "video_core/vulkan_common/vulkan_device.h" + +namespace Vulkan { + +namespace { + +vk::ShaderModule SelectScaleForceShader(const Device& device) { + if (device.IsFloat16Supported()) { + return BuildShader(device, VULKAN_PRESENT_SCALEFORCE_FP16_FRAG_SPV); + } else { + return BuildShader(device, VULKAN_PRESENT_SCALEFORCE_FP32_FRAG_SPV); + } +} + +} // Anonymous namespace + +std::unique_ptr<WindowAdaptPass> MakeNearestNeighbor(const Device& device, VkFormat frame_format) { + return std::make_unique<WindowAdaptPass>(device, frame_format, + CreateNearestNeighborSampler(device), + BuildShader(device, VULKAN_PRESENT_FRAG_SPV)); +} + +std::unique_ptr<WindowAdaptPass> MakeBilinear(const Device& device, VkFormat frame_format) { + return std::make_unique<WindowAdaptPass>(device, frame_format, CreateBilinearSampler(device), + BuildShader(device, VULKAN_PRESENT_FRAG_SPV)); +} + +std::unique_ptr<WindowAdaptPass> MakeBicubic(const Device& device, VkFormat frame_format) { + return std::make_unique<WindowAdaptPass>(device, frame_format, CreateBilinearSampler(device), + BuildShader(device, PRESENT_BICUBIC_FRAG_SPV)); +} + +std::unique_ptr<WindowAdaptPass> MakeGaussian(const Device& device, VkFormat frame_format) { + return std::make_unique<WindowAdaptPass>(device, frame_format, CreateBilinearSampler(device), + BuildShader(device, PRESENT_GAUSSIAN_FRAG_SPV)); +} + +std::unique_ptr<WindowAdaptPass> MakeScaleForce(const Device& device, VkFormat frame_format) { + return std::make_unique<WindowAdaptPass>(device, frame_format, CreateBilinearSampler(device), + SelectScaleForceShader(device)); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/filters.h b/src/video_core/renderer_vulkan/present/filters.h new file mode 100644 index 000000000..6c83726dd --- /dev/null +++ b/src/video_core/renderer_vulkan/present/filters.h @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "video_core/renderer_vulkan/present/window_adapt_pass.h" + +namespace Vulkan { + +class MemoryAllocator; + +std::unique_ptr<WindowAdaptPass> MakeNearestNeighbor(const Device& device, VkFormat frame_format); +std::unique_ptr<WindowAdaptPass> MakeBilinear(const Device& device, VkFormat frame_format); +std::unique_ptr<WindowAdaptPass> MakeBicubic(const Device& device, VkFormat frame_format); +std::unique_ptr<WindowAdaptPass> MakeGaussian(const Device& device, VkFormat frame_format); +std::unique_ptr<WindowAdaptPass> MakeScaleForce(const Device& device, VkFormat frame_format); + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/fsr.cpp b/src/video_core/renderer_vulkan/present/fsr.cpp new file mode 100644 index 000000000..3f708be70 --- /dev/null +++ b/src/video_core/renderer_vulkan/present/fsr.cpp @@ -0,0 +1,226 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/common_types.h" +#include "common/div_ceil.h" +#include "common/settings.h" + +#include "video_core/fsr.h" +#include "video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp16_frag_spv.h" +#include "video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp32_frag_spv.h" +#include "video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp16_frag_spv.h" +#include "video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp32_frag_spv.h" +#include "video_core/host_shaders/vulkan_fidelityfx_fsr_vert_spv.h" +#include "video_core/renderer_vulkan/present/fsr.h" +#include "video_core/renderer_vulkan/present/util.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/renderer_vulkan/vk_shader_util.h" +#include "video_core/vulkan_common/vulkan_device.h" + +namespace Vulkan { +using namespace FSR; + +using PushConstants = std::array<u32, 4 * 4>; + +FSR::FSR(const Device& device, MemoryAllocator& memory_allocator, size_t image_count, + VkExtent2D extent) + : m_device{device}, m_memory_allocator{memory_allocator}, + m_image_count{image_count}, m_extent{extent} { + + CreateImages(); + CreateRenderPasses(); + CreateSampler(); + CreateShaders(); + CreateDescriptorPool(); + CreateDescriptorSetLayout(); + CreateDescriptorSets(); + CreatePipelineLayouts(); + CreatePipelines(); +} + +void FSR::CreateImages() { + m_dynamic_images.resize(m_image_count); + for (auto& images : m_dynamic_images) { + images.images[Easu] = + CreateWrappedImage(m_memory_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT); + images.images[Rcas] = + CreateWrappedImage(m_memory_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT); + images.image_views[Easu] = + CreateWrappedImageView(m_device, images.images[Easu], VK_FORMAT_R16G16B16A16_SFLOAT); + images.image_views[Rcas] = + CreateWrappedImageView(m_device, images.images[Rcas], VK_FORMAT_R16G16B16A16_SFLOAT); + } +} + +void FSR::CreateRenderPasses() { + m_renderpass = CreateWrappedRenderPass(m_device, VK_FORMAT_R16G16B16A16_SFLOAT); + + for (auto& images : m_dynamic_images) { + images.framebuffers[Easu] = + CreateWrappedFramebuffer(m_device, m_renderpass, images.image_views[Easu], m_extent); + images.framebuffers[Rcas] = + CreateWrappedFramebuffer(m_device, m_renderpass, images.image_views[Rcas], m_extent); + } +} + +void FSR::CreateSampler() { + m_sampler = CreateBilinearSampler(m_device); +} + +void FSR::CreateShaders() { + m_vert_shader = BuildShader(m_device, VULKAN_FIDELITYFX_FSR_VERT_SPV); + + if (m_device.IsFloat16Supported()) { + m_easu_shader = BuildShader(m_device, VULKAN_FIDELITYFX_FSR_EASU_FP16_FRAG_SPV); + m_rcas_shader = BuildShader(m_device, VULKAN_FIDELITYFX_FSR_RCAS_FP16_FRAG_SPV); + } else { + m_easu_shader = BuildShader(m_device, VULKAN_FIDELITYFX_FSR_EASU_FP32_FRAG_SPV); + m_rcas_shader = BuildShader(m_device, VULKAN_FIDELITYFX_FSR_RCAS_FP32_FRAG_SPV); + } +} + +void FSR::CreateDescriptorPool() { + // EASU: 1 descriptor + // RCAS: 1 descriptor + // 2 descriptors, 2 descriptor sets per invocation + m_descriptor_pool = CreateWrappedDescriptorPool(m_device, 2 * m_image_count, 2 * m_image_count); +} + +void FSR::CreateDescriptorSetLayout() { + m_descriptor_set_layout = + CreateWrappedDescriptorSetLayout(m_device, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER}); +} + +void FSR::CreateDescriptorSets() { + std::vector<VkDescriptorSetLayout> layouts(MaxFsrStage, *m_descriptor_set_layout); + + for (auto& images : m_dynamic_images) { + images.descriptor_sets = CreateWrappedDescriptorSets(m_descriptor_pool, layouts); + } +} + +void FSR::CreatePipelineLayouts() { + const VkPushConstantRange range{ + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, + .offset = 0, + .size = sizeof(PushConstants), + }; + VkPipelineLayoutCreateInfo ci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .setLayoutCount = 1, + .pSetLayouts = m_descriptor_set_layout.address(), + .pushConstantRangeCount = 1, + .pPushConstantRanges = &range, + }; + + m_pipeline_layout = m_device.GetLogical().CreatePipelineLayout(ci); +} + +void FSR::CreatePipelines() { + m_easu_pipeline = CreateWrappedPipeline(m_device, m_renderpass, m_pipeline_layout, + std::tie(m_vert_shader, m_easu_shader)); + m_rcas_pipeline = CreateWrappedPipeline(m_device, m_renderpass, m_pipeline_layout, + std::tie(m_vert_shader, m_rcas_shader)); +} + +void FSR::UpdateDescriptorSets(VkImageView image_view, size_t image_index) { + Images& images = m_dynamic_images[image_index]; + std::vector<VkDescriptorImageInfo> image_infos; + std::vector<VkWriteDescriptorSet> updates; + image_infos.reserve(2); + + updates.push_back(CreateWriteDescriptorSet(image_infos, *m_sampler, image_view, + images.descriptor_sets[Easu], 0)); + updates.push_back(CreateWriteDescriptorSet(image_infos, *m_sampler, *images.image_views[Easu], + images.descriptor_sets[Rcas], 0)); + + m_device.GetLogical().UpdateDescriptorSets(updates, {}); +} + +void FSR::UploadImages(Scheduler& scheduler) { + if (m_images_ready) { + return; + } + + scheduler.Record([&](vk::CommandBuffer cmdbuf) { + for (auto& image : m_dynamic_images) { + ClearColorImage(cmdbuf, *image.images[Easu]); + ClearColorImage(cmdbuf, *image.images[Rcas]); + } + }); + scheduler.Finish(); + + m_images_ready = true; +} + +VkImageView FSR::Draw(Scheduler& scheduler, size_t image_index, VkImage source_image, + VkImageView source_image_view, VkExtent2D input_image_extent, + const Common::Rectangle<f32>& crop_rect) { + Images& images = m_dynamic_images[image_index]; + + VkImage easu_image = *images.images[Easu]; + VkImage rcas_image = *images.images[Rcas]; + VkDescriptorSet easu_descriptor_set = images.descriptor_sets[Easu]; + VkDescriptorSet rcas_descriptor_set = images.descriptor_sets[Rcas]; + VkFramebuffer easu_framebuffer = *images.framebuffers[Easu]; + VkFramebuffer rcas_framebuffer = *images.framebuffers[Rcas]; + VkPipeline easu_pipeline = *m_easu_pipeline; + VkPipeline rcas_pipeline = *m_rcas_pipeline; + VkPipelineLayout pipeline_layout = *m_pipeline_layout; + VkRenderPass renderpass = *m_renderpass; + VkExtent2D extent = m_extent; + + const f32 input_image_width = static_cast<f32>(input_image_extent.width); + const f32 input_image_height = static_cast<f32>(input_image_extent.height); + const f32 output_image_width = static_cast<f32>(extent.width); + const f32 output_image_height = static_cast<f32>(extent.height); + const f32 viewport_width = (crop_rect.right - crop_rect.left) * input_image_width; + const f32 viewport_x = crop_rect.left * input_image_width; + const f32 viewport_height = (crop_rect.bottom - crop_rect.top) * input_image_height; + const f32 viewport_y = crop_rect.top * input_image_height; + + PushConstants easu_con{}; + PushConstants rcas_con{}; + FsrEasuConOffset(easu_con.data() + 0, easu_con.data() + 4, easu_con.data() + 8, + easu_con.data() + 12, viewport_width, viewport_height, input_image_width, + input_image_height, output_image_width, output_image_height, viewport_x, + viewport_y); + + const float sharpening = + static_cast<float>(Settings::values.fsr_sharpening_slider.GetValue()) / 100.0f; + FsrRcasCon(rcas_con.data(), sharpening); + + UploadImages(scheduler); + UpdateDescriptorSets(source_image_view, image_index); + + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([=](vk::CommandBuffer cmdbuf) { + TransitionImageLayout(cmdbuf, source_image, VK_IMAGE_LAYOUT_GENERAL); + TransitionImageLayout(cmdbuf, easu_image, VK_IMAGE_LAYOUT_GENERAL); + BeginRenderPass(cmdbuf, renderpass, easu_framebuffer, extent); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, easu_pipeline); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, + easu_descriptor_set, {}); + cmdbuf.PushConstants(pipeline_layout, VK_SHADER_STAGE_FRAGMENT_BIT, easu_con); + cmdbuf.Draw(3, 1, 0, 0); + cmdbuf.EndRenderPass(); + + TransitionImageLayout(cmdbuf, easu_image, VK_IMAGE_LAYOUT_GENERAL); + TransitionImageLayout(cmdbuf, rcas_image, VK_IMAGE_LAYOUT_GENERAL); + BeginRenderPass(cmdbuf, renderpass, rcas_framebuffer, extent); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, rcas_pipeline); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, + rcas_descriptor_set, {}); + cmdbuf.PushConstants(pipeline_layout, VK_SHADER_STAGE_FRAGMENT_BIT, rcas_con); + cmdbuf.Draw(3, 1, 0, 0); + cmdbuf.EndRenderPass(); + + TransitionImageLayout(cmdbuf, rcas_image, VK_IMAGE_LAYOUT_GENERAL); + }); + + return *images.image_views[Rcas]; +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/fsr.h b/src/video_core/renderer_vulkan/present/fsr.h new file mode 100644 index 000000000..8602e8146 --- /dev/null +++ b/src/video_core/renderer_vulkan/present/fsr.h @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/math_util.h" +#include "video_core/vulkan_common/vulkan_memory_allocator.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Vulkan { + +class Device; +class Scheduler; + +class FSR { +public: + explicit FSR(const Device& device, MemoryAllocator& memory_allocator, size_t image_count, + VkExtent2D extent); + VkImageView Draw(Scheduler& scheduler, size_t image_index, VkImage source_image, + VkImageView source_image_view, VkExtent2D input_image_extent, + const Common::Rectangle<f32>& crop_rect); + +private: + void CreateImages(); + void CreateRenderPasses(); + void CreateSampler(); + void CreateShaders(); + void CreateDescriptorPool(); + void CreateDescriptorSetLayout(); + void CreateDescriptorSets(); + void CreatePipelineLayouts(); + void CreatePipelines(); + + void UploadImages(Scheduler& scheduler); + void UpdateDescriptorSets(VkImageView image_view, size_t image_index); + + const Device& m_device; + MemoryAllocator& m_memory_allocator; + const size_t m_image_count; + const VkExtent2D m_extent; + + enum FsrStage { + Easu, + Rcas, + MaxFsrStage, + }; + + vk::DescriptorPool m_descriptor_pool; + vk::DescriptorSetLayout m_descriptor_set_layout; + vk::PipelineLayout m_pipeline_layout; + vk::ShaderModule m_vert_shader; + vk::ShaderModule m_easu_shader; + vk::ShaderModule m_rcas_shader; + vk::Pipeline m_easu_pipeline; + vk::Pipeline m_rcas_pipeline; + vk::RenderPass m_renderpass; + vk::Sampler m_sampler; + + struct Images { + vk::DescriptorSets descriptor_sets; + std::array<vk::Image, MaxFsrStage> images; + std::array<vk::ImageView, MaxFsrStage> image_views; + std::array<vk::Framebuffer, MaxFsrStage> framebuffers; + }; + std::vector<Images> m_dynamic_images; + bool m_images_ready{}; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/fxaa.cpp b/src/video_core/renderer_vulkan/present/fxaa.cpp new file mode 100644 index 000000000..bdafd1f4d --- /dev/null +++ b/src/video_core/renderer_vulkan/present/fxaa.cpp @@ -0,0 +1,148 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/common_types.h" + +#include "video_core/host_shaders/fxaa_frag_spv.h" +#include "video_core/host_shaders/fxaa_vert_spv.h" +#include "video_core/renderer_vulkan/present/fxaa.h" +#include "video_core/renderer_vulkan/present/util.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/renderer_vulkan/vk_shader_util.h" +#include "video_core/vulkan_common/vulkan_device.h" + +namespace Vulkan { + +FXAA::FXAA(const Device& device, MemoryAllocator& allocator, size_t image_count, VkExtent2D extent) + : m_device(device), m_allocator(allocator), m_extent(extent), + m_image_count(static_cast<u32>(image_count)) { + CreateImages(); + CreateRenderPasses(); + CreateSampler(); + CreateShaders(); + CreateDescriptorPool(); + CreateDescriptorSetLayouts(); + CreateDescriptorSets(); + CreatePipelineLayouts(); + CreatePipelines(); +} + +FXAA::~FXAA() = default; + +void FXAA::CreateImages() { + for (u32 i = 0; i < m_image_count; i++) { + Image& image = m_dynamic_images.emplace_back(); + + image.image = CreateWrappedImage(m_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT); + image.image_view = + CreateWrappedImageView(m_device, image.image, VK_FORMAT_R16G16B16A16_SFLOAT); + } +} + +void FXAA::CreateRenderPasses() { + m_renderpass = CreateWrappedRenderPass(m_device, VK_FORMAT_R16G16B16A16_SFLOAT); + + for (auto& image : m_dynamic_images) { + image.framebuffer = + CreateWrappedFramebuffer(m_device, m_renderpass, image.image_view, m_extent); + } +} + +void FXAA::CreateSampler() { + m_sampler = CreateWrappedSampler(m_device); +} + +void FXAA::CreateShaders() { + m_vertex_shader = CreateWrappedShaderModule(m_device, FXAA_VERT_SPV); + m_fragment_shader = CreateWrappedShaderModule(m_device, FXAA_FRAG_SPV); +} + +void FXAA::CreateDescriptorPool() { + // 2 descriptors, 1 descriptor set per image + m_descriptor_pool = CreateWrappedDescriptorPool(m_device, 2 * m_image_count, m_image_count); +} + +void FXAA::CreateDescriptorSetLayouts() { + m_descriptor_set_layout = + CreateWrappedDescriptorSetLayout(m_device, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER}); +} + +void FXAA::CreateDescriptorSets() { + VkDescriptorSetLayout layout = *m_descriptor_set_layout; + + for (auto& images : m_dynamic_images) { + images.descriptor_sets = CreateWrappedDescriptorSets(m_descriptor_pool, {layout}); + } +} + +void FXAA::CreatePipelineLayouts() { + m_pipeline_layout = CreateWrappedPipelineLayout(m_device, m_descriptor_set_layout); +} + +void FXAA::CreatePipelines() { + m_pipeline = CreateWrappedPipeline(m_device, m_renderpass, m_pipeline_layout, + std::tie(m_vertex_shader, m_fragment_shader)); +} + +void FXAA::UpdateDescriptorSets(VkImageView image_view, size_t image_index) { + Image& image = m_dynamic_images[image_index]; + std::vector<VkDescriptorImageInfo> image_infos; + std::vector<VkWriteDescriptorSet> updates; + image_infos.reserve(2); + + updates.push_back( + CreateWriteDescriptorSet(image_infos, *m_sampler, image_view, image.descriptor_sets[0], 0)); + updates.push_back( + CreateWriteDescriptorSet(image_infos, *m_sampler, image_view, image.descriptor_sets[0], 1)); + + m_device.GetLogical().UpdateDescriptorSets(updates, {}); +} + +void FXAA::UploadImages(Scheduler& scheduler) { + if (m_images_ready) { + return; + } + + scheduler.Record([&](vk::CommandBuffer cmdbuf) { + for (auto& image : m_dynamic_images) { + ClearColorImage(cmdbuf, *image.image); + } + }); + scheduler.Finish(); + + m_images_ready = true; +} + +void FXAA::Draw(Scheduler& scheduler, size_t image_index, VkImage* inout_image, + VkImageView* inout_image_view) { + const Image& image{m_dynamic_images[image_index]}; + const VkImage input_image{*inout_image}; + const VkImage output_image{*image.image}; + const VkDescriptorSet descriptor_set{image.descriptor_sets[0]}; + const VkFramebuffer framebuffer{*image.framebuffer}; + const VkRenderPass renderpass{*m_renderpass}; + const VkPipeline pipeline{*m_pipeline}; + const VkPipelineLayout layout{*m_pipeline_layout}; + const VkExtent2D extent{m_extent}; + + UploadImages(scheduler); + UpdateDescriptorSets(*inout_image_view, image_index); + + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([=](vk::CommandBuffer cmdbuf) { + TransitionImageLayout(cmdbuf, input_image, VK_IMAGE_LAYOUT_GENERAL); + TransitionImageLayout(cmdbuf, output_image, VK_IMAGE_LAYOUT_GENERAL); + BeginRenderPass(cmdbuf, renderpass, framebuffer, extent); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set, {}); + cmdbuf.Draw(3, 1, 0, 0); + cmdbuf.EndRenderPass(); + TransitionImageLayout(cmdbuf, output_image, VK_IMAGE_LAYOUT_GENERAL); + }); + + *inout_image = *image.image; + *inout_image_view = *image.image_view; +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/fxaa.h b/src/video_core/renderer_vulkan/present/fxaa.h new file mode 100644 index 000000000..97a2e5c1c --- /dev/null +++ b/src/video_core/renderer_vulkan/present/fxaa.h @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "video_core/renderer_vulkan/present/anti_alias_pass.h" +#include "video_core/vulkan_common/vulkan_memory_allocator.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Vulkan { + +class Device; +class Scheduler; +class StagingBufferPool; + +class FXAA final : public AntiAliasPass { +public: + explicit FXAA(const Device& device, MemoryAllocator& allocator, size_t image_count, + VkExtent2D extent); + ~FXAA() override; + + void Draw(Scheduler& scheduler, size_t image_index, VkImage* inout_image, + VkImageView* inout_image_view) override; + +private: + void CreateImages(); + void CreateRenderPasses(); + void CreateSampler(); + void CreateShaders(); + void CreateDescriptorPool(); + void CreateDescriptorSetLayouts(); + void CreateDescriptorSets(); + void CreatePipelineLayouts(); + void CreatePipelines(); + void UpdateDescriptorSets(VkImageView image_view, size_t image_index); + void UploadImages(Scheduler& scheduler); + + const Device& m_device; + MemoryAllocator& m_allocator; + const VkExtent2D m_extent; + const u32 m_image_count; + + vk::ShaderModule m_vertex_shader{}; + vk::ShaderModule m_fragment_shader{}; + vk::DescriptorPool m_descriptor_pool{}; + vk::DescriptorSetLayout m_descriptor_set_layout{}; + vk::PipelineLayout m_pipeline_layout{}; + vk::Pipeline m_pipeline{}; + vk::RenderPass m_renderpass{}; + + struct Image { + vk::DescriptorSets descriptor_sets{}; + vk::Framebuffer framebuffer{}; + vk::Image image{}; + vk::ImageView image_view{}; + }; + std::vector<Image> m_dynamic_images{}; + bool m_images_ready{}; + + vk::Sampler m_sampler{}; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/layer.cpp b/src/video_core/renderer_vulkan/present/layer.cpp new file mode 100644 index 000000000..cfc04be44 --- /dev/null +++ b/src/video_core/renderer_vulkan/present/layer.cpp @@ -0,0 +1,336 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "video_core/renderer_vulkan/vk_rasterizer.h" + +#include "common/settings.h" +#include "video_core/framebuffer_config.h" +#include "video_core/renderer_vulkan/present/fsr.h" +#include "video_core/renderer_vulkan/present/fxaa.h" +#include "video_core/renderer_vulkan/present/layer.h" +#include "video_core/renderer_vulkan/present/present_push_constants.h" +#include "video_core/renderer_vulkan/present/smaa.h" +#include "video_core/renderer_vulkan/present/util.h" +#include "video_core/renderer_vulkan/vk_blit_screen.h" +#include "video_core/textures/decoders.h" + +namespace Vulkan { + +namespace { + +u32 GetBytesPerPixel(const Tegra::FramebufferConfig& framebuffer) { + using namespace VideoCore::Surface; + return BytesPerBlock(PixelFormatFromGPUPixelFormat(framebuffer.pixel_format)); +} + +std::size_t GetSizeInBytes(const Tegra::FramebufferConfig& framebuffer) { + return static_cast<std::size_t>(framebuffer.stride) * + static_cast<std::size_t>(framebuffer.height) * GetBytesPerPixel(framebuffer); +} + +VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) { + switch (framebuffer.pixel_format) { + case Service::android::PixelFormat::Rgba8888: + case Service::android::PixelFormat::Rgbx8888: + return VK_FORMAT_A8B8G8R8_UNORM_PACK32; + case Service::android::PixelFormat::Rgb565: + return VK_FORMAT_R5G6B5_UNORM_PACK16; + case Service::android::PixelFormat::Bgra8888: + return VK_FORMAT_B8G8R8A8_UNORM; + default: + UNIMPLEMENTED_MSG("Unknown framebuffer pixel format: {}", + static_cast<u32>(framebuffer.pixel_format)); + return VK_FORMAT_A8B8G8R8_UNORM_PACK32; + } +} + +} // Anonymous namespace + +Layer::Layer(const Device& device_, MemoryAllocator& memory_allocator_, Scheduler& scheduler_, + Tegra::MaxwellDeviceMemoryManager& device_memory_, size_t image_count_, + VkExtent2D output_size, VkDescriptorSetLayout layout) + : device(device_), memory_allocator(memory_allocator_), scheduler(scheduler_), + device_memory(device_memory_), image_count(image_count_) { + CreateDescriptorPool(); + CreateDescriptorSets(layout); + if (Settings::values.scaling_filter.GetValue() == Settings::ScalingFilter::Fsr) { + CreateFSR(output_size); + } +} + +Layer::~Layer() { + ReleaseRawImages(); +} + +void Layer::ConfigureDraw(PresentPushConstants* out_push_constants, + VkDescriptorSet* out_descriptor_set, RasterizerVulkan& rasterizer, + VkSampler sampler, size_t image_index, + const Tegra::FramebufferConfig& framebuffer, + const Layout::FramebufferLayout& layout) { + const auto texture_info = rasterizer.AccelerateDisplay( + framebuffer, framebuffer.address + framebuffer.offset, framebuffer.stride); + const u32 texture_width = texture_info ? texture_info->width : framebuffer.width; + const u32 texture_height = texture_info ? texture_info->height : framebuffer.height; + const u32 scaled_width = texture_info ? texture_info->scaled_width : texture_width; + const u32 scaled_height = texture_info ? texture_info->scaled_height : texture_height; + const bool use_accelerated = texture_info.has_value(); + + RefreshResources(framebuffer); + SetAntiAliasPass(); + + // Finish any pending renderpass + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Wait(resource_ticks[image_index]); + SCOPE_EXIT({ resource_ticks[image_index] = scheduler.CurrentTick(); }); + + if (!use_accelerated) { + UpdateRawImage(framebuffer, image_index); + } + + VkImage source_image = texture_info ? texture_info->image : *raw_images[image_index]; + VkImageView source_image_view = + texture_info ? texture_info->image_view : *raw_image_views[image_index]; + + anti_alias->Draw(scheduler, image_index, &source_image, &source_image_view); + + auto crop_rect = Tegra::NormalizeCrop(framebuffer, texture_width, texture_height); + const VkExtent2D render_extent{ + .width = scaled_width, + .height = scaled_height, + }; + + if (fsr) { + source_image_view = fsr->Draw(scheduler, image_index, source_image, source_image_view, + render_extent, crop_rect); + crop_rect = {0, 0, 1, 1}; + } + + SetMatrixData(*out_push_constants, layout); + SetVertexData(*out_push_constants, layout, crop_rect); + + UpdateDescriptorSet(source_image_view, sampler, image_index); + *out_descriptor_set = descriptor_sets[image_index]; +} + +void Layer::CreateDescriptorPool() { + descriptor_pool = CreateWrappedDescriptorPool(device, image_count, image_count); +} + +void Layer::CreateDescriptorSets(VkDescriptorSetLayout layout) { + const std::vector layouts(image_count, layout); + descriptor_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts); +} + +void Layer::CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer) { + const VkBufferCreateInfo ci{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .size = CalculateBufferSize(framebuffer), + .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + }; + + buffer = memory_allocator.CreateBuffer(ci, MemoryUsage::Upload); +} + +void Layer::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) { + const auto format = GetFormat(framebuffer); + resource_ticks.resize(image_count); + raw_images.resize(image_count); + raw_image_views.resize(image_count); + + for (size_t i = 0; i < image_count; ++i) { + raw_images[i] = + CreateWrappedImage(memory_allocator, {framebuffer.width, framebuffer.height}, format); + raw_image_views[i] = CreateWrappedImageView(device, raw_images[i], format); + } +} + +void Layer::CreateFSR(VkExtent2D output_size) { + fsr = std::make_unique<FSR>(device, memory_allocator, image_count, output_size); +} + +void Layer::RefreshResources(const Tegra::FramebufferConfig& framebuffer) { + if (framebuffer.width == raw_width && framebuffer.height == raw_height && + framebuffer.pixel_format == pixel_format && !raw_images.empty()) { + return; + } + + raw_width = framebuffer.width; + raw_height = framebuffer.height; + pixel_format = framebuffer.pixel_format; + anti_alias.reset(); + + ReleaseRawImages(); + CreateStagingBuffer(framebuffer); + CreateRawImages(framebuffer); +} + +void Layer::SetAntiAliasPass() { + if (anti_alias && anti_alias_setting == Settings::values.anti_aliasing.GetValue()) { + return; + } + + anti_alias_setting = Settings::values.anti_aliasing.GetValue(); + + const VkExtent2D render_area{ + .width = Settings::values.resolution_info.ScaleUp(raw_width), + .height = Settings::values.resolution_info.ScaleUp(raw_height), + }; + + switch (anti_alias_setting) { + case Settings::AntiAliasing::Fxaa: + anti_alias = std::make_unique<FXAA>(device, memory_allocator, image_count, render_area); + break; + case Settings::AntiAliasing::Smaa: + anti_alias = std::make_unique<SMAA>(device, memory_allocator, image_count, render_area); + break; + default: + anti_alias = std::make_unique<NoAA>(); + break; + } +} + +void Layer::ReleaseRawImages() { + for (const u64 tick : resource_ticks) { + scheduler.Wait(tick); + } + raw_images.clear(); + buffer.reset(); +} + +u64 Layer::CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const { + return GetSizeInBytes(framebuffer) * image_count; +} + +u64 Layer::GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer, + size_t image_index) const { + return GetSizeInBytes(framebuffer) * image_index; +} + +void Layer::SetMatrixData(PresentPushConstants& data, + const Layout::FramebufferLayout& layout) const { + data.modelview_matrix = + MakeOrthographicMatrix(static_cast<f32>(layout.width), static_cast<f32>(layout.height)); +} + +void Layer::SetVertexData(PresentPushConstants& data, const Layout::FramebufferLayout& layout, + const Common::Rectangle<f32>& crop) const { + // Map the coordinates to the screen. + const auto& screen = layout.screen; + const auto x = static_cast<f32>(screen.left); + const auto y = static_cast<f32>(screen.top); + const auto w = static_cast<f32>(screen.GetWidth()); + const auto h = static_cast<f32>(screen.GetHeight()); + + data.vertices[0] = ScreenRectVertex(x, y, crop.left, crop.top); + data.vertices[1] = ScreenRectVertex(x + w, y, crop.right, crop.top); + data.vertices[2] = ScreenRectVertex(x, y + h, crop.left, crop.bottom); + data.vertices[3] = ScreenRectVertex(x + w, y + h, crop.right, crop.bottom); +} + +void Layer::UpdateDescriptorSet(VkImageView image_view, VkSampler sampler, size_t image_index) { + const VkDescriptorImageInfo image_info{ + .sampler = sampler, + .imageView = image_view, + .imageLayout = VK_IMAGE_LAYOUT_GENERAL, + }; + + const VkWriteDescriptorSet sampler_write{ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .pNext = nullptr, + .dstSet = descriptor_sets[image_index], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = &image_info, + .pBufferInfo = nullptr, + .pTexelBufferView = nullptr, + }; + + device.GetLogical().UpdateDescriptorSets(std::array{sampler_write}, {}); +} + +void Layer::UpdateRawImage(const Tegra::FramebufferConfig& framebuffer, size_t image_index) { + const std::span<u8> mapped_span = buffer.Mapped(); + + const u64 image_offset = GetRawImageOffset(framebuffer, image_index); + + const DAddr framebuffer_addr = framebuffer.address + framebuffer.offset; + const u8* const host_ptr = device_memory.GetPointer<u8>(framebuffer_addr); + + // TODO(Rodrigo): Read this from HLE + constexpr u32 block_height_log2 = 4; + const u32 bytes_per_pixel = GetBytesPerPixel(framebuffer); + const u64 linear_size{GetSizeInBytes(framebuffer)}; + const u64 tiled_size{Tegra::Texture::CalculateSize( + true, bytes_per_pixel, framebuffer.stride, framebuffer.height, 1, block_height_log2, 0)}; + Tegra::Texture::UnswizzleTexture( + mapped_span.subspan(image_offset, linear_size), std::span(host_ptr, tiled_size), + bytes_per_pixel, framebuffer.width, framebuffer.height, 1, block_height_log2, 0); + + const VkBufferImageCopy copy{ + .bufferOffset = image_offset, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1, + }, + .imageOffset = {.x = 0, .y = 0, .z = 0}, + .imageExtent = + { + .width = framebuffer.width, + .height = framebuffer.height, + .depth = 1, + }, + }; + scheduler.Record([this, copy, index = image_index](vk::CommandBuffer cmdbuf) { + const VkImage image = *raw_images[index]; + const VkImageMemoryBarrier base_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = 0, + .dstAccessMask = 0, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + VkImageMemoryBarrier read_barrier = base_barrier; + read_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + read_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + read_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + + VkImageMemoryBarrier write_barrier = base_barrier; + write_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + write_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + write_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, + read_barrier); + cmdbuf.CopyBufferToImage(*buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copy); + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + 0, write_barrier); + }); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/layer.h b/src/video_core/renderer_vulkan/present/layer.h new file mode 100644 index 000000000..88d43fc5f --- /dev/null +++ b/src/video_core/renderer_vulkan/present/layer.h @@ -0,0 +1,92 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/math_util.h" +#include "video_core/host1x/gpu_device_memory_manager.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Layout { +struct FramebufferLayout; +} + +namespace Tegra { +struct FramebufferConfig; +} + +namespace Service::android { +enum class PixelFormat : u32; +} + +namespace Settings { +enum class AntiAliasing : u32; +} + +namespace Vulkan { + +class AntiAliasPass; +class Device; +class FSR; +class MemoryAllocator; +struct PresentPushConstants; +class RasterizerVulkan; +class Scheduler; + +class Layer final { +public: + explicit Layer(const Device& device, MemoryAllocator& memory_allocator, Scheduler& scheduler, + Tegra::MaxwellDeviceMemoryManager& device_memory, size_t image_count, + VkExtent2D output_size, VkDescriptorSetLayout layout); + ~Layer(); + + void ConfigureDraw(PresentPushConstants* out_push_constants, + VkDescriptorSet* out_descriptor_set, RasterizerVulkan& rasterizer, + VkSampler sampler, size_t image_index, + const Tegra::FramebufferConfig& framebuffer, + const Layout::FramebufferLayout& layout); + +private: + void CreateDescriptorPool(); + void CreateDescriptorSets(VkDescriptorSetLayout layout); + void CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer); + void CreateRawImages(const Tegra::FramebufferConfig& framebuffer); + void CreateFSR(VkExtent2D output_size); + + void RefreshResources(const Tegra::FramebufferConfig& framebuffer); + void SetAntiAliasPass(); + void ReleaseRawImages(); + + u64 CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const; + u64 GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer, size_t image_index) const; + + void SetMatrixData(PresentPushConstants& data, const Layout::FramebufferLayout& layout) const; + void SetVertexData(PresentPushConstants& data, const Layout::FramebufferLayout& layout, + const Common::Rectangle<f32>& crop) const; + void UpdateDescriptorSet(VkImageView image_view, VkSampler sampler, size_t image_index); + void UpdateRawImage(const Tegra::FramebufferConfig& framebuffer, size_t image_index); + +private: + const Device& device; + MemoryAllocator& memory_allocator; + Scheduler& scheduler; + Tegra::MaxwellDeviceMemoryManager& device_memory; + const size_t image_count{}; + vk::DescriptorPool descriptor_pool{}; + vk::DescriptorSets descriptor_sets{}; + + vk::Buffer buffer{}; + std::vector<vk::Image> raw_images{}; + std::vector<vk::ImageView> raw_image_views{}; + u32 raw_width{}; + u32 raw_height{}; + Service::android::PixelFormat pixel_format{}; + + Settings::AntiAliasing anti_alias_setting{}; + std::unique_ptr<AntiAliasPass> anti_alias{}; + + std::unique_ptr<FSR> fsr{}; + std::vector<u64> resource_ticks{}; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/present_push_constants.h b/src/video_core/renderer_vulkan/present/present_push_constants.h new file mode 100644 index 000000000..f1949e7aa --- /dev/null +++ b/src/video_core/renderer_vulkan/present/present_push_constants.h @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_types.h" + +namespace Vulkan { + +struct ScreenRectVertex { + ScreenRectVertex() = default; + explicit ScreenRectVertex(f32 x, f32 y, f32 u, f32 v) : position{{x, y}}, tex_coord{{u, v}} {} + + std::array<f32, 2> position; + std::array<f32, 2> tex_coord; +}; + +static inline std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) { + // clang-format off + return { 2.f / width, 0.f, 0.f, 0.f, + 0.f, 2.f / height, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + -1.f, -1.f, 0.f, 1.f}; + // clang-format on +} + +struct PresentPushConstants { + std::array<f32, 4 * 4> modelview_matrix; + std::array<ScreenRectVertex, 4> vertices; +}; + +static_assert(sizeof(PresentPushConstants) <= 128, "Push constants are too large"); + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/smaa.cpp b/src/video_core/renderer_vulkan/present/smaa.cpp new file mode 100644 index 000000000..39645fd1d --- /dev/null +++ b/src/video_core/renderer_vulkan/present/smaa.cpp @@ -0,0 +1,277 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <list> + +#include "common/assert.h" +#include "common/polyfill_ranges.h" + +#include "video_core/renderer_vulkan/present/smaa.h" +#include "video_core/renderer_vulkan/present/util.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/renderer_vulkan/vk_shader_util.h" +#include "video_core/smaa_area_tex.h" +#include "video_core/smaa_search_tex.h" +#include "video_core/vulkan_common/vulkan_device.h" + +#include "video_core/host_shaders/smaa_blending_weight_calculation_frag_spv.h" +#include "video_core/host_shaders/smaa_blending_weight_calculation_vert_spv.h" +#include "video_core/host_shaders/smaa_edge_detection_frag_spv.h" +#include "video_core/host_shaders/smaa_edge_detection_vert_spv.h" +#include "video_core/host_shaders/smaa_neighborhood_blending_frag_spv.h" +#include "video_core/host_shaders/smaa_neighborhood_blending_vert_spv.h" + +namespace Vulkan { + +SMAA::SMAA(const Device& device, MemoryAllocator& allocator, size_t image_count, VkExtent2D extent) + : m_device(device), m_allocator(allocator), m_extent(extent), + m_image_count(static_cast<u32>(image_count)) { + CreateImages(); + CreateRenderPasses(); + CreateSampler(); + CreateShaders(); + CreateDescriptorPool(); + CreateDescriptorSetLayouts(); + CreateDescriptorSets(); + CreatePipelineLayouts(); + CreatePipelines(); +} + +SMAA::~SMAA() = default; + +void SMAA::CreateImages() { + static constexpr VkExtent2D area_extent{AREATEX_WIDTH, AREATEX_HEIGHT}; + static constexpr VkExtent2D search_extent{SEARCHTEX_WIDTH, SEARCHTEX_HEIGHT}; + + m_static_images[Area] = CreateWrappedImage(m_allocator, area_extent, VK_FORMAT_R8G8_UNORM); + m_static_images[Search] = CreateWrappedImage(m_allocator, search_extent, VK_FORMAT_R8_UNORM); + + m_static_image_views[Area] = + CreateWrappedImageView(m_device, m_static_images[Area], VK_FORMAT_R8G8_UNORM); + m_static_image_views[Search] = + CreateWrappedImageView(m_device, m_static_images[Search], VK_FORMAT_R8_UNORM); + + for (u32 i = 0; i < m_image_count; i++) { + Images& images = m_dynamic_images.emplace_back(); + + images.images[Blend] = + CreateWrappedImage(m_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT); + images.images[Edges] = CreateWrappedImage(m_allocator, m_extent, VK_FORMAT_R16G16_SFLOAT); + images.images[Output] = + CreateWrappedImage(m_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT); + + images.image_views[Blend] = + CreateWrappedImageView(m_device, images.images[Blend], VK_FORMAT_R16G16B16A16_SFLOAT); + images.image_views[Edges] = + CreateWrappedImageView(m_device, images.images[Edges], VK_FORMAT_R16G16_SFLOAT); + images.image_views[Output] = + CreateWrappedImageView(m_device, images.images[Output], VK_FORMAT_R16G16B16A16_SFLOAT); + } +} + +void SMAA::CreateRenderPasses() { + m_renderpasses[EdgeDetection] = CreateWrappedRenderPass(m_device, VK_FORMAT_R16G16_SFLOAT); + m_renderpasses[BlendingWeightCalculation] = + CreateWrappedRenderPass(m_device, VK_FORMAT_R16G16B16A16_SFLOAT); + m_renderpasses[NeighborhoodBlending] = + CreateWrappedRenderPass(m_device, VK_FORMAT_R16G16B16A16_SFLOAT); + + for (auto& images : m_dynamic_images) { + images.framebuffers[EdgeDetection] = CreateWrappedFramebuffer( + m_device, m_renderpasses[EdgeDetection], images.image_views[Edges], m_extent); + + images.framebuffers[BlendingWeightCalculation] = + CreateWrappedFramebuffer(m_device, m_renderpasses[BlendingWeightCalculation], + images.image_views[Blend], m_extent); + + images.framebuffers[NeighborhoodBlending] = CreateWrappedFramebuffer( + m_device, m_renderpasses[NeighborhoodBlending], images.image_views[Output], m_extent); + } +} + +void SMAA::CreateSampler() { + m_sampler = CreateWrappedSampler(m_device); +} + +void SMAA::CreateShaders() { + // These match the order of the SMAAStage enum + static constexpr std::array vert_shader_sources{ + ARRAY_TO_SPAN(SMAA_EDGE_DETECTION_VERT_SPV), + ARRAY_TO_SPAN(SMAA_BLENDING_WEIGHT_CALCULATION_VERT_SPV), + ARRAY_TO_SPAN(SMAA_NEIGHBORHOOD_BLENDING_VERT_SPV), + }; + static constexpr std::array frag_shader_sources{ + ARRAY_TO_SPAN(SMAA_EDGE_DETECTION_FRAG_SPV), + ARRAY_TO_SPAN(SMAA_BLENDING_WEIGHT_CALCULATION_FRAG_SPV), + ARRAY_TO_SPAN(SMAA_NEIGHBORHOOD_BLENDING_FRAG_SPV), + }; + + for (size_t i = 0; i < MaxSMAAStage; i++) { + m_vertex_shaders[i] = CreateWrappedShaderModule(m_device, vert_shader_sources[i]); + m_fragment_shaders[i] = CreateWrappedShaderModule(m_device, frag_shader_sources[i]); + } +} + +void SMAA::CreateDescriptorPool() { + // Edge detection: 1 descriptor + // Blending weight calculation: 3 descriptors + // Neighborhood blending: 2 descriptors + + // 6 descriptors, 3 descriptor sets per image + m_descriptor_pool = CreateWrappedDescriptorPool(m_device, 6 * m_image_count, 3 * m_image_count); +} + +void SMAA::CreateDescriptorSetLayouts() { + m_descriptor_set_layouts[EdgeDetection] = + CreateWrappedDescriptorSetLayout(m_device, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER}); + m_descriptor_set_layouts[BlendingWeightCalculation] = + CreateWrappedDescriptorSetLayout(m_device, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER}); + m_descriptor_set_layouts[NeighborhoodBlending] = + CreateWrappedDescriptorSetLayout(m_device, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER}); +} + +void SMAA::CreateDescriptorSets() { + std::vector<VkDescriptorSetLayout> layouts(m_descriptor_set_layouts.size()); + std::ranges::transform(m_descriptor_set_layouts, layouts.begin(), + [](auto& layout) { return *layout; }); + + for (auto& images : m_dynamic_images) { + images.descriptor_sets = CreateWrappedDescriptorSets(m_descriptor_pool, layouts); + } +} + +void SMAA::CreatePipelineLayouts() { + for (size_t i = 0; i < MaxSMAAStage; i++) { + m_pipeline_layouts[i] = CreateWrappedPipelineLayout(m_device, m_descriptor_set_layouts[i]); + } +} + +void SMAA::CreatePipelines() { + for (size_t i = 0; i < MaxSMAAStage; i++) { + m_pipelines[i] = + CreateWrappedPipeline(m_device, m_renderpasses[i], m_pipeline_layouts[i], + std::tie(m_vertex_shaders[i], m_fragment_shaders[i])); + } +} + +void SMAA::UpdateDescriptorSets(VkImageView image_view, size_t image_index) { + Images& images = m_dynamic_images[image_index]; + std::vector<VkDescriptorImageInfo> image_infos; + std::vector<VkWriteDescriptorSet> updates; + image_infos.reserve(6); + + updates.push_back(CreateWriteDescriptorSet(image_infos, *m_sampler, image_view, + images.descriptor_sets[EdgeDetection], 0)); + + updates.push_back(CreateWriteDescriptorSet(image_infos, *m_sampler, *images.image_views[Edges], + images.descriptor_sets[BlendingWeightCalculation], + 0)); + updates.push_back(CreateWriteDescriptorSet(image_infos, *m_sampler, *m_static_image_views[Area], + images.descriptor_sets[BlendingWeightCalculation], + 1)); + updates.push_back( + CreateWriteDescriptorSet(image_infos, *m_sampler, *m_static_image_views[Search], + images.descriptor_sets[BlendingWeightCalculation], 2)); + + updates.push_back(CreateWriteDescriptorSet(image_infos, *m_sampler, image_view, + images.descriptor_sets[NeighborhoodBlending], 0)); + updates.push_back(CreateWriteDescriptorSet(image_infos, *m_sampler, *images.image_views[Blend], + images.descriptor_sets[NeighborhoodBlending], 1)); + + m_device.GetLogical().UpdateDescriptorSets(updates, {}); +} + +void SMAA::UploadImages(Scheduler& scheduler) { + if (m_images_ready) { + return; + } + + static constexpr VkExtent2D area_extent{AREATEX_WIDTH, AREATEX_HEIGHT}; + static constexpr VkExtent2D search_extent{SEARCHTEX_WIDTH, SEARCHTEX_HEIGHT}; + + UploadImage(m_device, m_allocator, scheduler, m_static_images[Area], area_extent, + VK_FORMAT_R8G8_UNORM, ARRAY_TO_SPAN(areaTexBytes)); + UploadImage(m_device, m_allocator, scheduler, m_static_images[Search], search_extent, + VK_FORMAT_R8_UNORM, ARRAY_TO_SPAN(searchTexBytes)); + + scheduler.Record([&](vk::CommandBuffer cmdbuf) { + for (auto& images : m_dynamic_images) { + for (size_t i = 0; i < MaxDynamicImage; i++) { + ClearColorImage(cmdbuf, *images.images[i]); + } + } + }); + scheduler.Finish(); + + m_images_ready = true; +} + +void SMAA::Draw(Scheduler& scheduler, size_t image_index, VkImage* inout_image, + VkImageView* inout_image_view) { + Images& images = m_dynamic_images[image_index]; + + VkImage input_image = *inout_image; + VkImage output_image = *images.images[Output]; + VkImage edges_image = *images.images[Edges]; + VkImage blend_image = *images.images[Blend]; + + VkDescriptorSet edge_detection_descriptor_set = images.descriptor_sets[EdgeDetection]; + VkDescriptorSet blending_weight_calculation_descriptor_set = + images.descriptor_sets[BlendingWeightCalculation]; + VkDescriptorSet neighborhood_blending_descriptor_set = + images.descriptor_sets[NeighborhoodBlending]; + + VkFramebuffer edge_detection_framebuffer = *images.framebuffers[EdgeDetection]; + VkFramebuffer blending_weight_calculation_framebuffer = + *images.framebuffers[BlendingWeightCalculation]; + VkFramebuffer neighborhood_blending_framebuffer = *images.framebuffers[NeighborhoodBlending]; + + UploadImages(scheduler); + UpdateDescriptorSets(*inout_image_view, image_index); + + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([=, this](vk::CommandBuffer cmdbuf) { + TransitionImageLayout(cmdbuf, input_image, VK_IMAGE_LAYOUT_GENERAL); + TransitionImageLayout(cmdbuf, edges_image, VK_IMAGE_LAYOUT_GENERAL); + BeginRenderPass(cmdbuf, *m_renderpasses[EdgeDetection], edge_detection_framebuffer, + m_extent); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, *m_pipelines[EdgeDetection]); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, + *m_pipeline_layouts[EdgeDetection], 0, + edge_detection_descriptor_set, {}); + cmdbuf.Draw(3, 1, 0, 0); + cmdbuf.EndRenderPass(); + + TransitionImageLayout(cmdbuf, edges_image, VK_IMAGE_LAYOUT_GENERAL); + TransitionImageLayout(cmdbuf, blend_image, VK_IMAGE_LAYOUT_GENERAL); + BeginRenderPass(cmdbuf, *m_renderpasses[BlendingWeightCalculation], + blending_weight_calculation_framebuffer, m_extent); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, + *m_pipelines[BlendingWeightCalculation]); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, + *m_pipeline_layouts[BlendingWeightCalculation], 0, + blending_weight_calculation_descriptor_set, {}); + cmdbuf.Draw(3, 1, 0, 0); + cmdbuf.EndRenderPass(); + + TransitionImageLayout(cmdbuf, blend_image, VK_IMAGE_LAYOUT_GENERAL); + TransitionImageLayout(cmdbuf, output_image, VK_IMAGE_LAYOUT_GENERAL); + BeginRenderPass(cmdbuf, *m_renderpasses[NeighborhoodBlending], + neighborhood_blending_framebuffer, m_extent); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, *m_pipelines[NeighborhoodBlending]); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, + *m_pipeline_layouts[NeighborhoodBlending], 0, + neighborhood_blending_descriptor_set, {}); + cmdbuf.Draw(3, 1, 0, 0); + cmdbuf.EndRenderPass(); + TransitionImageLayout(cmdbuf, output_image, VK_IMAGE_LAYOUT_GENERAL); + }); + + *inout_image = *images.images[Output]; + *inout_image_view = *images.image_views[Output]; +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/smaa.h b/src/video_core/renderer_vulkan/present/smaa.h new file mode 100644 index 000000000..fdf6def07 --- /dev/null +++ b/src/video_core/renderer_vulkan/present/smaa.h @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include "video_core/renderer_vulkan/present/anti_alias_pass.h" +#include "video_core/vulkan_common/vulkan_memory_allocator.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Vulkan { + +class Device; +class Scheduler; +class StagingBufferPool; + +class SMAA final : public AntiAliasPass { +public: + explicit SMAA(const Device& device, MemoryAllocator& allocator, size_t image_count, + VkExtent2D extent); + ~SMAA() override; + + void Draw(Scheduler& scheduler, size_t image_index, VkImage* inout_image, + VkImageView* inout_image_view) override; + +private: + enum SMAAStage { + EdgeDetection = 0, + BlendingWeightCalculation = 1, + NeighborhoodBlending = 2, + MaxSMAAStage = 3, + }; + + enum StaticImageType { + Area = 0, + Search = 1, + MaxStaticImage = 2, + }; + + enum DynamicImageType { + Blend = 0, + Edges = 1, + Output = 2, + MaxDynamicImage = 3, + }; + + void CreateImages(); + void CreateRenderPasses(); + void CreateSampler(); + void CreateShaders(); + void CreateDescriptorPool(); + void CreateDescriptorSetLayouts(); + void CreateDescriptorSets(); + void CreatePipelineLayouts(); + void CreatePipelines(); + void UpdateDescriptorSets(VkImageView image_view, size_t image_index); + void UploadImages(Scheduler& scheduler); + + const Device& m_device; + MemoryAllocator& m_allocator; + const VkExtent2D m_extent; + const u32 m_image_count; + + vk::DescriptorPool m_descriptor_pool{}; + std::array<vk::DescriptorSetLayout, MaxSMAAStage> m_descriptor_set_layouts{}; + std::array<vk::PipelineLayout, MaxSMAAStage> m_pipeline_layouts{}; + std::array<vk::ShaderModule, MaxSMAAStage> m_vertex_shaders{}; + std::array<vk::ShaderModule, MaxSMAAStage> m_fragment_shaders{}; + std::array<vk::Pipeline, MaxSMAAStage> m_pipelines{}; + std::array<vk::RenderPass, MaxSMAAStage> m_renderpasses{}; + + std::array<vk::Image, MaxStaticImage> m_static_images{}; + std::array<vk::ImageView, MaxStaticImage> m_static_image_views{}; + + struct Images { + vk::DescriptorSets descriptor_sets{}; + std::array<vk::Image, MaxDynamicImage> images{}; + std::array<vk::ImageView, MaxDynamicImage> image_views{}; + std::array<vk::Framebuffer, MaxSMAAStage> framebuffers{}; + }; + std::vector<Images> m_dynamic_images{}; + bool m_images_ready{}; + + vk::Sampler m_sampler{}; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/util.cpp b/src/video_core/renderer_vulkan/present/util.cpp new file mode 100644 index 000000000..6ee16595d --- /dev/null +++ b/src/video_core/renderer_vulkan/present/util.cpp @@ -0,0 +1,636 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/assert.h" +#include "common/polyfill_ranges.h" +#include "video_core/renderer_vulkan/present/util.h" + +namespace Vulkan { + +vk::Buffer CreateWrappedBuffer(MemoryAllocator& allocator, VkDeviceSize size, MemoryUsage usage) { + const VkBufferCreateInfo dst_buffer_info{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .size = size, + .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + }; + return allocator.CreateBuffer(dst_buffer_info, usage); +} + +vk::Image CreateWrappedImage(MemoryAllocator& allocator, VkExtent2D dimensions, VkFormat format) { + const VkImageCreateInfo image_ci{ + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .imageType = VK_IMAGE_TYPE_2D, + .format = format, + .extent = {.width = dimensions.width, .height = dimensions.height, .depth = 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_STORAGE_BIT | + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + }; + return allocator.CreateImage(image_ci); +} + +void TransitionImageLayout(vk::CommandBuffer& cmdbuf, VkImage image, VkImageLayout target_layout, + VkImageLayout source_layout) { + constexpr VkFlags flags{VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT}; + const VkImageMemoryBarrier barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = flags, + .dstAccessMask = flags, + .oldLayout = source_layout, + .newLayout = target_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + 0, barrier); +} + +void UploadImage(const Device& device, MemoryAllocator& allocator, Scheduler& scheduler, + vk::Image& image, VkExtent2D dimensions, VkFormat format, + std::span<const u8> initial_contents) { + const VkBufferCreateInfo upload_ci = { + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .size = initial_contents.size_bytes(), + .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + }; + auto upload_buffer = allocator.CreateBuffer(upload_ci, MemoryUsage::Upload); + std::ranges::copy(initial_contents, upload_buffer.Mapped().begin()); + upload_buffer.Flush(); + + const std::array<VkBufferImageCopy, 1> regions{{{ + .bufferOffset = 0, + .bufferRowLength = dimensions.width, + .bufferImageHeight = dimensions.height, + .imageSubresource{.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1}, + .imageOffset{}, + .imageExtent{.width = dimensions.width, .height = dimensions.height, .depth = 1}, + }}}; + + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([&](vk::CommandBuffer cmdbuf) { + TransitionImageLayout(cmdbuf, *image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_UNDEFINED); + cmdbuf.CopyBufferToImage(*upload_buffer, *image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + regions); + TransitionImageLayout(cmdbuf, *image, VK_IMAGE_LAYOUT_GENERAL, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + }); + scheduler.Finish(); +} + +void DownloadColorImage(vk::CommandBuffer& cmdbuf, VkImage image, VkBuffer buffer, + VkExtent3D extent) { + const VkImageMemoryBarrier read_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + const VkImageMemoryBarrier image_write_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = 0, + .dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + static constexpr VkMemoryBarrier memory_write_barrier{ + .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, + .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, + }; + const VkBufferImageCopy copy{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1, + }, + .imageOffset{.x = 0, .y = 0, .z = 0}, + .imageExtent{extent}, + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, + read_barrier); + cmdbuf.CopyImageToBuffer(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buffer, copy); + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, + memory_write_barrier, nullptr, image_write_barrier); +} + +vk::ImageView CreateWrappedImageView(const Device& device, vk::Image& image, VkFormat format) { + return device.GetLogical().CreateImageView(VkImageViewCreateInfo{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .image = *image, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = format, + .components{}, + .subresourceRange{.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}, + }); +} + +vk::RenderPass CreateWrappedRenderPass(const Device& device, VkFormat format, + VkImageLayout initial_layout) { + const VkAttachmentDescription attachment{ + .flags = VK_ATTACHMENT_DESCRIPTION_MAY_ALIAS_BIT, + .format = format, + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = initial_layout == VK_IMAGE_LAYOUT_UNDEFINED ? VK_ATTACHMENT_LOAD_OP_DONT_CARE + : VK_ATTACHMENT_LOAD_OP_LOAD, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE, + .initialLayout = initial_layout, + .finalLayout = VK_IMAGE_LAYOUT_GENERAL, + }; + + constexpr VkAttachmentReference color_attachment_ref{ + .attachment = 0, + .layout = VK_IMAGE_LAYOUT_GENERAL, + }; + + const VkSubpassDescription subpass_description{ + .flags = 0, + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .inputAttachmentCount = 0, + .pInputAttachments = nullptr, + .colorAttachmentCount = 1, + .pColorAttachments = &color_attachment_ref, + .pResolveAttachments = nullptr, + .pDepthStencilAttachment = nullptr, + .preserveAttachmentCount = 0, + .pPreserveAttachments = nullptr, + }; + + constexpr VkSubpassDependency dependency{ + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = 0, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dependencyFlags = 0, + }; + + return device.GetLogical().CreateRenderPass(VkRenderPassCreateInfo{ + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .attachmentCount = 1, + .pAttachments = &attachment, + .subpassCount = 1, + .pSubpasses = &subpass_description, + .dependencyCount = 1, + .pDependencies = &dependency, + }); +} + +vk::Framebuffer CreateWrappedFramebuffer(const Device& device, vk::RenderPass& render_pass, + vk::ImageView& dest_image, VkExtent2D extent) { + return device.GetLogical().CreateFramebuffer(VkFramebufferCreateInfo{ + .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .renderPass = *render_pass, + .attachmentCount = 1, + .pAttachments = dest_image.address(), + .width = extent.width, + .height = extent.height, + .layers = 1, + }); +} + +vk::Sampler CreateWrappedSampler(const Device& device, VkFilter filter) { + return device.GetLogical().CreateSampler(VkSamplerCreateInfo{ + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .magFilter = filter, + .minFilter = filter, + .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .mipLodBias = 0.0f, + .anisotropyEnable = VK_FALSE, + .maxAnisotropy = 0.0f, + .compareEnable = VK_FALSE, + .compareOp = VK_COMPARE_OP_NEVER, + .minLod = 0.0f, + .maxLod = 0.0f, + .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK, + .unnormalizedCoordinates = VK_FALSE, + }); +} + +vk::ShaderModule CreateWrappedShaderModule(const Device& device, std::span<const u32> code) { + return device.GetLogical().CreateShaderModule(VkShaderModuleCreateInfo{ + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .codeSize = code.size_bytes(), + .pCode = code.data(), + }); +} + +vk::DescriptorPool CreateWrappedDescriptorPool(const Device& device, size_t max_descriptors, + size_t max_sets, + std::initializer_list<VkDescriptorType> types) { + std::vector<VkDescriptorPoolSize> pool_sizes(types.size()); + for (u32 i = 0; i < types.size(); i++) { + pool_sizes[i] = VkDescriptorPoolSize{ + .type = std::data(types)[i], + .descriptorCount = static_cast<u32>(max_descriptors), + }; + } + + return device.GetLogical().CreateDescriptorPool(VkDescriptorPoolCreateInfo{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .maxSets = static_cast<u32>(max_sets), + .poolSizeCount = static_cast<u32>(pool_sizes.size()), + .pPoolSizes = pool_sizes.data(), + }); +} + +vk::DescriptorSetLayout CreateWrappedDescriptorSetLayout( + const Device& device, std::initializer_list<VkDescriptorType> types) { + std::vector<VkDescriptorSetLayoutBinding> bindings(types.size()); + for (size_t i = 0; i < types.size(); i++) { + bindings[i] = { + .binding = static_cast<u32>(i), + .descriptorType = std::data(types)[i], + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, + .pImmutableSamplers = nullptr, + }; + } + + return device.GetLogical().CreateDescriptorSetLayout(VkDescriptorSetLayoutCreateInfo{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .bindingCount = static_cast<u32>(bindings.size()), + .pBindings = bindings.data(), + }); +} + +vk::DescriptorSets CreateWrappedDescriptorSets(vk::DescriptorPool& pool, + vk::Span<VkDescriptorSetLayout> layouts) { + return pool.Allocate(VkDescriptorSetAllocateInfo{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .pNext = nullptr, + .descriptorPool = *pool, + .descriptorSetCount = layouts.size(), + .pSetLayouts = layouts.data(), + }); +} + +vk::PipelineLayout CreateWrappedPipelineLayout(const Device& device, + vk::DescriptorSetLayout& layout) { + return device.GetLogical().CreatePipelineLayout(VkPipelineLayoutCreateInfo{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .setLayoutCount = 1, + .pSetLayouts = layout.address(), + .pushConstantRangeCount = 0, + .pPushConstantRanges = nullptr, + }); +} + +vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderpass, + vk::PipelineLayout& layout, + std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders, + bool enable_blending) { + const std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages{{ + { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stage = VK_SHADER_STAGE_VERTEX_BIT, + .module = *std::get<0>(shaders), + .pName = "main", + .pSpecializationInfo = nullptr, + }, + { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stage = VK_SHADER_STAGE_FRAGMENT_BIT, + .module = *std::get<1>(shaders), + .pName = "main", + .pSpecializationInfo = nullptr, + }, + }}; + + constexpr VkPipelineVertexInputStateCreateInfo vertex_input_ci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .vertexBindingDescriptionCount = 0, + .pVertexBindingDescriptions = nullptr, + .vertexAttributeDescriptionCount = 0, + .pVertexAttributeDescriptions = nullptr, + }; + + constexpr VkPipelineInputAssemblyStateCreateInfo input_assembly_ci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, + .primitiveRestartEnable = VK_FALSE, + }; + + constexpr VkPipelineViewportStateCreateInfo viewport_state_ci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .viewportCount = 1, + .pViewports = nullptr, + .scissorCount = 1, + .pScissors = nullptr, + }; + + constexpr VkPipelineRasterizationStateCreateInfo rasterization_ci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .depthClampEnable = VK_FALSE, + .rasterizerDiscardEnable = VK_FALSE, + .polygonMode = VK_POLYGON_MODE_FILL, + .cullMode = VK_CULL_MODE_NONE, + .frontFace = VK_FRONT_FACE_CLOCKWISE, + .depthBiasEnable = VK_FALSE, + .depthBiasConstantFactor = 0.0f, + .depthBiasClamp = 0.0f, + .depthBiasSlopeFactor = 0.0f, + .lineWidth = 1.0f, + }; + + constexpr VkPipelineMultisampleStateCreateInfo multisampling_ci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, + .sampleShadingEnable = VK_FALSE, + .minSampleShading = 0.0f, + .pSampleMask = nullptr, + .alphaToCoverageEnable = VK_FALSE, + .alphaToOneEnable = VK_FALSE, + }; + + constexpr VkPipelineColorBlendAttachmentState color_blend_attachment_disabled{ + .blendEnable = VK_FALSE, + .srcColorBlendFactor = VK_BLEND_FACTOR_ZERO, + .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO, + .colorBlendOp = VK_BLEND_OP_ADD, + .srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, + .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, + .alphaBlendOp = VK_BLEND_OP_ADD, + .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, + }; + + constexpr VkPipelineColorBlendAttachmentState color_blend_attachment_enabled{ + .blendEnable = VK_TRUE, + .srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA, + .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + .colorBlendOp = VK_BLEND_OP_ADD, + .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, + .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, + .alphaBlendOp = VK_BLEND_OP_ADD, + .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, + }; + + const VkPipelineColorBlendStateCreateInfo color_blend_ci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .logicOpEnable = VK_FALSE, + .logicOp = VK_LOGIC_OP_COPY, + .attachmentCount = 1, + .pAttachments = + enable_blending ? &color_blend_attachment_enabled : &color_blend_attachment_disabled, + .blendConstants = {0.0f, 0.0f, 0.0f, 0.0f}, + }; + + constexpr std::array dynamic_states{ + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR, + }; + + const VkPipelineDynamicStateCreateInfo dynamic_state_ci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .dynamicStateCount = static_cast<u32>(dynamic_states.size()), + .pDynamicStates = dynamic_states.data(), + }; + + return device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{ + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stageCount = static_cast<u32>(shader_stages.size()), + .pStages = shader_stages.data(), + .pVertexInputState = &vertex_input_ci, + .pInputAssemblyState = &input_assembly_ci, + .pTessellationState = nullptr, + .pViewportState = &viewport_state_ci, + .pRasterizationState = &rasterization_ci, + .pMultisampleState = &multisampling_ci, + .pDepthStencilState = nullptr, + .pColorBlendState = &color_blend_ci, + .pDynamicState = &dynamic_state_ci, + .layout = *layout, + .renderPass = *renderpass, + .subpass = 0, + .basePipelineHandle = 0, + .basePipelineIndex = 0, + }); +} + +VkWriteDescriptorSet CreateWriteDescriptorSet(std::vector<VkDescriptorImageInfo>& images, + VkSampler sampler, VkImageView view, + VkDescriptorSet set, u32 binding) { + ASSERT(images.capacity() > images.size()); + auto& image_info = images.emplace_back(VkDescriptorImageInfo{ + .sampler = sampler, + .imageView = view, + .imageLayout = VK_IMAGE_LAYOUT_GENERAL, + }); + + return VkWriteDescriptorSet{ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .pNext = nullptr, + .dstSet = set, + .dstBinding = binding, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = &image_info, + .pBufferInfo = nullptr, + .pTexelBufferView = nullptr, + }; +} + +vk::Sampler CreateBilinearSampler(const Device& device) { + const VkSamplerCreateInfo ci{ + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .magFilter = VK_FILTER_LINEAR, + .minFilter = VK_FILTER_LINEAR, + .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, + .mipLodBias = 0.0f, + .anisotropyEnable = VK_FALSE, + .maxAnisotropy = 0.0f, + .compareEnable = VK_FALSE, + .compareOp = VK_COMPARE_OP_NEVER, + .minLod = 0.0f, + .maxLod = 0.0f, + .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK, + .unnormalizedCoordinates = VK_FALSE, + }; + + return device.GetLogical().CreateSampler(ci); +} + +vk::Sampler CreateNearestNeighborSampler(const Device& device) { + const VkSamplerCreateInfo ci_nn{ + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .magFilter = VK_FILTER_NEAREST, + .minFilter = VK_FILTER_NEAREST, + .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, + .mipLodBias = 0.0f, + .anisotropyEnable = VK_FALSE, + .maxAnisotropy = 0.0f, + .compareEnable = VK_FALSE, + .compareOp = VK_COMPARE_OP_NEVER, + .minLod = 0.0f, + .maxLod = 0.0f, + .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK, + .unnormalizedCoordinates = VK_FALSE, + }; + + return device.GetLogical().CreateSampler(ci_nn); +} + +void ClearColorImage(vk::CommandBuffer& cmdbuf, VkImage image) { + static constexpr std::array<VkImageSubresourceRange, 1> subresources{{{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }}}; + TransitionImageLayout(cmdbuf, image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_UNDEFINED); + cmdbuf.ClearColorImage(image, VK_IMAGE_LAYOUT_GENERAL, {}, subresources); +} + +void BeginRenderPass(vk::CommandBuffer& cmdbuf, VkRenderPass render_pass, VkFramebuffer framebuffer, + VkExtent2D extent) { + const VkRenderPassBeginInfo renderpass_bi{ + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .pNext = nullptr, + .renderPass = render_pass, + .framebuffer = framebuffer, + .renderArea{ + .offset{}, + .extent = extent, + }, + .clearValueCount = 0, + .pClearValues = nullptr, + }; + cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE); + + const VkViewport viewport{ + .x = 0.0f, + .y = 0.0f, + .width = static_cast<float>(extent.width), + .height = static_cast<float>(extent.height), + .minDepth = 0.0f, + .maxDepth = 1.0f, + }; + const VkRect2D scissor{ + .offset = {0, 0}, + .extent = extent, + }; + cmdbuf.SetViewport(0, viewport); + cmdbuf.SetScissor(0, scissor); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/util.h b/src/video_core/renderer_vulkan/present/util.h new file mode 100644 index 000000000..1104aaa15 --- /dev/null +++ b/src/video_core/renderer_vulkan/present/util.h @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/vulkan_common/vulkan_memory_allocator.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Vulkan { + +#define ARRAY_TO_SPAN(a) std::span(a, (sizeof(a) / sizeof(a[0]))) + +vk::Buffer CreateWrappedBuffer(MemoryAllocator& allocator, VkDeviceSize size, MemoryUsage usage); + +vk::Image CreateWrappedImage(MemoryAllocator& allocator, VkExtent2D dimensions, VkFormat format); +void TransitionImageLayout(vk::CommandBuffer& cmdbuf, VkImage image, VkImageLayout target_layout, + VkImageLayout source_layout = VK_IMAGE_LAYOUT_GENERAL); +void UploadImage(const Device& device, MemoryAllocator& allocator, Scheduler& scheduler, + vk::Image& image, VkExtent2D dimensions, VkFormat format, + std::span<const u8> initial_contents = {}); +void DownloadColorImage(vk::CommandBuffer& cmdbuf, VkImage image, VkBuffer buffer, + VkExtent3D extent); +void ClearColorImage(vk::CommandBuffer& cmdbuf, VkImage image); + +vk::ImageView CreateWrappedImageView(const Device& device, vk::Image& image, VkFormat format); +vk::RenderPass CreateWrappedRenderPass(const Device& device, VkFormat format, + VkImageLayout initial_layout = VK_IMAGE_LAYOUT_GENERAL); +vk::Framebuffer CreateWrappedFramebuffer(const Device& device, vk::RenderPass& render_pass, + vk::ImageView& dest_image, VkExtent2D extent); +vk::Sampler CreateWrappedSampler(const Device& device, VkFilter filter = VK_FILTER_LINEAR); +vk::ShaderModule CreateWrappedShaderModule(const Device& device, std::span<const u32> code); +vk::DescriptorPool CreateWrappedDescriptorPool(const Device& device, size_t max_descriptors, + size_t max_sets, + std::initializer_list<VkDescriptorType> types = { + VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER}); +vk::DescriptorSetLayout CreateWrappedDescriptorSetLayout( + const Device& device, std::initializer_list<VkDescriptorType> types); +vk::DescriptorSets CreateWrappedDescriptorSets(vk::DescriptorPool& pool, + vk::Span<VkDescriptorSetLayout> layouts); +vk::PipelineLayout CreateWrappedPipelineLayout(const Device& device, + vk::DescriptorSetLayout& layout); +vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderpass, + vk::PipelineLayout& layout, + std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders, + bool enable_blending = false); +VkWriteDescriptorSet CreateWriteDescriptorSet(std::vector<VkDescriptorImageInfo>& images, + VkSampler sampler, VkImageView view, + VkDescriptorSet set, u32 binding); +vk::Sampler CreateBilinearSampler(const Device& device); +vk::Sampler CreateNearestNeighborSampler(const Device& device); + +void BeginRenderPass(vk::CommandBuffer& cmdbuf, VkRenderPass render_pass, VkFramebuffer framebuffer, + VkExtent2D extent); + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp b/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp new file mode 100644 index 000000000..c5db0230d --- /dev/null +++ b/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp @@ -0,0 +1,137 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/frontend/framebuffer_layout.h" +#include "video_core/framebuffer_config.h" +#include "video_core/host_shaders/vulkan_present_vert_spv.h" +#include "video_core/renderer_vulkan/present/layer.h" +#include "video_core/renderer_vulkan/present/present_push_constants.h" +#include "video_core/renderer_vulkan/present/util.h" +#include "video_core/renderer_vulkan/present/window_adapt_pass.h" +#include "video_core/renderer_vulkan/vk_present_manager.h" +#include "video_core/renderer_vulkan/vk_shader_util.h" +#include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/vulkan_common/vulkan_memory_allocator.h" + +namespace Vulkan { + +WindowAdaptPass::WindowAdaptPass(const Device& device_, VkFormat frame_format, + vk::Sampler&& sampler_, vk::ShaderModule&& fragment_shader_) + : device(device_), sampler(std::move(sampler_)), fragment_shader(std::move(fragment_shader_)) { + CreateDescriptorSetLayout(); + CreatePipelineLayout(); + CreateVertexShader(); + CreateRenderPass(frame_format); + CreatePipeline(); +} + +WindowAdaptPass::~WindowAdaptPass() = default; + +void WindowAdaptPass::Draw(RasterizerVulkan& rasterizer, Scheduler& scheduler, size_t image_index, + std::list<Layer>& layers, + std::span<const Tegra::FramebufferConfig> configs, + const Layout::FramebufferLayout& layout, Frame* dst) { + + const VkFramebuffer host_framebuffer{*dst->framebuffer}; + const VkRenderPass renderpass{*render_pass}; + const VkPipeline graphics_pipeline{*pipeline}; + const VkPipelineLayout graphics_pipeline_layout{*pipeline_layout}; + const VkExtent2D render_area{ + .width = dst->width, + .height = dst->height, + }; + + const size_t layer_count = configs.size(); + std::vector<PresentPushConstants> push_constants(layer_count); + std::vector<VkDescriptorSet> descriptor_sets(layer_count); + + auto layer_it = layers.begin(); + for (size_t i = 0; i < layer_count; i++) { + layer_it->ConfigureDraw(&push_constants[i], &descriptor_sets[i], rasterizer, *sampler, + image_index, configs[i], layout); + layer_it++; + } + + scheduler.Record([=](vk::CommandBuffer cmdbuf) { + const f32 bg_red = Settings::values.bg_red.GetValue() / 255.0f; + const f32 bg_green = Settings::values.bg_green.GetValue() / 255.0f; + const f32 bg_blue = Settings::values.bg_blue.GetValue() / 255.0f; + const VkClearAttachment clear_attachment{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .colorAttachment = 0, + .clearValue = + { + .color = {.float32 = {bg_red, bg_green, bg_blue, 1.0f}}, + }, + }; + const VkClearRect clear_rect{ + .rect = + { + .offset = {0, 0}, + .extent = render_area, + }, + .baseArrayLayer = 0, + .layerCount = 1, + }; + + BeginRenderPass(cmdbuf, renderpass, host_framebuffer, render_area); + cmdbuf.ClearAttachments({clear_attachment}, {clear_rect}); + + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline); + for (size_t i = 0; i < layer_count; i++) { + cmdbuf.PushConstants(graphics_pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, + push_constants[i]); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline_layout, 0, + descriptor_sets[i], {}); + cmdbuf.Draw(4, 1, 0, 0); + } + + cmdbuf.EndRenderPass(); + }); +} + +VkDescriptorSetLayout WindowAdaptPass::GetDescriptorSetLayout() { + return *descriptor_set_layout; +} + +VkRenderPass WindowAdaptPass::GetRenderPass() { + return *render_pass; +} + +void WindowAdaptPass::CreateDescriptorSetLayout() { + descriptor_set_layout = + CreateWrappedDescriptorSetLayout(device, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER}); +} + +void WindowAdaptPass::CreatePipelineLayout() { + const VkPushConstantRange range{ + .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, + .offset = 0, + .size = sizeof(PresentPushConstants), + }; + + pipeline_layout = device.GetLogical().CreatePipelineLayout(VkPipelineLayoutCreateInfo{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .setLayoutCount = 1, + .pSetLayouts = descriptor_set_layout.address(), + .pushConstantRangeCount = 1, + .pPushConstantRanges = &range, + }); +} + +void WindowAdaptPass::CreateVertexShader() { + vertex_shader = BuildShader(device, VULKAN_PRESENT_VERT_SPV); +} + +void WindowAdaptPass::CreateRenderPass(VkFormat frame_format) { + render_pass = CreateWrappedRenderPass(device, frame_format, VK_IMAGE_LAYOUT_UNDEFINED); +} + +void WindowAdaptPass::CreatePipeline() { + pipeline = CreateWrappedPipeline(device, render_pass, pipeline_layout, + std::tie(vertex_shader, fragment_shader), false); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/window_adapt_pass.h b/src/video_core/renderer_vulkan/present/window_adapt_pass.h new file mode 100644 index 000000000..0e2edfc31 --- /dev/null +++ b/src/video_core/renderer_vulkan/present/window_adapt_pass.h @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <list> + +#include "common/math_util.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Layout { +struct FramebufferLayout; +} + +namespace Tegra { +struct FramebufferConfig; +} + +namespace Vulkan { + +class Device; +struct Frame; +class Layer; +class Scheduler; +class RasterizerVulkan; + +class WindowAdaptPass final { +public: + explicit WindowAdaptPass(const Device& device, VkFormat frame_format, vk::Sampler&& sampler, + vk::ShaderModule&& fragment_shader); + ~WindowAdaptPass(); + + void Draw(RasterizerVulkan& rasterizer, Scheduler& scheduler, size_t image_index, + std::list<Layer>& layers, std::span<const Tegra::FramebufferConfig> configs, + const Layout::FramebufferLayout& layout, Frame* dst); + + VkDescriptorSetLayout GetDescriptorSetLayout(); + VkRenderPass GetRenderPass(); + +private: + void CreateDescriptorSetLayout(); + void CreatePipelineLayout(); + void CreateVertexShader(); + void CreateRenderPass(VkFormat frame_format); + void CreatePipeline(); + +private: + const Device& device; + vk::DescriptorSetLayout descriptor_set_layout; + vk::PipelineLayout pipeline_layout; + vk::Sampler sampler; + vk::ShaderModule vertex_shader; + vk::ShaderModule fragment_shader; + vk::RenderPass render_pass; + vk::Pipeline pipeline; +}; + +} // namespace Vulkan |