Revert: video_core/renderer_vulkan: Add texture filtering (#1678)

This commit is contained in:
PabloMK7 2026-01-24 23:48:54 +01:00 committed by GitHub
parent ad9ab71301
commit f1fa564733
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 74 additions and 565 deletions

View File

@ -1,8 +1,7 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#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"
@ -17,19 +16,8 @@
#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 {
@ -67,33 +55,8 @@ 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 | vk::ShaderStageFlagBits::eFragment,
.stageFlags = vk::ShaderStageFlagBits::eVertex,
.offset = 0,
.size = sizeof(PushConstants),
};
@ -141,17 +104,12 @@ constexpr vk::PipelineDynamicStateCreateInfo PIPELINE_DYNAMIC_STATE_CREATE_INFO{
.dynamicStateCount = static_cast<u32>(DYNAMIC_STATES.size()),
.pDynamicStates = DYNAMIC_STATES.data(),
};
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{
constexpr vk::PipelineColorBlendStateCreateInfo PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO{
.logicOpEnable = VK_FALSE,
.attachmentCount = 1,
.pAttachments = &COLOR_BLEND_ATTACHMENT,
.logicOp = vk::LogicOp::eClear,
.attachmentCount = 0,
.pAttachments = nullptr,
.blendConstants = std::array{0.0f, 0.0f, 0.0f, 0.0f},
};
constexpr vk::PipelineDepthStencilStateCreateInfo PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO{
.depthTestEnable = VK_TRUE,
@ -170,9 +128,9 @@ inline constexpr vk::SamplerCreateInfo SAMPLER_CREATE_INFO{
.magFilter = filter,
.minFilter = filter,
.mipmapMode = vk::SamplerMipmapMode::eNearest,
.addressModeU = vk::SamplerAddressMode::eClampToEdge,
.addressModeV = vk::SamplerAddressMode::eClampToEdge,
.addressModeW = vk::SamplerAddressMode::eClampToEdge,
.addressModeU = vk::SamplerAddressMode::eClampToBorder,
.addressModeV = vk::SamplerAddressMode::eClampToBorder,
.addressModeW = vk::SamplerAddressMode::eClampToBorder,
.mipLodBias = 0.0f,
.anisotropyEnable = VK_FALSE,
.maxAnisotropy = 0.0f,
@ -185,14 +143,12 @@ inline constexpr vk::SamplerCreateInfo SAMPLER_CREATE_INFO{
};
constexpr vk::PipelineLayoutCreateInfo PipelineLayoutCreateInfo(
const vk::DescriptorSetLayout* set_layout, bool compute = false, bool filter = false) {
const vk::DescriptorSetLayout* set_layout, bool compute = false) {
return vk::PipelineLayoutCreateInfo{
.setLayoutCount = 1,
.pSetLayouts = set_layout,
.pushConstantRangeCount = 1,
.pPushConstantRanges =
(compute ? &COMPUTE_PUSH_CONSTANT_RANGE
: (filter ? &FILTER_PUSH_CONSTANT_RANGE : &PUSH_CONSTANT_RANGE)),
.pPushConstantRanges = (compute ? &COMPUTE_PUSH_CONSTANT_RANGE : &PUSH_CONSTANT_RANGE),
};
}
@ -229,20 +185,12 @@ 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,
@ -251,14 +199,6 @@ 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)},
@ -290,18 +230,10 @@ BlitHelper::~BlitHelper() {
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);
@ -310,7 +242,7 @@ BlitHelper::~BlitHelper() {
}
void BindBlitState(vk::CommandBuffer cmdbuf, vk::PipelineLayout layout,
const VideoCore::TextureBlit& blit, const Surface& dest) {
const VideoCore::TextureBlit& blit) {
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),
@ -340,9 +272,8 @@ void BindBlitState(vk::CommandBuffer cmdbuf, vk::PipelineLayout layout,
};
cmdbuf.setViewport(0, viewport);
cmdbuf.setScissor(0, scissor);
cmdbuf.pushConstants(layout,
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0,
sizeof(push_constants), &push_constants);
cmdbuf.pushConstants(layout, vk::ShaderStageFlagBits::eVertex, 0, sizeof(push_constants),
&push_constants);
}
bool BlitHelper::BlitDepthStencil(Surface& source, Surface& dest,
@ -369,12 +300,12 @@ bool BlitHelper::BlitDepthStencil(Surface& source, Surface& dest,
};
renderpass_cache.BeginRendering(depth_pass);
scheduler.Record([blit, descriptor_set, &dest, this](vk::CommandBuffer cmdbuf) {
scheduler.Record([blit, descriptor_set, 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, dest);
BindBlitState(cmdbuf, layout, blit);
cmdbuf.draw(3, 1, 0, 0);
});
scheduler.MakeDirty(StateFlags::Pipeline);
@ -600,7 +531,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_CREATE_INFO,
.pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO,
.pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
.layout = two_textures_pipeline_layout,
.renderPass = renderpass,
@ -616,280 +547,4 @@ 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();
const bool is_depth =
surface.type == VideoCore::SurfaceType::Depth ||
surface.type == VideoCore::SurfaceType::DepthStencil; // Skip filtering for depth textures
// and when no filter is selected
if (filter == Settings::TextureFilter::NoFilter || is_depth) {
return false;
} // Only filter base mipmap level
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) {
const bool is_depth = surface.type == VideoCore::SurfaceType::Depth ||
surface.type == VideoCore::SurfaceType::DepthStencil;
const auto color_format = is_depth ? VideoCore::PixelFormat::Invalid : surface.pixel_format;
auto pipeline = MakeFilterPipeline(refine_frag, three_textures_pipeline_layout, color_format);
FilterPassThreeTextures(surface, surface, surface, surface, pipeline,
three_textures_pipeline_layout, blit);
}
void BlitHelper::FilterBicubic(Surface& surface, const VideoCore::TextureBlit& blit) {
const bool is_depth = surface.type == VideoCore::SurfaceType::Depth ||
surface.type == VideoCore::SurfaceType::DepthStencil;
const auto color_format = is_depth ? VideoCore::PixelFormat::Invalid : surface.pixel_format;
auto pipeline = MakeFilterPipeline(bicubic_frag, single_texture_pipeline_layout, color_format);
FilterPass(surface, surface, pipeline, single_texture_pipeline_layout, blit);
}
void BlitHelper::FilterScaleForce(Surface& surface, const VideoCore::TextureBlit& blit) {
const bool is_depth = surface.type == VideoCore::SurfaceType::Depth ||
surface.type == VideoCore::SurfaceType::DepthStencil;
const auto color_format = is_depth ? VideoCore::PixelFormat::Invalid : surface.pixel_format;
auto pipeline =
MakeFilterPipeline(scale_force_frag, single_texture_pipeline_layout, color_format);
FilterPass(surface, surface, pipeline, single_texture_pipeline_layout, blit);
}
void BlitHelper::FilterXbrz(Surface& surface, const VideoCore::TextureBlit& blit) {
const bool is_depth = surface.type == VideoCore::SurfaceType::Depth ||
surface.type == VideoCore::SurfaceType::DepthStencil;
const auto color_format = is_depth ? VideoCore::PixelFormat::Invalid : surface.pixel_format;
auto pipeline = MakeFilterPipeline(xbrz_frag, single_texture_pipeline_layout, color_format);
FilterPass(surface, surface, pipeline, single_texture_pipeline_layout, blit);
}
void BlitHelper::FilterMMPX(Surface& surface, const VideoCore::TextureBlit& blit) {
const bool is_depth = surface.type == VideoCore::SurfaceType::Depth ||
surface.type == VideoCore::SurfaceType::DepthStencil;
const auto color_format = is_depth ? VideoCore::PixelFormat::Invalid : surface.pixel_format;
auto pipeline = MakeFilterPipeline(mmpx_frag, single_texture_pipeline_layout, color_format);
FilterPass(surface, surface, pipeline, single_texture_pipeline_layout, blit);
}
vk::Pipeline BlitHelper::MakeFilterPipeline(vk::ShaderModule fragment_shader,
vk::PipelineLayout layout,
VideoCore::PixelFormat color_format) {
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) {
return result.value;
} else {
LOG_CRITICAL(Render_Vulkan, "Filter pipeline creation failed!");
UNREACHABLE();
}
}
void BlitHelper::FilterPass(Surface& source, Surface& dest, 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, source.ImageView(0), linear_sampler,
vk::ImageLayout::eGeneral);
const bool is_depth = dest.type == VideoCore::SurfaceType::Depth ||
dest.type == VideoCore::SurfaceType::DepthStencil;
const auto color_format = is_depth ? VideoCore::PixelFormat::Invalid : dest.pixel_format;
const auto depth_format = is_depth ? dest.pixel_format : VideoCore::PixelFormat::Invalid;
const auto renderpass = renderpass_cache.GetRenderpass(color_format, depth_format, false);
const RenderPass render_pass = {
.framebuffer = dest.Framebuffer(),
.render_pass = renderpass,
.render_area =
{
.offset = {0, 0},
.extent = {dest.GetScaledWidth(), dest.GetScaledHeight()},
},
};
renderpass_cache.BeginRendering(render_pass);
const float src_scale = static_cast<float>(source.GetResScale());
// Calculate normalized texture coordinates like OpenGL does
const auto src_extent = source.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& source1, Surface& source2, Surface& source3,
Surface& dest, 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, source1.ImageView(0), linear_sampler,
vk::ImageLayout::eGeneral);
update_queue.AddImageSampler(texture_descriptor_set, 1, 0, source2.ImageView(0), linear_sampler,
vk::ImageLayout::eGeneral);
update_queue.AddImageSampler(texture_descriptor_set, 2, 0, source3.ImageView(0), linear_sampler,
vk::ImageLayout::eGeneral);
const bool is_depth = dest.type == VideoCore::SurfaceType::Depth ||
dest.type == VideoCore::SurfaceType::DepthStencil;
const auto color_format = is_depth ? VideoCore::PixelFormat::Invalid : dest.pixel_format;
const auto depth_format = is_depth ? dest.pixel_format : VideoCore::PixelFormat::Invalid;
const auto renderpass = renderpass_cache.GetRenderpass(color_format, depth_format, false);
const RenderPass render_pass = {
.framebuffer = dest.Framebuffer(),
.render_pass = renderpass,
.render_area =
{
.offset = {0, 0},
.extent = {dest.GetScaledWidth(), dest.GetScaledHeight()},
},
};
renderpass_cache.BeginRendering(render_pass);
const float src_scale = static_cast<float>(source1.GetResScale());
// Calculate normalized texture coordinates like OpenGL does
const auto src_extent = source1.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,10 +1,9 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "video_core/rasterizer_cache/pixel_format.h"
#include "video_core/renderer_vulkan/vk_resource_pool.h"
namespace VideoCore {
@ -28,7 +27,6 @@ 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);
@ -40,25 +38,6 @@ 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& source, Surface& dest, vk::Pipeline pipeline,
vk::PipelineLayout layout, const VideoCore::TextureBlit& blit);
void FilterPassThreeTextures(Surface& source1, Surface& source2, Surface& source3,
Surface& dest, vk::Pipeline pipeline, vk::PipelineLayout layout,
const VideoCore::TextureBlit& blit);
void FilterPassYGradient(Surface& source, Surface& dest, vk::Pipeline pipeline,
vk::PipelineLayout layout, const VideoCore::TextureBlit& blit);
private:
const Instance& instance;
@ -72,23 +51,14 @@ 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;

