Merge 3127b2cc82 into 068fec0d5f
This commit is contained in:
commit
d03dee582b
5 changed files with 629 additions and 65 deletions
|
|
@ -1,7 +1,8 @@
|
|||
// 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/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 +17,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 +67,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 +141,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 +170,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 +185,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 +229,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,10 +251,24 @@ 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)},
|
||||
depth_blit_pipeline{MakeDepthStencilBlitPipeline()},
|
||||
// Texture filtering pipelines
|
||||
bicubic_pipeline{MakeFilterPipeline(bicubic_frag, single_texture_pipeline_layout)},
|
||||
scale_force_pipeline{MakeFilterPipeline(scale_force_frag, single_texture_pipeline_layout)},
|
||||
xbrz_pipeline{MakeFilterPipeline(xbrz_frag, single_texture_pipeline_layout)},
|
||||
mmpx_pipeline{MakeFilterPipeline(mmpx_frag, single_texture_pipeline_layout)},
|
||||
refine_pipeline{MakeFilterPipeline(refine_frag, three_textures_pipeline_layout)},
|
||||
linear_sampler{device.createSampler(SAMPLER_CREATE_INFO<vk::Filter::eLinear>)},
|
||||
nearest_sampler{device.createSampler(SAMPLER_CREATE_INFO<vk::Filter::eNearest>)} {
|
||||
|
||||
|
|
@ -230,19 +296,33 @@ 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);
|
||||
// Destroy texture filtering pipelines
|
||||
device.destroyPipeline(bicubic_pipeline);
|
||||
device.destroyPipeline(scale_force_pipeline);
|
||||
device.destroyPipeline(xbrz_pipeline);
|
||||
device.destroyPipeline(mmpx_pipeline);
|
||||
device.destroyPipeline(refine_pipeline);
|
||||
device.destroySampler(linear_sampler);
|
||||
device.destroySampler(nearest_sampler);
|
||||
}
|
||||
|
||||
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 +352,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 +381,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 +612,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 +628,258 @@ 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) {
|
||||
FilterPassThreeTextures(surface, surface, surface, surface, refine_pipeline,
|
||||
three_textures_pipeline_layout, blit);
|
||||
}
|
||||
|
||||
void BlitHelper::FilterBicubic(Surface& surface, const VideoCore::TextureBlit& blit) {
|
||||
FilterPass(surface, surface, bicubic_pipeline, single_texture_pipeline_layout, blit);
|
||||
}
|
||||
|
||||
void BlitHelper::FilterScaleForce(Surface& surface, const VideoCore::TextureBlit& blit) {
|
||||
FilterPass(surface, surface, scale_force_pipeline, single_texture_pipeline_layout, blit);
|
||||
}
|
||||
|
||||
void BlitHelper::FilterXbrz(Surface& surface, const VideoCore::TextureBlit& blit) {
|
||||
FilterPass(surface, surface, xbrz_pipeline, single_texture_pipeline_layout, blit);
|
||||
}
|
||||
|
||||
void BlitHelper::FilterMMPX(Surface& surface, const VideoCore::TextureBlit& blit) {
|
||||
FilterPass(surface, surface, mmpx_pipeline, single_texture_pipeline_layout, blit);
|
||||
}
|
||||
|
||||
vk::Pipeline BlitHelper::MakeFilterPipeline(vk::ShaderModule fragment_shader,
|
||||
vk::PipelineLayout layout) {
|
||||
const std::array stages = MakeStages(full_screen_vert, fragment_shader);
|
||||
// Use color format for render pass, always a color target
|
||||
const auto renderpass = renderpass_cache.GetRenderpass(VideoCore::PixelFormat::RGBA8,
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -27,6 +27,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 +39,23 @@ public:
|
|||
private:
|
||||
vk::Pipeline MakeComputePipeline(vk::ShaderModule shader, vk::PipelineLayout layout);
|
||||
vk::Pipeline MakeDepthStencilBlitPipeline();
|
||||
vk::Pipeline MakeFilterPipeline(vk::ShaderModule fragment_shader, vk::PipelineLayout layout);
|
||||
|
||||
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;
|
||||
|
|
@ -51,18 +69,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::Pipeline bicubic_pipeline;
|
||||
vk::Pipeline scale_force_pipeline;
|
||||
vk::Pipeline xbrz_pipeline;
|
||||
vk::Pipeline mmpx_pipeline;
|
||||
vk::Pipeline refine_pipeline;
|
||||
vk::Sampler linear_sampler;
|
||||
vk::Sampler nearest_sampler;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2020 yuzu Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -103,13 +103,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()),
|
||||
|
|
|
|||
|
|
@ -1,9 +1,23 @@
|
|||
// 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.
|
||||
|
||||
#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"
|
||||
|
|
@ -451,6 +465,49 @@ void TextureRuntime::ClearTextureWithRenderpass(Surface& surface,
|
|||
});
|
||||
}
|
||||
|
||||
vk::UniqueImageView MakeFramebufferImageView(vk::Device device, vk::Image image, vk::Format format,
|
||||
vk::ImageAspectFlags aspect, u32 base_level = 0) {
|
||||
// For framebuffer attachments, we must always use levelCount=1 to avoid
|
||||
// Vulkan validation errors about mipLevel being outside of the allowed range
|
||||
const vk::ImageViewCreateInfo view_info = {
|
||||
.image = image,
|
||||
.viewType = vk::ImageViewType::e2D,
|
||||
.format = format,
|
||||
.subresourceRange{
|
||||
.aspectMask = aspect,
|
||||
.baseMipLevel = base_level, // Use the specified base mip level
|
||||
.levelCount = 1, // Framebuffers require a single mip level
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
},
|
||||
};
|
||||
return device.createImageViewUnique(view_info);
|
||||
}
|
||||
|
||||
vk::ImageView CreateFramebufferImageView(const Instance* instance, vk::Image image,
|
||||
vk::Format format, vk::ImageAspectFlags aspect) {
|
||||
// Always create a view with a single mip level for framebuffer attachments
|
||||
const vk::ImageViewCreateInfo view_info = {
|
||||
.image = image,
|
||||
.viewType = vk::ImageViewType::e2D,
|
||||
.format = format,
|
||||
.subresourceRange{
|
||||
.aspectMask = aspect,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1, // Always use 1 for framebuffers to avoid Vulkan validation errors
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
},
|
||||
};
|
||||
return instance->GetDevice().createImageView(view_info);
|
||||
}
|
||||
|
||||
bool IsImagelessFramebufferSupported(const Instance* instance) {
|
||||
// We're not using imageless framebuffers to avoid validation errors
|
||||
// Even if the extension is supported, we'll use standard framebuffers for better compatibility
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TextureRuntime::CopyTextures(Surface& source, Surface& dest,
|
||||
std::span<const VideoCore::TextureCopy> copies) {
|
||||
renderpass_cache.EndRendering();
|
||||
|
|
@ -700,7 +757,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;
|
||||
}
|
||||
|
||||
|
|
@ -720,18 +777,25 @@ 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, traits.usage,
|
||||
flags, traits.aspect, need_format_list, DebugName(false));
|
||||
handles[0] = MakeHandle(instance, width, height, levels, texture_type, format, 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));
|
||||
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);
|
||||
|
|
@ -788,6 +852,50 @@ 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;
|
||||
|
|
@ -876,15 +984,24 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Surface::UploadCustom(const VideoCore::Material* material, u32 level) {
|
||||
|
|
@ -1251,7 +1368,18 @@ vk::ImageView Surface::ImageView(u32 index) const noexcept {
|
|||
|
||||
vk::ImageView Surface::FramebufferView() noexcept {
|
||||
is_framebuffer = true;
|
||||
return ImageView();
|
||||
|
||||
// If we already have a framebuffer-compatible view, return it
|
||||
if (framebuffer_view) {
|
||||
return framebuffer_view.get();
|
||||
}
|
||||
|
||||
// Create a new view with a single mip level for framebuffer compatibility
|
||||
// This is critical to avoid VUID-VkFramebufferCreateInfo-pAttachments-00883 validation errors
|
||||
framebuffer_view = MakeFramebufferImageView(
|
||||
instance->GetDevice(), Image(), instance->GetTraits(pixel_format).native, Aspect(), 0);
|
||||
|
||||
return framebuffer_view.get();
|
||||
}
|
||||
|
||||
vk::ImageView Surface::DepthView() noexcept {
|
||||
|
|
@ -1329,6 +1457,8 @@ 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();
|
||||
|
|
@ -1339,7 +1469,8 @@ 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);
|
||||
const auto attachments = std::array{ImageView()};
|
||||
// Use FramebufferView() instead of ImageView() to ensure single mip level
|
||||
const auto attachments = std::array{FramebufferView()};
|
||||
framebuffers[index] = MakeFramebuffer(instance->GetDevice(), render_pass, GetScaledWidth(),
|
||||
GetScaledHeight(), attachments);
|
||||
return framebuffers[index].get();
|
||||
|
|
@ -1351,11 +1482,20 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
|
|||
if (is_depth_stencil && !depth_traits.blit_support) {
|
||||
LOG_WARNING(Render_Vulkan, "Depth scale unsupported by hardware");
|
||||
return;
|
||||
}
|
||||
} // Check if texture filtering is enabled
|
||||
const bool texture_filter_enabled =
|
||||
Settings::values.texture_filter.GetValue() != Settings::TextureFilter::NoFilter;
|
||||
|
||||
scheduler->Record([src_image = Image(!up_scale), aspect = Aspect(),
|
||||
filter = MakeFilter(pixel_format), dst_image = Image(up_scale),
|
||||
blit](vk::CommandBuffer render_cmdbuf) {
|
||||
// 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(), blit,
|
||||
texture_filter_enabled](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},
|
||||
|
|
@ -1368,7 +1508,15 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
|
|||
static_cast<s32>(blit.dst_rect.bottom), 0},
|
||||
vk::Offset3D{static_cast<s32>(blit.dst_rect.right), static_cast<s32>(blit.dst_rect.top),
|
||||
1},
|
||||
};
|
||||
}; // Ensure we're using the right filter for texture filtered upscaling
|
||||
vk::Filter actual_filter;
|
||||
if (texture_filter_enabled) {
|
||||
// When texture filtering is enabled, always use LINEAR filtering
|
||||
actual_filter = vk::Filter::eLinear;
|
||||
} else {
|
||||
// When texture filtering is disabled, use the filter appropriate for the texture format
|
||||
actual_filter = filter;
|
||||
}
|
||||
|
||||
const vk::ImageBlit blit_area = {
|
||||
.srcSubresource{
|
||||
|
|
@ -1389,7 +1537,7 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
|
|||
|
||||
const std::array read_barriers = {
|
||||
vk::ImageMemoryBarrier{
|
||||
.srcAccessMask = vk::AccessFlagBits::eMemoryWrite,
|
||||
.srcAccessMask = src_access,
|
||||
.dstAccessMask = vk::AccessFlagBits::eTransferRead,
|
||||
.oldLayout = vk::ImageLayout::eGeneral,
|
||||
.newLayout = vk::ImageLayout::eTransferSrcOptimal,
|
||||
|
|
@ -1399,10 +1547,7 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
|
|||
.subresourceRange = MakeSubresourceRange(aspect, blit.src_level),
|
||||
},
|
||||
vk::ImageMemoryBarrier{
|
||||
.srcAccessMask = vk::AccessFlagBits::eShaderRead |
|
||||
vk::AccessFlagBits::eDepthStencilAttachmentRead |
|
||||
vk::AccessFlagBits::eColorAttachmentRead |
|
||||
vk::AccessFlagBits::eTransferRead,
|
||||
.srcAccessMask = dst_access,
|
||||
.dstAccessMask = vk::AccessFlagBits::eTransferWrite,
|
||||
.oldLayout = vk::ImageLayout::eGeneral,
|
||||
.newLayout = vk::ImageLayout::eTransferDstOptimal,
|
||||
|
|
@ -1440,7 +1585,7 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
|
|||
vk::DependencyFlagBits::eByRegion, {}, {}, read_barriers);
|
||||
|
||||
render_cmdbuf.blitImage(src_image, vk::ImageLayout::eTransferSrcOptimal, dst_image,
|
||||
vk::ImageLayout::eTransferDstOptimal, blit_area, filter);
|
||||
vk::ImageLayout::eTransferDstOptimal, blit_area, actual_filter);
|
||||
|
||||
render_cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::PipelineStageFlagBits::eAllCommands,
|
||||
|
|
@ -1449,9 +1594,9 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
|
|||
}
|
||||
|
||||
Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferParams& params,
|
||||
Surface* color, Surface* depth)
|
||||
Surface* color, Surface* depth_stencil)
|
||||
: VideoCore::FramebufferParams{params},
|
||||
res_scale{color ? color->res_scale : (depth ? depth->res_scale : 1u)} {
|
||||
res_scale{color ? color->res_scale : (depth_stencil ? depth_stencil->res_scale : 1u)} {
|
||||
auto& renderpass_cache = runtime.GetRenderpassCache();
|
||||
if (shadow_rendering && !color) {
|
||||
return;
|
||||
|
|
@ -1468,30 +1613,62 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferPa
|
|||
}
|
||||
images[index] = surface->Image();
|
||||
aspects[index] = surface->Aspect();
|
||||
image_views[index] = shadow_rendering ? surface->StorageView() : surface->FramebufferView();
|
||||
};
|
||||
|
||||
boost::container::static_vector<vk::ImageView, 2> attachments;
|
||||
|
||||
// Prepare the surfaces for use in framebuffer
|
||||
if (color) {
|
||||
prepare(0, color);
|
||||
attachments.emplace_back(image_views[0]);
|
||||
}
|
||||
|
||||
if (depth) {
|
||||
prepare(1, depth);
|
||||
attachments.emplace_back(image_views[1]);
|
||||
if (depth_stencil) {
|
||||
prepare(1, depth_stencil);
|
||||
}
|
||||
|
||||
const vk::Device device = runtime.GetInstance().GetDevice();
|
||||
|
||||
// Create appropriate image views for the framebuffer
|
||||
boost::container::static_vector<vk::ImageView, 2> fb_attachments;
|
||||
|
||||
if (color) {
|
||||
vk::UniqueImageView single_level_view =
|
||||
MakeFramebufferImageView(device, color->Image(), color->traits.native, color->Aspect());
|
||||
fb_attachments.push_back(single_level_view.get());
|
||||
framebuffer_views.push_back(std::move(single_level_view));
|
||||
}
|
||||
|
||||
if (depth_stencil) {
|
||||
vk::UniqueImageView single_level_view = MakeFramebufferImageView(
|
||||
device, depth_stencil->Image(), depth_stencil->traits.native, depth_stencil->Aspect());
|
||||
fb_attachments.push_back(single_level_view.get());
|
||||
framebuffer_views.push_back(std::move(single_level_view));
|
||||
}
|
||||
if (shadow_rendering) {
|
||||
render_pass =
|
||||
renderpass_cache.GetRenderpass(PixelFormat::Invalid, PixelFormat::Invalid, false);
|
||||
framebuffer = MakeFramebuffer(device, render_pass, color->GetScaledWidth(),
|
||||
color->GetScaledHeight(), {});
|
||||
// For shadow rendering, we need a special render pass with depth-only
|
||||
// Since shadow rendering doesn't output to color buffer, we use depth-only render pass
|
||||
render_pass = renderpass_cache.GetRenderpass(PixelFormat::Invalid, formats[1], false);
|
||||
|
||||
// Find the depth attachment in fb_attachments
|
||||
boost::container::static_vector<vk::ImageView, 1> shadow_attachments;
|
||||
if (depth_stencil) {
|
||||
// Depth attachment is the last one added (after color if present)
|
||||
shadow_attachments.push_back(fb_attachments.back());
|
||||
} else if (!fb_attachments.empty()) {
|
||||
// Fallback to first attachment if no depth_stencil
|
||||
shadow_attachments.push_back(fb_attachments[0]);
|
||||
}
|
||||
|
||||
// Create framebuffer with depth attachment only
|
||||
framebuffer = MakeFramebuffer(
|
||||
device, render_pass, color ? color->GetScaledWidth() : depth_stencil->GetScaledWidth(),
|
||||
color ? color->GetScaledHeight() : depth_stencil->GetScaledHeight(),
|
||||
shadow_attachments);
|
||||
} else {
|
||||
render_pass = renderpass_cache.GetRenderpass(formats[0], formats[1], false);
|
||||
framebuffer = MakeFramebuffer(device, render_pass, width, height, attachments);
|
||||
// For normal rendering, create a render pass that matches our attachments
|
||||
render_pass = renderpass_cache.GetRenderpass(
|
||||
color ? formats[0] : PixelFormat::Invalid,
|
||||
depth_stencil ? formats[1] : PixelFormat::Invalid, false);
|
||||
// Create the framebuffer with attachments matching the render pass
|
||||
framebuffer = MakeFramebuffer(device, render_pass, width, height, fb_attachments);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -110,6 +110,8 @@ 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;
|
||||
|
|
@ -128,6 +130,21 @@ 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;
|
||||
|
||||
|
|
@ -184,6 +201,7 @@ public:
|
|||
std::array<Handle, 3> handles{};
|
||||
std::array<vk::UniqueFramebuffer, 2> framebuffers{};
|
||||
Handle copy_handle;
|
||||
vk::UniqueImageView framebuffer_view;
|
||||
vk::UniqueImageView depth_view;
|
||||
vk::UniqueImageView stencil_view;
|
||||
vk::UniqueImageView storage_view;
|
||||
|
|
@ -244,6 +262,7 @@ private:
|
|||
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};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue