From 1092295f2aeec32cee1aa6ad9120fe73dcf3d138 Mon Sep 17 00:00:00 2001 From: jbm11208 <81182113+jbm11208@users.noreply.github.com> Date: Fri, 20 Feb 2026 17:39:04 -0500 Subject: [PATCH] Fix Shadow Rendering / Texture Filtering (#1675) * video_core/renderer_vulkan: Add texture filtering * Fix Shadow Rendering (again...) * Make individual image views per res scale * Refactor texture runtime * Fix some magic numbers * More fixes and filter pipeline cache. * Refactor Surface and Handle move and destructor --------- Co-authored-by: PabloMK7 --- src/common/common_funcs.h | 46 ++ .../rasterizer_cache/pixel_format.h | 9 +- .../renderer_vulkan/vk_blit_helper.cpp | 385 +++++++++++++- .../renderer_vulkan/vk_blit_helper.h | 36 +- .../renderer_vulkan/vk_rasterizer.cpp | 6 +- .../renderer_vulkan/vk_render_manager.cpp | 20 +- .../renderer_vulkan/vk_render_manager.h | 7 +- .../renderer_vulkan/vk_resource_pool.cpp | 5 +- .../renderer_vulkan/vk_texture_runtime.cpp | 486 +++++++++--------- .../renderer_vulkan/vk_texture_runtime.h | 146 ++++-- 10 files changed, 835 insertions(+), 311 deletions(-) diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h index 8f109a8d3..0de82b040 100644 --- a/src/common/common_funcs.h +++ b/src/common/common_funcs.h @@ -1,3 +1,7 @@ +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + // Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -102,3 +106,45 @@ __declspec(dllimport) void __stdcall DebugBreak(void); using T = std::underlying_type_t; \ return static_cast(key) == 0; \ } + +#define DECLARE_ENUM_ARITHMETIC_OPERATORS(type) \ + [[nodiscard]] constexpr type operator+(type a, type b) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) + static_cast(b)); \ + } \ + [[nodiscard]] constexpr type operator-(type a, type b) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) - static_cast(b)); \ + } \ + [[nodiscard]] constexpr type operator*(type a, type b) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) * static_cast(b)); \ + } \ + [[nodiscard]] constexpr type operator/(type a, type b) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) / static_cast(b)); \ + } \ + [[nodiscard]] constexpr type operator%(type a, type b) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) % static_cast(b)); \ + } \ + constexpr type& operator+=(type& a, type b) noexcept { \ + a = a + b; \ + return a; \ + } \ + constexpr type& operator-=(type& a, type b) noexcept { \ + a = a - b; \ + return a; \ + } \ + constexpr type& operator*=(type& a, type b) noexcept { \ + a = a * b; \ + return a; \ + } \ + constexpr type& operator/=(type& a, type b) noexcept { \ + a = a / b; \ + return a; \ + } \ + constexpr type& operator%=(type& a, type b) noexcept { \ + a = a % b; \ + return a; \ + } diff --git a/src/video_core/rasterizer_cache/pixel_format.h b/src/video_core/rasterizer_cache/pixel_format.h index 06e31ca46..51c5d6480 100644 --- a/src/video_core/rasterizer_cache/pixel_format.h +++ b/src/video_core/rasterizer_cache/pixel_format.h @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -30,13 +30,20 @@ enum class PixelFormat : u32 { A4 = 11, ETC1 = 12, ETC1A4 = 13, + D16 = 14, D24 = 16, D24S8 = 17, + MaxPixelFormat = 18, + + NumColorFormat = (ETC1A4 - RGBA8) + 1, + NumDepthFormat = (D24S8 - D16) + 1, + Invalid = std::numeric_limits::max(), }; constexpr std::size_t PIXEL_FORMAT_COUNT = static_cast(PixelFormat::MaxPixelFormat); +DECLARE_ENUM_ARITHMETIC_OPERATORS(PixelFormat) enum class SurfaceType : u32 { Color = 0, diff --git a/src/video_core/renderer_vulkan/vk_blit_helper.cpp b/src/video_core/renderer_vulkan/vk_blit_helper.cpp index 0a7a3be44..3052082f7 100644 --- a/src/video_core/renderer_vulkan/vk_blit_helper.cpp +++ b/src/video_core/renderer_vulkan/vk_blit_helper.cpp @@ -1,7 +1,9 @@ -// Copyright 2022 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/hash.h" +#include "common/settings.h" #include "common/vector_math.h" #include "video_core/renderer_vulkan/vk_blit_helper.h" #include "video_core/renderer_vulkan/vk_descriptor_update_queue.h" @@ -16,8 +18,19 @@ #include "video_core/host_shaders/vulkan_blit_depth_stencil_frag.h" #include "video_core/host_shaders/vulkan_depth_to_buffer_comp.h" +// Texture filtering shader includes +#include "video_core/host_shaders/texture_filtering/bicubic_frag.h" +#include "video_core/host_shaders/texture_filtering/mmpx_frag.h" +#include "video_core/host_shaders/texture_filtering/refine_frag.h" +#include "video_core/host_shaders/texture_filtering/scale_force_frag.h" +#include "video_core/host_shaders/texture_filtering/x_gradient_frag.h" +#include "video_core/host_shaders/texture_filtering/xbrz_freescale_frag.h" +#include "video_core/host_shaders/texture_filtering/y_gradient_frag.h" +#include "vk_blit_helper.h" + namespace Vulkan { +using Settings::TextureFilter; using VideoCore::PixelFormat; namespace { @@ -55,8 +68,33 @@ constexpr std::array TWO_TEXTURES_BINDINGS = {1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}, }}; +// Texture filtering descriptor set bindings +constexpr std::array SINGLE_TEXTURE_BINDINGS = {{ + {0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}, +}}; + +constexpr std::array THREE_TEXTURES_BINDINGS = {{ + {0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}, + {1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}, + {2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}, +}}; + +// Note: Removed FILTER_UTILITY_BINDINGS as texture filtering doesn't need shadow buffers + +// Push constant structure for texture filtering +struct FilterPushConstants { + std::array tex_scale; + std::array tex_offset; + float res_scale; // For xBRZ filter +}; + +inline constexpr vk::PushConstantRange FILTER_PUSH_CONSTANT_RANGE{ + .stageFlags = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, + .offset = 0, + .size = sizeof(FilterPushConstants), +}; inline constexpr vk::PushConstantRange PUSH_CONSTANT_RANGE{ - .stageFlags = vk::ShaderStageFlagBits::eVertex, + .stageFlags = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, .offset = 0, .size = sizeof(PushConstants), }; @@ -104,12 +142,17 @@ constexpr vk::PipelineDynamicStateCreateInfo PIPELINE_DYNAMIC_STATE_CREATE_INFO{ .dynamicStateCount = static_cast(DYNAMIC_STATES.size()), .pDynamicStates = DYNAMIC_STATES.data(), }; -constexpr vk::PipelineColorBlendStateCreateInfo PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO{ + +constexpr vk::PipelineColorBlendAttachmentState COLOR_BLEND_ATTACHMENT{ + .blendEnable = VK_FALSE, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | + vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA, +}; + +constexpr vk::PipelineColorBlendStateCreateInfo PIPELINE_COLOR_BLEND_STATE_CREATE_INFO{ .logicOpEnable = VK_FALSE, - .logicOp = vk::LogicOp::eClear, - .attachmentCount = 0, - .pAttachments = nullptr, - .blendConstants = std::array{0.0f, 0.0f, 0.0f, 0.0f}, + .attachmentCount = 1, + .pAttachments = &COLOR_BLEND_ATTACHMENT, }; constexpr vk::PipelineDepthStencilStateCreateInfo PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO{ .depthTestEnable = VK_TRUE, @@ -128,9 +171,9 @@ inline constexpr vk::SamplerCreateInfo SAMPLER_CREATE_INFO{ .magFilter = filter, .minFilter = filter, .mipmapMode = vk::SamplerMipmapMode::eNearest, - .addressModeU = vk::SamplerAddressMode::eClampToBorder, - .addressModeV = vk::SamplerAddressMode::eClampToBorder, - .addressModeW = vk::SamplerAddressMode::eClampToBorder, + .addressModeU = vk::SamplerAddressMode::eClampToEdge, + .addressModeV = vk::SamplerAddressMode::eClampToEdge, + .addressModeW = vk::SamplerAddressMode::eClampToEdge, .mipLodBias = 0.0f, .anisotropyEnable = VK_FALSE, .maxAnisotropy = 0.0f, @@ -143,12 +186,14 @@ inline constexpr vk::SamplerCreateInfo SAMPLER_CREATE_INFO{ }; constexpr vk::PipelineLayoutCreateInfo PipelineLayoutCreateInfo( - const vk::DescriptorSetLayout* set_layout, bool compute = false) { + const vk::DescriptorSetLayout* set_layout, bool compute = false, bool filter = false) { return vk::PipelineLayoutCreateInfo{ .setLayoutCount = 1, .pSetLayouts = set_layout, .pushConstantRangeCount = 1, - .pPushConstantRanges = (compute ? &COMPUTE_PUSH_CONSTANT_RANGE : &PUSH_CONSTANT_RANGE), + .pPushConstantRanges = + (compute ? &COMPUTE_PUSH_CONSTANT_RANGE + : (filter ? &FILTER_PUSH_CONSTANT_RANGE : &PUSH_CONSTANT_RANGE)), }; } @@ -185,12 +230,20 @@ BlitHelper::BlitHelper(const Instance& instance_, Scheduler& scheduler_, compute_provider{instance, scheduler.GetMasterSemaphore(), COMPUTE_BINDINGS}, compute_buffer_provider{instance, scheduler.GetMasterSemaphore(), COMPUTE_BUFFER_BINDINGS}, two_textures_provider{instance, scheduler.GetMasterSemaphore(), TWO_TEXTURES_BINDINGS, 16}, + single_texture_provider{instance, scheduler.GetMasterSemaphore(), SINGLE_TEXTURE_BINDINGS, + 16}, + three_textures_provider{instance, scheduler.GetMasterSemaphore(), THREE_TEXTURES_BINDINGS, + 16}, compute_pipeline_layout{ device.createPipelineLayout(PipelineLayoutCreateInfo(&compute_provider.Layout(), true))}, compute_buffer_pipeline_layout{device.createPipelineLayout( PipelineLayoutCreateInfo(&compute_buffer_provider.Layout(), true))}, two_textures_pipeline_layout{ device.createPipelineLayout(PipelineLayoutCreateInfo(&two_textures_provider.Layout()))}, + single_texture_pipeline_layout{device.createPipelineLayout( + PipelineLayoutCreateInfo(&single_texture_provider.Layout(), false, true))}, + three_textures_pipeline_layout{device.createPipelineLayout( + PipelineLayoutCreateInfo(&three_textures_provider.Layout(), false, true))}, full_screen_vert{Compile(HostShaders::FULL_SCREEN_TRIANGLE_VERT, vk::ShaderStageFlagBits::eVertex, device)}, d24s8_to_rgba8_comp{Compile(HostShaders::VULKAN_D24S8_TO_RGBA8_COMP, @@ -199,6 +252,14 @@ BlitHelper::BlitHelper(const Instance& instance_, Scheduler& scheduler_, vk::ShaderStageFlagBits::eCompute, device)}, blit_depth_stencil_frag{Compile(HostShaders::VULKAN_BLIT_DEPTH_STENCIL_FRAG, vk::ShaderStageFlagBits::eFragment, device)}, + // Texture filtering shader modules + bicubic_frag{Compile(HostShaders::BICUBIC_FRAG, vk::ShaderStageFlagBits::eFragment, device)}, + scale_force_frag{ + Compile(HostShaders::SCALE_FORCE_FRAG, vk::ShaderStageFlagBits::eFragment, device)}, + xbrz_frag{ + Compile(HostShaders::XBRZ_FREESCALE_FRAG, vk::ShaderStageFlagBits::eFragment, device)}, + mmpx_frag{Compile(HostShaders::MMPX_FRAG, vk::ShaderStageFlagBits::eFragment, device)}, + refine_frag{Compile(HostShaders::REFINE_FRAG, vk::ShaderStageFlagBits::eFragment, device)}, d24s8_to_rgba8_pipeline{MakeComputePipeline(d24s8_to_rgba8_comp, compute_pipeline_layout)}, depth_to_buffer_pipeline{ MakeComputePipeline(depth_to_buffer_comp, compute_buffer_pipeline_layout)}, @@ -212,6 +273,10 @@ BlitHelper::BlitHelper(const Instance& instance_, Scheduler& scheduler_, "BlitHelper: compute_buffer_pipeline_layout"); SetObjectName(device, two_textures_pipeline_layout, "BlitHelper: two_textures_pipeline_layout"); + SetObjectName(device, single_texture_pipeline_layout, + "BlitHelper: single_texture_pipeline_layout"); + SetObjectName(device, three_textures_pipeline_layout, + "BlitHelper: three_textures_pipeline_layout"); SetObjectName(device, full_screen_vert, "BlitHelper: full_screen_vert"); SetObjectName(device, d24s8_to_rgba8_comp, "BlitHelper: d24s8_to_rgba8_comp"); SetObjectName(device, depth_to_buffer_comp, "BlitHelper: depth_to_buffer_comp"); @@ -227,13 +292,25 @@ BlitHelper::BlitHelper(const Instance& instance_, Scheduler& scheduler_, } BlitHelper::~BlitHelper() { + for (const auto& [_, pipeline] : filter_pipeline_cache) { + device.destroyPipeline(pipeline); + } + filter_pipeline_cache.clear(); device.destroyPipelineLayout(compute_pipeline_layout); device.destroyPipelineLayout(compute_buffer_pipeline_layout); device.destroyPipelineLayout(two_textures_pipeline_layout); + device.destroyPipelineLayout(single_texture_pipeline_layout); + device.destroyPipelineLayout(three_textures_pipeline_layout); device.destroyShaderModule(full_screen_vert); device.destroyShaderModule(d24s8_to_rgba8_comp); device.destroyShaderModule(depth_to_buffer_comp); device.destroyShaderModule(blit_depth_stencil_frag); + // Destroy texture filtering shader modules + device.destroyShaderModule(bicubic_frag); + device.destroyShaderModule(scale_force_frag); + device.destroyShaderModule(xbrz_frag); + device.destroyShaderModule(mmpx_frag); + device.destroyShaderModule(refine_frag); device.destroyPipeline(depth_to_buffer_pipeline); device.destroyPipeline(d24s8_to_rgba8_pipeline); device.destroyPipeline(depth_blit_pipeline); @@ -242,7 +319,7 @@ BlitHelper::~BlitHelper() { } void BindBlitState(vk::CommandBuffer cmdbuf, vk::PipelineLayout layout, - const VideoCore::TextureBlit& blit) { + const VideoCore::TextureBlit& blit, const Surface& dest) { const vk::Offset2D offset{ .x = std::min(blit.dst_rect.left, blit.dst_rect.right), .y = std::min(blit.dst_rect.bottom, blit.dst_rect.top), @@ -272,8 +349,9 @@ void BindBlitState(vk::CommandBuffer cmdbuf, vk::PipelineLayout layout, }; cmdbuf.setViewport(0, viewport); cmdbuf.setScissor(0, scissor); - cmdbuf.pushConstants(layout, vk::ShaderStageFlagBits::eVertex, 0, sizeof(push_constants), - &push_constants); + cmdbuf.pushConstants(layout, + vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0, + sizeof(push_constants), &push_constants); } bool BlitHelper::BlitDepthStencil(Surface& source, Surface& dest, @@ -300,12 +378,12 @@ bool BlitHelper::BlitDepthStencil(Surface& source, Surface& dest, }; renderpass_cache.BeginRendering(depth_pass); - scheduler.Record([blit, descriptor_set, this](vk::CommandBuffer cmdbuf) { + scheduler.Record([blit, descriptor_set, &dest, this](vk::CommandBuffer cmdbuf) { const vk::PipelineLayout layout = two_textures_pipeline_layout; cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, depth_blit_pipeline); cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, layout, 0, descriptor_set, {}); - BindBlitState(cmdbuf, layout, blit); + BindBlitState(cmdbuf, layout, blit, dest); cmdbuf.draw(3, 1, 0, 0); }); scheduler.MakeDirty(StateFlags::Pipeline); @@ -531,7 +609,7 @@ vk::Pipeline BlitHelper::MakeDepthStencilBlitPipeline() { .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .pDepthStencilState = &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, - .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO, + .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, .layout = two_textures_pipeline_layout, .renderPass = renderpass, @@ -547,4 +625,275 @@ vk::Pipeline BlitHelper::MakeDepthStencilBlitPipeline() { return VK_NULL_HANDLE; } +bool BlitHelper::Filter(Surface& surface, const VideoCore::TextureBlit& blit) { + const auto filter = Settings::values.texture_filter.GetValue(); + if (filter == Settings::TextureFilter::NoFilter) { + return false; + } + if (blit.src_level != 0) { + return true; + } + + switch (filter) { + case TextureFilter::Anime4K: + FilterAnime4K(surface, blit); + break; + case TextureFilter::Bicubic: + FilterBicubic(surface, blit); + break; + case TextureFilter::ScaleForce: + FilterScaleForce(surface, blit); + break; + case TextureFilter::xBRZ: + FilterXbrz(surface, blit); + break; + case TextureFilter::MMPX: + FilterMMPX(surface, blit); + break; + default: + LOG_ERROR(Render_Vulkan, "Unknown texture filter {}", filter); + return false; + } + return true; +} + +void BlitHelper::FilterAnime4K(Surface& surface, const VideoCore::TextureBlit& blit) { + auto pipeline = + MakeFilterPipeline(refine_frag, three_textures_pipeline_layout, surface.pixel_format); + FilterPassThreeTextures(surface, pipeline, three_textures_pipeline_layout, blit); +} + +void BlitHelper::FilterBicubic(Surface& surface, const VideoCore::TextureBlit& blit) { + auto pipeline = + MakeFilterPipeline(bicubic_frag, single_texture_pipeline_layout, surface.pixel_format); + FilterPass(surface, pipeline, single_texture_pipeline_layout, blit); +} + +void BlitHelper::FilterScaleForce(Surface& surface, const VideoCore::TextureBlit& blit) { + auto pipeline = + MakeFilterPipeline(scale_force_frag, single_texture_pipeline_layout, surface.pixel_format); + FilterPass(surface, pipeline, single_texture_pipeline_layout, blit); +} + +void BlitHelper::FilterXbrz(Surface& surface, const VideoCore::TextureBlit& blit) { + auto pipeline = + MakeFilterPipeline(xbrz_frag, single_texture_pipeline_layout, surface.pixel_format); + FilterPass(surface, pipeline, single_texture_pipeline_layout, blit); +} + +void BlitHelper::FilterMMPX(Surface& surface, const VideoCore::TextureBlit& blit) { + auto pipeline = + MakeFilterPipeline(mmpx_frag, single_texture_pipeline_layout, surface.pixel_format); + FilterPass(surface, pipeline, single_texture_pipeline_layout, blit); +} + +vk::Pipeline BlitHelper::MakeFilterPipeline(vk::ShaderModule fragment_shader, + vk::PipelineLayout layout, + VideoCore::PixelFormat color_format) { + + const VkShaderModule c_shader = static_cast(fragment_shader); + const VkPipelineLayout c_layout = static_cast(layout); + const u64 cache_key = Common::HashCombine( + Common::HashCombine(static_cast(reinterpret_cast(c_shader)), + static_cast(reinterpret_cast(c_layout))), + static_cast(color_format)); + + if (const auto it = filter_pipeline_cache.find(cache_key); it != filter_pipeline_cache.end()) { + return it->second; + } + + const std::array stages = MakeStages(full_screen_vert, fragment_shader); + // Use the provided color format for render pass compatibility + const auto renderpass = + renderpass_cache.GetRenderpass(color_format, VideoCore::PixelFormat::Invalid, false); + + vk::GraphicsPipelineCreateInfo pipeline_info = { + .stageCount = static_cast(stages.size()), + .pStages = stages.data(), + .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pTessellationState = nullptr, + .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .pDepthStencilState = nullptr, + .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .layout = layout, + .renderPass = renderpass, + }; + + if (const auto result = device.createGraphicsPipeline({}, pipeline_info); + result.result == vk::Result::eSuccess) { + const vk::Pipeline pipeline = result.value; + filter_pipeline_cache.emplace(cache_key, pipeline); + return pipeline; + } else { + LOG_CRITICAL(Render_Vulkan, "Filter pipeline creation failed!"); + UNREACHABLE(); + } +} + +void BlitHelper::FilterPass(Surface& surface, vk::Pipeline pipeline, vk::PipelineLayout layout, + const VideoCore::TextureBlit& blit) { + const auto texture_descriptor_set = single_texture_provider.Commit(); + update_queue.AddImageSampler(texture_descriptor_set, 0, 0, + surface.ImageView(ViewType::Sample, Type::Base), linear_sampler, + vk::ImageLayout::eGeneral); + + const auto renderpass = renderpass_cache.GetRenderpass(surface.pixel_format, + VideoCore::PixelFormat::Invalid, false); + + const RenderPass render_pass = { + .framebuffer = surface.Framebuffer(), + .render_pass = renderpass, + .render_area = + { + .offset = {0, 0}, + .extent = {surface.GetScaledWidth(), surface.GetScaledHeight()}, + }, + }; + renderpass_cache.BeginRendering(render_pass); + const float src_scale = static_cast(surface.GetResScale()); + // Calculate normalized texture coordinates like OpenGL does + const auto src_extent = surface.RealExtent(false); // Get unscaled texture extent + const float tex_scale_x = + static_cast(blit.src_rect.GetWidth()) / static_cast(src_extent.width); + const float tex_scale_y = + static_cast(blit.src_rect.GetHeight()) / static_cast(src_extent.height); + const float tex_offset_x = + static_cast(blit.src_rect.left) / static_cast(src_extent.width); + const float tex_offset_y = + static_cast(blit.src_rect.bottom) / static_cast(src_extent.height); + + scheduler.Record([pipeline, layout, texture_descriptor_set, blit, tex_scale_x, tex_scale_y, + tex_offset_x, tex_offset_y, src_scale](vk::CommandBuffer cmdbuf) { + const FilterPushConstants push_constants{.tex_scale = {tex_scale_x, tex_scale_y}, + .tex_offset = {tex_offset_x, tex_offset_y}, + .res_scale = src_scale}; + + cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline); + + // Bind single texture descriptor set + cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, layout, 0, + texture_descriptor_set, {}); + + cmdbuf.pushConstants(layout, FILTER_PUSH_CONSTANT_RANGE.stageFlags, + FILTER_PUSH_CONSTANT_RANGE.offset, FILTER_PUSH_CONSTANT_RANGE.size, + &push_constants); + + // Set up viewport and scissor for filtering (don't use BindBlitState as it overwrites push + // constants) + const vk::Offset2D offset{ + .x = std::min(blit.dst_rect.left, blit.dst_rect.right), + .y = std::min(blit.dst_rect.bottom, blit.dst_rect.top), + }; + const vk::Extent2D extent{ + .width = blit.dst_rect.GetWidth(), + .height = blit.dst_rect.GetHeight(), + }; + const vk::Viewport viewport{ + .x = static_cast(offset.x), + .y = static_cast(offset.y), + .width = static_cast(extent.width), + .height = static_cast(extent.height), + .minDepth = 0.0f, + .maxDepth = 1.0f, + }; + const vk::Rect2D scissor{ + .offset = offset, + .extent = extent, + }; + cmdbuf.setViewport(0, viewport); + cmdbuf.setScissor(0, scissor); + cmdbuf.draw(3, 1, 0, 0); + }); + scheduler.MakeDirty(StateFlags::Pipeline); +} + +void BlitHelper::FilterPassThreeTextures(Surface& surface, vk::Pipeline pipeline, + vk::PipelineLayout layout, + const VideoCore::TextureBlit& blit) { + const auto texture_descriptor_set = three_textures_provider.Commit(); + + update_queue.AddImageSampler(texture_descriptor_set, 0, 0, + surface.ImageView(ViewType::Sample, Type::Base), linear_sampler, + vk::ImageLayout::eGeneral); + update_queue.AddImageSampler(texture_descriptor_set, 1, 0, + surface.ImageView(ViewType::Sample, Type::Base), linear_sampler, + vk::ImageLayout::eGeneral); + update_queue.AddImageSampler(texture_descriptor_set, 2, 0, + surface.ImageView(ViewType::Sample, Type::Base), linear_sampler, + vk::ImageLayout::eGeneral); + + const auto renderpass = renderpass_cache.GetRenderpass(surface.pixel_format, + VideoCore::PixelFormat::Invalid, false); + + const RenderPass render_pass = { + .framebuffer = surface.Framebuffer(), + .render_pass = renderpass, + .render_area = + { + .offset = {0, 0}, + .extent = {surface.GetScaledWidth(), surface.GetScaledHeight()}, + }, + }; + renderpass_cache.BeginRendering(render_pass); + + const float src_scale = static_cast(surface.GetResScale()); + // Calculate normalized texture coordinates like OpenGL does + const auto src_extent = surface.RealExtent(false); // Get unscaled texture extent + const float tex_scale_x = + static_cast(blit.src_rect.GetWidth()) / static_cast(src_extent.width); + const float tex_scale_y = + static_cast(blit.src_rect.GetHeight()) / static_cast(src_extent.height); + const float tex_offset_x = + static_cast(blit.src_rect.left) / static_cast(src_extent.width); + const float tex_offset_y = + static_cast(blit.src_rect.bottom) / static_cast(src_extent.height); + + scheduler.Record([pipeline, layout, texture_descriptor_set, blit, tex_scale_x, tex_scale_y, + tex_offset_x, tex_offset_y, src_scale](vk::CommandBuffer cmdbuf) { + const FilterPushConstants push_constants{.tex_scale = {tex_scale_x, tex_scale_y}, + .tex_offset = {tex_offset_x, tex_offset_y}, + .res_scale = src_scale}; + + cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline); + + // Bind single texture descriptor set + cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, layout, 0, + texture_descriptor_set, {}); + + cmdbuf.pushConstants(layout, FILTER_PUSH_CONSTANT_RANGE.stageFlags, + FILTER_PUSH_CONSTANT_RANGE.offset, FILTER_PUSH_CONSTANT_RANGE.size, + &push_constants); + + // Set up viewport and scissor using safe viewport like working filters + const vk::Offset2D offset{ + .x = std::min(blit.dst_rect.left, blit.dst_rect.right), + .y = std::min(blit.dst_rect.bottom, blit.dst_rect.top), + }; + const vk::Extent2D extent{ + .width = blit.dst_rect.GetWidth(), + .height = blit.dst_rect.GetHeight(), + }; + const vk::Viewport viewport{ + .x = static_cast(offset.x), + .y = static_cast(offset.y), + .width = static_cast(extent.width), + .height = static_cast(extent.height), + .minDepth = 0.0f, + .maxDepth = 1.0f, + }; + const vk::Rect2D scissor{ + .offset = offset, + .extent = extent, + }; + cmdbuf.setViewport(0, viewport); + cmdbuf.setScissor(0, scissor); + cmdbuf.draw(3, 1, 0, 0); + }); + scheduler.MakeDirty(StateFlags::Pipeline); +} + } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_blit_helper.h b/src/video_core/renderer_vulkan/vk_blit_helper.h index d9b5c7760..59aee655f 100644 --- a/src/video_core/renderer_vulkan/vk_blit_helper.h +++ b/src/video_core/renderer_vulkan/vk_blit_helper.h @@ -1,9 +1,12 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once +#include + +#include "video_core/rasterizer_cache/pixel_format.h" #include "video_core/renderer_vulkan/vk_resource_pool.h" namespace VideoCore { @@ -27,6 +30,7 @@ public: explicit BlitHelper(const Instance& instance, Scheduler& scheduler, RenderManager& renderpass_cache, DescriptorUpdateQueue& update_queue); ~BlitHelper(); + bool Filter(Surface& surface, const VideoCore::TextureBlit& blit); bool BlitDepthStencil(Surface& source, Surface& dest, const VideoCore::TextureBlit& blit); @@ -38,6 +42,24 @@ public: private: vk::Pipeline MakeComputePipeline(vk::ShaderModule shader, vk::PipelineLayout layout); vk::Pipeline MakeDepthStencilBlitPipeline(); + vk::Pipeline MakeFilterPipeline( + vk::ShaderModule fragment_shader, vk::PipelineLayout layout, + VideoCore::PixelFormat color_format = VideoCore::PixelFormat::RGBA8); + + void FilterAnime4K(Surface& surface, const VideoCore::TextureBlit& blit); + void FilterBicubic(Surface& surface, const VideoCore::TextureBlit& blit); + void FilterScaleForce(Surface& surface, const VideoCore::TextureBlit& blit); + void FilterXbrz(Surface& surface, const VideoCore::TextureBlit& blit); + void FilterMMPX(Surface& surface, const VideoCore::TextureBlit& blit); + + void FilterPass(Surface& surface, vk::Pipeline pipeline, vk::PipelineLayout layout, + const VideoCore::TextureBlit& blit); + + void FilterPassThreeTextures(Surface& surface, vk::Pipeline pipeline, vk::PipelineLayout layout, + const VideoCore::TextureBlit& blit); + + void FilterPassYGradient(Surface& surface, vk::Pipeline pipeline, vk::PipelineLayout layout, + const VideoCore::TextureBlit& blit); private: const Instance& instance; @@ -51,20 +73,32 @@ private: DescriptorHeap compute_provider; DescriptorHeap compute_buffer_provider; DescriptorHeap two_textures_provider; + DescriptorHeap single_texture_provider; + DescriptorHeap three_textures_provider; vk::PipelineLayout compute_pipeline_layout; vk::PipelineLayout compute_buffer_pipeline_layout; vk::PipelineLayout two_textures_pipeline_layout; + vk::PipelineLayout single_texture_pipeline_layout; + vk::PipelineLayout three_textures_pipeline_layout; vk::ShaderModule full_screen_vert; vk::ShaderModule d24s8_to_rgba8_comp; vk::ShaderModule depth_to_buffer_comp; vk::ShaderModule blit_depth_stencil_frag; + vk::ShaderModule bicubic_frag; + vk::ShaderModule scale_force_frag; + vk::ShaderModule xbrz_frag; + vk::ShaderModule mmpx_frag; + vk::ShaderModule refine_frag; vk::Pipeline d24s8_to_rgba8_pipeline; vk::Pipeline depth_to_buffer_pipeline; vk::Pipeline depth_blit_pipeline; vk::Sampler linear_sampler; vk::Sampler nearest_sampler; + + /// Cache of texture filter pipelines (keyed by shader+layout+format hash) + std::unordered_map filter_pipeline_cache; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 7a27032e9..abab77c4e 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -634,7 +634,7 @@ void RasterizerVulkan::SyncTextureUnits(const Framebuffer* framebuffer) { // If the texture unit is disabled bind a null surface to it if (!texture.enabled) { - const Surface& null_surface = res_cache.GetSurface(VideoCore::NULL_SURFACE_ID); + Surface& null_surface = res_cache.GetSurface(VideoCore::NULL_SURFACE_ID); const Sampler& null_sampler = res_cache.GetSampler(VideoCore::NULL_SAMPLER_ID); update_queue.AddImageSampler(texture_set, texture_index, 0, null_surface.ImageView(), null_sampler.Handle()); @@ -669,7 +669,7 @@ void RasterizerVulkan::SyncTextureUnits(const Framebuffer* framebuffer) { Surface& surface = res_cache.GetTextureSurface(texture); Sampler& sampler = res_cache.GetSampler(texture.config); const vk::ImageView color_view = framebuffer->ImageView(SurfaceType::Color); - const bool is_feedback_loop = color_view == surface.ImageView(); + const bool is_feedback_loop = color_view == surface.FramebufferView(); const vk::ImageView texture_view = is_feedback_loop ? surface.CopyImageView() : surface.ImageView(); update_queue.AddImageSampler(texture_set, texture_index, 0, texture_view, sampler.Handle()); @@ -785,7 +785,7 @@ bool RasterizerVulkan::AccelerateDisplay(const Pica::FramebufferConfig& config, return false; } - const Surface& src_surface = res_cache.GetSurface(src_surface_id); + Surface& src_surface = res_cache.GetSurface(src_surface_id); const u32 scaled_width = src_surface.GetScaledWidth(); const u32 scaled_height = src_surface.GetScaledHeight(); diff --git a/src/video_core/renderer_vulkan/vk_render_manager.cpp b/src/video_core/renderer_vulkan/vk_render_manager.cpp index 7b986e6b2..b3615e1ae 100644 --- a/src/video_core/renderer_vulkan/vk_render_manager.cpp +++ b/src/video_core/renderer_vulkan/vk_render_manager.cpp @@ -42,6 +42,7 @@ void RenderManager::BeginRendering(const Framebuffer* framebuffer, }; images = framebuffer->Images(); aspects = framebuffer->Aspects(); + shadow_rendering = framebuffer->shadow_rendering; BeginRendering(new_pass); } @@ -71,9 +72,11 @@ void RenderManager::EndRendering() { return; } - scheduler.Record([images = images, aspects = aspects](vk::CommandBuffer cmdbuf) { + scheduler.Record([images = images, aspects = aspects, + shadow_rendering = shadow_rendering](vk::CommandBuffer cmdbuf) { u32 num_barriers = 0; vk::PipelineStageFlags pipeline_flags{}; + vk::AccessFlags src_access_flags{}; std::array barriers; for (u32 i = 0; i < images.size(); i++) { if (!images[i]) { @@ -81,14 +84,18 @@ void RenderManager::EndRendering() { } const bool is_color = static_cast(aspects[i] & vk::ImageAspectFlagBits::eColor); if (is_color) { - pipeline_flags |= vk::PipelineStageFlagBits::eColorAttachmentOutput; + pipeline_flags |= shadow_rendering + ? vk::PipelineStageFlagBits::eFragmentShader + : vk::PipelineStageFlagBits::eColorAttachmentOutput; + src_access_flags = shadow_rendering ? vk::AccessFlagBits::eShaderWrite + : vk::AccessFlagBits::eColorAttachmentWrite; } else { pipeline_flags |= vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests; + src_access_flags = vk::AccessFlagBits::eDepthStencilAttachmentWrite; } barriers[num_barriers++] = vk::ImageMemoryBarrier{ - .srcAccessMask = is_color ? vk::AccessFlagBits::eColorAttachmentWrite - : vk::AccessFlagBits::eDepthStencilAttachmentWrite, + .srcAccessMask = src_access_flags, .dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferRead, .oldLayout = vk::ImageLayout::eGeneral, @@ -120,6 +127,7 @@ void RenderManager::EndRendering() { pass.render_pass = VK_NULL_HANDLE; images = {}; aspects = {}; + shadow_rendering = false; // The Mali guide recommends flushing at the end of each major renderpass // Testing has shown this has a significant effect on rendering performance @@ -136,7 +144,9 @@ vk::RenderPass RenderManager::GetRenderpass(VideoCore::PixelFormat color, const u32 color_index = color == VideoCore::PixelFormat::Invalid ? NumColorFormats : static_cast(color); const u32 depth_index = - depth == VideoCore::PixelFormat::Invalid ? NumDepthFormats : (static_cast(depth) - 14); + depth == VideoCore::PixelFormat::Invalid + ? NumDepthFormats + : (static_cast(depth - VideoCore::PixelFormat::NumColorFormat)); ASSERT_MSG(color_index <= NumColorFormats && depth_index <= NumDepthFormats, "Invalid color index {} and/or depth_index {}", color_index, depth_index); diff --git a/src/video_core/renderer_vulkan/vk_render_manager.h b/src/video_core/renderer_vulkan/vk_render_manager.h index 9d8a8fdff..3ebbd817b 100644 --- a/src/video_core/renderer_vulkan/vk_render_manager.h +++ b/src/video_core/renderer_vulkan/vk_render_manager.h @@ -1,4 +1,4 @@ -// Copyright 2024 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -35,8 +35,8 @@ struct RenderPass { }; class RenderManager { - static constexpr u32 NumColorFormats = 13; - static constexpr u32 NumDepthFormats = 4; + static constexpr u32 NumColorFormats = static_cast(VideoCore::PixelFormat::NumColorFormat); + static constexpr u32 NumDepthFormats = static_cast(VideoCore::PixelFormat::NumDepthFormat); public: explicit RenderManager(const Instance& instance, Scheduler& scheduler); @@ -67,6 +67,7 @@ private: std::mutex cache_mutex; std::array images; std::array aspects; + bool shadow_rendering{}; RenderPass pass{}; u32 num_draws{}; }; diff --git a/src/video_core/renderer_vulkan/vk_resource_pool.cpp b/src/video_core/renderer_vulkan/vk_resource_pool.cpp index 03b644ea2..7dbe90ebd 100644 --- a/src/video_core/renderer_vulkan/vk_resource_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_resource_pool.cpp @@ -107,13 +107,14 @@ vk::CommandBuffer CommandPool::Commit() { return cmd_buffers[index]; } -constexpr u32 DESCRIPTOR_SET_BATCH = 32; +constexpr u32 DESCRIPTOR_SET_BATCH = 64; +constexpr u32 DESCRIPTOR_MULTIPLIER = 4; // Increase capacity of each pool DescriptorHeap::DescriptorHeap(const Instance& instance, MasterSemaphore* master_semaphore, std::span bindings, u32 descriptor_heap_count_) : ResourcePool{master_semaphore, DESCRIPTOR_SET_BATCH}, device{instance.GetDevice()}, - descriptor_heap_count{descriptor_heap_count_} { + descriptor_heap_count{descriptor_heap_count_ * DESCRIPTOR_MULTIPLIER} { // Increase pool size // Create descriptor set layout. const vk::DescriptorSetLayoutCreateInfo layout_ci = { .bindingCount = static_cast(bindings.size()), diff --git a/src/video_core/renderer_vulkan/vk_texture_runtime.cpp b/src/video_core/renderer_vulkan/vk_texture_runtime.cpp index cfcb199a1..86f46787e 100644 --- a/src/video_core/renderer_vulkan/vk_texture_runtime.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_runtime.cpp @@ -2,8 +2,20 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "video_core/renderer_vulkan/vk_texture_runtime.h" + +#include +#include +#include #include #include +#include +#include "video_core/custom_textures/custom_tex_manager.h" +#include "video_core/rasterizer_cache/pixel_format.h" +#include "video_core/rasterizer_cache/surface_params.h" +#include "video_core/renderer_vulkan/vk_blit_helper.h" +#include "video_core/renderer_vulkan/vk_descriptor_update_queue.h" +#include "video_core/renderer_vulkan/vk_stream_buffer.h" #include "common/literals.h" #include "common/microprofile.h" @@ -20,12 +32,6 @@ #include #include -// Ignore the -Wclass-memaccess warning on memcpy for non-trivially default constructible objects. -#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wclass-memaccess" -#endif - MICROPROFILE_DEFINE(Vulkan_ImageAlloc, "Vulkan", "Texture Allocation", MP_RGB(192, 52, 235)); namespace Vulkan { @@ -118,18 +124,18 @@ u32 UnpackDepthStencil(const VideoCore::StagingData& data, vk::Format dest) { return depth_offset; } -boost::container::small_vector MakeInitBarriers( - vk::ImageAspectFlags aspect, std::span images) { - boost::container::small_vector barriers; - for (const vk::Image& image : images) { - barriers.push_back(vk::ImageMemoryBarrier{ +void MakeInitBarriers(vk::ImageAspectFlags aspect, u32 num_images, + std::span images, + std::span out_barriers) { + for (u32 i = 0; i < num_images; i++) { + out_barriers[i] = vk::ImageMemoryBarrier{ .srcAccessMask = vk::AccessFlagBits::eNone, .dstAccessMask = vk::AccessFlagBits::eNone, .oldLayout = vk::ImageLayout::eUndefined, .newLayout = vk::ImageLayout::eGeneral, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = image, + .image = images[i], .subresourceRange{ .aspectMask = aspect, .baseMipLevel = 0, @@ -137,19 +143,38 @@ boost::container::small_vector MakeInitBarriers( .baseArrayLayer = 0, .layerCount = VK_REMAINING_ARRAY_LAYERS, }, - }); + }; } - return barriers; } -Handle MakeHandle(const Instance* instance, u32 width, u32 height, u32 levels, TextureType type, - vk::Format format, vk::ImageUsageFlags usage, vk::ImageCreateFlags flags, - vk::ImageAspectFlags aspect, bool need_format_list, - std::string_view debug_name = {}) { - // On tvOS/iOS, fall back to 2D textures when layered rendering isn't supported +vk::ImageSubresourceRange MakeSubresourceRange(vk::ImageAspectFlags aspect, u32 level = 0, + u32 levels = 1, u32 layer = 0) { + return vk::ImageSubresourceRange{ + .aspectMask = aspect, + .baseMipLevel = level, + .levelCount = levels, + .baseArrayLayer = layer, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }; +} + +constexpr u64 UPLOAD_BUFFER_SIZE = 512_MiB; +constexpr u64 DOWNLOAD_BUFFER_SIZE = 16_MiB; + +} // Anonymous namespace + +void Handle::Create(const Instance* instance, u32 width, u32 height, u32 levels, TextureType type, + vk::Format format, vk::ImageUsageFlags usage, vk::ImageCreateFlags flags, + vk::ImageAspectFlags aspect, bool need_format_list, + std::string_view debug_name) { const bool is_cube_map = type == TextureType::CubeMap && instance->IsLayeredRenderingSupported(); - const u32 layers = is_cube_map ? 6 : 1; + + this->instance = instance; + this->width = width; + this->height = height; + this->levels = levels; + this->layers = is_cube_map ? 6 : 1; const std::array format_list = { vk::Format::eR8G8B8A8Unorm, @@ -183,7 +208,6 @@ Handle MakeHandle(const Instance* instance, u32 width, u32 height, u32 levels, T VkImage unsafe_image{}; VkImageCreateInfo unsafe_image_info = static_cast(image_info); - VmaAllocation allocation{}; VkResult result = vmaCreateImage(instance->GetAllocator(), &unsafe_image_info, &alloc_info, &unsafe_image, &allocation, nullptr); @@ -192,7 +216,8 @@ Handle MakeHandle(const Instance* instance, u32 width, u32 height, u32 levels, T UNREACHABLE(); } - const vk::Image image{unsafe_image}; + image = vk::Image{unsafe_image}; + const vk::ImageViewCreateInfo view_info = { .image = image, .viewType = is_cube_map ? vk::ImageViewType::eCube : vk::ImageViewType::e2D, @@ -200,55 +225,61 @@ Handle MakeHandle(const Instance* instance, u32 width, u32 height, u32 levels, T .subresourceRange{ .aspectMask = aspect, .baseMipLevel = 0, - .levelCount = levels, + .levelCount = VK_REMAINING_MIP_LEVELS, .baseArrayLayer = 0, - .layerCount = layers, + .layerCount = VK_REMAINING_ARRAY_LAYERS, }, }; - vk::UniqueImageView image_view = instance->GetDevice().createImageViewUnique(view_info); + image_views[ViewType::Sample] = instance->GetDevice().createImageView(view_info); + if (levels == 1) { + image_views[ViewType::Mip0] = image_views[ViewType::Mip0]; + } if (!debug_name.empty() && instance->HasDebuggingToolAttached()) { SetObjectName(instance->GetDevice(), image, debug_name); - SetObjectName(instance->GetDevice(), image_view.get(), "{} View({})", debug_name, - vk::to_string(aspect)); + SetObjectName(instance->GetDevice(), image_views[ViewType::Sample], "{} View({})", + debug_name, vk::to_string(aspect)); + } +} + +void Handle::Destroy() { + if (!allocation || !instance) { + return; } - return Handle{ - .alloc = allocation, - .image = image, - .image_view = std::move(image_view), - }; + const auto device = instance->GetDevice(); + const auto allocator = instance->GetAllocator(); + + // Image views + if (auto view = image_views[ViewType::Sample]) { + device.destroyImageView(view); + } + if (auto view = image_views[ViewType::Mip0]; view && view != image_views[ViewType::Sample]) { + device.destroyImageView(view); + } + if (auto view = image_views[ViewType::Storage]) { + device.destroyImageView(view); + } + if (auto view = image_views[ViewType::Depth]) { + device.destroyImageView(view); + } + if (auto view = image_views[ViewType::Stencil]) { + device.destroyImageView(view); + } + + image_views = {}; + + if (framebuffer) { + device.destroyFramebuffer(framebuffer); + framebuffer = VK_NULL_HANDLE; + } + + vmaDestroyImage(allocator, image, allocation); + + image = VK_NULL_HANDLE; + allocation = VK_NULL_HANDLE; } -vk::UniqueFramebuffer MakeFramebuffer(vk::Device device, vk::RenderPass render_pass, u32 width, - u32 height, std::span attachments) { - const vk::FramebufferCreateInfo framebuffer_info = { - .renderPass = render_pass, - .attachmentCount = static_cast(attachments.size()), - .pAttachments = attachments.data(), - .width = width, - .height = height, - .layers = 1, - }; - return device.createFramebufferUnique(framebuffer_info); -} - -vk::ImageSubresourceRange MakeSubresourceRange(vk::ImageAspectFlags aspect, u32 level = 0, - u32 levels = 1, u32 layer = 0) { - return vk::ImageSubresourceRange{ - .aspectMask = aspect, - .baseMipLevel = level, - .levelCount = levels, - .baseArrayLayer = layer, - .layerCount = VK_REMAINING_ARRAY_LAYERS, - }; -} - -constexpr u64 UPLOAD_BUFFER_SIZE = 512_MiB; -constexpr u64 DOWNLOAD_BUFFER_SIZE = 16_MiB; - -} // Anonymous namespace - TextureRuntime::TextureRuntime(const Instance& instance, Scheduler& scheduler, RenderManager& renderpass_cache, DescriptorUpdateQueue& update_queue, u32 num_swapchain_images_) @@ -702,7 +733,7 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& param : SurfaceBase{params}, runtime{&runtime_}, instance{&runtime_.GetInstance()}, scheduler{&runtime_.GetScheduler()}, traits{instance->GetTraits(pixel_format)} { - if (pixel_format == VideoCore::PixelFormat::Invalid) { + if (pixel_format == VideoCore::PixelFormat::Invalid || !traits.transfer_support) { return; } @@ -712,7 +743,8 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& param ASSERT_MSG(format != vk::Format::eUndefined && levels >= 1, "Image allocation parameters are invalid"); - boost::container::static_vector raw_images; + u32 num_images{}; + std::array raw_images; vk::ImageCreateFlags flags{}; if (texture_type == VideoCore::TextureType::CubeMap) { @@ -722,24 +754,35 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& param flags |= vk::ImageCreateFlagBits::eMutableFormat; } - const bool need_format_list = is_mutable && instance->IsImageFormatListSupported(); - handles[0] = MakeHandle(instance, width, height, levels, texture_type, format, traits.usage, - flags, traits.aspect, need_format_list, DebugName(false)); - raw_images.emplace_back(handles[0].image); - - if (res_scale != 1) { - handles[1] = - MakeHandle(instance, GetScaledWidth(), GetScaledHeight(), levels, texture_type, format, - traits.usage, flags, traits.aspect, need_format_list, DebugName(true)); - raw_images.emplace_back(handles[1].image); + // Ensure color formats have the color attachment bit set for framebuffers + auto usage = traits.usage; + const bool is_color = + (traits.aspect & vk::ImageAspectFlagBits::eColor) != vk::ImageAspectFlags{}; + if (is_color) { + usage |= vk::ImageUsageFlagBits::eColorAttachment; } + const bool need_format_list = is_mutable && instance->IsImageFormatListSupported(); + handles[Type::Base].Create(instance, width, height, levels, texture_type, format, usage, flags, + traits.aspect, need_format_list, DebugName(false)); + raw_images[num_images++] = handles[Type::Base].image; + + if (res_scale != 1) { + handles[Type::Scaled].Create(instance, GetScaledWidth(), GetScaledHeight(), levels, + texture_type, format, usage, flags, traits.aspect, + need_format_list, DebugName(true)); + raw_images[num_images++] = handles[Type::Scaled].image; + } + + current = res_scale != 1 ? Type::Scaled : Type::Base; + runtime->renderpass_cache.EndRendering(); - scheduler->Record([raw_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) { - const auto barriers = MakeInitBarriers(aspect, raw_images); - cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe, - vk::PipelineStageFlagBits::eTopOfPipe, - vk::DependencyFlagBits::eByRegion, {}, {}, barriers); + scheduler->Record([raw_images, num_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) { + std::array barriers; + MakeInitBarriers(aspect, num_images, raw_images, barriers); + cmdbuf.pipelineBarrier( + vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eTopOfPipe, + vk::DependencyFlagBits::eByRegion, 0, nullptr, 0, nullptr, num_images, barriers.data()); }); } @@ -754,7 +797,8 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceBase& surface const bool has_normal = mat && mat->Map(MapType::Normal); const vk::Format format = traits.native; - boost::container::static_vector raw_images; + u32 num_images{}; + std::array raw_images; vk::ImageCreateFlags flags{}; if (texture_type == VideoCore::TextureType::CubeMap) { @@ -762,48 +806,37 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceBase& surface } const std::string debug_name = DebugName(false, true); - handles[0] = MakeHandle(instance, mat->width, mat->height, levels, texture_type, format, - traits.usage, flags, traits.aspect, false, debug_name); - raw_images.emplace_back(handles[0].image); + handles[Type::Base].Create(instance, mat->width, mat->height, levels, texture_type, format, + traits.usage, flags, traits.aspect, false, debug_name); + raw_images[num_images++] = handles[Type::Base].image; if (res_scale != 1) { - handles[1] = MakeHandle(instance, mat->width, mat->height, levels, texture_type, - vk::Format::eR8G8B8A8Unorm, traits.usage, flags, traits.aspect, - false, debug_name); - raw_images.emplace_back(handles[1].image); + handles[Type::Scaled].Create(instance, mat->width, mat->height, levels, texture_type, + vk::Format::eR8G8B8A8Unorm, traits.usage, flags, traits.aspect, + false, debug_name); + raw_images[num_images++] = handles[Type::Scaled].image; } if (has_normal) { - handles[2] = MakeHandle(instance, mat->width, mat->height, levels, texture_type, format, - traits.usage, flags, traits.aspect, false, debug_name); - raw_images.emplace_back(handles[2].image); + handles[Type::Custom].Create(instance, mat->width, mat->height, levels, texture_type, + format, traits.usage, flags, traits.aspect, false, debug_name); + raw_images[num_images++] = handles[Type::Custom].image; } + current = res_scale != 1 ? Type::Scaled : Type::Base; + runtime->renderpass_cache.EndRendering(); - scheduler->Record([raw_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) { - const auto barriers = MakeInitBarriers(aspect, raw_images); - cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe, - vk::PipelineStageFlagBits::eTopOfPipe, - vk::DependencyFlagBits::eByRegion, {}, {}, barriers); + scheduler->Record([raw_images, num_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) { + std::array barriers; + MakeInitBarriers(aspect, num_images, raw_images, barriers); + cmdbuf.pipelineBarrier( + vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eTopOfPipe, + vk::DependencyFlagBits::eByRegion, 0, nullptr, 0, nullptr, num_images, barriers.data()); }); custom_format = mat->format; material = mat; } -Surface::~Surface() { - if (!handles[0].image_view) { - return; - } - for (const auto& [alloc, image, image_view] : handles) { - if (image) { - vmaDestroyImage(instance->GetAllocator(), image, alloc); - } - } - if (copy_handle.image_view) { - vmaDestroyImage(instance->GetAllocator(), copy_handle.image, copy_handle.alloc); - } -} - void Surface::Upload(const VideoCore::BufferTextureCopy& upload, const VideoCore::StagingData& staging) { runtime->renderpass_cache.EndRendering(); @@ -812,7 +845,7 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload, .aspect = Aspect(), .pipeline_flags = PipelineStageFlags(), .src_access = AccessFlags(), - .src_image = Image(0), + .src_image = Image(Type::Base), }; scheduler->Record([buffer = runtime->upload_buffer.Handle(), format = traits.native, params, @@ -878,14 +911,17 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload, runtime->upload_buffer.Commit(staging.size); if (res_scale != 1) { + ASSERT_MSG(handles[Type::Scaled], "Scaled allocation missing during upload"); + const VideoCore::TextureBlit blit = { .src_level = upload.texture_level, .dst_level = upload.texture_level, .src_rect = upload.texture_rect, .dst_rect = upload.texture_rect * res_scale, }; - - BlitScale(blit, true); + if (type != SurfaceType::Texture || !runtime->blit_helper.Filter(*this, blit)) { + BlitScale(blit, true); + } } } @@ -895,13 +931,13 @@ void Surface::UploadCustom(const VideoCore::Material* material, u32 level) { const auto color = material->textures[0]; const Common::Rectangle rect{0U, height, width, 0U}; - const auto upload = [&](u32 index, VideoCore::CustomTexture* texture) { + const auto upload = [&](Type type, VideoCore::CustomTexture* texture) { const u32 custom_size = static_cast(texture->data.size()); const RecordParams params = { .aspect = vk::ImageAspectFlagBits::eColor, .pipeline_flags = PipelineStageFlags(), .src_access = AccessFlags(), - .src_image = Image(index), + .src_image = Image(type), }; const auto [data, offset, invalidate] = runtime->upload_buffer.Map(custom_size, 0); @@ -956,14 +992,9 @@ void Surface::UploadCustom(const VideoCore::Material* material, u32 level) { }); }; - upload(0, color); - - for (u32 i = 1; i < VideoCore::MAX_MAPS; i++) { - const auto texture = material->textures[i]; - if (!texture) { - continue; - } - upload(i + 1, texture); + upload(Type::Base, color); + if (auto* texture = material->textures[u32(MapType::Normal)]) { + upload(Type::Custom, texture); } } @@ -996,7 +1027,7 @@ void Surface::Download(const VideoCore::BufferTextureCopy& download, .aspect = Aspect(), .pipeline_flags = PipelineStageFlags(), .src_access = AccessFlags(), - .src_image = Image(0), + .src_image = Image(Type::Base), }; scheduler->Record( @@ -1070,14 +1101,16 @@ void Surface::ScaleUp(u32 new_scale) { flags |= vk::ImageCreateFlagBits::eMutableFormat; } - handles[1] = - MakeHandle(instance, GetScaledWidth(), GetScaledHeight(), levels, texture_type, - traits.native, traits.usage, flags, traits.aspect, false, DebugName(true)); + handles[Type::Scaled].Create(instance, GetScaledWidth(), GetScaledHeight(), levels, + texture_type, traits.native, traits.usage, flags, traits.aspect, + false, DebugName(true)); + current = Type::Scaled; runtime->renderpass_cache.EndRendering(); scheduler->Record( [raw_images = std::array{Image()}, aspect = traits.aspect](vk::CommandBuffer cmdbuf) { - const auto barriers = MakeInitBarriers(aspect, raw_images); + std::array barriers; + MakeInitBarriers(aspect, 1, raw_images, barriers); cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eTopOfPipe, vk::DependencyFlagBits::eByRegion, {}, {}, barriers); @@ -1131,24 +1164,16 @@ vk::PipelineStageFlags Surface::PipelineStageFlags() const noexcept { : vk::PipelineStageFlagBits::eNone); } -vk::Image Surface::Image(u32 index) const noexcept { - const vk::Image image = handles[index].image; - if (!image) { - return handles[0].image; - } - return image; -} - vk::ImageView Surface::CopyImageView() noexcept { + auto& copy_handle = handles[Type::Copy]; vk::ImageLayout copy_layout = vk::ImageLayout::eGeneral; - if (!copy_handle.image) { + if (!copy_handle) { vk::ImageCreateFlags flags{}; if (texture_type == VideoCore::TextureType::CubeMap) { flags |= vk::ImageCreateFlagBits::eCubeCompatible; } - copy_handle = - MakeHandle(instance, GetScaledWidth(), GetScaledHeight(), levels, texture_type, - traits.native, traits.usage, flags, traits.aspect, false); + copy_handle.Create(instance, GetScaledWidth(), GetScaledHeight(), levels, texture_type, + traits.native, traits.usage, flags, traits.aspect, false); copy_layout = vk::ImageLayout::eUndefined; } @@ -1240,123 +1265,81 @@ vk::ImageView Surface::CopyImageView() noexcept { vk::DependencyFlagBits::eByRegion, {}, {}, post_barriers); }); - return copy_handle.image_view.get(); + return copy_handle.image_views[ViewType::Sample]; } -vk::ImageView Surface::ImageView(u32 index) const noexcept { - const auto& image_view = handles[index].image_view.get(); - if (!image_view) { - return handles[0].image_view.get(); +vk::ImageView Surface::ImageView(ViewType view_type, Type type) noexcept { + auto& handle = handles[type == Type::Current ? current : type]; + if (auto image_view = handle.image_views[view_type]) { + return image_view; } - return image_view; -} -vk::ImageView Surface::FramebufferView() noexcept { - is_framebuffer = true; - return ImageView(); -} + auto aspect = traits.aspect; -vk::ImageView Surface::DepthView() noexcept { - if (depth_view) { - return depth_view.get(); + if (view_type == ViewType::Storage) { + ASSERT(pixel_format == PixelFormat::RGBA8); + is_storage = true; + } + if (view_type == ViewType::Depth || view_type == ViewType::Stencil) { + ASSERT(this->type == SurfaceType::DepthStencil); + aspect = view_type == ViewType::Depth ? vk::ImageAspectFlagBits::eDepth + : vk::ImageAspectFlagBits::eStencil; } const vk::ImageViewCreateInfo view_info = { - .image = Image(), + .image = handle.image, .viewType = vk::ImageViewType::e2D, - .format = instance->GetTraits(pixel_format).native, + .format = view_type == ViewType::Storage ? vk::Format::eR32Uint : traits.native, .subresourceRange{ - .aspectMask = vk::ImageAspectFlagBits::eDepth, + .aspectMask = aspect, .baseMipLevel = 0, - .levelCount = VK_REMAINING_MIP_LEVELS, + .levelCount = (view_type == ViewType::Mip0 || view_type == ViewType::Storage) + ? 1u + : VK_REMAINING_MIP_LEVELS, .baseArrayLayer = 0, .layerCount = VK_REMAINING_ARRAY_LAYERS, }, }; - - depth_view = instance->GetDevice().createImageViewUnique(view_info); - return depth_view.get(); + handle.image_views[view_type] = instance->GetDevice().createImageView(view_info); + return handle.image_views[view_type]; } -vk::ImageView Surface::StencilView() noexcept { - if (stencil_view) { - return stencil_view.get(); +vk::Framebuffer Surface::Framebuffer(Type type) noexcept { + auto& handle = handles[type == Type::Current ? current : type]; + if (handle.framebuffer) { + return handle.framebuffer; } - const vk::ImageViewCreateInfo view_info = { - .image = Image(), - .viewType = vk::ImageViewType::e2D, - .format = instance->GetTraits(pixel_format).native, - .subresourceRange{ - .aspectMask = vk::ImageAspectFlagBits::eStencil, - .baseMipLevel = 0, - .levelCount = VK_REMAINING_MIP_LEVELS, - .baseArrayLayer = 0, - .layerCount = VK_REMAINING_ARRAY_LAYERS, - }, - }; - - stencil_view = instance->GetDevice().createImageViewUnique(view_info); - return stencil_view.get(); -} - -vk::ImageView Surface::StorageView() noexcept { - if (storage_view) { - return storage_view.get(); - } - - if (pixel_format != VideoCore::PixelFormat::RGBA8) { - LOG_WARNING(Render_Vulkan, - "Attempted to retrieve storage view from unsupported surface with format {}", - VideoCore::PixelFormatAsString(pixel_format)); - return ImageView(); - } - - is_storage = true; - - const vk::ImageViewCreateInfo storage_view_info = { - .image = Image(), - .viewType = vk::ImageViewType::e2D, - .format = vk::Format::eR32Uint, - .subresourceRange{ - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = VK_REMAINING_MIP_LEVELS, - .baseArrayLayer = 0, - .layerCount = VK_REMAINING_ARRAY_LAYERS, - }, - }; - storage_view = instance->GetDevice().createImageViewUnique(storage_view_info); - return storage_view.get(); -} - -vk::Framebuffer Surface::Framebuffer() noexcept { - const u32 index = res_scale == 1 ? 0u : 1u; - if (framebuffers[index]) { - return framebuffers[index].get(); - } - - const bool is_depth = type == SurfaceType::Depth || type == SurfaceType::DepthStencil; + const bool is_depth = + this->type == SurfaceType::Depth || this->type == SurfaceType::DepthStencil; const auto color_format = is_depth ? PixelFormat::Invalid : pixel_format; const auto depth_format = is_depth ? pixel_format : PixelFormat::Invalid; - const auto render_pass = - runtime->renderpass_cache.GetRenderpass(color_format, depth_format, false); - const auto attachments = std::array{ImageView()}; - framebuffers[index] = MakeFramebuffer(instance->GetDevice(), render_pass, GetScaledWidth(), - GetScaledHeight(), attachments); - return framebuffers[index].get(); + + const auto image_view = ImageView(ViewType::Mip0, type); + const vk::FramebufferCreateInfo framebuffer_info = { + .renderPass = runtime->renderpass_cache.GetRenderpass(color_format, depth_format, false), + .attachmentCount = 1u, + .pAttachments = &image_view, + .width = handle.width, + .height = handle.height, + .layers = handle.layers, + }; + handle.framebuffer = instance->GetDevice().createFramebuffer(framebuffer_info); + return handle.framebuffer; } void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) { - const FormatTraits& depth_traits = instance->GetTraits(pixel_format); const bool is_depth_stencil = pixel_format == PixelFormat::D24S8; - if (is_depth_stencil && !depth_traits.blit_support) { + if (is_depth_stencil && !traits.blit_support) { LOG_WARNING(Render_Vulkan, "Depth scale unsupported by hardware"); return; } - scheduler->Record([src_image = Image(!up_scale), aspect = Aspect(), - filter = MakeFilter(pixel_format), dst_image = Image(up_scale), + const auto src_type = up_scale ? Type::Base : Type::Scaled; + const auto dst_type = up_scale ? Type::Scaled : Type::Base; + + scheduler->Record([src_image = Image(src_type), aspect = Aspect(), + filter = MakeFilter(pixel_format), dst_image = Image(dst_type), blit](vk::CommandBuffer render_cmdbuf) { const std::array source_offsets = { vk::Offset3D{static_cast(blit.src_rect.left), @@ -1461,40 +1444,49 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferPa width = height = std::numeric_limits::max(); + u32 num_attachments{}; + std::array attachments; + const auto prepare = [&](u32 index, Surface* surface) { - const VideoCore::Extent extent = surface->RealExtent(); + const auto extent = surface->RealExtent(); width = std::min(width, extent.width); height = std::min(height, extent.height); - if (!shadow_rendering) { - formats[index] = surface->pixel_format; - } + formats[index] = surface->pixel_format; images[index] = surface->Image(); aspects[index] = surface->Aspect(); - image_views[index] = shadow_rendering ? surface->StorageView() : surface->FramebufferView(); + image_views[index] = surface->FramebufferView(); }; - boost::container::static_vector attachments; - - if (color) { - prepare(0, color); - attachments.emplace_back(image_views[0]); - } - - if (depth) { - prepare(1, depth); - attachments.emplace_back(image_views[1]); - } - - const vk::Device device = runtime.GetInstance().GetDevice(); if (shadow_rendering) { + const auto extent = color->RealExtent(); + width = extent.width; + height = extent.height; render_pass = renderpass_cache.GetRenderpass(PixelFormat::Invalid, PixelFormat::Invalid, false); - framebuffer = MakeFramebuffer(device, render_pass, color->GetScaledWidth(), - color->GetScaledHeight(), {}); + images[0] = color->Image(); + image_views[0] = color->StorageView(); + aspects[0] = vk::ImageAspectFlagBits::eColor; } else { + if (color) { + prepare(0, color); + attachments[num_attachments++] = image_views[0]; + } + if (depth) { + prepare(1, depth); + attachments[num_attachments++] = image_views[1]; + } render_pass = renderpass_cache.GetRenderpass(formats[0], formats[1], false); - framebuffer = MakeFramebuffer(device, render_pass, width, height, attachments); } + + const vk::FramebufferCreateInfo framebuffer_info = { + .renderPass = render_pass, + .attachmentCount = num_attachments, + .pAttachments = attachments.data(), + .width = width, + .height = height, + .layers = 1, + }; + framebuffer = runtime.GetInstance().GetDevice().createFramebuffer(framebuffer_info); } Framebuffer::~Framebuffer() = default; diff --git a/src/video_core/renderer_vulkan/vk_texture_runtime.h b/src/video_core/renderer_vulkan/vk_texture_runtime.h index e1745b22b..bb5b5ce91 100644 --- a/src/video_core/renderer_vulkan/vk_texture_runtime.h +++ b/src/video_core/renderer_vulkan/vk_texture_runtime.h @@ -1,10 +1,9 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once -#include #include #include "video_core/rasterizer_cache/framebuffer_base.h" #include "video_core/rasterizer_cache/rasterizer_cache_base.h" @@ -26,10 +25,76 @@ class RenderManager; class Surface; class DescriptorUpdateQueue; +enum Type { + Current = -1, + Base = 0, + Scaled, + Custom, + Copy, + Num, +}; + +enum ViewType { + Sample = 0, + Mip0, + Storage, + Depth, + Stencil, + Max, +}; + struct Handle { - VmaAllocation alloc; - vk::Image image; - vk::UniqueImageView image_view; + explicit Handle() = default; + + ~Handle() { + Destroy(); + } + + Handle(Handle&& other) noexcept + : allocation(std::exchange(other.allocation, VK_NULL_HANDLE)), + image(std::exchange(other.image, VK_NULL_HANDLE)), + image_views(std::exchange(other.image_views, {})), + framebuffer(std::exchange(other.framebuffer, VK_NULL_HANDLE)), + width(std::exchange(other.width, 0)), height(std::exchange(other.height, 0)), + levels(std::exchange(other.levels, 0)), layers(std::exchange(other.layers, 0)) {} + + Handle& operator=(Handle&& other) noexcept { + if (this == &other) + return *this; + + allocation = std::exchange(other.allocation, VK_NULL_HANDLE); + image = std::exchange(other.image, VK_NULL_HANDLE); + image_views = std::exchange(other.image_views, {}); + framebuffer = std::exchange(other.framebuffer, VK_NULL_HANDLE); + width = std::exchange(other.width, 0); + height = std::exchange(other.height, 0); + levels = std::exchange(other.levels, 0); + layers = std::exchange(other.layers, 0); + + return *this; + } + + void Create(const Instance* instance, u32 width, u32 height, u32 levels, + VideoCore::TextureType type, vk::Format format, vk::ImageUsageFlags usage, + vk::ImageCreateFlags flags, vk::ImageAspectFlags aspect, bool need_format_list, + std::string_view debug_name = {}); + + void Destroy(); + + operator bool() const { + return allocation; + } + + const Instance* instance{nullptr}; + + VmaAllocation allocation{VK_NULL_HANDLE}; + vk::Image image{VK_NULL_HANDLE}; + std::array image_views{}; + vk::Framebuffer framebuffer{VK_NULL_HANDLE}; + u32 width{}; + u32 height{}; + u32 levels{}; + u32 layers{}; }; /** @@ -110,7 +175,6 @@ public: explicit Surface(TextureRuntime& runtime, const VideoCore::SurfaceParams& params); explicit Surface(TextureRuntime& runtime, const VideoCore::SurfaceBase& surface, const VideoCore::Material* materal); - ~Surface(); Surface(const Surface&) = delete; Surface& operator=(const Surface&) = delete; @@ -123,28 +187,56 @@ public: } /// Returns the image at index, otherwise the base image - vk::Image Image(u32 index = 1) const noexcept; + vk::Image Image(Type type = Type::Current) const noexcept { + return handles[type == Type::Current ? current : type].image; + } /// Returns the image view at index, otherwise the base view - vk::ImageView ImageView(u32 index = 1) const noexcept; + vk::ImageView ImageView(ViewType view_type = ViewType::Sample, + Type type = Type::Current) noexcept; + + /// Returns a framebuffer handle for rendering to this surface + vk::Framebuffer Framebuffer(Type type = Type::Current) noexcept; + + /// Returns width of the surface + u32 GetWidth() const noexcept { + return width; + } + + /// Returns height of the surface + u32 GetHeight() const noexcept { + return height; + } + + /// Returns resolution scale of the surface + u32 GetResScale() const noexcept { + return res_scale; + } /// Returns a copy of the upscaled image handle, used for feedback loops. vk::ImageView CopyImageView() noexcept; /// Returns the framebuffer view of the surface image - vk::ImageView FramebufferView() noexcept; + vk::ImageView FramebufferView() noexcept { + is_framebuffer = true; + return ImageView(ViewType::Mip0); + } /// Returns the depth view of the surface image - vk::ImageView DepthView() noexcept; + vk::ImageView DepthView() noexcept { + return ImageView(ViewType::Depth); + } /// Returns the stencil view of the surface image - vk::ImageView StencilView() noexcept; + vk::ImageView StencilView() noexcept { + return ImageView(ViewType::Stencil); + } - /// Returns the R32 image view used for atomic load/store - vk::ImageView StorageView() noexcept; - - /// Returns a framebuffer handle for rendering to this surface - vk::Framebuffer Framebuffer() noexcept; + /// Returns the R32 image view used for atomic load/store. + vk::ImageView StorageView() noexcept { + is_storage = true; + return ImageView(ViewType::Storage); + } /// Uploads pixel data in staging to a rectangle region of the surface texture void Upload(const VideoCore::BufferTextureCopy& upload, const VideoCore::StagingData& staging); @@ -181,9 +273,8 @@ public: const Instance* instance; Scheduler* scheduler; FormatTraits traits; - std::array handles{}; - std::array framebuffers{}; - Handle copy_handle; + std::array handles; + Type current{}; vk::UniqueImageView depth_view; vk::UniqueImageView stencil_view; vk::UniqueImageView storage_view; @@ -212,7 +303,7 @@ public: } [[nodiscard]] vk::Framebuffer Handle() const noexcept { - return framebuffer.get(); + return framebuffer; } [[nodiscard]] std::array Images() const noexcept { @@ -231,24 +322,17 @@ public: return res_scale; } - u32 Width() const noexcept { - return width; - } - - u32 Height() const noexcept { - return height; - } - private: std::array images{}; std::array image_views{}; - vk::UniqueFramebuffer framebuffer; + vk::Framebuffer framebuffer; vk::RenderPass render_pass; + std::vector framebuffer_views; std::array aspects{}; std::array formats{VideoCore::PixelFormat::Invalid, VideoCore::PixelFormat::Invalid}; - u32 width{}; - u32 height{}; + u32 width; + u32 height; u32 res_scale{1}; };