mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2026-03-27 13:50:22 -06:00
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:
parent
9628300ff5
commit
1092295f2a
@ -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; \
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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{};
|
||||
};
|
||||
|
||||
@ -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()),
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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};
|
||||
};
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user