Compare commits

...

6 commits

Author SHA1 Message Date
d3558b794d [Vk] Improve Stencil Handling and Fix Read-After-Write Hazard
1. Improves stencil handling:
   - Adds surface type detection to distinguish between color, depth, stencil, and depth-stencil formats
   - Only enables stencil load/store operations for surfaces that actually contain stencil data
   - Avoids unnecessary stencil operations for non-stencil formats (DONT_CARE)

2. Fixes read-after-write (RAW) synchronization hazards:
   - Adds a subpass self-dependency (subpass 0 → subpass 0)
   - Synchronizes color/depth writes with subsequent shader reads
   - Uses VK_DEPENDENCY_BY_REGION_BIT for efficient synchronization
   - Covers all relevant stages:
     • src: Color output + Early/Late fragment tests
     • dst: Fragment shader
     • Access: Write → Read transitions
here is what hazard looks like [1147.550616] Render.Vulkan <Critical> video_core/vulkan_common/vulkan_debug_callback.cpp:DebugUtilCallback:55: Validation Error: [ SYNC-HAZARD-READ-AFTER-WRITE ] Object 0: handle = 0x7409630000000192, type = VK_OBJECT_TYPE_IMAGE_VIEW; | MessageID = 0xe4d96472 | vkCmdDrawIndexed: Hazard READ_AFTER_WRITE for VkImageView 0x7409630000000192[], in VkCommandBuffer 0xb400007cb003ea70[], and VkPipeline 0x44d3470000000213[], VkDescriptorSet 0x0[], type: VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, imageLayout: VK_IMAGE_LAYOUT_GENERAL, binding #2, index 0. Access info (usage: SYNC_FRAGMENT_SHADER_SHADER_SAMPLED_READ, prior_usage: SYNC_IMAGE_LAYOUT_TRANSITION, write_barriers: SYNC_FRAGMENT_SHADER_COLOR_ATTACHMENT_READ|SYNC_FRAGMENT_SHADER_DEPTH_STENCIL_ATTACHMENT_READ|SYNC_FRAGMENT_SHADER_INPUT_ATTACHMENT_READ|SYNC_EARLY_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_READ|SYNC_EARLY_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE|SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_READ|SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE|SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_READ|SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE|SYNC_SUBPASS_SHADER_HUAWEI_INPUT_ATTACHMENT_READ, command: vkCmdPipelineBarrier, seq_no: 45, reset_no: 129).
2025-08-13 15:40:45 +02:00
a5d883c910 revert c93ba3f4ca
revert Fix VUID error 02816

