video_core/renderer_vulkan: fix shadow rendering (#1634)
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux (appimage) (push) Waiting to run
citra-build / linux (appimage-wayland) (push) Waiting to run
citra-build / linux (fresh) (push) Waiting to run
citra-build / macos (arm64) (push) Waiting to run
citra-build / macos (x86_64) (push) Waiting to run
citra-build / macos-universal (push) Blocked by required conditions
citra-build / windows (msvc) (push) Waiting to run
citra-build / windows (msys2) (push) Waiting to run
citra-build / android (googleplay) (push) Waiting to run
citra-build / android (vanilla) (push) Waiting to run
citra-build / docker (push) Waiting to run
citra-format / clang-format (push) Waiting to run
citra-transifex / transifex (push) Waiting to run

This commit is contained in:
jbm11208 2026-01-18 17:56:35 -05:00 committed by GitHub
parent 4deb7e63b5
commit 0571187bd3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 85 additions and 150 deletions

View File

@ -263,12 +263,6 @@ BlitHelper::BlitHelper(const Instance& instance_, Scheduler& scheduler_,
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>)} {
@ -311,12 +305,6 @@ BlitHelper::~BlitHelper() {
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);
}
@ -665,32 +653,54 @@ bool BlitHelper::Filter(Surface& surface, const VideoCore::TextureBlit& blit) {
}
void BlitHelper::FilterAnime4K(Surface& surface, const VideoCore::TextureBlit& blit) {
FilterPassThreeTextures(surface, surface, surface, surface, refine_pipeline,
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) {
FilterPass(surface, surface, bicubic_pipeline, single_texture_pipeline_layout, 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) {
FilterPass(surface, surface, scale_force_pipeline, single_texture_pipeline_layout, 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) {
FilterPass(surface, surface, xbrz_pipeline, single_texture_pipeline_layout, 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) {
FilterPass(surface, surface, mmpx_pipeline, single_texture_pipeline_layout, 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) {
vk::PipelineLayout layout,
VideoCore::PixelFormat color_format) {
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);
// 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()),

View File

@ -4,6 +4,7 @@
#pragma once
#include "video_core/rasterizer_cache/pixel_format.h"
#include "video_core/renderer_vulkan/vk_resource_pool.h"
namespace VideoCore {
@ -39,7 +40,9 @@ public:
private:
vk::Pipeline MakeComputePipeline(vk::ShaderModule shader, vk::PipelineLayout layout);
vk::Pipeline MakeDepthStencilBlitPipeline();
vk::Pipeline MakeFilterPipeline(vk::ShaderModule fragment_shader, vk::PipelineLayout layout);
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);
@ -90,11 +93,6 @@ private:
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;
};

View File

@ -465,49 +465,6 @@ 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();
@ -866,7 +823,6 @@ Surface::Surface(TextureRuntime& runtime_, u32 width_, u32 height_, VideoCore::P
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");
@ -1366,24 +1322,6 @@ vk::ImageView Surface::ImageView(u32 index) const noexcept {
return image_view;
}
vk::ImageView Surface::FramebufferView() noexcept {
is_framebuffer = true;
const u32 index = res_scale == 1 ? 0u : 1u;
// If we already have a framebuffer-compatible view, return it
if (framebuffer_view[index]) {
return framebuffer_view[index].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[index] = MakeFramebufferImageView(
instance->GetDevice(), Image(), instance->GetTraits(pixel_format).native, Aspect(), 0);
return framebuffer_view[index].get();
}
vk::ImageView Surface::DepthView() noexcept {
if (depth_view) {
return depth_view.get();
@ -1471,13 +1409,29 @@ 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 FramebufferView() instead of ImageView() to ensure single mip level
const auto attachments = std::array{FramebufferView()};
// Use AttachmentView() to get single mip level view for framebuffer
const auto attachments = std::array{AttachmentView()};
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;
@ -1605,62 +1559,44 @@ 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();
};
boost::container::static_vector<vk::ImageView, 2> attachments;
// Prepare the surfaces for use in framebuffer
if (color) {
prepare(0, color);
}
if (!shadow_rendering) {
// Prepare the surfaces for use in framebuffer
if (color) {
prepare(0, color);
attachments.emplace_back(image_views[0]);
}
if (depth_stencil) {
prepare(1, depth_stencil);
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);
}
}
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) {
// 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);
// 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();
} else {
// 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);
render_pass = renderpass_cache.GetRenderpass(formats[0], formats[1], false);
framebuffer = MakeFramebuffer(device, render_pass, width, height, attachments);
}
}

View File

@ -148,9 +148,6 @@ public:
/// 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;
@ -163,6 +160,9 @@ 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);
@ -201,7 +201,6 @@ public:
std::array<Handle, 3> handles{};
std::array<vk::UniqueFramebuffer, 2> framebuffers{};
Handle copy_handle;
std::array<vk::UniqueImageView, 2> framebuffer_view;
vk::UniqueImageView depth_view;
vk::UniqueImageView stencil_view;
vk::UniqueImageView storage_view;
@ -249,14 +248,6 @@ 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{};