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 <hackyglitch2@gmail.com>
This commit is contained in:
jbm11208 2026-02-20 17:39:04 -05:00 committed by GitHub
parent 9628300ff5
commit 1092295f2a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 835 additions and 311 deletions

View File

@ -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<type>; \
return static_cast<T>(key) == 0; \
}
#define DECLARE_ENUM_ARITHMETIC_OPERATORS(type) \
[[nodiscard]] constexpr type operator+(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) + static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator-(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) - static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator*(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) * static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator/(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) / static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator%(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) % static_cast<T>(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; \
}

View File

@ -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<u32>::max(),
};
constexpr std::size_t PIXEL_FORMAT_COUNT = static_cast<std::size_t>(PixelFormat::MaxPixelFormat);
DECLARE_ENUM_ARITHMETIC_OPERATORS(PixelFormat)
enum class SurfaceType : u32 {
Color = 0,

View File

@ -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<vk::DescriptorSetLayoutBinding, 2> TWO_TEXTURES_BINDINGS =
{1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
}};
// Texture filtering descriptor set bindings
constexpr std::array<vk::DescriptorSetLayoutBinding, 1> SINGLE_TEXTURE_BINDINGS = {{
{0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
}};
constexpr std::array<vk::DescriptorSetLayoutBinding, 3> 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<float, 2> tex_scale;
std::array<float, 2> 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<u32>(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<s32>(blit.dst_rect.left, blit.dst_rect.right),
.y = std::min<s32>(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<VkShaderModule>(fragment_shader);
const VkPipelineLayout c_layout = static_cast<VkPipelineLayout>(layout);
const u64 cache_key = Common::HashCombine(
Common::HashCombine(static_cast<u64>(reinterpret_cast<uintptr_t>(c_shader)),
static_cast<u64>(reinterpret_cast<uintptr_t>(c_layout))),
static_cast<u64>(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<u32>(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<float>(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<float>(blit.src_rect.GetWidth()) / static_cast<float>(src_extent.width);
const float tex_scale_y =
static_cast<float>(blit.src_rect.GetHeight()) / static_cast<float>(src_extent.height);
const float tex_offset_x =
static_cast<float>(blit.src_rect.left) / static_cast<float>(src_extent.width);
const float tex_offset_y =
static_cast<float>(blit.src_rect.bottom) / static_cast<float>(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<s32>(blit.dst_rect.left, blit.dst_rect.right),
.y = std::min<s32>(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<float>(offset.x),
.y = static_cast<float>(offset.y),
.width = static_cast<float>(extent.width),
.height = static_cast<float>(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<float>(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<float>(blit.src_rect.GetWidth()) / static_cast<float>(src_extent.width);
const float tex_scale_y =
static_cast<float>(blit.src_rect.GetHeight()) / static_cast<float>(src_extent.height);
const float tex_offset_x =
static_cast<float>(blit.src_rect.left) / static_cast<float>(src_extent.width);
const float tex_offset_y =
static_cast<float>(blit.src_rect.bottom) / static_cast<float>(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<s32>(blit.dst_rect.left, blit.dst_rect.right),
.y = std::min<s32>(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<float>(offset.x),
.y = static_cast<float>(offset.y),
.width = static_cast<float>(extent.width),
.height = static_cast<float>(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

View File

@ -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 <unordered_map>
#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<std::uint64_t, vk::Pipeline> filter_pipeline_cache;
};
} // namespace Vulkan

View File

@ -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();

View File

@ -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<vk::ImageMemoryBarrier, 2> 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<bool>(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<u32>(color);
const u32 depth_index =
depth == VideoCore::PixelFormat::Invalid ? NumDepthFormats : (static_cast<u32>(depth) - 14);
depth == VideoCore::PixelFormat::Invalid
? NumDepthFormats
: (static_cast<u32>(depth - VideoCore::PixelFormat::NumColorFormat));
ASSERT_MSG(color_index <= NumColorFormats && depth_index <= NumDepthFormats,
"Invalid color index {} and/or depth_index {}", color_index, depth_index);

View File

@ -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<u32>(VideoCore::PixelFormat::NumColorFormat);
static constexpr u32 NumDepthFormats = static_cast<u32>(VideoCore::PixelFormat::NumDepthFormat);
public:
explicit RenderManager(const Instance& instance, Scheduler& scheduler);
@ -67,6 +67,7 @@ private:
std::mutex cache_mutex;
std::array<vk::Image, 2> images;
std::array<vk::ImageAspectFlags, 2> aspects;
bool shadow_rendering{};
RenderPass pass{};
u32 num_draws{};
};

View File

@ -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<const vk::DescriptorSetLayoutBinding> 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<u32>(bindings.size()),

View File

@ -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 <limits>
#include <span>
#include <string>
#include <boost/container/small_vector.hpp>
#include <boost/container/static_vector.hpp>
#include <vulkan/vulkan.hpp>
#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 <vk_mem_alloc.h>
#include <vulkan/vulkan_format_traits.hpp>
// 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<vk::ImageMemoryBarrier, 3> MakeInitBarriers(
vk::ImageAspectFlags aspect, std::span<const vk::Image> images) {
boost::container::small_vector<vk::ImageMemoryBarrier, 3> barriers;
for (const vk::Image& image : images) {
barriers.push_back(vk::ImageMemoryBarrier{
void MakeInitBarriers(vk::ImageAspectFlags aspect, u32 num_images,
std::span<const vk::Image> images,
std::span<vk::ImageMemoryBarrier> 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<vk::ImageMemoryBarrier, 3> 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<VkImageCreateInfo>(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<const vk::ImageView> attachments) {
const vk::FramebufferCreateInfo framebuffer_info = {
.renderPass = render_pass,
.attachmentCount = static_cast<u32>(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<vk::Image, 3> raw_images;
u32 num_images{};
std::array<vk::Image, 2> 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<vk::ImageMemoryBarrier, 3> 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<vk::Image, 2> raw_images;
u32 num_images{};
std::array<vk::Image, 3> 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<vk::ImageMemoryBarrier, 3> 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<u32>(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<vk::ImageMemoryBarrier, 1> 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<s32>(blit.src_rect.left),
@ -1461,40 +1444,49 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferPa
width = height = std::numeric_limits<u32>::max();
u32 num_attachments{};
std::array<vk::ImageView, 2> 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<vk::ImageView, 2> 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;

View File

@ -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 <deque>
#include <span>
#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<vk::ImageView, ViewType::Max> 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<Handle, 3> handles{};
std::array<vk::UniqueFramebuffer, 2> framebuffers{};
Handle copy_handle;
std::array<Handle, Type::Num> 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<vk::Image, 2> 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<vk::Image, 2> images{};
std::array<vk::ImageView, 2> image_views{};
vk::UniqueFramebuffer framebuffer;
vk::Framebuffer framebuffer;
vk::RenderPass render_pass;
std::vector<vk::UniqueImageView> framebuffer_views;
std::array<vk::ImageAspectFlags, 2> aspects{};
std::array<VideoCore::PixelFormat, 2> formats{VideoCore::PixelFormat::Invalid,
VideoCore::PixelFormat::Invalid};
u32 width{};
u32 height{};
u32 width;
u32 height;
u32 res_scale{1};
};