[ VUID-vkCmdPipelineBarrier-dstAccessMask-02816 ] The Vulkan spec states: The dstAccessMask member of each element of pMemoryBarriers must only include access flags that are supported by one or more of the pipeline stages in dstStageMask, as specified in the table of supported access types. Fixes the said validation error.
2025-08-13 15:40:45 +02:00
86cfd7ed87 Fix VUID error 02816
[ VUID-vkCmdPipelineBarrier-dstAccessMask-02816 ] The Vulkan spec states: The dstAccessMask member of each element of pMemoryBarriers must only include access flags that are supported by one or more of the pipeline stages in dstStageMask, as specified in the table of supported access types. Fixes the said validation error.
2025-08-13 15:40:45 +02:00
383fb23348
Revert image view usage flags regression introduced in 492d3856e8. (#241)
Maxwell format `VK_FORMAT_A8B8G8R8_SRGB_PACK32` does not support storage. However a `A8B8G8R8_UNORM` view is created for a image with that format which supports storage. The previous patch ignored image view format usage making it impossible for the pipeline to render to the texture.
This commit reverts the image usage override. However, there is still a mismatch between image format usage and image view format usage.

Reviewed-on: eden-emu/eden#241
Reviewed-by: Shinmegumi <shinmegumi@eden-emu.dev>
Co-authored-by: weakboson <weakboson@quantum-field.net>
Co-committed-by: weakboson <weakboson@quantum-field.net>
2025-08-13 15:35:57 +02:00
89d40c6302
[vk, texture_cache] MSAA ensure no more crash (#245)
> The shared_ptr<Image> capture ensures the temporary image outlives all queued GPU work (both the upload/download step and the MSAA compute copy). Without this, drivers read freed memory
> The temp image always has STORAGE & TRANSFER usage, matching what the compute MSAA path actually binds.
> Since this only a temp fix ontop of the previous commit, we only use the MSAA path for color; depth/stencil still use the original safe route.
> Should still be reworked though, as seen in the other MSAA commis that are open.

Reviewed-on: eden-emu/eden#245
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: Shinmegumi <shinmegumi@eden-emu.dev>
Co-authored-by: Maufeat <sahyno1996@gmail.com>
Co-committed-by: Maufeat <sahyno1996@gmail.com>
2025-08-13 01:42:56 +02:00
234e41193e
[desktop] Fix Default theme on Windows 10 (#246)
This fixes the default theme on Windows 10. Regression introduced in commit: [3f02d77](3f02d7713f)

Reviewed-on: eden-emu/eden#246
Reviewed-by: crueter <crueter@eden-emu.dev>
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2025-08-12 20:01:55 +02:00
3 changed files with 149 additions and 76 deletions

View file

@ -14,23 +14,55 @@
namespace Vulkan {
namespace {
using VideoCore::Surface::PixelFormat;
using VideoCore::Surface::SurfaceType;
VkAttachmentDescription AttachmentDescription(const Device& device, PixelFormat format,
VkSampleCountFlagBits samples) {
using MaxwellToVK::SurfaceFormat;
return {
.flags = {},
.format = SurfaceFormat(device, FormatType::Optimal, true, format).format,
.samples = samples,
.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE,
.initialLayout = VK_IMAGE_LAYOUT_GENERAL,
.finalLayout = VK_IMAGE_LAYOUT_GENERAL,
};
}
} // Anonymous namespace
constexpr SurfaceType GetSurfaceType(PixelFormat format) {
switch (format) {
// Depth formats
case PixelFormat::D16_UNORM:
case PixelFormat::D32_FLOAT:
case PixelFormat::X8_D24_UNORM:
return SurfaceType::Depth;
// Stencil formats
case PixelFormat::S8_UINT:
return SurfaceType::Stencil;
// Depth+Stencil formats
case PixelFormat::D24_UNORM_S8_UINT:
case PixelFormat::S8_UINT_D24_UNORM:
case PixelFormat::D32_FLOAT_S8_UINT:
return SurfaceType::DepthStencil;
// Everything else is a color texture
default:
return SurfaceType::ColorTexture;
}
}
VkAttachmentDescription AttachmentDescription(const Device& device, PixelFormat format,
VkSampleCountFlagBits samples) {
using MaxwellToVK::SurfaceFormat;
const SurfaceType surface_type = GetSurfaceType(format);
const bool has_stencil = surface_type == SurfaceType::DepthStencil ||
surface_type == SurfaceType::Stencil;
return {
.flags = {},
.format = SurfaceFormat(device, FormatType::Optimal, true, format).format,
.samples = samples,
.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.stencilLoadOp = has_stencil ? VK_ATTACHMENT_LOAD_OP_LOAD
: VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = has_stencil ? VK_ATTACHMENT_STORE_OP_STORE
: VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = VK_IMAGE_LAYOUT_GENERAL,
.finalLayout = VK_IMAGE_LAYOUT_GENERAL,
};
}
} // Anonymous namespace
RenderPassCache::RenderPassCache(const Device& device_) : device{&device_} {}
@ -78,6 +110,18 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
.preserveAttachmentCount = 0,
.pPreserveAttachments = nullptr,
};
const VkSubpassDependency dependency{
.srcSubpass = 0, // Current subpass
.dstSubpass = 0, // Same subpass (self-dependency)
.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
.dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT
};
pair->second = device->GetLogical().CreateRenderPass({
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
.pNext = nullptr,
@ -86,8 +130,8 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
.pAttachments = descriptions.empty() ? nullptr : descriptions.data(),
.subpassCount = 1,
.pSubpasses = &subpass,
.dependencyCount = 0,
.pDependencies = nullptr,
.dependencyCount = 1,
.pDependencies = &dependency,
});
return *pair->second;
}

View file

@ -7,6 +7,7 @@
#include <algorithm>
#include <array>
#include <span>
#include <memory>
#include <vector>
#include <boost/container/small_vector.hpp>
@ -1538,7 +1539,7 @@ Image::Image(const VideoCommon::NullImageParams& params) : VideoCommon::ImageBas
Image::~Image() = default;
void Image::UploadMemory(VkBuffer buffer, VkDeviceSize offset,
std::span<const VideoCommon::BufferImageCopy> copies) {
std::span<const VideoCommon::BufferImageCopy> copies) {
// TODO: Move this to another API
const bool is_rescaled = True(flags & ImageFlagBits::Rescaled);
if (is_rescaled) {
@ -1546,70 +1547,89 @@ void Image::UploadMemory(VkBuffer buffer, VkDeviceSize offset,
}
// Handle MSAA upload if necessary
/* WARNING, TODO: This code uses some hacks, besides being fundamentally ugly
since tropic didn't want to touch it for a long time, so it needs a rewrite from someone better than me at vulkan.*/
if (info.num_samples > 1 && runtime->CanUploadMSAA()) {
// Only use MSAA copy pass for color formats
// TODO: Depth/stencil formats need special handling
if (aspect_mask == VK_IMAGE_ASPECT_COLOR_BIT) {
// Create a temporary non-MSAA image to upload the data first
ImageInfo temp_info = info;
temp_info.num_samples = 1;
/* WARNING, TODO: This code uses some hacks, besides being fundamentally ugly
since tropic didn't want to touch it for a long time, so it needs a rewrite from someone
better than me at vulkan. */
// CHANGE: Gate the MSAA path more strictly and only use it for color, when the pass and device
// support are available. Avoid running the MSAA path when prerequisites aren't met, preventing
// validation and runtime issues.
const bool wants_msaa_upload = info.num_samples > 1 &&
aspect_mask == VK_IMAGE_ASPECT_COLOR_BIT &&
runtime->CanUploadMSAA() && runtime->msaa_copy_pass != nullptr &&
runtime->device.IsStorageImageMultisampleSupported();
// Create image with same usage flags as the target image to avoid validation errors
VkImageCreateInfo image_ci = MakeImageCreateInfo(runtime->device, temp_info);
image_ci.usage = original_image.UsageFlags();
vk::Image temp_image = runtime->memory_allocator.CreateImage(image_ci);
if (wants_msaa_upload) {
// Create a temporary non-MSAA image to upload the data first
ImageInfo temp_info = info;
temp_info.num_samples = 1;
// Upload to the temporary non-MSAA image
scheduler->RequestOutsideRenderPassOperationContext();
auto vk_copies = TransformBufferImageCopies(copies, offset, aspect_mask);
const VkBuffer src_buffer = buffer;
const VkImage temp_vk_image = *temp_image;
const VkImageAspectFlags vk_aspect_mask = aspect_mask;
scheduler->Record([src_buffer, temp_vk_image, vk_aspect_mask, vk_copies](vk::CommandBuffer cmdbuf) {
CopyBufferToImage(cmdbuf, src_buffer, temp_vk_image, vk_aspect_mask, false, vk_copies);
});
// CHANGE: Build a fresh VkImageCreateInfo with robust usage flags for the temp image.
// Using the target image's usage as-is could miss STORAGE/TRANSFER bits and trigger validation errors.
VkImageCreateInfo image_ci = MakeImageCreateInfo(runtime->device, temp_info);
image_ci.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT;
// Use MSAACopyPass to convert from non-MSAA to MSAA
std::vector<VideoCommon::ImageCopy> image_copies;
for (const auto& copy : copies) {
VideoCommon::ImageCopy image_copy;
image_copy.src_offset = {0, 0, 0}; // Use zero offset for source
image_copy.dst_offset = copy.image_offset;
image_copy.src_subresource = copy.image_subresource;
image_copy.dst_subresource = copy.image_subresource;
image_copy.extent = copy.image_extent;
image_copies.push_back(image_copy);
}
// CHANGE: The previous stack-allocated wrapper was destroyed at function exit,
// which could destroy VkImage before the GPU used it.
auto temp_wrapper = std::make_shared<Image>(*runtime, temp_info, 0, 0);
temp_wrapper->original_image = runtime->memory_allocator.CreateImage(image_ci);
temp_wrapper->current_image = &Image::original_image;
temp_wrapper->aspect_mask = VK_IMAGE_ASPECT_COLOR_BIT;
temp_wrapper->initialized = true;
// wrapper image for the temporary image
Image temp_wrapper(*runtime, temp_info, 0, 0);
temp_wrapper.original_image = std::move(temp_image);
temp_wrapper.current_image = &Image::original_image;
temp_wrapper.aspect_mask = aspect_mask;
temp_wrapper.initialized = true;
// Use MSAACopyPass to convert from non-MSAA to MSAA
runtime->msaa_copy_pass->CopyImage(*this, temp_wrapper, image_copies, false);
std::exchange(initialized, true);
return;
}
// For depth/stencil formats, fall back to regular upload
} else {
// Regular non-MSAA upload
// Upload to the temporary non-MSAA image
scheduler->RequestOutsideRenderPassOperationContext();
auto vk_copies = TransformBufferImageCopies(copies, offset, aspect_mask);
auto vk_copies = TransformBufferImageCopies(copies, offset, temp_wrapper->aspect_mask);
const VkBuffer src_buffer = buffer;
const VkImage vk_image = *original_image;
const VkImageAspectFlags vk_aspect_mask = aspect_mask;
const bool is_initialized = std::exchange(initialized, true);
scheduler->Record([src_buffer, vk_image, vk_aspect_mask, is_initialized,
vk_copies](vk::CommandBuffer cmdbuf) {
CopyBufferToImage(cmdbuf, src_buffer, vk_image, vk_aspect_mask, is_initialized, vk_copies);
const VkImage temp_vk_image = *temp_wrapper->original_image;
const VkImageAspectFlags vk_aspect_mask = temp_wrapper->aspect_mask;
scheduler->Record([src_buffer, temp_vk_image, vk_aspect_mask, vk_copies,
// CHANGE: capture shared_ptr to pin lifetime through submission
keep = temp_wrapper](vk::CommandBuffer cmdbuf) {
CopyBufferToImage(cmdbuf, src_buffer, temp_vk_image, vk_aspect_mask, false, vk_copies);
});
// Use MSAACopyPass to convert from non-MSAA to MSAA
// CHANGE: only lifetime and usage were fixed.
std::vector<VideoCommon::ImageCopy> image_copies;
image_copies.reserve(copies.size());
for (const auto& copy : copies) {
VideoCommon::ImageCopy image_copy;
image_copy.src_offset = {0, 0, 0}; // Use zero offset for source
image_copy.dst_offset = copy.image_offset;
image_copy.src_subresource = copy.image_subresource;
image_copy.dst_subresource = copy.image_subresource;
image_copy.extent = copy.image_extent;
image_copies.push_back(image_copy);
}
runtime->msaa_copy_pass->CopyImage(*this, *temp_wrapper, image_copies,
/*msaa_to_non_msaa=*/false);
std::exchange(initialized, true);
// CHANGE: Add a no-op recording that captures temp_wrapper to ensure it stays alive
// at least until commands are submitted/recorded.
scheduler->Record([keep = std::move(temp_wrapper)](vk::CommandBuffer) {});
// CHANGE: Restore scaling before returning from the MSAA path.
if (is_rescaled) {
ScaleUp();
}
return;
}
// Regular non-MSAA upload (original behavior preserved)
scheduler->RequestOutsideRenderPassOperationContext();
auto vk_copies = TransformBufferImageCopies(copies, offset, aspect_mask);
const VkBuffer src_buffer = buffer;
const VkImage vk_image = *original_image;
const VkImageAspectFlags vk_aspect_mask = aspect_mask;
const bool is_initialized = std::exchange(initialized, true);
scheduler->Record([src_buffer, vk_image, vk_aspect_mask, is_initialized,
vk_copies](vk::CommandBuffer cmdbuf) {
CopyBufferToImage(cmdbuf, src_buffer, vk_image, vk_aspect_mask, is_initialized, vk_copies);
});
if (is_rescaled) {
ScaleUp();
}
@ -1994,10 +2014,15 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
}
}
const auto format_info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format);
if (ImageUsageFlags(format_info, format) != image.UsageFlags()) {
LOG_WARNING(Render_Vulkan,
"Image view format {} has different usage flags than image format {}", format,
image.info.format);
}
const VkImageViewUsageCreateInfo image_view_usage{
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO,
.pNext = nullptr,
.usage = image.UsageFlags(),
.usage = ImageUsageFlags(format_info, format),
};
const VkImageViewCreateInfo create_info{
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,

View file

@ -444,7 +444,6 @@ GMainWindow::GMainWindow(bool has_broken_vulkan)
ui->setupUi(this);
statusBar()->hide();
// Check dark mode before a theme is loaded
startup_icon_theme = QIcon::themeName();
// fallback can only be set once, colorful theme icons are okay on both light/dark
QIcon::setFallbackThemeName(QStringLiteral("colorful"));
@ -5475,6 +5474,10 @@ void GMainWindow::UpdateUITheme() {
current_theme = default_theme;
}
#ifdef _WIN32
QIcon::setThemeName(current_theme);
AdjustLinkColor();
#else
if (current_theme == QStringLiteral("default") || current_theme == QStringLiteral("colorful")) {
QIcon::setThemeName(current_theme == QStringLiteral("colorful") ? current_theme
: startup_icon_theme);
@ -5487,6 +5490,7 @@ void GMainWindow::UpdateUITheme() {
QIcon::setThemeSearchPaths(QStringList(QStringLiteral(":/icons")));
AdjustLinkColor();
}
#endif
if (current_theme != default_theme) {
QString theme_uri{QStringLiteral(":%1/style.qss").arg(current_theme)};