View File

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -103,14 +103,13 @@ vk::CommandBuffer CommandPool::Commit() {
return cmd_buffers[index];
}
constexpr u32 DESCRIPTOR_SET_BATCH = 64;
constexpr u32 DESCRIPTOR_MULTIPLIER = 4; // Increase capacity of each pool
constexpr u32 DESCRIPTOR_SET_BATCH = 32;
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_MULTIPLIER} { // Increase pool size
descriptor_heap_count{descriptor_heap_count_} {
// Create descriptor set layout.
const vk::DescriptorSetLayoutCreateInfo layout_ci = {
.bindingCount = static_cast<u32>(bindings.size()),

View File

@ -1,23 +1,9 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "video_core/renderer_vulkan/vk_texture_runtime.h"
#include <cmath>
#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_base.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"
@ -714,7 +700,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 || !traits.transfer_support) {
if (pixel_format == VideoCore::PixelFormat::Invalid) {
return;
}
@ -734,25 +720,18 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& param
flags |= vk::ImageCreateFlagBits::eMutableFormat;
}
// 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[0] = MakeHandle(instance, width, height, levels, texture_type, format, usage, flags,
traits.aspect, need_format_list, DebugName(false));
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,
usage, flags, traits.aspect, need_format_list, DebugName(true));
traits.usage, flags, traits.aspect, need_format_list, DebugName(true));
raw_images.emplace_back(handles[1].image);
}
runtime->renderpass_cache.EndRendering();
scheduler->Record([raw_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) {
const auto barriers = MakeInitBarriers(aspect, raw_images);
@ -809,49 +788,6 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceBase& surface
material = mat;
}
Surface::Surface(TextureRuntime& runtime_, u32 width_, u32 height_, VideoCore::PixelFormat format_)
: SurfaceBase{{
.width = width_,
.height = height_,
.pixel_format = format_,
.type = VideoCore::SurfaceType::Texture,
}},
runtime{&runtime_}, instance{&runtime_.GetInstance()}, scheduler{&runtime_.GetScheduler()},
traits{instance->GetTraits(format_)} {
// Create texture with requested size and format
const vk::ImageUsageFlags usage =
vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst |
vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled;
handles[0] = MakeHandle(instance, width_, height_, 1, VideoCore::TextureType::Texture2D,
traits.native, usage, {}, traits.aspect, false, "Temporary Surface");
// Create image view
const vk::ImageViewCreateInfo view_info = {
.image = handles[0].image,
.viewType = vk::ImageViewType::e2D,
.format = traits.native,
.subresourceRange{
.aspectMask = traits.aspect,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
};
handles[0].image_view = instance->GetDevice().createImageViewUnique(view_info);
runtime->renderpass_cache.EndRendering();
scheduler->Record(
[raw_images = std::array{Image()}, 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);
});
}
Surface::~Surface() {
if (!handles[0].image_view) {
return;
@ -940,23 +876,14 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload,
runtime->upload_buffer.Commit(staging.size);
if (res_scale != 1) {
// Always ensure the scaled image exists
if (!handles[1].image) {
// This will create handles[1] and perform the initial scaling
ScaleUp(res_scale);
} else {
// Update the scaled version of the uploaded area
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,
};
// Only apply texture filtering when upscaling, matching OpenGL behavior
if (res_scale != 1 && !runtime->blit_helper.Filter(*this, blit)) {
BlitScale(blit, true);
}
}
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);
}
}
@ -1322,6 +1249,11 @@ vk::ImageView Surface::ImageView(u32 index) const noexcept {
return image_view;
}
vk::ImageView Surface::FramebufferView() noexcept {
is_framebuffer = true;
return ImageView();
}
vk::ImageView Surface::DepthView() noexcept {
if (depth_view) {
return depth_view.get();
@ -1397,8 +1329,6 @@ vk::ImageView Surface::StorageView() noexcept {
}
vk::Framebuffer Surface::Framebuffer() noexcept {
is_framebuffer = true;
const u32 index = res_scale == 1 ? 0u : 1u;
if (framebuffers[index]) {
return framebuffers[index].get();
@ -1409,29 +1339,12 @@ vk::Framebuffer Surface::Framebuffer() noexcept {
const auto depth_format = is_depth ? pixel_format : PixelFormat::Invalid;
const auto render_pass =
runtime->renderpass_cache.GetRenderpass(color_format, depth_format, false);
// Use AttachmentView() to get single mip level view for framebuffer
const auto attachments = std::array{AttachmentView()};
const auto attachments = std::array{ImageView()};
framebuffers[index] = MakeFramebuffer(instance->GetDevice(), render_pass, GetScaledWidth(),
GetScaledHeight(), attachments);
return framebuffers[index].get();
}
vk::ImageView Surface::AttachmentView() noexcept {
const vk::ImageViewCreateInfo view_info = {
.image = Image(),
.viewType = vk::ImageViewType::e2D,
.format = traits.native,
.subresourceRange{
.aspectMask = traits.aspect,
.baseMipLevel = 0,
.levelCount = 1, // Single mip level for framebuffer
.baseArrayLayer = 0,
.layerCount = 1,
},
};
return instance->GetDevice().createImageViewUnique(view_info).release();
}
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;
@ -1440,16 +1353,9 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
return;
}
// Always use consistent source and destination images for proper scaling
// When upscaling: source = unscaled (0), destination = scaled (1)
// When downscaling: source = scaled (1), destination = unscaled (0)
const vk::Image src_image = up_scale ? Image(0) : Image(1);
const vk::Image dst_image = up_scale ? Image(1) : Image(0);
scheduler->Record([src_image, aspect = Aspect(), filter = MakeFilter(pixel_format), dst_image,
src_access = AccessFlags(), dst_access = AccessFlags(),
scheduler->Record([src_image = Image(!up_scale), aspect = Aspect(),
filter = MakeFilter(pixel_format), dst_image = Image(up_scale),
blit](vk::CommandBuffer render_cmdbuf) {
// Adjust blitting parameters for filtered upscaling
const std::array source_offsets = {
vk::Offset3D{static_cast<s32>(blit.src_rect.left),
static_cast<s32>(blit.src_rect.bottom), 0},
@ -1483,7 +1389,7 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
const std::array read_barriers = {
vk::ImageMemoryBarrier{
.srcAccessMask = src_access,
.srcAccessMask = vk::AccessFlagBits::eMemoryWrite,
.dstAccessMask = vk::AccessFlagBits::eTransferRead,
.oldLayout = vk::ImageLayout::eGeneral,
.newLayout = vk::ImageLayout::eTransferSrcOptimal,
@ -1493,7 +1399,10 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
.subresourceRange = MakeSubresourceRange(aspect, blit.src_level),
},
vk::ImageMemoryBarrier{
.srcAccessMask = dst_access,
.srcAccessMask = vk::AccessFlagBits::eShaderRead |
vk::AccessFlagBits::eDepthStencilAttachmentRead |
vk::AccessFlagBits::eColorAttachmentRead |
vk::AccessFlagBits::eTransferRead,
.dstAccessMask = vk::AccessFlagBits::eTransferWrite,
.oldLayout = vk::ImageLayout::eGeneral,
.newLayout = vk::ImageLayout::eTransferDstOptimal,
@ -1540,9 +1449,9 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
}
Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferParams& params,
Surface* color, Surface* depth_stencil)
Surface* color, Surface* depth)
: VideoCore::FramebufferParams{params},
res_scale{color ? color->res_scale : (depth_stencil ? depth_stencil->res_scale : 1u)} {
res_scale{color ? color->res_scale : (depth ? depth->res_scale : 1u)} {
auto& renderpass_cache = runtime.GetRenderpassCache();
if (shadow_rendering && !color) {
return;
@ -1559,41 +1468,27 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferPa
}
images[index] = surface->Image();
aspects[index] = surface->Aspect();
// Use AttachmentView() for single-mip-level framebuffer attachment
image_views[index] = surface->AttachmentView();
image_views[index] = shadow_rendering ? surface->StorageView() : surface->FramebufferView();
};
boost::container::static_vector<vk::ImageView, 2> attachments;
if (!shadow_rendering) {
// Prepare the surfaces for use in framebuffer
if (color) {
prepare(0, color);
attachments.emplace_back(image_views[0]);
}
if (color) {
prepare(0, color);
attachments.emplace_back(image_views[0]);
}
if (depth_stencil) {
prepare(1, depth_stencil);
attachments.emplace_back(image_views[1]);
}
} else {
// For shadow rendering, just collect surface info without adding attachments
if (color) {
prepare(0, color);
}
if (depth_stencil) {
prepare(1, depth_stencil);
}
if (depth) {
prepare(1, depth);
attachments.emplace_back(image_views[1]);
}
const vk::Device device = runtime.GetInstance().GetDevice();
if (shadow_rendering) {
// For shadow rendering, we don't need a framebuffer with attachments
// Just create a dummy render pass for the rendering pipeline
render_pass =
renderpass_cache.GetRenderpass(PixelFormat::Invalid, PixelFormat::Invalid, false);
// Don't create a framebuffer for shadow rendering
framebuffer.reset();
framebuffer = MakeFramebuffer(device, render_pass, color->GetScaledWidth(),
color->GetScaledHeight(), {});
} else {
render_pass = renderpass_cache.GetRenderpass(formats[0], formats[1], false);
framebuffer = MakeFramebuffer(device, render_pass, width, height, attachments);

View File

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -110,8 +110,6 @@ public:
explicit Surface(TextureRuntime& runtime, const VideoCore::SurfaceParams& params);
explicit Surface(TextureRuntime& runtime, const VideoCore::SurfaceBase& surface,
const VideoCore::Material* materal);
explicit Surface(TextureRuntime& runtime, u32 width_, u32 height_,
VideoCore::PixelFormat format_);
~Surface();
Surface(const Surface&) = delete;
@ -130,24 +128,12 @@ public:
/// Returns the image view at index, otherwise the base view
vk::ImageView ImageView(u32 index = 1) const 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;
/// Returns the depth view of the surface image
vk::ImageView DepthView() noexcept;
@ -160,9 +146,6 @@ public:
/// Returns a framebuffer handle for rendering to this surface
vk::Framebuffer Framebuffer() noexcept;
/// Returns a single-mip-level view suitable for framebuffer attachments
vk::ImageView AttachmentView() noexcept;
/// Uploads pixel data in staging to a rectangle region of the surface texture
void Upload(const VideoCore::BufferTextureCopy& upload, const VideoCore::StagingData& staging);
@ -248,12 +231,19 @@ 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::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};