From 5af01bef9fe3b3b8711e277d230f09ff0872c493 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 3 Jul 2026 00:58:40 -0400 Subject: [PATCH 01/70] [vulkan] Initial implementation Synchronization2 --- .../renderer_vulkan/vk_master_semaphore.cpp | 124 ++++++++++++++++++ .../vulkan_common/vulkan_device.cpp | 5 + src/video_core/vulkan_common/vulkan_device.h | 9 +- .../vulkan_common/vulkan_wrapper.cpp | 10 ++ src/video_core/vulkan_common/vulkan_wrapper.h | 78 +++++++++++ 5 files changed, 225 insertions(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp index b535097b36..be5a1144ce 100644 --- a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp +++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp @@ -130,6 +130,70 @@ VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf, VkSemaphore wait_semaphore, u64 host_tick) { const VkSemaphore timeline_semaphore = *semaphore; + if (device.HasSynchronization2()) { + const std::array cmdbuffer_infos{{ + { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, + .pNext = nullptr, + .commandBuffer = *upload_cmdbuf, + .deviceMask = 0, + }, + { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, + .pNext = nullptr, + .commandBuffer = *cmdbuf, + .deviceMask = 0, + }, + }}; + + std::array signal_infos{{ + { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .pNext = nullptr, + .semaphore = timeline_semaphore, + .value = host_tick, + .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, + .deviceIndex = 0, + }, + {}, + }}; + u32 num_signal_semaphores = 1; + if (signal_semaphore) { + signal_infos[1] = VkSemaphoreSubmitInfo{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .pNext = nullptr, + .semaphore = signal_semaphore, + .value = 0, + .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, + .deviceIndex = 0, + }; + num_signal_semaphores = 2; + } + + const u32 num_wait_semaphores = wait_semaphore ? 1 : 0; + const VkSemaphoreSubmitInfo wait_info{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .pNext = nullptr, + .semaphore = wait_semaphore, + .value = 0, + .stageMask = static_cast(wait_stage_mask), + .deviceIndex = 0, + }; + + const VkSubmitInfo2 submit_info2{ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2, + .pNext = nullptr, + .flags = 0, + .waitSemaphoreInfoCount = num_wait_semaphores, + .pWaitSemaphoreInfos = num_wait_semaphores ? &wait_info : nullptr, + .commandBufferInfoCount = static_cast(cmdbuffer_infos.size()), + .pCommandBufferInfos = cmdbuffer_infos.data(), + .signalSemaphoreInfoCount = num_signal_semaphores, + .pSignalSemaphoreInfos = signal_infos.data(), + }; + return device.GetGraphicsQueue().Submit2(submit_info2); + } + const u32 num_signal_semaphores = signal_semaphore ? 2 : 1; const std::array signal_values{host_tick, u64(0)}; const std::array signal_semaphores{timeline_semaphore, signal_semaphore}; @@ -172,6 +236,66 @@ VkResult MasterSemaphore::SubmitQueueFence(vk::CommandBuffer& cmdbuf, vk::CommandBuffer& upload_cmdbuf, VkSemaphore signal_semaphore, VkSemaphore wait_semaphore, u64 host_tick) { + if (device.HasSynchronization2()) { + const std::array cmdbuffer_infos{{ + { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, + .pNext = nullptr, + .commandBuffer = *upload_cmdbuf, + .deviceMask = 0, + }, + { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, + .pNext = nullptr, + .commandBuffer = *cmdbuf, + .deviceMask = 0, + }, + }}; + + const u32 num_signal_semaphores = signal_semaphore ? 1 : 0; + const VkSemaphoreSubmitInfo signal_info{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .pNext = nullptr, + .semaphore = signal_semaphore, + .value = 0, + .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, + .deviceIndex = 0, + }; + + const u32 num_wait_semaphores = wait_semaphore ? 1 : 0; + const VkSemaphoreSubmitInfo wait_info{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .pNext = nullptr, + .semaphore = wait_semaphore, + .value = 0, + .stageMask = static_cast(wait_stage_mask), + .deviceIndex = 0, + }; + + const VkSubmitInfo2 submit_info2{ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2, + .pNext = nullptr, + .flags = 0, + .waitSemaphoreInfoCount = num_wait_semaphores, + .pWaitSemaphoreInfos = num_wait_semaphores ? &wait_info : nullptr, + .commandBufferInfoCount = static_cast(cmdbuffer_infos.size()), + .pCommandBufferInfos = cmdbuffer_infos.data(), + .signalSemaphoreInfoCount = num_signal_semaphores, + .pSignalSemaphoreInfos = num_signal_semaphores ? &signal_info : nullptr, + }; + + auto fence = GetFreeFence(); + auto result = device.GetGraphicsQueue().Submit2(submit_info2, *fence); + + if (result == VK_SUCCESS) { + std::scoped_lock lock{wait_mutex}; + wait_queue.emplace(host_tick, std::move(fence)); + wait_cv.notify_one(); + } + + return result; + } + const u32 num_signal_semaphores = signal_semaphore ? 1 : 0; const u32 num_wait_semaphores = wait_semaphore ? 1 : 0; diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 7bf73dcead..ce46234fff 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -1375,6 +1375,11 @@ void Device::RemoveUnsuitableExtensions() { // VK_KHR_maintenance8 extensions.maintenance8 = loaded_extensions.contains(VK_KHR_MAINTENANCE_8_EXTENSION_NAME); RemoveExtensionIfUnsuitable(extensions.maintenance8, VK_KHR_MAINTENANCE_8_EXTENSION_NAME); + + // VK_KHR_synchronization2 + extensions.synchronization2 = features.synchronization2.synchronization2; + RemoveExtensionFeatureIfUnsuitable(extensions.synchronization2, features.synchronization2, + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); } void Device::SetupFamilies(VkSurfaceKHR surface) { diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 110d0c1199..d224ad62b0 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -43,7 +43,8 @@ VK_DEFINE_HANDLE(VmaAllocator) FEATURE(EXT, ShaderDemoteToHelperInvocation, SHADER_DEMOTE_TO_HELPER_INVOCATION, \ shader_demote_to_helper_invocation) \ FEATURE(EXT, SubgroupSizeControl, SUBGROUP_SIZE_CONTROL, subgroup_size_control) \ - FEATURE(KHR, Maintenance4, MAINTENANCE_4, maintenance4) + FEATURE(KHR, Maintenance4, MAINTENANCE_4, maintenance4) \ + FEATURE(KHR, Synchronization2, SYNCHRONIZATION_2, synchronization2) #define FOR_EACH_VK_FEATURE_1_4(FEATURE) @@ -180,6 +181,7 @@ VK_DEFINE_HANDLE(VmaAllocator) FEATURE_NAME(robustness2, nullDescriptor) \ FEATURE_NAME(shader_float16_int8, shaderFloat16) \ FEATURE_NAME(shader_float16_int8, shaderInt8) \ + FEATURE_NAME(synchronization2, synchronization2) \ FEATURE_NAME(timeline_semaphore, timelineSemaphore) \ FEATURE_NAME(transform_feedback, transformFeedback) \ FEATURE_NAME(uniform_buffer_standard_layout, uniformBufferStandardLayout) \ @@ -722,6 +724,11 @@ FN_MAX_LIMIT_LIST bool HasTimelineSemaphore() const; + /// Returns true if the device supports VK_KHR_synchronization2. + bool HasSynchronization2() const { + return extensions.synchronization2; + } + /// Returns the minimum supported version of SPIR-V. u32 SupportedSpirvVersion() const { if (instance_version >= VK_API_VERSION_1_3) { diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp index 3d082c8360..95793d252f 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.cpp +++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp @@ -123,6 +123,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkCmdEndDebugUtilsLabelEXT); X(vkCmdFillBuffer); X(vkCmdPipelineBarrier); + X(vkCmdPipelineBarrier2); X(vkCmdPushConstants); X(vkCmdPushDescriptorSetWithTemplateKHR); X(vkCmdSetBlendConstants); @@ -226,6 +227,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkGetSemaphoreCounterValue); X(vkMapMemory); X(vkQueueSubmit); + X(vkQueueSubmit2); X(vkResetFences); X(vkResetQueryPool); X(vkSetDebugUtilsObjectNameEXT); @@ -252,6 +254,14 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { Proc(dld.vkCmdDrawIndirectCount, dld, "vkCmdDrawIndirectCountKHR", device); Proc(dld.vkCmdDrawIndexedIndirectCount, dld, "vkCmdDrawIndexedIndirectCountKHR", device); } + + // Synchronization2 is core in Vulkan 1.3, otherwise requires VK_KHR_synchronization2 + if (!dld.vkCmdPipelineBarrier2) { + Proc(dld.vkCmdPipelineBarrier2, dld, "vkCmdPipelineBarrier2KHR", device); + } + if (!dld.vkQueueSubmit2) { + Proc(dld.vkQueueSubmit2, dld, "vkQueueSubmit2KHR", device); + } #undef X } diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h index 87870893c5..6b458080d3 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.h +++ b/src/video_core/vulkan_common/vulkan_wrapper.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -237,6 +238,7 @@ struct DeviceDispatch : InstanceDispatch { PFN_vkCmdEndTransformFeedbackEXT vkCmdEndTransformFeedbackEXT{}; PFN_vkCmdFillBuffer vkCmdFillBuffer{}; PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier{}; + PFN_vkCmdPipelineBarrier2 vkCmdPipelineBarrier2{}; PFN_vkCmdPushConstants vkCmdPushConstants{}; PFN_vkCmdPushDescriptorSetWithTemplateKHR vkCmdPushDescriptorSetWithTemplateKHR{}; PFN_vkCmdResolveImage vkCmdResolveImage{}; @@ -340,6 +342,7 @@ struct DeviceDispatch : InstanceDispatch { PFN_vkGetSemaphoreCounterValue vkGetSemaphoreCounterValue{}; PFN_vkMapMemory vkMapMemory{}; PFN_vkQueueSubmit vkQueueSubmit{}; + PFN_vkQueueSubmit2 vkQueueSubmit2{}; PFN_vkResetFences vkResetFences{}; PFN_vkResetQueryPool vkResetQueryPool{}; PFN_vkSetDebugUtilsObjectNameEXT vkSetDebugUtilsObjectNameEXT{}; @@ -819,6 +822,13 @@ public: return dld->vkQueueSubmit(queue, submit_infos.size(), submit_infos.data(), fence); } + /// Submits using VK_KHR_synchronization2 / Vulkan 1.3 vkQueueSubmit2. + /// Only valid to call when the device dispatch table has vkQueueSubmit2 loaded. + VkResult Submit2(Span submit_infos, + VkFence fence = VK_NULL_HANDLE) const noexcept { + return dld->vkQueueSubmit2(queue, submit_infos.size(), submit_infos.data(), fence); + } + VkResult Present(const VkPresentInfoKHR& present_info) const noexcept { return dld->vkQueuePresentKHR(queue, &present_info); } @@ -1287,6 +1297,74 @@ public: VkDependencyFlags dependency_flags, Span memory_barriers, Span buffer_barriers, Span image_barriers) const noexcept { + // Legacy VkPipelineStageFlagBits/VkAccessFlagBits are bit-compatible with their + // Synchronization2 *2 counterparts, so barriers can be widened without a lookup table. + static constexpr u32 MaxBarriers = 16; + if (dld->vkCmdPipelineBarrier2 && memory_barriers.size() <= MaxBarriers && + buffer_barriers.size() <= MaxBarriers && image_barriers.size() <= MaxBarriers) { + const auto src_stage_mask2 = static_cast(src_stage_mask); + const auto dst_stage_mask2 = static_cast(dst_stage_mask); + + std::array memory_barriers2; + for (u32 i = 0; i < memory_barriers.size(); ++i) { + memory_barriers2[i] = VkMemoryBarrier2{ + .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2, + .pNext = nullptr, + .srcStageMask = src_stage_mask2, + .srcAccessMask = static_cast(memory_barriers[i].srcAccessMask), + .dstStageMask = dst_stage_mask2, + .dstAccessMask = static_cast(memory_barriers[i].dstAccessMask), + }; + } + std::array buffer_barriers2; + for (u32 i = 0; i < buffer_barriers.size(); ++i) { + const auto& barrier = buffer_barriers[i]; + buffer_barriers2[i] = VkBufferMemoryBarrier2{ + .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2, + .pNext = nullptr, + .srcStageMask = src_stage_mask2, + .srcAccessMask = static_cast(barrier.srcAccessMask), + .dstStageMask = dst_stage_mask2, + .dstAccessMask = static_cast(barrier.dstAccessMask), + .srcQueueFamilyIndex = barrier.srcQueueFamilyIndex, + .dstQueueFamilyIndex = barrier.dstQueueFamilyIndex, + .buffer = barrier.buffer, + .offset = barrier.offset, + .size = barrier.size, + }; + } + std::array image_barriers2; + for (u32 i = 0; i < image_barriers.size(); ++i) { + const auto& barrier = image_barriers[i]; + image_barriers2[i] = VkImageMemoryBarrier2{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, + .pNext = nullptr, + .srcStageMask = src_stage_mask2, + .srcAccessMask = static_cast(barrier.srcAccessMask), + .dstStageMask = dst_stage_mask2, + .dstAccessMask = static_cast(barrier.dstAccessMask), + .oldLayout = barrier.oldLayout, + .newLayout = barrier.newLayout, + .srcQueueFamilyIndex = barrier.srcQueueFamilyIndex, + .dstQueueFamilyIndex = barrier.dstQueueFamilyIndex, + .image = barrier.image, + .subresourceRange = barrier.subresourceRange, + }; + } + const VkDependencyInfo dependency_info{ + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, + .pNext = nullptr, + .dependencyFlags = dependency_flags, + .memoryBarrierCount = memory_barriers.size(), + .pMemoryBarriers = memory_barriers2.data(), + .bufferMemoryBarrierCount = buffer_barriers.size(), + .pBufferMemoryBarriers = buffer_barriers2.data(), + .imageMemoryBarrierCount = image_barriers.size(), + .pImageMemoryBarriers = image_barriers2.data(), + }; + dld->vkCmdPipelineBarrier2(handle, &dependency_info); + return; + } dld->vkCmdPipelineBarrier(handle, src_stage_mask, dst_stage_mask, dependency_flags, memory_barriers.size(), memory_barriers.data(), buffer_barriers.size(), buffer_barriers.data(), From e1721012d8dfabcd96aa1fc17bd10d68c2fa5de4 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 3 Jul 2026 01:49:56 -0400 Subject: [PATCH 02/70] [vulkan] Set actual dynamic state handling for topology --- .../renderer_vulkan/fixed_pipeline_state.cpp | 24 ++++++++++++++++++- .../renderer_vulkan/vk_graphics_pipeline.cpp | 1 + .../renderer_vulkan/vk_rasterizer.cpp | 16 ++++++------- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp index c6aa28fa23..3c041a48cb 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp @@ -39,6 +39,24 @@ constexpr std::array POLYGON_OFFSET_ENABLE_LUT = { POLYGON, // Patches }; +constexpr std::array TOPOLOGY_CLASS_REPRESENTATIVE_LUT = { + Maxwell::PrimitiveTopology::Points, // Points + Maxwell::PrimitiveTopology::Lines, // Lines + Maxwell::PrimitiveTopology::LineLoop, // LineLoop + Maxwell::PrimitiveTopology::LineStrip, // LineStrip + Maxwell::PrimitiveTopology::Triangles, // Triangles + Maxwell::PrimitiveTopology::Triangles, // TriangleStrip + Maxwell::PrimitiveTopology::Triangles, // TriangleFan + Maxwell::PrimitiveTopology::Triangles, // Quads + Maxwell::PrimitiveTopology::Triangles, // QuadStrip + Maxwell::PrimitiveTopology::Triangles, // Polygon + Maxwell::PrimitiveTopology::LinesAdjacency, // LinesAdjacency + Maxwell::PrimitiveTopology::LinesAdjacency, // LineStripAdjacency + Maxwell::PrimitiveTopology::TrianglesAdjacency, // TrianglesAdjacency + Maxwell::PrimitiveTopology::TrianglesAdjacency, // TriangleStripAdjacency + Maxwell::PrimitiveTopology::Patches, // Patches +}; + void RefreshXfbState(VideoCommon::TransformFeedbackState& state, const Maxwell& regs) { std::ranges::transform(regs.transform_feedback.controls, state.layouts.begin(), [](const auto& layout) { @@ -71,7 +89,11 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe tessellation_clockwise.Assign(regs.tessellation.params.output_primitives.Value() == Maxwell::Tessellation::OutputPrimitives::Triangles_CW); patch_control_points_minus_one.Assign(regs.patch_vertices - 1); - topology.Assign(topology_); + const bool can_collapse_topology_class = + features.has_extended_dynamic_state && features.has_extended_dynamic_state_2; + topology.Assign(can_collapse_topology_class + ? TOPOLOGY_CLASS_REPRESENTATIVE_LUT[static_cast(topology_)] + : topology_); msaa_mode.Assign(regs.anti_alias_samples_mode); raw2 = 0; diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 0c0174391c..6f1c07f84b 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -865,6 +865,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE_EXT, VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE_EXT, VK_DYNAMIC_STATE_STENCIL_OP_EXT, + VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT, }; dynamic_states.insert(dynamic_states.end(), extended.begin(), extended.end()); diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 863ebb72ec..c4ac861927 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -1011,12 +1011,12 @@ void RasterizerVulkan::UpdateDynamicStates() { auto& regs = maxwell3d->regs; auto& flags = maxwell3d->dirty.flags; const auto topology = maxwell3d->draw_manager.draw_state.topology; - if (state_tracker.ChangePrimitiveTopology(topology)) { + const bool topology_changed = state_tracker.ChangePrimitiveTopology(topology); + if (topology_changed) { flags[Dirty::DepthBiasEnable] = true; flags[Dirty::PrimitiveRestartEnable] = true; } - // Core Dynamic States (Vulkan 1.0) - Always active regardless of dyna_state setting UpdateViewportsState(regs); UpdateScissorsState(regs); UpdateDepthBias(regs); @@ -1025,7 +1025,6 @@ void RasterizerVulkan::UpdateDynamicStates() { UpdateStencilFaces(regs); UpdateLineWidth(regs); - // EDS1: CullMode, DepthCompare, FrontFace, StencilOp, DepthBoundsTest, DepthTest, DepthWrite, StencilTest if (device.IsExtExtendedDynamicStateSupported()) { UpdateCullMode(regs); UpdateDepthCompareOp(regs); @@ -1037,21 +1036,24 @@ void RasterizerVulkan::UpdateDynamicStates() { UpdateDepthWriteEnable(regs); UpdateStencilTestEnable(regs); } + if (topology_changed) { + scheduler.Record([topology_vk = MaxwellToVK::PrimitiveTopology(device, topology)]( + vk::CommandBuffer cmdbuf) { + cmdbuf.SetPrimitiveTopologyEXT(topology_vk); + }); + } } - // EDS2: PrimitiveRestart, RasterizerDiscard, DepthBias enable/disable if (device.IsExtExtendedDynamicState2Supported()) { UpdatePrimitiveRestartEnable(regs); UpdateRasterizerDiscardEnable(regs); UpdateDepthBiasEnable(regs); } - // EDS2 Extras: LogicOp operation selection if (device.IsExtExtendedDynamicState2ExtrasSupported()) { UpdateLogicOp(regs); } - // EDS3 Enables: LogicOpEnable, DepthClamp, LineStipple, ConservativeRaster if (device.IsExtExtendedDynamicState3EnablesSupported()) { using namespace Tegra::Engines; // AMD Workaround: LogicOp incompatible with float render targets @@ -1076,12 +1078,10 @@ void RasterizerVulkan::UpdateDynamicStates() { UpdateAlphaToOneEnable(regs); } - // EDS3 Blending: ColorBlendEnable, ColorBlendEquation, ColorWriteMask if (device.IsExtExtendedDynamicState3BlendingSupported()) { UpdateBlending(regs); } - // Vertex Input Dynamic State: Independent from EDS levels if (device.IsExtVertexInputDynamicStateSupported()) { if (auto* gp = pipeline_cache.CurrentGraphicsPipeline(); gp && gp->HasDynamicVertexInput()) { UpdateVertexInput(regs); From 48cc8f4c8ced97ce8d99e00032fd4a6c60561320 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 3 Jul 2026 02:29:06 -0400 Subject: [PATCH 03/70] [vulkan] XFB memory barrier added --- src/video_core/renderer_vulkan/vk_query_cache.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp index 52f52bd577..9a41bc52b2 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp @@ -922,19 +922,31 @@ private: return; } has_flushed_end_pending = false; - + // Refresh buffer state before ending transform feedback to ensure counters_count is up-to-date. UpdateBuffers(); + static constexpr VkMemoryBarrier XFB_OUTPUT_BARRIER{ + .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_TRANSFORM_FEEDBACK_WRITE_BIT_EXT, + .dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT, + }; if (buffers_count == 0) { LOG_DEBUG(Render_Vulkan, "EndTransformFeedbackEXT called with no counters (buffers_count=0)"); scheduler.Record([](vk::CommandBuffer cmdbuf) { cmdbuf.EndTransformFeedbackEXT(0, 0, nullptr, nullptr); + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT, + vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, 0, + XFB_OUTPUT_BARRIER); }); } else { LOG_DEBUG(Render_Vulkan, "EndTransformFeedbackEXT called with counters (buffers_count={})", buffers_count); scheduler.Record([this, total = static_cast(buffers_count)](vk::CommandBuffer cmdbuf) { cmdbuf.EndTransformFeedbackEXT(0, total, counter_buffers.data(), offsets.data()); + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT, + vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, 0, + XFB_OUTPUT_BARRIER); }); } } From fbd94e4237ce84cac1f23bf7bbcd5b6e388c4c84 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 3 Jul 2026 20:35:45 -0400 Subject: [PATCH 04/70] [vulkan] Implementation color write enable + color border swizzle --- .../renderer_vulkan/fixed_pipeline_state.cpp | 10 ++++++++++ .../renderer_vulkan/fixed_pipeline_state.h | 2 ++ .../renderer_vulkan/vk_graphics_pipeline.cpp | 11 +++++++---- .../renderer_vulkan/vk_pipeline_cache.cpp | 7 ++++++- .../renderer_vulkan/vk_rasterizer.cpp | 17 ++++++++++++++++- src/video_core/renderer_vulkan/vk_rasterizer.h | 1 + .../renderer_vulkan/vk_texture_cache.cpp | 5 ++++- src/video_core/vulkan_common/vulkan_device.cpp | 15 +++++++++++++++ src/video_core/vulkan_common/vulkan_device.h | 17 +++++++++++++++++ src/video_core/vulkan_common/vulkan_wrapper.cpp | 1 + src/video_core/vulkan_common/vulkan_wrapper.h | 5 +++++ 11 files changed, 84 insertions(+), 7 deletions(-) diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp index 3c041a48cb..1378c30efd 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp @@ -80,6 +80,7 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe extended_dynamic_state_2_logic_op.Assign(features.has_extended_dynamic_state_2_logic_op ? 1 : 0); extended_dynamic_state_3_blend.Assign(features.has_extended_dynamic_state_3_blend ? 1 : 0); extended_dynamic_state_3_enables.Assign(features.has_extended_dynamic_state_3_enables ? 1 : 0); + color_write_enable_dynamic.Assign(features.has_color_write_enable ? 1 : 0); dynamic_vertex_input.Assign(features.has_dynamic_vertex_input ? 1 : 0); xfb_enabled.Assign(regs.transform_feedback_enabled != 0); ndc_minus_one_to_one.Assign(regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1 : 0); @@ -209,6 +210,15 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe maxwell3d.dirty.flags[Dirty::Blending] = false; for (size_t index = 0; index < attachments.size(); ++index) { attachments[index].Refresh(regs, index); + auto& attachment = attachments[index]; + if (color_write_enable_dynamic && attachment.mask_r == 0 && + attachment.mask_g == 0 && attachment.mask_b == 0 && + attachment.mask_a == 0) { + attachment.mask_r.Assign(1); + attachment.mask_g.Assign(1); + attachment.mask_b.Assign(1); + attachment.mask_a.Assign(1); + } } } } diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.h b/src/video_core/renderer_vulkan/fixed_pipeline_state.h index 44157d686d..0b631a9e89 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.h +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.h @@ -31,6 +31,7 @@ struct DynamicFeatures { bool has_dynamic_state3_logic_op_enable; bool has_dynamic_state3_line_stipple_enable; bool has_dynamic_vertex_input; + bool has_color_write_enable; bool has_provoking_vertex; bool has_provoking_vertex_first_mode; bool has_provoking_vertex_last_mode; @@ -208,6 +209,7 @@ struct FixedPipelineState { BitField<12, 2, u32> tessellation_spacing; BitField<14, 1, u32> tessellation_clockwise; BitField<15, 5, u32> patch_control_points_minus_one; + BitField<20, 1, u32> color_write_enable_dynamic; BitField<24, 4, Maxwell::PrimitiveTopology> topology; BitField<28, 4, Tegra::Texture::MsaaMode> msaa_mode; diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 6f1c07f84b..b6c2d855d2 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -869,15 +869,13 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { }; dynamic_states.insert(dynamic_states.end(), extended.begin(), extended.end()); - // VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT is part of EDS1 - // Only use it if VIDS is not active (VIDS replaces it with full vertex input control) + // VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT if (!key.state.dynamic_vertex_input) { dynamic_states.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT); } } - // VK_DYNAMIC_STATE_VERTEX_INPUT_EXT (VIDS) - Independent from EDS - // Provides full dynamic vertex input control, replaces VERTEX_INPUT_BINDING_STRIDE + // VK_DYNAMIC_STATE_VERTEX_INPUT_EXT if (key.state.dynamic_vertex_input) { dynamic_states.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_EXT); } @@ -907,6 +905,11 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { dynamic_states.insert(dynamic_states.end(), extended3.begin(), extended3.end()); } + // VK_EXT_color_write_enable fallback for fully on/off render targets when EDS3 blending is not available. + if (!key.state.extended_dynamic_state_3_blend && key.state.color_write_enable_dynamic) { + dynamic_states.push_back(VK_DYNAMIC_STATE_COLOR_WRITE_ENABLE_EXT); + } + // EDS3 - Enables (composite: per-feature) if (key.state.extended_dynamic_state_3_enables) { if (device.SupportsDynamicState3DepthClampEnable()) { diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 15f7e9bf43..24ec481018 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -500,6 +500,8 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, device.IsExtExtendedDynamicState3BlendingSupported(); dynamic_features.has_extended_dynamic_state_3_enables = device.IsExtExtendedDynamicState3EnablesSupported(); + dynamic_features.has_color_write_enable = + device.IsExtColorWriteEnableSupported(); dynamic_features.has_dynamic_state3_depth_clamp_enable = dynamic_features.has_extended_dynamic_state_3_enables && device.SupportsDynamicState3DepthClampEnable(); @@ -632,7 +634,10 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading dynamic_features.has_extended_dynamic_state_3_blend || (key.state.extended_dynamic_state_3_enables != 0) != dynamic_features.has_extended_dynamic_state_3_enables || - (key.state.dynamic_vertex_input != 0) != dynamic_features.has_dynamic_vertex_input) { + (key.state.color_write_enable_dynamic != 0) != + dynamic_features.has_color_write_enable || + (key.state.dynamic_vertex_input != 0) != + dynamic_features.has_dynamic_vertex_input) { return; } diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index c4ac861927..618a8a8a6b 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -1080,6 +1080,8 @@ void RasterizerVulkan::UpdateDynamicStates() { if (device.IsExtExtendedDynamicState3BlendingSupported()) { UpdateBlending(regs); + } else if (device.IsExtColorWriteEnableSupported()) { + UpdateColorWriteEnable(regs); } if (device.IsExtVertexInputDynamicStateSupported()) { @@ -1094,7 +1096,6 @@ void RasterizerVulkan::HandleTransformFeedback() { const auto& regs = maxwell3d->regs; if (!device.IsExtTransformFeedbackSupported()) { - // If the guest enabled transform feedback, warn once that the device lacks support. if (regs.transform_feedback_enabled != 0) { std::call_once(warn_unsupported, [&] { LOG_WARNING(Render_Vulkan, "Transform feedback requested by guest but VK_EXT_transform_feedback is unavailable; queries disabled"); @@ -1774,6 +1775,20 @@ void RasterizerVulkan::UpdateBlending(Tegra::Engines::Maxwell3D::Regs& regs) { } } +void RasterizerVulkan::UpdateColorWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs) { + if (!state_tracker.TouchColorMask()) { + return; + } + std::array setup_enables{}; + for (size_t index = 0; index < Maxwell::NumRenderTargets; index++) { + const auto& mask = regs.color_mask[regs.color_mask_common ? 0 : index]; + setup_enables[index] = (mask.R || mask.G || mask.B || mask.A) ? VK_TRUE : VK_FALSE; + } + scheduler.Record([setup_enables](vk::CommandBuffer cmdbuf) { + cmdbuf.SetColorWriteEnableEXT(0, setup_enables); + }); +} + void RasterizerVulkan::UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) { if (!state_tracker.TouchStencilTestEnable()) { return; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 841933d31d..206bf1043a 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -191,6 +191,7 @@ private: void UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateLogicOp(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateBlending(Tegra::Engines::Maxwell3D::Regs& regs); + void UpdateColorWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs); diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 7804e33d46..93ea5cf8db 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -2297,12 +2297,15 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t const void* pnext = nullptr; if (has_custom_border_colors) { pnext = &border_ci; - // Log extension usage for custom border color if (GPU::Logging::IsActive()) { GPU::Logging::GPULogger::GetInstance().LogExtensionUsage( "VK_EXT_custom_border_color", "Sampler::Sampler"); } } + if (device.IsExtBorderColorSwizzleSupported() && GPU::Logging::IsActive()) { + GPU::Logging::GPULogger::GetInstance().LogExtensionUsage( + "VK_EXT_border_color_swizzle", "Sampler::Sampler"); + } const VkSamplerReductionModeCreateInfoEXT reduction_ci{ .sType = VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO_EXT, .pNext = pnext, diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index ce46234fff..e99d721c16 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -1170,6 +1170,21 @@ bool Device::GetSuitability(bool requires_swapchain) { } void Device::RemoveUnsuitableExtensions() { + // VK_EXT_color_write_enable + extensions.color_write_enable = features.color_write_enable.colorWriteEnable; + RemoveExtensionFeatureIfUnsuitable(extensions.color_write_enable, features.color_write_enable, + VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME); + + // VK_EXT_border_color_swizzle + if (extensions.border_color_swizzle) { + extensions.border_color_swizzle = + features.border_color_swizzle.borderColorSwizzle && + features.border_color_swizzle.borderColorSwizzleFromImage; + } + RemoveExtensionFeatureIfUnsuitable(extensions.border_color_swizzle, + features.border_color_swizzle, + VK_EXT_BORDER_COLOR_SWIZZLE_EXTENSION_NAME); + // VK_EXT_custom_border_color if (extensions.custom_border_color) { extensions.custom_border_color = diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index d224ad62b0..e0924543e6 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -50,6 +50,8 @@ VK_DEFINE_HANDLE(VmaAllocator) // Define all features which may be used by the implementation and require an extension here. #define FOR_EACH_VK_FEATURE_EXT(FEATURE) \ + FEATURE(EXT, BorderColorSwizzle, BORDER_COLOR_SWIZZLE, border_color_swizzle) \ + FEATURE(EXT, ColorWriteEnable, COLOR_WRITE_ENABLE, color_write_enable) \ FEATURE(EXT, CustomBorderColor, CUSTOM_BORDER_COLOR, custom_border_color) \ FEATURE(EXT, DepthBiasControl, DEPTH_BIAS_CONTROL, depth_bias_control) \ FEATURE(EXT, DepthClipControl, DEPTH_CLIP_CONTROL, depth_clip_control) \ @@ -584,6 +586,21 @@ FN_MAX_LIMIT_LIST return features.custom_border_color.customBorderColorWithoutFormat; } + /// Returns true if the device supports VK_EXT_color_write_enable. + bool IsExtColorWriteEnableSupported() const { + return extensions.color_write_enable; + } + + /// Returns true if the device supports VK_EXT_border_color_swizzle. + bool IsExtBorderColorSwizzleSupported() const { + return extensions.border_color_swizzle; + } + + /// Returns true if borderColorSwizzleFromImage is available. + bool IsBorderColorSwizzleFromImageSupported() const { + return features.border_color_swizzle.borderColorSwizzleFromImage; + } + /// Returns true if the device supports VK_EXT_extended_dynamic_state. bool IsExtExtendedDynamicStateSupported() const { return extensions.extended_dynamic_state; diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp index 95793d252f..6f35e0b0c3 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.cpp +++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp @@ -162,6 +162,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkCmdSetStencilTestEnableEXT); X(vkCmdSetVertexInputEXT); X(vkCmdSetColorWriteMaskEXT); + X(vkCmdSetColorWriteEnableEXT); X(vkCmdSetColorBlendEnableEXT); X(vkCmdSetColorBlendEquationEXT); X(vkCmdResolveImage); diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h index 6b458080d3..80e1a34154 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.h +++ b/src/video_core/vulkan_common/vulkan_wrapper.h @@ -277,6 +277,7 @@ struct DeviceDispatch : InstanceDispatch { PFN_vkCmdSetVertexInputEXT vkCmdSetVertexInputEXT{}; PFN_vkCmdSetViewport vkCmdSetViewport{}; PFN_vkCmdSetColorWriteMaskEXT vkCmdSetColorWriteMaskEXT{}; + PFN_vkCmdSetColorWriteEnableEXT vkCmdSetColorWriteEnableEXT{}; PFN_vkCmdSetColorBlendEnableEXT vkCmdSetColorBlendEnableEXT{}; PFN_vkCmdSetColorBlendEquationEXT vkCmdSetColorBlendEquationEXT{}; PFN_vkCmdWaitEvents vkCmdWaitEvents{}; @@ -1589,6 +1590,10 @@ public: dld->vkCmdSetColorWriteMaskEXT(handle, first, masks.size(), masks.data()); } + void SetColorWriteEnableEXT(u32 first, Span enables) const noexcept { + dld->vkCmdSetColorWriteEnableEXT(handle, first, enables.size(), enables.data()); + } + void SetColorBlendEnableEXT(u32 first, Span enables) const noexcept { dld->vkCmdSetColorBlendEnableEXT(handle, first, enables.size(), enables.data()); } From d7dc4f21433c5c065226ee834db14b6487ae1db2 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 3 Jul 2026 20:58:34 -0400 Subject: [PATCH 05/70] [vulkan] Correction on mislead of BindVertexBuffer entries --- src/video_core/renderer_vulkan/vk_buffer_cache.cpp | 4 ++-- src/video_core/renderer_vulkan/vk_buffer_cache.h | 8 +++++++- src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index 7bd0a260b3..7bc23a5e8b 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -556,7 +556,7 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset if (index >= device.GetMaxVertexInputBindings()) { return; } - if (device.IsExtExtendedDynamicStateSupported()) { + if (device.IsExtExtendedDynamicStateSupported() && !vertex_input_dynamic_state_active) { scheduler.Record([index, buffer, offset, size, stride](vk::CommandBuffer cmdbuf) { const VkDeviceSize vk_offset = buffer != VK_NULL_HANDLE ? offset : 0; const VkDeviceSize vk_size = buffer != VK_NULL_HANDLE ? size : VK_WHOLE_SIZE; @@ -596,7 +596,7 @@ void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bi if (binding_count == 0) { return; } - if (device.IsExtExtendedDynamicStateSupported()) { + if (device.IsExtExtendedDynamicStateSupported() && !vertex_input_dynamic_state_active) { scheduler.Record([bindings_ = std::move(bindings), buffer_handles_ = std::move(buffer_handles), binding_count](vk::CommandBuffer cmdbuf) { cmdbuf.BindVertexBuffers2EXT(bindings_.min_index, binding_count, buffer_handles_.data(), bindings_.offsets.data(), bindings_.sizes.data(), bindings_.strides.data()); }); diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index b73fcd162b..b26e5d2ffe 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project @@ -127,6 +127,10 @@ public: void BindVertexBuffers(VideoCommon::HostBindings& bindings); + void SetVertexInputDynamicState(bool is_active) { + vertex_input_dynamic_state_active = is_active; + } + void BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size); void BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings); @@ -185,6 +189,8 @@ private: bool limit_dynamic_storage_buffers = false; u32 max_dynamic_storage_buffers = (std::numeric_limits::max)(); + + bool vertex_input_dynamic_state_active = false; }; struct BufferCacheParams { diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index b6c2d855d2..32ae502b82 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -472,6 +472,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) { } buffer_cache.UpdateGraphicsBuffers(is_indexed); + buffer_cache.runtime.SetVertexInputDynamicState(HasDynamicVertexInput()); buffer_cache.BindHostGeometryBuffers(is_indexed); guest_descriptor_queue.Acquire(scheduler, num_descriptor_entries); From decf41a3cb00a98588aac931804440795bcf4b06 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 3 Jul 2026 21:21:14 -0400 Subject: [PATCH 06/70] Fix build --- src/video_core/renderer_vulkan/vk_rasterizer.cpp | 2 +- src/video_core/vulkan_common/vulkan_wrapper.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 618a8a8a6b..8d43fcb2a6 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -1785,7 +1785,7 @@ void RasterizerVulkan::UpdateColorWriteEnable(Tegra::Engines::Maxwell3D::Regs& r setup_enables[index] = (mask.R || mask.G || mask.B || mask.A) ? VK_TRUE : VK_FALSE; } scheduler.Record([setup_enables](vk::CommandBuffer cmdbuf) { - cmdbuf.SetColorWriteEnableEXT(0, setup_enables); + cmdbuf.SetColorWriteEnableEXT(setup_enables); }); } diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h index 80e1a34154..5c68ef9d25 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.h +++ b/src/video_core/vulkan_common/vulkan_wrapper.h @@ -1590,8 +1590,8 @@ public: dld->vkCmdSetColorWriteMaskEXT(handle, first, masks.size(), masks.data()); } - void SetColorWriteEnableEXT(u32 first, Span enables) const noexcept { - dld->vkCmdSetColorWriteEnableEXT(handle, first, enables.size(), enables.data()); + void SetColorWriteEnableEXT(Span enables) const noexcept { + dld->vkCmdSetColorWriteEnableEXT(handle, enables.size(), enables.data()); } void SetColorBlendEnableEXT(u32 first, Span enables) const noexcept { From aa5d2cf9d7230069c4f9b1bd6e496475eca3531a Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 3 Jul 2026 22:10:12 -0400 Subject: [PATCH 07/70] [qcom] Removal custom border color from QCOM drivers --- src/video_core/vulkan_common/vulkan_device.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index e99d721c16..006d8ce276 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -492,17 +492,15 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR CollectToolingInfo(); if (is_qualcomm) { - LOG_WARNING(Render_Vulkan, - "Qualcomm drivers require scaled vertex format emulation"); + LOG_WARNING(Render_Vulkan, "Qualcomm drivers require scaled vertex format emulation"); must_emulate_scaled_formats = true; - LOG_WARNING(Render_Vulkan, - "Qualcomm drivers have broken provoking vertex"); + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken CustomBorderColor."); + RemoveExtension(extensions.custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken Provoking Vertex."); RemoveExtension(extensions.provoking_vertex, VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME); - LOG_WARNING(Render_Vulkan, - "Qualcomm drivers have slow push descriptor implementation"); + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have slow push descriptor implementation."); RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); - LOG_WARNING(Render_Vulkan, - "Disabling shader float controls and 64-bit integer features on Qualcomm proprietary drivers"); + LOG_WARNING(Render_Vulkan, "Disabling shader float controls and 64-bit integer features."); RemoveExtension(extensions.shader_float_controls, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); RemoveExtensionFeature(extensions.shader_atomic_int64, features.shader_atomic_int64, VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME); From d707d241fc6f3cb1cebf1dde94d5de736ee30b11 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 3 Jul 2026 22:28:42 -0400 Subject: [PATCH 08/70] [host_shaders] Corrections on mismatchs for SINT on depth formats --- src/video_core/host_shaders/vulkan_blit_depth_stencil.frag | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/video_core/host_shaders/vulkan_blit_depth_stencil.frag b/src/video_core/host_shaders/vulkan_blit_depth_stencil.frag index 484b99773c..78581cea52 100644 --- a/src/video_core/host_shaders/vulkan_blit_depth_stencil.frag +++ b/src/video_core/host_shaders/vulkan_blit_depth_stencil.frag @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -5,11 +8,11 @@ #extension GL_ARB_shader_stencil_export : require layout(binding = 0) uniform sampler2D depth_tex; -layout(binding = 1) uniform isampler2D stencil_tex; +layout(binding = 1) uniform usampler2D stencil_tex; layout(location = 0) in vec2 texcoord; void main() { gl_FragDepth = textureLod(depth_tex, texcoord, 0).r; - gl_FragStencilRefARB = textureLod(stencil_tex, texcoord, 0).r; + gl_FragStencilRefARB = int(textureLod(stencil_tex, texcoord, 0).r); } From 3ddf349ca1f3c0a4333d79c0f7b3c3fb21ac1ba6 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 3 Jul 2026 23:01:13 -0400 Subject: [PATCH 09/70] [buffer_cache] Force offsets to 0 on descriptors and use VK_WHOLE_SIZE instead --- src/video_core/renderer_vulkan/vk_buffer_cache.h | 6 +++++- src/video_core/vulkan_common/vulkan_device.cpp | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index b26e5d2ffe..6a4f720231 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h @@ -167,7 +167,11 @@ public: private: void BindBuffer(VkBuffer buffer, u32 offset, u32 size) { - guest_descriptor_queue.AddBuffer(buffer, offset, size); + if (buffer == VK_NULL_HANDLE) { + guest_descriptor_queue.AddBuffer(buffer, 0, VK_WHOLE_SIZE); + } else { + guest_descriptor_queue.AddBuffer(buffer, offset, size); + } } void ReserveNullBuffer(); diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 006d8ce276..90eb9b05b3 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -495,7 +495,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR LOG_WARNING(Render_Vulkan, "Qualcomm drivers require scaled vertex format emulation"); must_emulate_scaled_formats = true; LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken CustomBorderColor."); - RemoveExtension(extensions.custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); + RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color, + VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken Provoking Vertex."); RemoveExtension(extensions.provoking_vertex, VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have slow push descriptor implementation."); From bd8caa5294df40a7abdda80f9727e189672e29de Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 3 Jul 2026 23:36:05 -0400 Subject: [PATCH 10/70] [texture_cache] Added b5g6r5 pixel format to depth blit resolves --- src/video_core/renderer_vulkan/vk_texture_cache.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 93ea5cf8db..0f4691bc95 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -1299,7 +1299,9 @@ void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, Im case PixelFormat::R32G32_FLOAT: case PixelFormat::R32G32_SINT: case PixelFormat::R32_FLOAT: - if ((src_view.format == PixelFormat::D32_FLOAT) && Settings::values.fix_bloom_effects.GetValue()) { + if (src_view.format == PixelFormat::D32_FLOAT && + (dst_view.format == PixelFormat::B5G6R5_UNORM || + Settings::values.fix_bloom_effects.GetValue())) { const Region2D region{ .start = {0, 0}, .end = {static_cast(dst->RenderArea().width), From 943f91cf0ba7f431de44a2d2c25cf2104792c520 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 4 Jul 2026 00:01:31 -0400 Subject: [PATCH 11/70] [texture_cache] Added typeless storage form --- src/video_core/renderer_vulkan/vk_texture_cache.cpp | 7 ++++++- src/video_core/renderer_vulkan/vk_texture_cache.h | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 0f4691bc95..bb421871bd 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -2244,7 +2244,12 @@ VkImageView ImageView::StorageView(Shader::TextureType texture_type, Shader::ImageFormat image_format) { if (image_handle) { if (image_format == Shader::ImageFormat::Typeless) { - return Handle(texture_type); + if (!typeless_storage_view) { + const auto& info = + MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format); + typeless_storage_view = MakeView(info.format, VK_IMAGE_ASPECT_COLOR_BIT); + } + return *typeless_storage_view; } const bool is_signed = image_format == Shader::ImageFormat::R8_SINT || image_format == Shader::ImageFormat::R16_SINT; diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 881135da1b..5b7769b254 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -377,6 +377,7 @@ private: std::array image_views; std::optional storage_views; + vk::ImageView typeless_storage_view; vk::ImageView depth_view; vk::ImageView stencil_view; vk::ImageView color_view; From b06b9cc7a2990803063666e39effe3f29543d216 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 4 Jul 2026 00:16:01 -0400 Subject: [PATCH 12/70] [vulkan] Pixel format handling for formats --- .../renderer_vulkan/pipeline_helper.h | 24 +++++++++++++++++++ .../renderer_vulkan/vk_compute_pipeline.cpp | 8 ++++++- .../renderer_vulkan/vk_graphics_pipeline.cpp | 8 ++++++- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/video_core/renderer_vulkan/pipeline_helper.h b/src/video_core/renderer_vulkan/pipeline_helper.h index fce1f2e252..e9adaee1d2 100644 --- a/src/video_core/renderer_vulkan/pipeline_helper.h +++ b/src/video_core/renderer_vulkan/pipeline_helper.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include @@ -22,6 +23,29 @@ namespace Vulkan { using Shader::Backend::SPIRV::NUM_TEXTURE_AND_IMAGE_SCALING_WORDS; +[[nodiscard]] inline std::optional PixelFormatFromImageFormat( + Shader::ImageFormat format) { + switch (format) { + case Shader::ImageFormat::Typeless: + return std::nullopt; + case Shader::ImageFormat::R8_UINT: + return PixelFormat::R8_UINT; + case Shader::ImageFormat::R8_SINT: + return PixelFormat::R8_SINT; + case Shader::ImageFormat::R16_UINT: + return PixelFormat::R16_UINT; + case Shader::ImageFormat::R16_SINT: + return PixelFormat::R16_SINT; + case Shader::ImageFormat::R32_UINT: + return PixelFormat::R32_UINT; + case Shader::ImageFormat::R32G32_UINT: + return PixelFormat::R32G32_UINT; + case Shader::ImageFormat::R32G32B32A32_UINT: + return PixelFormat::R32G32B32A32_UINT; + } + return std::nullopt; +} + [[nodiscard]] inline u32 NumDescriptorEntries(const Shader::Info& info) { return Shader::NumDescriptors(info.constant_buffer_descriptors) + Shader::NumDescriptors(info.storage_buffers_descriptors) + diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 13bd654a80..5525ce0e95 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -193,8 +193,14 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, is_written = desc.is_written; } ImageView& image_view = texture_cache.GetImageView(views[index].id); + PixelFormat format{image_view.format}; + if constexpr (is_image) { + if (const auto explicit_format{PixelFormatFromImageFormat(desc.format)}) { + format = *explicit_format; + } + } buffer_cache.BindComputeTextureBuffer(index, image_view.GpuAddr(), - image_view.BufferSize(), image_view.format, + image_view.BufferSize(), format, is_written, is_image); ++index; } diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 32ae502b82..ebc6da5a4b 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -426,8 +426,14 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) { is_written = desc.is_written; } ImageView& image_view{texture_cache.GetImageView(texture_buffer_it->id)}; + PixelFormat format{image_view.format}; + if constexpr (is_image) { + if (const auto explicit_format{PixelFormatFromImageFormat(desc.format)}) { + format = *explicit_format; + } + } buffer_cache.BindGraphicsTextureBuffer(stage, index, image_view.GpuAddr(), - image_view.BufferSize(), image_view.format, + image_view.BufferSize(), format, is_written, is_image); ++index; ++texture_buffer_it; From 27a0010b6e45f6da02098c7fca044dda1aa14605 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 4 Jul 2026 00:32:36 -0400 Subject: [PATCH 13/70] [vulkan] Verify UniformAndStorage capabilities on device --- src/shader_recompiler/backend/spirv/spirv_emit_context.cpp | 2 +- src/shader_recompiler/profile.h | 1 + src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | 2 ++ src/video_core/vulkan_common/vulkan_device.h | 5 +++++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 169e83d9fd..b97e0b55f6 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -1126,7 +1126,7 @@ void EmitContext::DefineConstantBuffers(const Info& info, u32& binding) { } IR::Type types{info.used_constant_buffer_types | info.used_indirect_cbuf_types}; if (True(types & IR::Type::U8)) { - if (profile.support_int8) { + if (profile.support_int8 && profile.support_uniform_and_storage_buffer_8bit) { DefineConstBuffers(*this, info, &UniformDefinitions::U8, binding, U8, 'u', sizeof(u8)); DefineConstBuffers(*this, info, &UniformDefinitions::S8, binding, S8, 's', sizeof(s8)); } else { diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h index bd7bc6ac7b..4e200d5120 100644 --- a/src/shader_recompiler/profile.h +++ b/src/shader_recompiler/profile.h @@ -15,6 +15,7 @@ struct Profile { bool unified_descriptor_binding{}; bool support_descriptor_aliasing{}; bool support_int8{}; + bool support_uniform_and_storage_buffer_8bit{}; bool support_int16{}; bool support_int64{}; bool support_vertex_instance_id{}; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 24ec481018..74caf044cb 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -383,6 +383,8 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, .unified_descriptor_binding = true, .support_descriptor_aliasing = device.IsDescriptorAliasingSupported(), .support_int8 = device.IsInt8Supported(), + .support_uniform_and_storage_buffer_8bit = + device.IsUniformAndStorageBuffer8BitAccessSupported(), .support_int16 = device.IsShaderInt16Supported(), .support_int64 = device.IsShaderInt64Supported(), .support_vertex_instance_id = false, diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index e0924543e6..457cd3286e 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -387,6 +387,11 @@ FN_MAX_LIMIT_LIST return features.shader_float16_int8.shaderInt8; } + /// Returns true if the device allows 8-bit integer members + bool IsUniformAndStorageBuffer8BitAccessSupported() const { + return features.bit8_storage.uniformAndStorageBuffer8BitAccess; + } + /// Returns true if the device supports binding multisample images as storage images. bool IsStorageImageMultisampleSupported() const { return features.features.shaderStorageImageMultisample; From f6ccf2c36852fa855181986e51001dcd3e308196 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 4 Jul 2026 01:31:25 -0400 Subject: [PATCH 14/70] [shader_recompiler] Added texture type functions --- .../backend/spirv/emit_spirv_image.cpp | 63 +++++++++++++------ .../backend/spirv/spirv_emit_context.cpp | 3 +- .../backend/spirv/spirv_emit_context.h | 1 + src/shader_recompiler/frontend/ir/modifiers.h | 7 +++ src/shader_recompiler/ir_opt/texture_pass.cpp | 4 ++ src/shader_recompiler/shader_info.h | 1 + 6 files changed, 60 insertions(+), 19 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp index df4da81356..0b3a1a7c08 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp @@ -260,6 +260,13 @@ bool IsTextureMsaa(EmitContext& ctx, const IR::TextureInstInfo& info) { return ctx.textures.at(info.descriptor_index).is_multisample; } +bool IsTextureInteger(EmitContext& ctx, const IR::TextureInstInfo& info) { + if (info.type == TextureType::Buffer) { + return false; + } + return ctx.textures.at(info.descriptor_index).is_integer; +} + Id Decorate(EmitContext& ctx, IR::Inst* inst, Id sample) { const auto info{inst->Flags()}; if (info.relaxed_precision != 0) { @@ -480,11 +487,14 @@ Id EmitBoundImageWrite(EmitContext&) { Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id bias_lc, const IR::Value& offset) { const auto info{inst->Flags()}; + const bool is_integer{IsTextureInteger(ctx, info)}; + const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]}; + Id color; if (ctx.stage == Stage::Fragment) { const ImageOperands operands(ctx, info.has_bias != 0, false, info.has_lod_clamp != 0, bias_lc, offset); - return Emit(&EmitContext::OpImageSparseSampleImplicitLod, - &EmitContext::OpImageSampleImplicitLod, ctx, inst, ctx.F32[4], + color = Emit(&EmitContext::OpImageSparseSampleImplicitLod, + &EmitContext::OpImageSampleImplicitLod, ctx, inst, result_type, Texture(ctx, info, index), coords, operands.MaskOptional(), operands.Span()); } else { // We can't use implicit lods on non-fragment stages on SPIR-V. Maxwell hardware behaves as @@ -492,26 +502,29 @@ Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& // derivatives const Id lod{ctx.Const(0.0f)}; const ImageOperands operands(ctx, false, true, info.has_lod_clamp != 0, lod, offset); - return Emit(&EmitContext::OpImageSparseSampleExplicitLod, - &EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4], + color = Emit(&EmitContext::OpImageSparseSampleExplicitLod, + &EmitContext::OpImageSampleExplicitLod, ctx, inst, result_type, Texture(ctx, info, index), coords, operands.Mask(), operands.Span()); } + return is_integer ? ctx.OpBitcast(ctx.F32[4], color) : color; } Id EmitImageSampleExplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id lod, const IR::Value& offset) { const auto info{inst->Flags()}; + const bool is_integer{IsTextureInteger(ctx, info)}; + const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]}; const ImageOperands operands(ctx, false, true, false, lod, offset); Id result = Emit(&EmitContext::OpImageSparseSampleExplicitLod, - &EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4], + &EmitContext::OpImageSampleExplicitLod, ctx, inst, result_type, Texture(ctx, info, index), coords, operands.Mask(), operands.Span()); #ifdef __ANDROID__ - if (Settings::values.fix_bloom_effects.GetValue()) { + if (!is_integer && Settings::values.fix_bloom_effects.GetValue()) { result = ctx.OpVectorTimesScalar(ctx.F32[4], result, ctx.Const(0.98f)); } #endif - return result; + return is_integer ? ctx.OpBitcast(ctx.F32[4], result) : result; } Id EmitImageSampleDrefImplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, @@ -547,30 +560,39 @@ Id EmitImageSampleDrefExplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Va Id EmitImageGather(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, const IR::Value& offset, const IR::Value& offset2) { const auto info{inst->Flags()}; + const bool is_integer{IsTextureInteger(ctx, info)}; + const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]}; const ImageOperands operands(ctx, offset, offset2); if (ctx.profile.need_gather_subpixel_offset) { coords = ImageGatherSubpixelOffset(ctx, info, TextureImage(ctx, info, index), coords); } - return Emit(&EmitContext::OpImageSparseGather, &EmitContext::OpImageGather, ctx, inst, - ctx.F32[4], Texture(ctx, info, index), coords, ctx.Const(info.gather_component), - operands.MaskOptional(), operands.Span()); + const Id color{Emit(&EmitContext::OpImageSparseGather, &EmitContext::OpImageGather, ctx, inst, + result_type, Texture(ctx, info, index), coords, + ctx.Const(info.gather_component), operands.MaskOptional(), + operands.Span())}; + return is_integer ? ctx.OpBitcast(ctx.F32[4], color) : color; } Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, const IR::Value& offset, const IR::Value& offset2, Id dref) { const auto info{inst->Flags()}; + const bool is_integer{IsTextureInteger(ctx, info)}; + const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]}; const ImageOperands operands(ctx, offset, offset2); if (ctx.profile.need_gather_subpixel_offset) { coords = ImageGatherSubpixelOffset(ctx, info, TextureImage(ctx, info, index), coords); } - return Emit(&EmitContext::OpImageSparseDrefGather, &EmitContext::OpImageDrefGather, ctx, inst, - ctx.F32[4], Texture(ctx, info, index), coords, dref, operands.MaskOptional(), - operands.Span()); + const Id color{Emit(&EmitContext::OpImageSparseDrefGather, &EmitContext::OpImageDrefGather, + ctx, inst, result_type, Texture(ctx, info, index), coords, dref, + operands.MaskOptional(), operands.Span())}; + return is_integer ? ctx.OpBitcast(ctx.F32[4], color) : color; } Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id offset, Id lod, Id ms) { const auto info{inst->Flags()}; + const bool is_integer{IsTextureInteger(ctx, info)}; + const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]}; AddOffsetToCoordinates(ctx, info, coords, offset); if (info.type == TextureType::Buffer) { lod = Id{}; @@ -580,8 +602,10 @@ Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id c lod = Id{}; } const ImageOperands operands(lod, ms); - return Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst, ctx.F32[4], - TextureImage(ctx, info, index), coords, operands.MaskOptional(), operands.Span()); + const Id color{Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst, + result_type, TextureImage(ctx, info, index), coords, + operands.MaskOptional(), operands.Span())}; + return is_integer ? ctx.OpBitcast(ctx.F32[4], color) : color; } Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id lod, @@ -626,14 +650,17 @@ Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, I Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id derivatives, const IR::Value& offset, Id lod_clamp) { const auto info{inst->Flags()}; + const bool is_integer{IsTextureInteger(ctx, info)}; + const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]}; const auto operands = info.num_derivatives == 3 ? ImageOperands(ctx, info.has_lod_clamp != 0, derivatives, ctx.Def(offset), {}, lod_clamp) : ImageOperands(ctx, info.has_lod_clamp != 0, derivatives, info.num_derivatives, offset, lod_clamp); - return Emit(&EmitContext::OpImageSparseSampleExplicitLod, - &EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4], - Texture(ctx, info, index), coords, operands.Mask(), operands.Span()); + const Id color{Emit(&EmitContext::OpImageSparseSampleExplicitLod, + &EmitContext::OpImageSampleExplicitLod, ctx, inst, result_type, + Texture(ctx, info, index), coords, operands.Mask(), operands.Span())}; + return is_integer ? ctx.OpBitcast(ctx.F32[4], color) : color; } Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords) { diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index b97e0b55f6..c5c6b2655a 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -30,7 +30,7 @@ enum class Operation { Id ImageType(EmitContext& ctx, const TextureDescriptor& desc) { const spv::ImageFormat format{spv::ImageFormat::Unknown}; - const Id type{ctx.F32[1]}; + const Id type{desc.is_integer ? ctx.U32[1] : ctx.F32[1]}; const bool depth{desc.is_depth}; const bool ms{desc.is_multisample}; switch (desc.type) { @@ -1375,6 +1375,7 @@ void EmitContext::DefineTextures(const Info& info, u32& binding, u32& scaling_in .image_type = image_type, .count = desc.count, .is_multisample = desc.is_multisample, + .is_integer = desc.is_integer, }); if (profile.supported_spirv >= 0x00010400) { interfaces.push_back(id); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index 55385de0b0..504bffc0ed 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -42,6 +42,7 @@ struct TextureDefinition { Id image_type; u32 count; bool is_multisample; + bool is_integer; }; struct TextureBufferDefinition { diff --git a/src/shader_recompiler/frontend/ir/modifiers.h b/src/shader_recompiler/frontend/ir/modifiers.h index c20c2401f7..991c51b1d6 100644 --- a/src/shader_recompiler/frontend/ir/modifiers.h +++ b/src/shader_recompiler/frontend/ir/modifiers.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -43,6 +46,10 @@ union TextureInstInfo { BitField<25, 2, u32> num_derivatives; BitField<27, 3, ImageFormat> image_format; BitField<30, 1, u32> ndv_is_active; + /// Only meaningful for the sampled-texture family (ImageSampleImplicitLod, ImageFetch, + /// ImageGather, etc.); unused/zero for storage-image opcodes, which carry their own + /// is_integer via ImageDescriptor/ImageBufferDescriptor instead. + BitField<31, 1, u32> is_integer; }; static_assert(sizeof(TextureInstInfo) <= sizeof(u32)); diff --git a/src/shader_recompiler/ir_opt/texture_pass.cpp b/src/shader_recompiler/ir_opt/texture_pass.cpp index b5ad274292..b97f6dbf17 100644 --- a/src/shader_recompiler/ir_opt/texture_pass.cpp +++ b/src/shader_recompiler/ir_opt/texture_pass.cpp @@ -562,6 +562,7 @@ public: })}; // TODO: Read this from TIC texture_descriptors[index].is_multisample |= desc.is_multisample; + texture_descriptors[index].is_integer |= desc.is_integer; return index; } @@ -791,10 +792,12 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo }); } else { count = std::min(count, sampled_dynamic_cap); + const bool is_integer{IsTexturePixelFormatIntegerCached(env, cbuf)}; index = descriptors.Add(TextureDescriptor{ .type = flags.type, .is_depth = flags.is_depth != 0, .is_multisample = is_multisample, + .is_integer = is_integer, .has_secondary = cbuf.has_secondary, .cbuf_index = cbuf.index, .cbuf_offset = cbuf.offset, @@ -805,6 +808,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo .count = count, .size_shift = size_shift, }); + flags.is_integer.Assign(is_integer ? 1 : 0); } break; } diff --git a/src/shader_recompiler/shader_info.h b/src/shader_recompiler/shader_info.h index dfacc06802..0c11a0d72a 100644 --- a/src/shader_recompiler/shader_info.h +++ b/src/shader_recompiler/shader_info.h @@ -209,6 +209,7 @@ struct TextureDescriptor { TextureType type; bool is_depth; bool is_multisample; + bool is_integer; bool has_secondary; u32 cbuf_index; u32 cbuf_offset; From b049f7d2738e01d56e3b234f680834331d437bcc Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 4 Jul 2026 12:57:20 -0400 Subject: [PATCH 15/70] [texture_cache] Relaxed TIC's lookup for integer type image formats --- src/shader_recompiler/ir_opt/texture_pass.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/shader_recompiler/ir_opt/texture_pass.cpp b/src/shader_recompiler/ir_opt/texture_pass.cpp index b97f6dbf17..e30c8eb4b3 100644 --- a/src/shader_recompiler/ir_opt/texture_pass.cpp +++ b/src/shader_recompiler/ir_opt/texture_pass.cpp @@ -686,6 +686,21 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo program.info.image_descriptors, }; const u32 sampled_dynamic_cap = DynamicSampledTextureCap(program.info, host_info, DynamicSampledTextureArrayCount(to_replace)); + bool has_last_is_integer{false}; + u32 last_cbuf_index{}; + u32 last_cbuf_offset{}; + bool last_is_integer{false}; + const auto is_texture_pixel_format_integer{[&](const ConstBufferAddr& cbuf_addr) { + if (has_last_is_integer && last_cbuf_index == cbuf_addr.index && + last_cbuf_offset == cbuf_addr.offset) { + return last_is_integer; + } + last_is_integer = IsTexturePixelFormatIntegerCached(env, cbuf_addr); + last_cbuf_index = cbuf_addr.index; + last_cbuf_offset = cbuf_addr.offset; + has_last_is_integer = true; + return last_is_integer; + }}; for (TextureInst& texture_inst : to_replace) { // TODO: Handle arrays IR::Inst* const inst{texture_inst.inst}; @@ -750,7 +765,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo } const bool is_written{inst->GetOpcode() != IR::Opcode::ImageRead}; const bool is_read{inst->GetOpcode() != IR::Opcode::ImageWrite}; - const bool is_integer{IsTexturePixelFormatIntegerCached(env, cbuf)}; + const bool is_integer{is_texture_pixel_format_integer(cbuf)}; if (flags.type == TextureType::Buffer) { index = descriptors.Add(ImageBufferDescriptor{ .format = flags.image_format, @@ -792,7 +807,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo }); } else { count = std::min(count, sampled_dynamic_cap); - const bool is_integer{IsTexturePixelFormatIntegerCached(env, cbuf)}; + const bool is_integer{is_texture_pixel_format_integer(cbuf)}; index = descriptors.Add(TextureDescriptor{ .type = flags.type, .is_depth = flags.is_depth != 0, From 28630ff109f042e028e9fef7de8f7922f894466e Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 4 Jul 2026 13:47:44 -0400 Subject: [PATCH 16/70] [vulkan] Sampled linear images fallback to nearest --- .../renderer_vulkan/pipeline_helper.h | 9 ++++-- .../renderer_vulkan/vk_texture_cache.cpp | 31 ++++++++++++++----- .../renderer_vulkan/vk_texture_cache.h | 9 ++++++ 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/video_core/renderer_vulkan/pipeline_helper.h b/src/video_core/renderer_vulkan/pipeline_helper.h index e9adaee1d2..0523a040c8 100644 --- a/src/video_core/renderer_vulkan/pipeline_helper.h +++ b/src/video_core/renderer_vulkan/pipeline_helper.h @@ -16,6 +16,7 @@ #include "shader_recompiler/shader_info.h" #include "video_core/renderer_vulkan/vk_texture_cache.h" #include "video_core/renderer_vulkan/vk_update_descriptor.h" +#include "video_core/surface.h" #include "video_core/texture_cache/types.h" #include "video_core/vulkan_common/vulkan_device.h" @@ -235,8 +236,12 @@ inline void PushImageDescriptors(TextureCache& texture_cache, const Sampler& sampler{texture_cache.GetSampler(sampler_id)}; const bool use_fallback_sampler{sampler.HasAddedAnisotropy() && !image_view.SupportsAnisotropy()}; - const VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy() - : sampler.Handle()}; + VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy() + : sampler.Handle()}; + if (sampler.HasLinearFiltering() && + VideoCore::Surface::IsPixelFormatInteger(image_view.format)) { + vk_sampler = sampler.HandleWithNearestFilter(); + } guest_descriptor_queue.AddSampledImage(vk_image_view, vk_sampler); const bool element_rescaled{texture_cache.IsRescaling(image_view)}; is_rescaled |= element_rescaled; diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index bb421871bd..a0db94b4af 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -2326,20 +2326,28 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t // Some games have samplers with garbage. Sanitize them here. const f32 max_anisotropy = std::clamp(tsc.MaxAnisotropy(), 1.0f, 16.0f); - const auto create_sampler = [&](const f32 anisotropy) { + const VkFilter mag_filter{MaxwellToVK::Sampler::Filter(tsc.mag_filter)}; + const VkFilter min_filter{MaxwellToVK::Sampler::Filter(tsc.min_filter)}; + const VkSamplerMipmapMode mipmap_mode{MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter)}; + const bool has_linear_filtering{mag_filter == VK_FILTER_LINEAR || + min_filter == VK_FILTER_LINEAR || + mipmap_mode == VK_SAMPLER_MIPMAP_MODE_LINEAR}; + + const auto create_sampler = [&](const f32 anisotropy, bool force_nearest) { return device.GetLogical().CreateSampler(VkSamplerCreateInfo{ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .pNext = pnext, .flags = 0, - .magFilter = MaxwellToVK::Sampler::Filter(tsc.mag_filter), - .minFilter = MaxwellToVK::Sampler::Filter(tsc.min_filter), - .mipmapMode = MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter), + .magFilter = force_nearest ? VK_FILTER_NEAREST : mag_filter, + .minFilter = force_nearest ? VK_FILTER_NEAREST : min_filter, + .mipmapMode = force_nearest ? VK_SAMPLER_MIPMAP_MODE_NEAREST : mipmap_mode, .addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter), .addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter), .addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter), .mipLodBias = tsc.LodBias(), - .anisotropyEnable = static_cast(anisotropy > 1.0f ? VK_TRUE : VK_FALSE), - .maxAnisotropy = anisotropy, + .anisotropyEnable = + static_cast(!force_nearest && anisotropy > 1.0f ? VK_TRUE : VK_FALSE), + .maxAnisotropy = force_nearest ? 1.0f : anisotropy, .compareEnable = tsc.depth_compare_enabled, .compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func), .minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.MinLod(), @@ -2350,11 +2358,18 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t }); }; - sampler = create_sampler(max_anisotropy); + sampler = create_sampler(max_anisotropy, false); const f32 max_anisotropy_default = static_cast(1U << tsc.max_anisotropy); if (max_anisotropy > max_anisotropy_default) { - sampler_default_anisotropy = create_sampler(max_anisotropy_default); + sampler_default_anisotropy = create_sampler(max_anisotropy_default, false); + } + if (has_linear_filtering) { + // Integer-format image views can never be linearly filtered + // (VUID-vkCmdDraw*-magFilter-04553); this sampler is cached purely from the guest's TSC, + // decoupled from whichever ImageView it ends up paired with, so build a nearest-forced + // fallback here for callers to swap to when the paired view turns out to be integer. + sampler_nearest = create_sampler(1.0f, true); } } diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 5b7769b254..005ab94762 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -406,9 +406,18 @@ public: return static_cast(sampler_default_anisotropy); } + [[nodiscard]] VkSampler HandleWithNearestFilter() const noexcept { + return *sampler_nearest; + } + + [[nodiscard]] bool HasLinearFiltering() const noexcept { + return static_cast(sampler_nearest); + } + private: vk::Sampler sampler; vk::Sampler sampler_default_anisotropy; + vk::Sampler sampler_nearest; }; struct TextureCacheParams { From 80609ee974f0553015e3d7c35f7665e4812dee84 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 4 Jul 2026 16:17:01 -0400 Subject: [PATCH 17/70] [TEST] Remove border color swizzle from qcom --- src/video_core/vulkan_common/vulkan_device.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 90eb9b05b3..20c84d5f99 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -497,6 +497,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken CustomBorderColor."); RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); + RemoveExtensionFeature(extensions.border_color_swizzle, features.border_color_swizzle, + VK_EXT_BORDER_COLOR_SWIZZLE_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken Provoking Vertex."); RemoveExtension(extensions.provoking_vertex, VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have slow push descriptor implementation."); From eb7f950322c70a1a4a24fcee83f6e5a0810497ab Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 4 Jul 2026 16:24:22 -0400 Subject: [PATCH 18/70] [spir-v] Workaorund for 1D sampled images --- src/shader_recompiler/backend/spirv/emit_spirv.cpp | 3 +++ .../ir_opt/collect_shader_info_pass.cpp | 12 ++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index 0447dfe90d..f73fb233cb 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -403,6 +403,9 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct if (info.uses_sampled_1d) { ctx.AddCapability(spv::Capability::Sampled1D); } + if (info.uses_image_1d) { + ctx.AddCapability(spv::Capability::Image1D); + } if (info.uses_sparse_residency) { ctx.AddCapability(spv::Capability::SparseResidency); } diff --git a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp index 2bfa3227a8..5089f3a101 100644 --- a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp +++ b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -569,6 +569,8 @@ void VisitUsages(Info& info, IR::Inst& inst) { case IR::Opcode::ImageRead: { const auto flags{inst.Flags()}; info.uses_typeless_image_reads |= flags.image_format == ImageFormat::Typeless; + info.uses_image_1d |= + flags.type == TextureType::Color1D || flags.type == TextureType::ColorArray1D; info.uses_sparse_residency |= inst.GetAssociatedPseudoOperation(IR::Opcode::GetSparseFromOp) != nullptr; break; @@ -577,6 +579,8 @@ void VisitUsages(Info& info, IR::Inst& inst) { const auto flags{inst.Flags()}; info.uses_typeless_image_writes |= flags.image_format == ImageFormat::Typeless; info.uses_image_buffers |= flags.type == TextureType::Buffer; + info.uses_image_1d |= + flags.type == TextureType::Color1D || flags.type == TextureType::ColorArray1D; break; } case IR::Opcode::SubgroupEqMask: @@ -761,9 +765,13 @@ void VisitUsages(Info& info, IR::Inst& inst) { case IR::Opcode::ImageAtomicAnd32: case IR::Opcode::ImageAtomicOr32: case IR::Opcode::ImageAtomicXor32: - case IR::Opcode::ImageAtomicExchange32: + case IR::Opcode::ImageAtomicExchange32: { + const auto flags{inst.Flags()}; info.uses_atomic_image_u32 = true; + info.uses_image_1d |= + flags.type == TextureType::Color1D || flags.type == TextureType::ColorArray1D; break; + } default: break; } From c7668fd43e44f78a5741aa65bc3ee48170f2801a Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 4 Jul 2026 16:40:15 -0400 Subject: [PATCH 19/70] [vulkan] Adjusted checks for 8 - 16bits UBO's iteration --- .../backend/spirv/emit_spirv_context_get_set.cpp | 12 ++++++++---- .../backend/spirv/spirv_emit_context.cpp | 14 +++++++++++--- src/shader_recompiler/profile.h | 1 + .../renderer_vulkan/vk_pipeline_cache.cpp | 2 ++ src/video_core/vulkan_common/vulkan_device.h | 7 ++++++- 5 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp index fb9d3dbbb2..f4d9de79d3 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp @@ -202,7 +202,8 @@ void EmitGetIndirectBranchVariable(EmitContext&) { } Id EmitGetCbufU8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { - if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int8) { + if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int8 && + ctx.profile.support_uniform_and_storage_buffer_8bit) { const Id load{GetCbuf(ctx, ctx.U8, &UniformDefinitions::U8, sizeof(u8), binding, offset, ctx.load_const_func_u8)}; return ctx.OpUConvert(ctx.U32[1], load); @@ -219,7 +220,8 @@ Id EmitGetCbufU8(EmitContext& ctx, const IR::Value& binding, const IR::Value& of } Id EmitGetCbufS8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { - if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int8) { + if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int8 && + ctx.profile.support_uniform_and_storage_buffer_8bit) { const Id load{GetCbuf(ctx, ctx.S8, &UniformDefinitions::S8, sizeof(s8), binding, offset, ctx.load_const_func_u8)}; return ctx.OpSConvert(ctx.U32[1], load); @@ -236,7 +238,8 @@ Id EmitGetCbufS8(EmitContext& ctx, const IR::Value& binding, const IR::Value& of } Id EmitGetCbufU16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { - if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int16) { + if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int16 && + ctx.profile.support_uniform_and_storage_buffer_16bit) { const Id load{GetCbuf(ctx, ctx.U16, &UniformDefinitions::U16, sizeof(u16), binding, offset, ctx.load_const_func_u16)}; return ctx.OpUConvert(ctx.U32[1], load); @@ -253,7 +256,8 @@ Id EmitGetCbufU16(EmitContext& ctx, const IR::Value& binding, const IR::Value& o } Id EmitGetCbufS16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { - if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int16) { + if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int16 && + ctx.profile.support_uniform_and_storage_buffer_16bit) { const Id load{GetCbuf(ctx, ctx.S16, &UniformDefinitions::S16, sizeof(s16), binding, offset, ctx.load_const_func_u16)}; return ctx.OpSConvert(ctx.U32[1], load); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index c5c6b2655a..b1bed263a6 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -1134,7 +1134,7 @@ void EmitContext::DefineConstantBuffers(const Info& info, u32& binding) { } } if (True(types & IR::Type::U16)) { - if (profile.support_int16) { + if (profile.support_int16 && profile.support_uniform_and_storage_buffer_16bit) { DefineConstBuffers(*this, info, &UniformDefinitions::U16, binding, U16, 'u', sizeof(u16)); DefineConstBuffers(*this, info, &UniformDefinitions::S16, binding, S16, 's', @@ -1196,10 +1196,18 @@ void EmitContext::DefineConstantBufferIndirectFunctions(const Info& info) { IR::Type types{info.used_indirect_cbuf_types}; bool supports_aliasing = profile.support_descriptor_aliasing; if (supports_aliasing && True(types & IR::Type::U8)) { - load_const_func_u8 = make_accessor(U8, &UniformDefinitions::U8); + if (profile.support_int8 && profile.support_uniform_and_storage_buffer_8bit) { + load_const_func_u8 = make_accessor(U8, &UniformDefinitions::U8); + } else { + types |= IR::Type::U32; + } } if (supports_aliasing && True(types & IR::Type::U16)) { - load_const_func_u16 = make_accessor(U16, &UniformDefinitions::U16); + if (profile.support_int16 && profile.support_uniform_and_storage_buffer_16bit) { + load_const_func_u16 = make_accessor(U16, &UniformDefinitions::U16); + } else { + types |= IR::Type::U32; + } } if (supports_aliasing && True(types & IR::Type::F32)) { load_const_func_f32 = make_accessor(F32[1], &UniformDefinitions::F32); diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h index 4e200d5120..e95778a0de 100644 --- a/src/shader_recompiler/profile.h +++ b/src/shader_recompiler/profile.h @@ -17,6 +17,7 @@ struct Profile { bool support_int8{}; bool support_uniform_and_storage_buffer_8bit{}; bool support_int16{}; + bool support_uniform_and_storage_buffer_16bit{}; bool support_int64{}; bool support_vertex_instance_id{}; bool support_float_controls{}; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 74caf044cb..903b97db6a 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -386,6 +386,8 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, .support_uniform_and_storage_buffer_8bit = device.IsUniformAndStorageBuffer8BitAccessSupported(), .support_int16 = device.IsShaderInt16Supported(), + .support_uniform_and_storage_buffer_16bit = + device.IsUniformAndStorageBuffer16BitAccessSupported(), .support_int64 = device.IsShaderInt64Supported(), .support_vertex_instance_id = false, .support_float_controls = device.IsKhrShaderFloatControlsSupported(), diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 457cd3286e..5b6c3da57d 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -387,11 +387,16 @@ FN_MAX_LIMIT_LIST return features.shader_float16_int8.shaderInt8; } - /// Returns true if the device allows 8-bit integer members + /// Returns true if the device allows 8-bit integer members in uniform/storage buffers. bool IsUniformAndStorageBuffer8BitAccessSupported() const { return features.bit8_storage.uniformAndStorageBuffer8BitAccess; } + /// Returns true if the device allows 16-bit integer members in uniform/storage buffers. + bool IsUniformAndStorageBuffer16BitAccessSupported() const { + return features.bit16_storage.uniformAndStorageBuffer16BitAccess; + } + /// Returns true if the device supports binding multisample images as storage images. bool IsStorageImageMultisampleSupported() const { return features.features.shaderStorageImageMultisample; From 20a68e1e9d22e320f05d68b1928be14ac858965b Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 4 Jul 2026 17:59:07 -0400 Subject: [PATCH 20/70] [vulkan, qcom] Returned custom border color and derivates to QCOM drivers --- .../vulkan_common/vulkan_device.cpp | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 20c84d5f99..b68dc2f842 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -494,11 +494,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR if (is_qualcomm) { LOG_WARNING(Render_Vulkan, "Qualcomm drivers require scaled vertex format emulation"); must_emulate_scaled_formats = true; - LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken CustomBorderColor."); - RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color, - VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); - RemoveExtensionFeature(extensions.border_color_swizzle, features.border_color_swizzle, - VK_EXT_BORDER_COLOR_SWIZZLE_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken Provoking Vertex."); RemoveExtension(extensions.provoking_vertex, VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have slow push descriptor implementation."); @@ -1176,16 +1171,6 @@ void Device::RemoveUnsuitableExtensions() { RemoveExtensionFeatureIfUnsuitable(extensions.color_write_enable, features.color_write_enable, VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME); - // VK_EXT_border_color_swizzle - if (extensions.border_color_swizzle) { - extensions.border_color_swizzle = - features.border_color_swizzle.borderColorSwizzle && - features.border_color_swizzle.borderColorSwizzleFromImage; - } - RemoveExtensionFeatureIfUnsuitable(extensions.border_color_swizzle, - features.border_color_swizzle, - VK_EXT_BORDER_COLOR_SWIZZLE_EXTENSION_NAME); - // VK_EXT_custom_border_color if (extensions.custom_border_color) { extensions.custom_border_color = @@ -1195,6 +1180,17 @@ void Device::RemoveUnsuitableExtensions() { RemoveExtensionFeatureIfUnsuitable(extensions.custom_border_color, features.custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); + // VK_EXT_border_color_swizzle + if (extensions.border_color_swizzle) { + extensions.border_color_swizzle = + extensions.custom_border_color && + features.border_color_swizzle.borderColorSwizzle && + features.border_color_swizzle.borderColorSwizzleFromImage; + } + RemoveExtensionFeatureIfUnsuitable(extensions.border_color_swizzle, + features.border_color_swizzle, + VK_EXT_BORDER_COLOR_SWIZZLE_EXTENSION_NAME); + // VK_EXT_depth_bias_control extensions.depth_bias_control = features.depth_bias_control.depthBiasControl && From 53927e175e2545598ee156e335919ac868774fd3 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 4 Jul 2026 18:26:29 -0400 Subject: [PATCH 21/70] [vulkan] Adjustments on previous TFB barrier added --- src/video_core/renderer_vulkan/vk_query_cache.cpp | 12 ------------ src/video_core/renderer_vulkan/vk_scheduler.cpp | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp index 9a41bc52b2..ea8c971080 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp @@ -925,28 +925,16 @@ private: // Refresh buffer state before ending transform feedback to ensure counters_count is up-to-date. UpdateBuffers(); - static constexpr VkMemoryBarrier XFB_OUTPUT_BARRIER{ - .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, - .pNext = nullptr, - .srcAccessMask = VK_ACCESS_TRANSFORM_FEEDBACK_WRITE_BIT_EXT, - .dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT, - }; if (buffers_count == 0) { LOG_DEBUG(Render_Vulkan, "EndTransformFeedbackEXT called with no counters (buffers_count=0)"); scheduler.Record([](vk::CommandBuffer cmdbuf) { cmdbuf.EndTransformFeedbackEXT(0, 0, nullptr, nullptr); - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT, - vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, 0, - XFB_OUTPUT_BARRIER); }); } else { LOG_DEBUG(Render_Vulkan, "EndTransformFeedbackEXT called with counters (buffers_count={})", buffers_count); scheduler.Record([this, total = static_cast(buffers_count)](vk::CommandBuffer cmdbuf) { cmdbuf.EndTransformFeedbackEXT(0, total, counter_buffers.data(), offsets.data()); - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT, - vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, 0, - XFB_OUTPUT_BARRIER); }); } } diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 6a631f68a2..48268df708 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -344,7 +344,9 @@ void Scheduler::EndRenderPass() Record([num_images = num_renderpass_images, images = renderpass_images, - ranges = renderpass_image_ranges](vk::CommandBuffer cmdbuf) { + ranges = renderpass_image_ranges, + has_transform_feedback = device.IsExtTransformFeedbackSupported()]( + vk::CommandBuffer cmdbuf) { std::array barriers; for (size_t i = 0; i < num_images; ++i) { const VkImageSubresourceRange& range = ranges[i]; @@ -384,6 +386,17 @@ void Scheduler::EndRenderPass() cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, nullptr, nullptr, vk::Span(barriers.data(), num_images)); + if (has_transform_feedback) { + static constexpr VkMemoryBarrier XFB_OUTPUT_BARRIER{ + .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_TRANSFORM_FEEDBACK_WRITE_BIT_EXT, + .dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT, + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT, + VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, XFB_OUTPUT_BARRIER); + } }); state.renderpass = VkRenderPass{}; From f5720663c2927050aa9ffca3cd6aeebc76c1bedf Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 4 Jul 2026 18:55:00 -0400 Subject: [PATCH 22/70] [vulkan] Adjust blend targets from texture types --- src/video_core/renderer_vulkan/vk_rasterizer.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 8d43fcb2a6..6f7ce1930d 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -1724,9 +1724,13 @@ void RasterizerVulkan::UpdateBlending(Tegra::Engines::Maxwell3D::Regs& regs) { if (state_tracker.TouchBlendEnable()) { std::array setup_enables{}; - std::ranges::transform( - regs.blend.enable, setup_enables.begin(), - [&](const auto& is_enabled) { return is_enabled != 0 ? VK_TRUE : VK_FALSE; }); + for (size_t index = 0; index < Maxwell::NumRenderTargets; index++) { + const auto format = + VideoCore::Surface::PixelFormatFromRenderTargetFormat(regs.rt[index].format); + const bool is_integer = IsPixelFormatInteger(format); + setup_enables[index] = + (!is_integer && regs.blend.enable[index] != 0) ? VK_TRUE : VK_FALSE; + } scheduler.Record([setup_enables](vk::CommandBuffer cmdbuf) { cmdbuf.SetColorBlendEnableEXT(0, setup_enables); }); From 7bb4991eddb4f16e5e6bef7b3482978c22168625 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 4 Jul 2026 19:03:27 -0400 Subject: [PATCH 23/70] [vulkan] Refactored dual blending/ dual source attachments --- .../renderer_vulkan/fixed_pipeline_state.cpp | 32 +++++++++++++++++++ .../renderer_vulkan/fixed_pipeline_state.h | 2 ++ .../renderer_vulkan/vk_pipeline_cache.cpp | 27 +--------------- 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp index 1378c30efd..4662e531be 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp @@ -57,6 +57,37 @@ constexpr std::array TOPOLOGY_CLASS_REPRESENTATIVE_LUT = { Maxwell::PrimitiveTopology::Patches, // Patches }; +bool IsDualSourceBlendFactor(Maxwell::Blend::Factor factor) { + using F = Maxwell::Blend::Factor; + switch (factor) { + case F::Source1Color_D3D: + case F::OneMinusSource1Color_D3D: + case F::Source1Alpha_D3D: + case F::OneMinusSource1Alpha_D3D: + case F::Source1Color_GL: + case F::OneMinusSource1Color_GL: + case F::Source1Alpha_GL: + case F::OneMinusSource1Alpha_GL: + return true; + default: + return false; + } +} + +bool ComputeAttachment0DualSourceBlend(const Maxwell& regs) { + if (!regs.blend.enable[0]) { + return false; + } + const auto uses_dual_source = [](const auto& blend) { + return IsDualSourceBlendFactor(blend.color_source) || + IsDualSourceBlendFactor(blend.color_dest) || + IsDualSourceBlendFactor(blend.alpha_source) || + IsDualSourceBlendFactor(blend.alpha_dest); + }; + return regs.blend_per_target_enabled ? uses_dual_source(regs.blend_per_target[0]) + : uses_dual_source(regs.blend); +} + void RefreshXfbState(VideoCommon::TransformFeedbackState& state, const Maxwell& regs) { std::ranges::transform(regs.transform_feedback.controls, state.layouts.begin(), [](const auto& layout) { @@ -96,6 +127,7 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe ? TOPOLOGY_CLASS_REPRESENTATIVE_LUT[static_cast(topology_)] : topology_); msaa_mode.Assign(regs.anti_alias_samples_mode); + attachment0_dual_source_blend.Assign(ComputeAttachment0DualSourceBlend(regs) ? 1 : 0); raw2 = 0; diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.h b/src/video_core/renderer_vulkan/fixed_pipeline_state.h index 0b631a9e89..78a5fef365 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.h +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.h @@ -211,6 +211,8 @@ struct FixedPipelineState { BitField<15, 5, u32> patch_control_points_minus_one; BitField<20, 1, u32> color_write_enable_dynamic; + BitField<21, 1, u32> attachment0_dual_source_blend; + BitField<24, 4, Maxwell::PrimitiveTopology> topology; BitField<28, 4, Tegra::Texture::MsaaMode> msaa_mode; }; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 903b97db6a..7fd87b17cd 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -246,32 +246,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span program key.state.UnpackComparisonOp(key.state.alpha_test_func.Value())); info.alpha_test_reference = std::bit_cast(key.state.alpha_test_ref); - // Check for dual source blending - const auto& blend0 = key.state.attachments[0]; - if (blend0.enable != 0) { - using F = Maxwell::Blend::Factor; - const auto src_rgb = blend0.SourceRGBFactor(); - const auto dst_rgb = blend0.DestRGBFactor(); - const auto src_a = blend0.SourceAlphaFactor(); - const auto dst_a = blend0.DestAlphaFactor(); - info.dual_source_blend = - src_rgb == F::Source1Color_D3D || src_rgb == F::OneMinusSource1Color_D3D || - src_rgb == F::Source1Alpha_D3D || src_rgb == F::OneMinusSource1Alpha_D3D || - src_rgb == F::Source1Color_GL || src_rgb == F::OneMinusSource1Color_GL || - src_rgb == F::Source1Alpha_GL || src_rgb == F::OneMinusSource1Alpha_GL || - dst_rgb == F::Source1Color_D3D || dst_rgb == F::OneMinusSource1Color_D3D || - dst_rgb == F::Source1Alpha_D3D || dst_rgb == F::OneMinusSource1Alpha_D3D || - dst_rgb == F::Source1Color_GL || dst_rgb == F::OneMinusSource1Color_GL || - dst_rgb == F::Source1Alpha_GL || dst_rgb == F::OneMinusSource1Alpha_GL || - src_a == F::Source1Color_D3D || src_a == F::OneMinusSource1Color_D3D || - src_a == F::Source1Alpha_D3D || src_a == F::OneMinusSource1Alpha_D3D || - src_a == F::Source1Color_GL || src_a == F::OneMinusSource1Color_GL || - src_a == F::Source1Alpha_GL || src_a == F::OneMinusSource1Alpha_GL || - dst_a == F::Source1Color_D3D || dst_a == F::OneMinusSource1Color_D3D || - dst_a == F::Source1Alpha_D3D || dst_a == F::OneMinusSource1Alpha_D3D || - dst_a == F::Source1Color_GL || dst_a == F::OneMinusSource1Color_GL || - dst_a == F::Source1Alpha_GL || dst_a == F::OneMinusSource1Alpha_GL; - } + info.dual_source_blend = key.state.attachment0_dual_source_blend != 0; if (device.IsMoltenVK()) { for (size_t i = 0; i < 8; ++i) { From 307a32f9a08a2f112ec519dfea91a168dd1afa44 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 4 Jul 2026 20:44:25 -0400 Subject: [PATCH 24/70] [vulkan, qcom] Updated list of features removed for QCOM stock drivers --- .../vulkan_common/vulkan_device.cpp | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index b68dc2f842..4934ec1e00 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -494,12 +494,16 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR if (is_qualcomm) { LOG_WARNING(Render_Vulkan, "Qualcomm drivers require scaled vertex format emulation"); must_emulate_scaled_formats = true; + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken CustomBorderColor."); + RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color, + VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken Provoking Vertex."); RemoveExtension(extensions.provoking_vertex, VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME); - LOG_WARNING(Render_Vulkan, "Qualcomm drivers have slow push descriptor implementation."); + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken push descriptor."); RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); - LOG_WARNING(Render_Vulkan, "Disabling shader float controls and 64-bit integer features."); + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader float controls."); RemoveExtension(extensions.shader_float_controls, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader atomic int64."); RemoveExtensionFeature(extensions.shader_atomic_int64, features.shader_atomic_int64, VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME); features.shader_atomic_int64.shaderBufferInt64Atomics = false; @@ -557,7 +561,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR features.shader_float16_int8.shaderFloat16 = false; } - // Mali/ NVIDIA proprietary drivers: Shader stencil export not supported // Use hardware depth/stencil blits instead when available if (!extensions.shader_stencil_export) { LOG_INFO(Render_Vulkan, @@ -653,14 +656,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR } const auto dyna_state = Settings::values.dyna_state.GetValue(); - - // Base dynamic states (VIEWPORT, SCISSOR, DEPTH_BIAS, etc.) are ALWAYS active in vk_graphics_pipeline.cpp - // This slider controls EXTENDED dynamic states with accumulative levels per Vulkan specs: - // Level 0 = Core Dynamic States only (Vulkan 1.0) - // Level 1 = Core + VK_EXT_extended_dynamic_state - // Level 2 = Core + VK_EXT_extended_dynamic_state + VK_EXT_extended_dynamic_state2 - // Level 3 = Core + VK_EXT_extended_dynamic_state + VK_EXT_extended_dynamic_state2 + VK_EXT_extended_dynamic_state3 - switch (dyna_state) { case Settings::ExtendedDynamicState::Disabled: // Level 0: Disable all extended dynamic state extensions @@ -695,8 +690,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR break; } - // VK_EXT_vertex_input_dynamic_state is independent from EDS - // It can be enabled even without extended_dynamic_state + // VK_EXT_vertex_input_dynamic_state if (!Settings::values.vertex_input_dynamic_state.GetValue()) { RemoveExtensionFeature(extensions.vertex_input_dynamic_state, features.vertex_input_dynamic_state, VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); } From f5560deab7b04f4a86d2a4c48952c09f5f8550eb Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 4 Jul 2026 21:41:00 -0400 Subject: [PATCH 25/70] [vulkan] Added tracker for reused uploads --- src/video_core/renderer_vulkan/vk_buffer_cache.cpp | 7 +++++-- src/video_core/renderer_vulkan/vk_buffer_cache.h | 7 +++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index 7bc23a5e8b..43d32c2213 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -84,7 +84,7 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo } // Anonymous namespace Buffer::Buffer(BufferCacheRuntime& runtime, VideoCommon::NullBufferParams null_params) - : VideoCommon::BufferBase(null_params), tracker{4096} { + : VideoCommon::BufferBase(null_params), scheduler{&runtime.scheduler}, tracker{4096} { if (runtime.device.HasNullDescriptor()) { return; } @@ -95,6 +95,7 @@ Buffer::Buffer(BufferCacheRuntime& runtime, VideoCommon::NullBufferParams null_p Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_) : VideoCommon::BufferBase(cpu_addr_, size_bytes_), device{&runtime.device}, + scheduler{&runtime.scheduler}, buffer{CreateBuffer(*device, runtime.memory_allocator, SizeBytes())}, tracker{SizeBytes()} { if (runtime.device.HasDebuggingToolAttached()) { buffer.SetObjectNameEXT(fmt::format("Buffer 0x{:x}", CpuAddr()).c_str()); @@ -384,7 +385,9 @@ u32 BufferCacheRuntime::GetStorageBufferAlignment() const { void BufferCacheRuntime::TickFrame(Common::SlotVector& slot_buffers) noexcept { for (auto it = slot_buffers.begin(); it != slot_buffers.end(); it++) { - it->ResetUsageTracking(); + if (scheduler.IsFree(it->LastUsageTick())) { + it->ResetUsageTracking(); + } } } diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index 6a4f720231..f0ec5399b1 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h @@ -45,12 +45,17 @@ public: void MarkUsage(u64 offset, u64 size) noexcept { tracker.Track(offset, size); + last_usage_tick = scheduler->CurrentTick(); } void ResetUsageTracking() noexcept { tracker.Reset(); } + [[nodiscard]] u64 LastUsageTick() const noexcept { + return last_usage_tick; + } + operator VkBuffer() const noexcept { return *buffer; } @@ -64,9 +69,11 @@ private: }; const Device* device{}; + Scheduler* scheduler{}; vk::Buffer buffer; std::vector views; VideoCommon::UsageTracker tracker; + u64 last_usage_tick{}; bool is_null{}; }; From 34df1b4a167b980f2807125a9a557fe6968cba41 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 4 Jul 2026 22:03:22 -0400 Subject: [PATCH 26/70] [vulkan] Another approach for queries reset --- .../renderer_vulkan/vk_query_cache.cpp | 38 ++++++++++++++++--- src/video_core/vulkan_common/vulkan_device.h | 6 ++- .../vulkan_common/vulkan_wrapper.cpp | 1 + src/video_core/vulkan_common/vulkan_wrapper.h | 5 +++ 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp index ea8c971080..935f8b79fb 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp @@ -41,8 +41,8 @@ class SamplesQueryBank : public VideoCommon::BankBase { public: static constexpr size_t BANK_SIZE = 256; static constexpr size_t QUERY_SIZE = 8; - explicit SamplesQueryBank(const Device& device_, size_t index_) - : BankBase(BANK_SIZE), device{device_}, index{index_} { + explicit SamplesQueryBank(const Device& device_, Scheduler& scheduler_, size_t index_) + : BankBase(BANK_SIZE), device{device_}, scheduler{scheduler_}, index{index_} { const auto& dev = device.GetLogical(); query_pool = dev.CreateQueryPool({ .sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO, @@ -60,12 +60,28 @@ public: void Reset() override { ASSERT(references == 0); VideoCommon::BankBase::Reset(); - const auto& dev = device.GetLogical(); - dev.ResetQueryPool(*query_pool, 0, BANK_SIZE); + if (device.IsHostQueryResetSupported()) { + const auto& dev = device.GetLogical(); + dev.ResetQueryPool(*query_pool, 0, BANK_SIZE); + } else { + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([pool = *query_pool](vk::CommandBuffer cmdbuf) { + cmdbuf.ResetQueryPool(pool, 0, BANK_SIZE); + }); + } host_results.fill(0ULL); next_bank = 0; } + void AddReference(size_t how_many = 1) { + BankBase::AddReference(how_many); + last_used_tick = scheduler.CurrentTick(); + } + + [[nodiscard]] bool IsDead() const { + return BankBase::IsDead() && scheduler.IsFree(last_used_tick); + } + void Sync(size_t start, size_t size) { const auto& dev = device.GetLogical(); const VkResult query_result = dev.GetQueryResults( @@ -98,9 +114,11 @@ public: private: const Device& device; + Scheduler& scheduler; const size_t index; vk::QueryPool query_pool; std::array host_results; + u64 last_used_tick{}; }; using BaseStreamer = VideoCommon::SimpleStreamer; @@ -431,7 +449,7 @@ private: void ReserveBank() { current_bank_id = bank_pool.ReserveBank([this](std::deque& queue, size_t index) { - queue.emplace_back(device, index); + queue.emplace_back(device, scheduler, index); }); if (current_bank) { current_bank->next_bank = current_bank_id + 1; @@ -620,6 +638,15 @@ public: VideoCommon::BankBase::Reset(); } + void AddReference(size_t how_many = 1) { + BankBase::AddReference(how_many); + last_used_tick = scheduler.CurrentTick(); + } + + [[nodiscard]] bool IsDead() const { + return BankBase::IsDead() && scheduler.IsFree(last_used_tick); + } + void Sync(StagingBufferRef& stagging_buffer, size_t extra_offset, size_t start, size_t size) { scheduler.RequestOutsideRenderPassOperationContext(); scheduler.Record([this, dst_buffer = stagging_buffer.buffer, extra_offset, start, @@ -645,6 +672,7 @@ private: Scheduler& scheduler; const size_t index; vk::Buffer buffer; + u64 last_used_tick{}; }; class PrimitivesSucceededStreamer; diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 5b6c3da57d..cf039768fd 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -523,7 +523,6 @@ FN_MAX_LIMIT_LIST } /// Returns true if the device supports VK_EXT_shader_stencil_export. - /// Note: Most Mali/NVIDIA drivers don't support this. Use hardware blits as fallback. bool IsExtShaderStencilExportSupported() const { return extensions.shader_stencil_export; } @@ -560,6 +559,11 @@ FN_MAX_LIMIT_LIST return extensions.subgroup_size_control; } + /// Returns true if vkResetQueryPool (host-side query reset) is supported. + bool IsHostQueryResetSupported() const { + return features.host_query_reset.hostQueryReset != VK_FALSE; + } + /// Returns true if the device supports VK_EXT_transform_feedback. bool IsExtTransformFeedbackSupported() const { return extensions.transform_feedback; diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp index 6f35e0b0c3..e5d5e93426 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.cpp +++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp @@ -165,6 +165,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkCmdSetColorWriteEnableEXT); X(vkCmdSetColorBlendEnableEXT); X(vkCmdSetColorBlendEquationEXT); + X(vkCmdResetQueryPool); X(vkCmdResolveImage); X(vkCreateBuffer); X(vkCreateBufferView); diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h index 5c68ef9d25..691390f002 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.h +++ b/src/video_core/vulkan_common/vulkan_wrapper.h @@ -241,6 +241,7 @@ struct DeviceDispatch : InstanceDispatch { PFN_vkCmdPipelineBarrier2 vkCmdPipelineBarrier2{}; PFN_vkCmdPushConstants vkCmdPushConstants{}; PFN_vkCmdPushDescriptorSetWithTemplateKHR vkCmdPushDescriptorSetWithTemplateKHR{}; + PFN_vkCmdResetQueryPool vkCmdResetQueryPool{}; PFN_vkCmdResolveImage vkCmdResolveImage{}; PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants{}; PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT{}; @@ -1191,6 +1192,10 @@ public: dld->vkCmdEndQuery(handle, query_pool, query); } + void ResetQueryPool(VkQueryPool query_pool, u32 first, u32 count) const noexcept { + dld->vkCmdResetQueryPool(handle, query_pool, first, count); + } + void BindDescriptorSets(VkPipelineBindPoint bind_point, VkPipelineLayout layout, u32 first, Span sets, Span dynamic_offsets) const noexcept { dld->vkCmdBindDescriptorSets(handle, bind_point, layout, first, sets.size(), sets.data(), From 4900fee53666e1f18700870e70eadbe449a16801 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 4 Jul 2026 22:05:28 -0400 Subject: [PATCH 27/70] [TEST] Re-test removed features from QCOM --- src/video_core/vulkan_common/vulkan_device.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 4934ec1e00..ef9b672b4d 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -497,10 +497,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken CustomBorderColor."); RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); - LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken Provoking Vertex."); - RemoveExtension(extensions.provoking_vertex, VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME); - LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken push descriptor."); - RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader float controls."); RemoveExtension(extensions.shader_float_controls, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader atomic int64."); From 3dc15a4f96d50a85cb34417256de93f43d402805 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 4 Jul 2026 22:10:02 -0400 Subject: [PATCH 28/70] Smolio Gidolardo --- src/video_core/renderer_vulkan/vk_buffer_cache.cpp | 5 +++++ src/video_core/renderer_vulkan/vk_buffer_cache.h | 5 +---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index 43d32c2213..bc28bb0111 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -102,6 +102,11 @@ Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_) } } +void Buffer::MarkUsage(u64 offset, u64 size) noexcept { + tracker.Track(offset, size); + last_usage_tick = scheduler->CurrentTick(); +} + VkBufferView Buffer::View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format) { if (!device) { // Null buffer supported, return a null descriptor diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index f0ec5399b1..9cf0107e46 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h @@ -43,10 +43,7 @@ public: return tracker.IsUsed(offset, size); } - void MarkUsage(u64 offset, u64 size) noexcept { - tracker.Track(offset, size); - last_usage_tick = scheduler->CurrentTick(); - } + void MarkUsage(u64 offset, u64 size) noexcept; void ResetUsageTracking() noexcept { tracker.Reset(); From dbd59bd119b06644b5e4b0f365358a7a5875f413 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 4 Jul 2026 23:21:46 -0400 Subject: [PATCH 29/70] [vulkan] Override default behavior on rasterizer channel --- src/video_core/renderer_vulkan/vk_rasterizer.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 6f7ce1930d..3030f23410 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -223,7 +223,10 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra scheduler.SetQueryCache(query_cache); } -RasterizerVulkan::~RasterizerVulkan() = default; +RasterizerVulkan::~RasterizerVulkan() { + scheduler.WaitWorker(); + scheduler.Finish(); +} template void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) { From 6eec277428449df74a46afa1d636d4269db3e3da Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 5 Jul 2026 00:07:41 -0400 Subject: [PATCH 30/70] [vulkan] Silence unimplemented format entry --- src/video_core/renderer_vulkan/vk_rasterizer.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 3030f23410..26663d4b25 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -1728,9 +1728,12 @@ void RasterizerVulkan::UpdateBlending(Tegra::Engines::Maxwell3D::Regs& regs) { if (state_tracker.TouchBlendEnable()) { std::array setup_enables{}; for (size_t index = 0; index < Maxwell::NumRenderTargets; index++) { - const auto format = - VideoCore::Surface::PixelFormatFromRenderTargetFormat(regs.rt[index].format); - const bool is_integer = IsPixelFormatInteger(format); + bool is_integer = false; + if (regs.rt[index].format != Tegra::RenderTargetFormat::NONE) { + const auto format = + VideoCore::Surface::PixelFormatFromRenderTargetFormat(regs.rt[index].format); + is_integer = IsPixelFormatInteger(format); + } setup_enables[index] = (!is_integer && regs.blend.enable[index] != 0) ? VK_TRUE : VK_FALSE; } From 963e1f633ba7b52cc82e7a670432f4d55a999202 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 5 Jul 2026 00:36:26 -0400 Subject: [PATCH 31/70] [TEST] Add preliminary/ missing formats based on driver reports for Switch --- src/video_core/renderer_vulkan/maxwell_to_vk.cpp | 16 +++++++++++++++- src/video_core/surface.cpp | 4 ++++ src/video_core/surface.h | 4 ++++ .../texture_cache/format_lookup_table.cpp | 9 +++++++++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp index 77ef8bf7bc..6c18ade926 100644 --- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp +++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp @@ -223,6 +223,10 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK, 0, ETC2_RGB_SRGB) \ SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK, 0, ETC2_RGBA_SRGB) \ SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK, 0, ETC2_RGB_PTA_SRGB) \ + SURFACE_FORMAT_ELEM(VK_FORMAT_EAC_R11_UNORM_BLOCK, 0, EAC_R11_UNORM) \ + SURFACE_FORMAT_ELEM(VK_FORMAT_EAC_R11_SNORM_BLOCK, 0, EAC_R11_SNORM) \ + SURFACE_FORMAT_ELEM(VK_FORMAT_EAC_R11G11_UNORM_BLOCK, 0, EAC_R11G11_UNORM) \ + SURFACE_FORMAT_ELEM(VK_FORMAT_EAC_R11G11_SNORM_BLOCK, 0, EAC_R11G11_SNORM) \ /* Depth formats */ \ SURFACE_FORMAT_ELEM(VK_FORMAT_D32_SFLOAT, usage_attachable, D32_FLOAT) \ SURFACE_FORMAT_ELEM(VK_FORMAT_D16_UNORM, usage_attachable, D16_UNORM) \ @@ -278,7 +282,17 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with } } else if (!device.IsOptimalEtc2Supported() && VideoCore::Surface::IsPixelFormatETC2(pixel_format)) { // Transcode on hardware that doesn't support ETC2 natively - tuple.format = is_srgb ? VK_FORMAT_A8B8G8R8_SRGB_PACK32 : VK_FORMAT_A8B8G8R8_UNORM_PACK32; + if (pixel_format == PixelFormat::EAC_R11_SNORM) { + tuple.format = VK_FORMAT_R8_SNORM; + } else if (pixel_format == PixelFormat::EAC_R11_UNORM) { + tuple.format = VK_FORMAT_R8_UNORM; + } else if (pixel_format == PixelFormat::EAC_R11G11_SNORM) { + tuple.format = VK_FORMAT_R8G8_SNORM; + } else if (pixel_format == PixelFormat::EAC_R11G11_UNORM) { + tuple.format = VK_FORMAT_R8G8_UNORM; + } else { + tuple.format = is_srgb ? VK_FORMAT_A8B8G8R8_SRGB_PACK32 : VK_FORMAT_A8B8G8R8_UNORM_PACK32; + } } bool const attachable = (tuple.usage & usage_attachable) != 0; bool const storage = (tuple.usage & usage_storage) != 0; diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp index 69c1720943..4603870bfa 100644 --- a/src/video_core/surface.cpp +++ b/src/video_core/surface.cpp @@ -344,6 +344,10 @@ bool IsPixelFormatETC2(PixelFormat format) { case PixelFormat::ETC2_RGB_SRGB: case PixelFormat::ETC2_RGBA_SRGB: case PixelFormat::ETC2_RGB_PTA_SRGB: + case PixelFormat::EAC_R11_UNORM: + case PixelFormat::EAC_R11_SNORM: + case PixelFormat::EAC_R11G11_UNORM: + case PixelFormat::EAC_R11G11_SNORM: return true; default: return false; diff --git a/src/video_core/surface.h b/src/video_core/surface.h index 5611dbdffa..8e1973d3b9 100644 --- a/src/video_core/surface.h +++ b/src/video_core/surface.h @@ -119,6 +119,10 @@ namespace VideoCore::Surface { PIXEL_FORMAT_ELEM(ETC2_RGB_SRGB, 4, 4, 64) \ PIXEL_FORMAT_ELEM(ETC2_RGBA_SRGB, 4, 4, 128) \ PIXEL_FORMAT_ELEM(ETC2_RGB_PTA_SRGB, 4, 4, 64) \ + PIXEL_FORMAT_ELEM(EAC_R11_UNORM, 4, 4, 64) \ + PIXEL_FORMAT_ELEM(EAC_R11_SNORM, 4, 4, 64) \ + PIXEL_FORMAT_ELEM(EAC_R11G11_UNORM, 4, 4, 128) \ + PIXEL_FORMAT_ELEM(EAC_R11G11_SNORM, 4, 4, 128) \ /* Depth formats */ \ PIXEL_FORMAT_ELEM(D32_FLOAT, 1, 1, 32) \ PIXEL_FORMAT_ELEM(D16_UNORM, 1, 1, 16) \ diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp index ea68a1c063..d6e019cddf 100644 --- a/src/video_core/texture_cache/format_lookup_table.cpp +++ b/src/video_core/texture_cache/format_lookup_table.cpp @@ -204,6 +204,15 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red, return PixelFormat::ETC2_RGB_PTA_SRGB; case Hash(TextureFormat::ETC2_RGBA, UNORM, SRGB): return PixelFormat::ETC2_RGBA_SRGB; + /* EAC */ + case Hash(TextureFormat::EAC, UNORM): + return PixelFormat::EAC_R11_UNORM; + case Hash(TextureFormat::EAC, SNORM): + return PixelFormat::EAC_R11_SNORM; + case Hash(TextureFormat::EACX2, UNORM): + return PixelFormat::EAC_R11G11_UNORM; + case Hash(TextureFormat::EACX2, SNORM): + return PixelFormat::EAC_R11G11_SNORM; /* ASTC */ case Hash(TextureFormat::ASTC_2D_4X4, UNORM, LINEAR): return PixelFormat::ASTC_2D_4X4_UNORM; From 3b617a673361f08e00823477341d1b878b32d236 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 5 Jul 2026 01:31:51 -0400 Subject: [PATCH 32/70] [TEST] Add B10G11R11 as target format --- src/video_core/vulkan_common/vulkan_device.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index ef9b672b4d..00b30637ad 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -95,6 +95,12 @@ constexpr std::array VK_FORMAT_A4B4G4R4_UNORM_PACK16{ VK_FORMAT_UNDEFINED, }; +constexpr std::array B10G11R11_UFLOAT_PACK32{ + VK_FORMAT_R16G16B16A16_SFLOAT, + VK_FORMAT_A8B8G8R8_SRGB_PACK32, + VK_FORMAT_UNDEFINED, +}; + } // namespace Alternatives template @@ -127,6 +133,8 @@ constexpr const VkFormat* GetFormatAlternatives(VkFormat format) { return Alternatives::VK_FORMAT_R32G32B32_SFLOAT.data(); case VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT: return Alternatives::VK_FORMAT_A4B4G4R4_UNORM_PACK16.data(); + case VK_FORMAT_B10G11R11_UFLOAT_PACK32: + return Alternatives::B10G11R11_UFLOAT_PACK32.data(); default: return nullptr; } From 1987384dec1bbb9e45eed4c7c6932093dc20fc29 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 5 Jul 2026 01:35:12 -0400 Subject: [PATCH 33/70] [TEST] Test for RefreshParametter actual concurrency --- src/video_core/engines/maxwell_3d.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index e7da9d9138..56f8e5f3e9 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -223,9 +223,7 @@ void Maxwell3D::ProcessMacro(Core::System& system, u32 method, const u32* base_s } void Maxwell3D::RefreshParametersImpl() { - if (!Settings::IsGPULevelHigh()) { - return; - } + // Temporary change size_t current_index = 0; for (auto& segment : macro_segments) { if (segment.first == 0) { From 55627c38960808c1840065a375240bb0a92ad22a Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 5 Jul 2026 02:53:52 -0400 Subject: [PATCH 34/70] [video_core] Initial implementation on HDR endpoints for ASTC decoding --- src/video_core/textures/astc.cpp | 371 ++++++++++++++++++++++++++++++- 1 file changed, 361 insertions(+), 10 deletions(-) diff --git a/src/video_core/textures/astc.cpp b/src/video_core/textures/astc.cpp index b7b5f5bd4a..a5db12d75f 100644 --- a/src/video_core/textures/astc.cpp +++ b/src/video_core/textures/astc.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2016 The University of North Carolina at Chapel Hill @@ -1269,6 +1269,220 @@ static inline u32 Select2DPartition(s32 seed, s32 x, s32 y, s32 partitionCount, return SelectPartition(seed, x, y, 0, partitionCount, smallBlock); } +static constexpr bool IsHDRColorEndpointMode(u32 cem) { + switch (cem) { + case 2: + case 3: + case 7: + case 11: + case 14: + case 15: + return true; + default: + return false; + } +} + +// Sign-extends the low nbits of value (a 2's complement field packed into +// the bottom of an otherwise-unsigned integer), per C.2.15's HDR endpoint +// bitfield unpacking. +static constexpr s32 SignExtend(s32 value, u32 nbits) { + const s32 sign_bit = 1 << (nbits - 1); + return (value ^ sign_bit) - sign_bit; +} + +struct HDREndpointRGB { + s32 r0, g0, b0; + s32 r1, g1, b1; +}; + +// HDR Endpoint Mode 7 (C.2.15): base RGB + scale factor. +static void DecodeHDREndpointMode7(u32 v0, u32 v1, u32 v2, u32 v3, s32& r0, s32& g0, s32& b0, + s32& r1, s32& g1, s32& b1) { + const u32 modeval = ((v0 & 0xC0) >> 6) | ((v1 & 0x80) >> 5) | ((v2 & 0x80) >> 4); + + u32 majcomp; + u32 mode; + if ((modeval & 0xC) != 0xC) { + majcomp = modeval >> 2; + mode = modeval & 3; + } else if (modeval != 0xF) { + majcomp = modeval & 3; + mode = 4; + } else { + majcomp = 0; + mode = 5; + } + + s32 red = static_cast(v0 & 0x3f); + s32 green = static_cast(v1 & 0x1f); + s32 blue = static_cast(v2 & 0x1f); + s32 scale = static_cast(v3 & 0x1f); + + const u32 x0 = (v1 >> 6) & 1; + const u32 x1 = (v1 >> 5) & 1; + const u32 x2 = (v2 >> 6) & 1; + const u32 x3 = (v2 >> 5) & 1; + const u32 x4 = (v3 >> 7) & 1; + const u32 x5 = (v3 >> 6) & 1; + const u32 x6 = (v3 >> 5) & 1; + + const u32 ohm = 1u << mode; + if (ohm & 0x30) + green |= static_cast(x0 << 6); + if (ohm & 0x3A) + green |= static_cast(x1 << 5); + if (ohm & 0x30) + blue |= static_cast(x2 << 6); + if (ohm & 0x3A) + blue |= static_cast(x3 << 5); + if (ohm & 0x3D) + scale |= static_cast(x6 << 5); + if (ohm & 0x2D) + scale |= static_cast(x5 << 6); + if (ohm & 0x04) + scale |= static_cast(x4 << 7); + if (ohm & 0x3B) + red |= static_cast(x4 << 6); + if (ohm & 0x04) + red |= static_cast(x3 << 6); + if (ohm & 0x10) + red |= static_cast(x5 << 7); + if (ohm & 0x0F) + red |= static_cast(x2 << 7); + if (ohm & 0x05) + red |= static_cast(x1 << 8); + if (ohm & 0x0A) + red |= static_cast(x0 << 8); + if (ohm & 0x05) + red |= static_cast(x0 << 9); + if (ohm & 0x02) + red |= static_cast(x6 << 9); + if (ohm & 0x01) + red |= static_cast(x3 << 10); + if (ohm & 0x02) + red |= static_cast(x5 << 10); + + static constexpr int shamts[6] = {1, 1, 2, 3, 4, 5}; + const s32 shamt = shamts[mode]; + red <<= shamt; + green <<= shamt; + blue <<= shamt; + scale <<= shamt; + + if (mode != 5) { + green = red - green; + blue = red - blue; + } + + if (majcomp == 1) + std::swap(red, green); + if (majcomp == 2) + std::swap(red, blue); + + r1 = std::clamp(red, 0, 0xFFF); + g1 = std::clamp(green, 0, 0xFFF); + b1 = std::clamp(blue, 0, 0xFFF); + + r0 = std::clamp(red - scale, 0, 0xFFF); + g0 = std::clamp(green - scale, 0, 0xFFF); + b0 = std::clamp(blue - scale, 0, 0xFFF); +} + +// HDR Endpoint Mode 11 (C.2.15): direct RGB pair. Shared by modes 11, 14 and 15, +// which all decode their RGB the same way and only differ in how alpha is filled in. +static HDREndpointRGB DecodeHDREndpointMode11(u32 v0, u32 v1, u32 v2, u32 v3, u32 v4, u32 v5) { + const u32 majcomp = ((v4 & 0x80) >> 7) | ((v5 & 0x80) >> 6); + if (majcomp == 3) { + HDREndpointRGB result; + result.r0 = static_cast(v0 << 4); + result.g0 = static_cast(v2 << 4); + result.b0 = static_cast((v4 & 0x7f) << 5); + result.r1 = static_cast(v1 << 4); + result.g1 = static_cast(v3 << 4); + result.b1 = static_cast((v5 & 0x7f) << 5); + return result; + } + + const u32 mode = ((v1 & 0x80) >> 7) | ((v2 & 0x80) >> 6) | ((v3 & 0x80) >> 5); + s32 va = static_cast(v0 | ((v1 & 0x40) << 2)); + s32 vb0 = static_cast(v2 & 0x3f); + s32 vb1 = static_cast(v3 & 0x3f); + s32 vc = static_cast(v1 & 0x3f); + s32 vd0 = static_cast(v4 & 0x7f); + s32 vd1 = static_cast(v5 & 0x7f); + + static constexpr int dbitstab[8] = {7, 6, 7, 6, 5, 6, 5, 6}; + vd0 = SignExtend(vd0, dbitstab[mode]); + vd1 = SignExtend(vd1, dbitstab[mode]); + + const u32 x0 = (v2 >> 6) & 1; + const u32 x1 = (v3 >> 6) & 1; + const u32 x2 = (v4 >> 6) & 1; + const u32 x3 = (v5 >> 6) & 1; + const u32 x4 = (v4 >> 5) & 1; + const u32 x5 = (v5 >> 5) & 1; + + const u32 ohm = 1u << mode; + if (ohm & 0xA4) + va |= static_cast(x0 << 9); + if (ohm & 0x08) + va |= static_cast(x2 << 9); + if (ohm & 0x50) + va |= static_cast(x4 << 9); + if (ohm & 0x50) + va |= static_cast(x5 << 10); + if (ohm & 0xA0) + va |= static_cast(x1 << 10); + if (ohm & 0xC0) + va |= static_cast(x2 << 11); + if (ohm & 0x04) + vc |= static_cast(x1 << 6); + if (ohm & 0xE8) + vc |= static_cast(x3 << 6); + if (ohm & 0x20) + vc |= static_cast(x2 << 7); + if (ohm & 0x5B) + vb0 |= static_cast(x0 << 6); + if (ohm & 0x5B) + vb1 |= static_cast(x1 << 6); + if (ohm & 0x12) + vb0 |= static_cast(x2 << 7); + if (ohm & 0x12) + vb1 |= static_cast(x3 << 7); + + // NOTE: the published spec text says "modeval >> 1" here, but no "modeval" is defined + // in this decode (that name belongs to Mode 7's unrelated decode) -- substituting the + // "mode" computed just above reproduces exactly Table C.2.23's per-mode shift amounts + // (3,3,2,2,1,1,0,0 for modes 0..7), so this is a spec transcription error, not a real + // "modeval" this function forgot to compute. + const s32 shamt = (static_cast(mode) >> 1) ^ 3; + va <<= shamt; + vb0 <<= shamt; + vb1 <<= shamt; + vc <<= shamt; + vd0 <<= shamt; + vd1 <<= shamt; + + HDREndpointRGB result; + result.r1 = std::clamp(va, 0, 0xFFF); + result.g1 = std::clamp(va - vb0, 0, 0xFFF); + result.b1 = std::clamp(va - vb1, 0, 0xFFF); + + result.r0 = std::clamp(va - vc, 0, 0xFFF); + result.g0 = std::clamp(va - vb0 - vc - vd0, 0, 0xFFF); + result.b0 = std::clamp(va - vb1 - vc - vd1, 0, 0xFFF); + + if (majcomp == 1) { + std::swap(result.r0, result.g0); + std::swap(result.r1, result.g1); + } else if (majcomp == 2) { + std::swap(result.r0, result.b0); + std::swap(result.r1, result.b1); + } + return result; +} + // Section C.2.14 static void ComputeEndpoints(Pixel& ep1, Pixel& ep2, const u32*& colorValues, u32 colorEndpointMode) { @@ -1382,8 +1596,85 @@ static void ComputeEndpoints(Pixel& ep1, Pixel& ep2, const u32*& colorValues, ep2.ClampByte(); } break; + case 2: { + READ_UINT_VALUES(2) + u32 y0, y1; + if (v[1] >= v[0]) { + y0 = v[0] << 4; + y1 = v[1] << 4; + } else { + y0 = (v[1] << 4) + 8; + y1 = (v[0] << 4) - 8; + } + ep1 = Pixel(0x780, y0, y0, y0); + ep2 = Pixel(0x780, y1, y1, y1); + } break; + + case 3: { + READ_UINT_VALUES(2) + u32 y0, d; + if (v[0] & 0x80) { + y0 = ((v[1] & 0xE0) << 4) | ((v[0] & 0x7F) << 2); + d = (v[1] & 0x1F) << 2; + } else { + y0 = ((v[1] & 0xF0) << 4) | ((v[0] & 0x7F) << 1); + d = (v[1] & 0x0F) << 1; + } + const u32 y1 = (std::min)(y0 + d, 0xFFFU); + ep1 = Pixel(0x780, y0, y0, y0); + ep2 = Pixel(0x780, y1, y1, y1); + } break; + + case 7: { + READ_UINT_VALUES(4) + s32 r0, g0, b0, r1, g1, b1; + DecodeHDREndpointMode7(v[0], v[1], v[2], v[3], r0, g0, b0, r1, g1, b1); + ep1 = Pixel(0x780, r0, g0, b0); + ep2 = Pixel(0x780, r1, g1, b1); + } break; + + case 11: { + READ_UINT_VALUES(6) + const HDREndpointRGB rgb = DecodeHDREndpointMode11(v[0], v[1], v[2], v[3], v[4], v[5]); + ep1 = Pixel(0x780, rgb.r0, rgb.g0, rgb.b0); + ep2 = Pixel(0x780, rgb.r1, rgb.g1, rgb.b1); + } break; + + case 14: { + READ_UINT_VALUES(8) + const HDREndpointRGB rgb = DecodeHDREndpointMode11(v[0], v[1], v[2], v[3], v[4], v[5]); + // Only mode with LDR (8-bit UNORM)-interpreted alpha; left as-is (0-255). + ep1 = Pixel(v[6], rgb.r0, rgb.g0, rgb.b0); + ep2 = Pixel(v[7], rgb.r1, rgb.g1, rgb.b1); + } break; + + case 15: { + READ_UINT_VALUES(8) + const HDREndpointRGB rgb = DecodeHDREndpointMode11(v[0], v[1], v[2], v[3], v[4], v[5]); + const u32 mode = ((v[6] >> 7) & 1) | ((v[7] >> 6) & 2); + s32 a6 = static_cast(v[6] & 0x7F); + s32 a7 = static_cast(v[7] & 0x7F); + s32 alpha0, alpha1; + if (mode == 3) { + alpha0 = a6 << 5; + alpha1 = a7 << 5; + } else { + a6 |= (a7 << (mode + 1)) & 0x780; + a7 &= (0x3F >> mode); + a7 ^= 0x20 >> mode; + a7 -= 0x20 >> mode; + a6 <<= (4 - mode); + a7 <<= (4 - mode); + a7 += a6; + alpha0 = a6; + alpha1 = std::clamp(a7, 0, 0xFFF); + } + ep1 = Pixel(alpha0, rgb.r0, rgb.g0, rgb.b0); + ep2 = Pixel(alpha1, rgb.r1, rgb.g1, rgb.b1); + } break; + default: - assert(false && "Unsupported color endpoint mode (is it HDR?)"); + assert(false && "Unsupported color endpoint mode"); break; } @@ -1414,6 +1705,39 @@ static void FillVoidExtentLDR(InputBitStream& strm, std::span outBuf, u32 b } } +static float HalfToFloat(u16 h) { + const u32 sign = static_cast(h & 0x8000) << 16; + u32 exp = (h & 0x7C00) >> 10; + u32 mant = h & 0x3FF; + u32 bits; + if (exp == 0) { + if (mant == 0) { + bits = sign; + } else { + s32 e = 127 - 15 + 1; + while ((mant & 0x400) == 0) { + mant <<= 1; + --e; + } + mant &= 0x3FF; + bits = sign | (static_cast(e) << 23) | (mant << 13); + } + } else if (exp == 0x1F) { + bits = sign | 0x7F800000 | (mant << 13); + } else { + bits = sign | ((exp - 15 + 127) << 23) | (mant << 13); + } + float result; + std::memcpy(&result, &bits, sizeof(result)); + return result; +} + +static u16 HalfToClampedByte(u16 half_bits) { + const float value = HalfToFloat(half_bits); + const float clamped = std::clamp(value, 0.0f, 1.0f); + return static_cast(clamped * 255.0f + 0.5f); +} + static void FillError(std::span outBuf, u32 blockWidth, u32 blockHeight) { for (u32 j = 0; j < blockHeight; j++) { for (u32 i = 0; i < blockWidth; i++) { @@ -1631,22 +1955,49 @@ static void DecompressBlock(std::span inBuf, const u32 blockWidth, Pixel p; for (u32 c = 0; c < 4; c++) { u32 C0 = endpoints[partition][0].Component(c); - C0 = ReplicateByteTo16(C0); u32 C1 = endpoints[partition][1].Component(c); - C1 = ReplicateByteTo16(C1); u32 plane = 0; if (weightParams.m_bDualPlane && (((planeIdx + 1) & 3) == c)) { plane = 1; } - u32 weight = weights[plane][j * blockWidth + i]; - u32 C = (C0 * (64 - weight) + C1 * weight + 32) / 64; - if (C == 65535) { - p.Component(c) = 255; + + // Mode 14 is RGB-HDR but keeps an LDR (8-bit UNORM)-interpreted alpha + // (component 0 here, see Pixel::A()) -- the only HDR mode with this split. + const bool is_hdr = IsHDRColorEndpointMode(colorEndpointMode[partition]) && + !(colorEndpointMode[partition] == 14 && c == 0); + + if (is_hdr) { + // Endpoints are raw 12-bit pseudo-logarithmic values; shift left 4 bits + // to become 16-bit before interpolating, per C.2.19. + C0 <<= 4; + C1 <<= 4; + const u32 C = (C0 * (64 - weight) + C1 * weight + 32) / 64; + const u32 E = (C & 0xF800) >> 11; + const u32 M = C & 0x7FF; + u32 Mt; + if (M < 512) { + Mt = 3 * M; + } else if (M >= 1536) { + Mt = 5 * M - 2048; + } else { + Mt = 4 * M - 512; + } + const u32 Cf = (E << 10) + (Mt >> 3); + // +Inf/NaN clamps to the largest finite FP16 value (0x7BFF). + const u16 half_bits = (Cf >= 0x7C00) ? u16{0x7BFF} : static_cast(Cf); + p.Component(c) = HalfToClampedByte(half_bits); } else { - double Cf = static_cast(C); - p.Component(c) = static_cast(255.0 * (Cf / 65536.0) + 0.5); + C0 = ReplicateByteTo16(C0); + C1 = ReplicateByteTo16(C1); + const u32 C = (C0 * (64 - weight) + C1 * weight + 32) / 64; + if (C == 65535) { + p.Component(c) = 255; + } else { + double Cf = static_cast(C); + p.Component(c) = static_cast(255.0 * (Cf / 65536.0) + 0.5); + } } } From 7bf0e49a9133330a09ce3511b5cb2581a19e94f9 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 5 Jul 2026 03:45:32 -0400 Subject: [PATCH 35/70] [vulkan] Plumbing ASTC decoding capabilities for HDR --- src/video_core/host_shaders/astc_decoder.comp | 313 +++++++++++++++++- .../renderer_vulkan/vk_compute_pass.cpp | 6 +- .../renderer_vulkan/vk_texture_cache.cpp | 52 ++- src/video_core/vulkan_common/vulkan_device.h | 9 +- 4 files changed, 360 insertions(+), 20 deletions(-) diff --git a/src/video_core/host_shaders/astc_decoder.comp b/src/video_core/host_shaders/astc_decoder.comp index da21b4bde8..f922b4c44f 100644 --- a/src/video_core/host_shaders/astc_decoder.comp +++ b/src/video_core/host_shaders/astc_decoder.comp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -30,6 +33,7 @@ UNIFORM(3) uint block_size; UNIFORM(4) uint x_shift; UNIFORM(5) uint block_height; UNIFORM(6) uint block_height_mask; +UNIFORM(7) uint is_srgb; END_PUSH_CONSTANTS struct EncodingData { @@ -40,7 +44,11 @@ layout(binding = BINDING_INPUT_BUFFER, std430) readonly restrict buffer InputBuf uvec4 astc_data[]; }; +#ifdef VULKAN +layout(binding = BINDING_OUTPUT_IMAGE, rgba16f) uniform writeonly restrict image2DArray dest_image; +#else layout(binding = BINDING_OUTPUT_IMAGE, rgba8) uniform writeonly restrict image2DArray dest_image; +#endif const uint GOB_SIZE_X_SHIFT = 6; const uint GOB_SIZE_Y_SHIFT = 3; @@ -130,6 +138,16 @@ uvec4 ReplicateByteTo16(uvec4 value) { return value * 0x101; } +// sRGB EOTF (decode: encoded value -> linear). Only used on the Vulkan path, where the +// destination image is a linear float format with no format-level sRGB tag of its own. +float SRGBToLinear(float c) { + return (c <= 0.04045) ? (c / 12.92) : pow((c + 0.055) / 1.055, 2.4); +} + +vec3 SRGBToLinear(vec3 c) { + return vec3(SRGBToLinear(c.x), SRGBToLinear(c.y), SRGBToLinear(c.z)); +} + uint ReplicateBitTo7(uint value) { return value * 127; } @@ -589,6 +607,163 @@ ivec4 BlueContract(int a, int r, int g, int b) { return ivec4(a, (r + b) >> 1, (g + b) >> 1, b); } +bool IsHDRColorEndpointMode(uint cem) { + return cem == 2u || cem == 3u || cem == 7u || cem == 11u || cem == 14u || cem == 15u; +} + +// Sign-extends the low nbits of value (a 2's complement field packed into the bottom of an +// otherwise-unsigned integer), per C.2.15's HDR endpoint bitfield unpacking. +int SignExtend(int value, uint nbits) { + int sign_bit = 1 << (nbits - 1u); + return (value ^ sign_bit) - sign_bit; +} + +// HDR Endpoint Mode 7 (C.2.15): base RGB + scale factor. +void DecodeHDREndpointMode7(uint v0, uint v1, uint v2, uint v3, out ivec3 e0, out ivec3 e1) { + uint modeval = ((v0 & 0xC0u) >> 6u) | ((v1 & 0x80u) >> 5u) | ((v2 & 0x80u) >> 4u); + + uint majcomp; + uint mode; + if ((modeval & 0xCu) != 0xCu) { + majcomp = modeval >> 2u; + mode = modeval & 3u; + } else if (modeval != 0xFu) { + majcomp = modeval & 3u; + mode = 4u; + } else { + majcomp = 0u; + mode = 5u; + } + + int red = int(v0 & 0x3Fu); + int green = int(v1 & 0x1Fu); + int blue = int(v2 & 0x1Fu); + int scale = int(v3 & 0x1Fu); + + uint x0 = (v1 >> 6u) & 1u; + uint x1 = (v1 >> 5u) & 1u; + uint x2 = (v2 >> 6u) & 1u; + uint x3 = (v2 >> 5u) & 1u; + uint x4 = (v3 >> 7u) & 1u; + uint x5 = (v3 >> 6u) & 1u; + uint x6 = (v3 >> 5u) & 1u; + + uint ohm = 1u << mode; + if ((ohm & 0x30u) != 0u) green |= int(x0 << 6u); + if ((ohm & 0x3Au) != 0u) green |= int(x1 << 5u); + if ((ohm & 0x30u) != 0u) blue |= int(x2 << 6u); + if ((ohm & 0x3Au) != 0u) blue |= int(x3 << 5u); + if ((ohm & 0x3Du) != 0u) scale |= int(x6 << 5u); + if ((ohm & 0x2Du) != 0u) scale |= int(x5 << 6u); + if ((ohm & 0x04u) != 0u) scale |= int(x4 << 7u); + if ((ohm & 0x3Bu) != 0u) red |= int(x4 << 6u); + if ((ohm & 0x04u) != 0u) red |= int(x3 << 6u); + if ((ohm & 0x10u) != 0u) red |= int(x5 << 7u); + if ((ohm & 0x0Fu) != 0u) red |= int(x2 << 7u); + if ((ohm & 0x05u) != 0u) red |= int(x1 << 8u); + if ((ohm & 0x0Au) != 0u) red |= int(x0 << 8u); + if ((ohm & 0x05u) != 0u) red |= int(x0 << 9u); + if ((ohm & 0x02u) != 0u) red |= int(x6 << 9u); + if ((ohm & 0x01u) != 0u) red |= int(x3 << 10u); + if ((ohm & 0x02u) != 0u) red |= int(x5 << 10u); + + int shamts[6] = int[](1, 1, 2, 3, 4, 5); + int shamt = shamts[mode]; + red <<= shamt; + green <<= shamt; + blue <<= shamt; + scale <<= shamt; + + if (mode != 5u) { + green = red - green; + blue = red - blue; + } + + if (majcomp == 1u) { + int t = red; red = green; green = t; + } + if (majcomp == 2u) { + int t = red; red = blue; blue = t; + } + + e1 = ivec3(clamp(red, 0, 0xFFF), clamp(green, 0, 0xFFF), clamp(blue, 0, 0xFFF)); + e0 = ivec3(clamp(red - scale, 0, 0xFFF), clamp(green - scale, 0, 0xFFF), + clamp(blue - scale, 0, 0xFFF)); +} + +// HDR Endpoint Mode 11 (C.2.15): direct RGB pair. Shared by modes 11, 14 and 15, which all +// decode their RGB the same way and only differ in how alpha is filled in. +void DecodeHDREndpointMode11(uint v0, uint v1, uint v2, uint v3, uint v4, uint v5, out ivec3 e0, + out ivec3 e1) { + uint majcomp = ((v4 & 0x80u) >> 7u) | ((v5 & 0x80u) >> 6u); + if (majcomp == 3u) { + e0 = ivec3(int(v0 << 4u), int(v2 << 4u), int((v4 & 0x7Fu) << 5u)); + e1 = ivec3(int(v1 << 4u), int(v3 << 4u), int((v5 & 0x7Fu) << 5u)); + return; + } + + uint mode = ((v1 & 0x80u) >> 7u) | ((v2 & 0x80u) >> 6u) | ((v3 & 0x80u) >> 5u); + int va = int(v0 | ((v1 & 0x40u) << 2u)); + int vb0 = int(v2 & 0x3Fu); + int vb1 = int(v3 & 0x3Fu); + int vc = int(v1 & 0x3Fu); + int vd0 = int(v4 & 0x7Fu); + int vd1 = int(v5 & 0x7Fu); + + int dbitstab[8] = int[](7, 6, 7, 6, 5, 6, 5, 6); + vd0 = SignExtend(vd0, uint(dbitstab[mode])); + vd1 = SignExtend(vd1, uint(dbitstab[mode])); + + uint x0 = (v2 >> 6u) & 1u; + uint x1 = (v3 >> 6u) & 1u; + uint x2 = (v4 >> 6u) & 1u; + uint x3 = (v5 >> 6u) & 1u; + uint x4 = (v4 >> 5u) & 1u; + uint x5 = (v5 >> 5u) & 1u; + + uint ohm = 1u << mode; + if ((ohm & 0xA4u) != 0u) va |= int(x0 << 9u); + if ((ohm & 0x08u) != 0u) va |= int(x2 << 9u); + if ((ohm & 0x50u) != 0u) va |= int(x4 << 9u); + if ((ohm & 0x50u) != 0u) va |= int(x5 << 10u); + if ((ohm & 0xA0u) != 0u) va |= int(x1 << 10u); + if ((ohm & 0xC0u) != 0u) va |= int(x2 << 11u); + if ((ohm & 0x04u) != 0u) vc |= int(x1 << 6u); + if ((ohm & 0xE8u) != 0u) vc |= int(x3 << 6u); + if ((ohm & 0x20u) != 0u) vc |= int(x2 << 7u); + if ((ohm & 0x5Bu) != 0u) vb0 |= int(x0 << 6u); + if ((ohm & 0x5Bu) != 0u) vb1 |= int(x1 << 6u); + if ((ohm & 0x12u) != 0u) vb0 |= int(x2 << 7u); + if ((ohm & 0x12u) != 0u) vb1 |= int(x3 << 7u); + + int shamt = (int(mode) >> 1) ^ 3; + va <<= shamt; + vb0 <<= shamt; + vb1 <<= shamt; + vc <<= shamt; + vd0 <<= shamt; + vd1 <<= shamt; + + int r1 = clamp(va, 0, 0xFFF); + int g1 = clamp(va - vb0, 0, 0xFFF); + int b1 = clamp(va - vb1, 0, 0xFFF); + int r0 = clamp(va - vc, 0, 0xFFF); + int g0 = clamp(va - vb0 - vc - vd0, 0, 0xFFF); + int b0 = clamp(va - vb1 - vc - vd1, 0, 0xFFF); + + if (majcomp == 1u) { + int t; + t = r0; r0 = g0; g0 = t; + t = r1; r1 = g1; g1 = t; + } else if (majcomp == 2u) { + int t; + t = r0; r0 = b0; b0 = t; + t = r1; r1 = b1; b1 = t; + } + e0 = ivec3(r0, g0, b0); + e1 = ivec3(r1, g1, b1); +} + void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode, uint color_values[32], inout uint colvals_index) { #define READ_UINT_VALUES(N) \ @@ -715,8 +890,88 @@ void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode, ui } break; } + case 2: { + READ_UINT_VALUES(2) + uint y0, y1; + if (V[0].y >= V[0].x) { + y0 = V[0].x << 4u; + y1 = V[0].y << 4u; + } else { + y0 = (V[0].y << 4u) + 8u; + y1 = (V[0].x << 4u) - 8u; + } + ep1 = uvec4(0x780u, y0, y0, y0); + ep2 = uvec4(0x780u, y1, y1, y1); + break; + } + case 3: { + READ_UINT_VALUES(2) + uint y0, d; + if ((V[0].x & 0x80u) != 0u) { + y0 = ((V[0].y & 0xE0u) << 4u) | ((V[0].x & 0x7Fu) << 2u); + d = (V[0].y & 0x1Fu) << 2u; + } else { + y0 = ((V[0].y & 0xF0u) << 4u) | ((V[0].x & 0x7Fu) << 1u); + d = (V[0].y & 0x0Fu) << 1u; + } + const uint y1 = min(y0 + d, 0xFFFu); + ep1 = uvec4(0x780u, y0, y0, y0); + ep2 = uvec4(0x780u, y1, y1, y1); + break; + } + case 7: { + READ_UINT_VALUES(4) + ivec3 e0, e1; + DecodeHDREndpointMode7(V[0].x, V[0].y, V[0].z, V[0].w, e0, e1); + ep1 = uvec4(0x780u, uint(e0.x), uint(e0.y), uint(e0.z)); + ep2 = uvec4(0x780u, uint(e1.x), uint(e1.y), uint(e1.z)); + break; + } + case 11: { + READ_UINT_VALUES(6) + ivec3 e0, e1; + DecodeHDREndpointMode11(V[0].x, V[0].y, V[0].z, V[0].w, V[1].x, V[1].y, e0, e1); + ep1 = uvec4(0x780u, uint(e0.x), uint(e0.y), uint(e0.z)); + ep2 = uvec4(0x780u, uint(e1.x), uint(e1.y), uint(e1.z)); + break; + } + case 14: { + READ_UINT_VALUES(8) + ivec3 e0, e1; + DecodeHDREndpointMode11(V[0].x, V[0].y, V[0].z, V[0].w, V[1].x, V[1].y, e0, e1); + // Only HDR mode with LDR (8-bit UNORM)-interpreted alpha; left as-is (0-255). + ep1 = uvec4(V[1].z, uint(e0.x), uint(e0.y), uint(e0.z)); + ep2 = uvec4(V[1].w, uint(e1.x), uint(e1.y), uint(e1.z)); + break; + } + case 15: { + READ_UINT_VALUES(8) + ivec3 e0, e1; + DecodeHDREndpointMode11(V[0].x, V[0].y, V[0].z, V[0].w, V[1].x, V[1].y, e0, e1); + const uint mode = ((V[1].z >> 7u) & 1u) | ((V[1].w >> 6u) & 2u); + int a6 = int(V[1].z & 0x7Fu); + int a7 = int(V[1].w & 0x7Fu); + int alpha0, alpha1; + if (mode == 3u) { + alpha0 = a6 << 5; + alpha1 = a7 << 5; + } else { + a6 |= (a7 << int(mode + 1u)) & 0x780; + a7 &= int(0x3Fu >> mode); + a7 ^= int(0x20u >> mode); + a7 -= int(0x20u >> mode); + a6 <<= int(4u - mode); + a7 <<= int(4u - mode); + a7 += a6; + alpha0 = a6; + alpha1 = clamp(a7, 0, 0xFFF); + } + ep1 = uvec4(uint(alpha0), uint(e0.x), uint(e0.y), uint(e0.z)); + ep2 = uvec4(uint(alpha1), uint(e1.x), uint(e1.y), uint(e1.z)); + break; + } default: { - // HDR mode, or more likely a bug computing the color_endpoint_mode + // Not a valid CEM at all (all 16 values are now handled above). ep1 = uvec4(0xFF, 0xFF, 0, 0); ep2 = uvec4(0xFF, 0xFF, 0, 0); break; @@ -1112,13 +1367,59 @@ void DecompressBlock(ivec3 coord) { if (num_partitions > 1) { local_partition = Select2DPartition(partition_index, i, j, num_partitions); } - const uvec4 C0 = ReplicateByteTo16(endpoints0[local_partition]); - const uvec4 C1 = ReplicateByteTo16(endpoints1[local_partition]); + const uint local_cem = color_endpoint_mode[local_partition]; const uvec4 weight_vec = GetUnquantizedWeightVector(j, i, size_params, plane_index, dual_plane); - const vec4 Cf = - vec4((C0 * (uvec4(64) - weight_vec) + C1 * weight_vec + uvec4(32)) / 64); - const vec4 p = (Cf / 65535.0f); + + vec4 p; + if (IsHDRColorEndpointMode(local_cem)) { + // Endpoints are raw 12-bit pseudo-logarithmic values; shift left 4 bits to + // become 16-bit before interpolating, per C.2.19. + const uvec4 C0 = endpoints0[local_partition] << 4u; + const uvec4 C1 = endpoints1[local_partition] << 4u; + const uvec4 C = (C0 * (uvec4(64) - weight_vec) + C1 * weight_vec + uvec4(32)) / 64u; + const uvec4 E = (C & uvec4(0xF800u)) >> 11u; + const uvec4 M = C & uvec4(0x7FFu); + const uvec4 Mt_lo = 3u * M; + const uvec4 Mt_mid = 4u * M - 512u; + const uvec4 Mt_hi = 5u * M - 2048u; + const uvec4 Mt = + mix(Mt_lo, mix(Mt_mid, Mt_hi, greaterThanEqual(M, uvec4(1536u))), + greaterThanEqual(M, uvec4(512u))); + const uvec4 Cf = (E << 10u) + (Mt >> 3u); + // +Inf/NaN clamps to the largest finite FP16 value (0x7BFF). + const uvec4 half_bits = mix(Cf, uvec4(0x7BFFu), greaterThanEqual(Cf, uvec4(0x7C00u))); + p = vec4(unpackHalf2x16(half_bits.x).x, unpackHalf2x16(half_bits.y).x, + unpackHalf2x16(half_bits.z).x, unpackHalf2x16(half_bits.w).x); + + // Mode 14 keeps an LDR (8-bit UNORM)-interpreted alpha; component 0 here (A, + // see the ep1/ep2 layout used throughout this file). + if (local_cem == 14u) { + const uint a0 = ReplicateByteTo16(uvec4(endpoints0[local_partition].x)).x; + const uint a1 = ReplicateByteTo16(uvec4(endpoints1[local_partition].x)).x; + const uint Ca = (a0 * (64u - weight_vec.x) + a1 * weight_vec.x + 32u) / 64u; + p.x = float(Ca) / 65535.0f; + } + } else { + const uvec4 C0 = ReplicateByteTo16(endpoints0[local_partition]); + const uvec4 C1 = ReplicateByteTo16(endpoints1[local_partition]); + const vec4 Cf = + vec4((C0 * (uvec4(64) - weight_vec) + C1 * weight_vec + uvec4(32)) / 64); + p = Cf / 65535.0f; +#ifdef VULKAN + // Destination is always linear RGBA16F on this path; apply the sRGB curve + // ourselves since there's no sRGB-tagged format left to do it automatically. + // p is (A,R,G,B); alpha (p.x) is never gamma-encoded. + if (is_srgb != 0u) { + p.yzw = SRGBToLinear(p.yzw); + } +#endif + } + +#ifdef VULKAN imageStore(dest_image, coord + ivec3(i, j, 0), p.gbar); +#else + imageStore(dest_image, coord + ivec3(i, j, 0), clamp(p, 0.0f, 1.0f).gbar); +#endif } } } diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index 0011ec3bf5..5246b284ca 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -26,6 +26,7 @@ #include "video_core/host_shaders/vulkan_uint8_comp_spv.h" #include "video_core/host_shaders/block_linear_unswizzle_3d_bcn_comp_spv.h" #include "video_core/renderer_vulkan/vk_compute_pass.h" +#include "video_core/surface.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" @@ -220,6 +221,7 @@ struct AstcPushConstants { u32 x_shift; u32 block_height; u32 block_height_mask; + u32 is_srgb; }; struct QueriesPrefixScanPushConstants { @@ -563,6 +565,7 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map, VideoCore::Surface::DefaultBlockWidth(image.info.format), VideoCore::Surface::DefaultBlockHeight(image.info.format), }; + const bool is_srgb = VideoCore::Surface::IsPixelFormatSRGB(image.info.format); scheduler.RequestOutsideRenderPassOperationContext(); const VkPipeline vk_pipeline = *pipeline; const VkImageAspectFlags aspect_mask = image.AspectMask(); @@ -612,7 +615,7 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map, ASSERT(params.destination == (std::array{0, 0, 0})); ASSERT(params.bytes_per_block_log2 == 4); scheduler.Record([this, num_dispatches_x, num_dispatches_y, num_dispatches_z, block_dims, - params, descriptor_data](vk::CommandBuffer cmdbuf) { + params, descriptor_data, is_srgb](vk::CommandBuffer cmdbuf) { const AstcPushConstants uniforms{ .blocks_dims = block_dims, .layer_stride = params.layer_stride, @@ -620,6 +623,7 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map, .x_shift = params.x_shift, .block_height = params.block_height, .block_height_mask = params.block_height_mask, + .is_srgb = is_srgb ? 1u : 0u, }; const VkDescriptorSet set = descriptor_allocator.Commit(); device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index a0db94b4af..c0920be751 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -128,9 +129,34 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { return usage; } -[[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info) { - const auto format_info = +// Whether an ASTC texture will be decoded via the GPU compute pass (ASTCDecoderPass / +// astc_decoder.comp) rather than the CPU ConvertImage path. Must stay the single source of +// truth for this decision: both the destination image's real format (below) and the +// AcceleratedUpload flag (Image::Image()) key off of it, and they must never disagree -- +// the compute shader's declared storage image format has to match what was actually allocated. +[[nodiscard]] bool WillUseAcceleratedAstcDecode(const Device& device, const ImageInfo& info) { + if (!IsPixelFormatASTC(info.format) || device.IsOptimalAstcSupported()) { + return false; + } + if (Settings::values.accelerate_astc.GetValue() != Settings::AstcDecodeMode::Gpu) { + return false; + } + return Settings::values.astc_recompression.GetValue() == + Settings::AstcRecompression::Uncompressed && + info.size.depth == 1; +} + +[[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info, + std::optional format_override = {}) { + auto format_info = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, info.format); + if (format_override) { + // Accelerated ASTC HDR decode target: real (unclamped) values need a linear float + // format, not whatever the guest ASTC pixel format would normally map to. + format_info.format = *format_override; + format_info.attachable = false; + format_info.storage = true; + } VkImageCreateFlags flags{}; if (info.type == ImageType::e2D && info.resources.layers >= 6 && info.size.width == info.size.height && !device.HasBrokenCubeImageCompatibility()) { @@ -164,11 +190,12 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { } [[nodiscard]] vk::Image MakeImage(const Device& device, const MemoryAllocator& allocator, - const ImageInfo& info, std::span view_formats) { + const ImageInfo& info, std::span view_formats, + std::optional format_override = {}) { if (info.type == ImageType::Buffer) { return vk::Image{}; } - VkImageCreateInfo image_ci = MakeImageCreateInfo(device, info); + VkImageCreateInfo image_ci = MakeImageCreateInfo(device, info, format_override); const VkImageFormatListCreateInfo image_format_list = { .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO, .pNext = nullptr, @@ -1557,15 +1584,17 @@ void TextureCacheRuntime::TickFrame() {} Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu_addr_, VAddr cpu_addr_) : VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime_.scheduler}, - runtime{&runtime_}, original_image(MakeImage(runtime_.device, runtime_.memory_allocator, info, - runtime->ViewFormats(info.format))), + runtime{&runtime_}, + original_image(MakeImage(runtime_.device, runtime_.memory_allocator, info, + runtime->ViewFormats(info.format), + WillUseAcceleratedAstcDecode(runtime_.device, info) + ? std::make_optional(VK_FORMAT_R16G16B16A16_SFLOAT) + : std::nullopt)), aspect_mask(ImageAspectMask(info.format)) { if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported()) { switch (Settings::values.accelerate_astc.GetValue()) { case Settings::AstcDecodeMode::Gpu: - if (Settings::values.astc_recompression.GetValue() == - Settings::AstcRecompression::Uncompressed && - info.size.depth == 1) { + if (WillUseAcceleratedAstcDecode(runtime->device, info)) { flags |= VideoCommon::ImageFlagBits::AcceleratedUpload; } break; @@ -1591,9 +1620,12 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu Settings::values.astc_recompression.GetValue() == Settings::AstcRecompression::Uncompressed) { const auto& device = runtime->device.GetLogical(); + const VkFormat storage_format = WillUseAcceleratedAstcDecode(runtime->device, info) + ? VK_FORMAT_R16G16B16A16_SFLOAT + : VK_FORMAT_A8B8G8R8_UNORM_PACK32; for (s32 level = 0; level < info.resources.levels; ++level) { storage_image_views[level] = - MakeStorageView(device, level, *original_image, VK_FORMAT_A8B8G8R8_UNORM_PACK32); + MakeStorageView(device, level, *original_image, storage_format); } } } diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index cf039768fd..a944e7e297 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -72,7 +72,9 @@ VK_DEFINE_HANDLE(VmaAllocator) FEATURE(KHR, PipelineExecutableProperties, PIPELINE_EXECUTABLE_PROPERTIES, \ pipeline_executable_properties) \ FEATURE(KHR, WorkgroupMemoryExplicitLayout, WORKGROUP_MEMORY_EXPLICIT_LAYOUT, \ - workgroup_memory_explicit_layout) + workgroup_memory_explicit_layout) \ + FEATURE(EXT, TextureCompressionASTCHDR, TEXTURE_COMPRESSION_ASTC_HDR, \ + texture_compression_astc_hdr) // Define miscellaneous extensions which may be used by the implementation here. @@ -348,9 +350,10 @@ FN_MAX_LIMIT_LIST return properties.float_controls; } - /// Returns true if ASTC is natively supported. + /// Returns true if ASTC is natively supported, including HDR-profile (non-LDR-only) bool IsOptimalAstcSupported() const { - return features.features.textureCompressionASTC_LDR; + return features.features.textureCompressionASTC_LDR && + features.texture_compression_astc_hdr.textureCompressionASTC_HDR; } /// Returns true if BCn is natively supported. From 785461a26c285fcfe54284751545beda2cd529a0 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 5 Jul 2026 05:06:16 -0400 Subject: [PATCH 36/70] [vulkan] Verify actual format of ASTC before decode --- src/video_core/renderer_vulkan/vk_texture_cache.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index c0920be751..a9d8abde02 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -1586,7 +1586,9 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu : VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime_.scheduler}, runtime{&runtime_}, original_image(MakeImage(runtime_.device, runtime_.memory_allocator, info, - runtime->ViewFormats(info.format), + WillUseAcceleratedAstcDecode(runtime_.device, info) + ? std::span{} + : runtime->ViewFormats(info.format), WillUseAcceleratedAstcDecode(runtime_.device, info) ? std::make_optional(VK_FORMAT_R16G16B16A16_SFLOAT) : std::nullopt)), From 8cc658e5be23d3adb8c6524ad76efd9f95497a4e Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 5 Jul 2026 05:26:47 -0400 Subject: [PATCH 37/70] [vulkan] Updated Typeless format structure for ASTC decoding --- .../renderer_vulkan/vk_texture_cache.cpp | 17 +++++++++++++---- .../renderer_vulkan/vk_texture_cache.h | 2 ++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index a9d8abde02..dd705e013a 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -1976,8 +1976,11 @@ void Image::DownloadMemory(const StagingBufferRef& map, std::spandevice, FormatType::Optimal, true, info.format); + if (WillUseAcceleratedAstcDecode(runtime->device, info)) { + format_info.format = VK_FORMAT_R16G16B16A16_SFLOAT; + } view = MakeStorageView(runtime->device.GetLogical(), level, *(this->*current_image), format_info.format); } @@ -2144,7 +2147,11 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI SanitizeDepthStencilSwizzle(swizzle, device->SupportsDepthStencilSwizzleOne()); } } - const auto format_info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format); + uses_accelerated_astc_decode = WillUseAcceleratedAstcDecode(*device, image.info); + auto format_info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format); + if (uses_accelerated_astc_decode) { + format_info.format = VK_FORMAT_R16G16B16A16_SFLOAT; + } const VkImageUsageFlags requested_view_usage = ImageUsageFlags(format_info, format); const VkImageUsageFlags image_usage = image.UsageFlags(); const VkImageUsageFlags clamped_view_usage = requested_view_usage & image_usage; @@ -2279,8 +2286,10 @@ VkImageView ImageView::StorageView(Shader::TextureType texture_type, if (image_handle) { if (image_format == Shader::ImageFormat::Typeless) { if (!typeless_storage_view) { - const auto& info = - MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format); + auto info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format); + if (uses_accelerated_astc_decode) { + info.format = VK_FORMAT_R16G16B16A16_SFLOAT; + } typeless_storage_view = MakeView(info.format, VK_IMAGE_ASPECT_COLOR_BIT); } return *typeless_storage_view; diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 005ab94762..0d11c71a42 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -386,6 +386,8 @@ private: VkImageView render_target = VK_NULL_HANDLE; VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT; u32 buffer_size = 0; + + bool uses_accelerated_astc_decode = false; }; class ImageAlloc : public VideoCommon::ImageAllocBase {}; From dbb48c4e4ed5a73b2a510f11943693f777dfff34 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 5 Jul 2026 06:24:17 -0400 Subject: [PATCH 38/70] [TEST] Format swap --- src/video_core/host_shaders/astc_decoder.comp | 2 +- src/video_core/renderer_vulkan/vk_texture_cache.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/video_core/host_shaders/astc_decoder.comp b/src/video_core/host_shaders/astc_decoder.comp index f922b4c44f..ef3b2d09bd 100644 --- a/src/video_core/host_shaders/astc_decoder.comp +++ b/src/video_core/host_shaders/astc_decoder.comp @@ -45,7 +45,7 @@ layout(binding = BINDING_INPUT_BUFFER, std430) readonly restrict buffer InputBuf }; #ifdef VULKAN -layout(binding = BINDING_OUTPUT_IMAGE, rgba16f) uniform writeonly restrict image2DArray dest_image; +layout(binding = BINDING_OUTPUT_IMAGE, rgba32f) uniform writeonly restrict image2DArray dest_image; #else layout(binding = BINDING_OUTPUT_IMAGE, rgba8) uniform writeonly restrict image2DArray dest_image; #endif diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index dd705e013a..29ec1a5127 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -1590,7 +1590,7 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu ? std::span{} : runtime->ViewFormats(info.format), WillUseAcceleratedAstcDecode(runtime_.device, info) - ? std::make_optional(VK_FORMAT_R16G16B16A16_SFLOAT) + ? std::make_optional(VK_FORMAT_R32G32B32A32_SFLOAT) : std::nullopt)), aspect_mask(ImageAspectMask(info.format)) { if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported()) { @@ -1623,7 +1623,7 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu Settings::AstcRecompression::Uncompressed) { const auto& device = runtime->device.GetLogical(); const VkFormat storage_format = WillUseAcceleratedAstcDecode(runtime->device, info) - ? VK_FORMAT_R16G16B16A16_SFLOAT + ? VK_FORMAT_R32G32B32A32_SFLOAT : VK_FORMAT_A8B8G8R8_UNORM_PACK32; for (s32 level = 0; level < info.resources.levels; ++level) { storage_image_views[level] = @@ -1979,7 +1979,7 @@ VkImageView Image::StorageImageView(s32 level) noexcept { auto format_info = MaxwellToVK::SurfaceFormat(runtime->device, FormatType::Optimal, true, info.format); if (WillUseAcceleratedAstcDecode(runtime->device, info)) { - format_info.format = VK_FORMAT_R16G16B16A16_SFLOAT; + format_info.format = VK_FORMAT_R32G32B32A32_SFLOAT; } view = MakeStorageView(runtime->device.GetLogical(), level, *(this->*current_image), format_info.format); @@ -2150,7 +2150,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI uses_accelerated_astc_decode = WillUseAcceleratedAstcDecode(*device, image.info); auto format_info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format); if (uses_accelerated_astc_decode) { - format_info.format = VK_FORMAT_R16G16B16A16_SFLOAT; + format_info.format = VK_FORMAT_R32G32B32A32_SFLOAT; } const VkImageUsageFlags requested_view_usage = ImageUsageFlags(format_info, format); const VkImageUsageFlags image_usage = image.UsageFlags(); @@ -2288,7 +2288,7 @@ VkImageView ImageView::StorageView(Shader::TextureType texture_type, if (!typeless_storage_view) { auto info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format); if (uses_accelerated_astc_decode) { - info.format = VK_FORMAT_R16G16B16A16_SFLOAT; + info.format = VK_FORMAT_R32G32B32A32_SFLOAT; } typeless_storage_view = MakeView(info.format, VK_IMAGE_ASPECT_COLOR_BIT); } From 7cb8ae0c0d640e00ca188cfc6077867b7fb5e447 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 5 Jul 2026 06:31:42 -0400 Subject: [PATCH 39/70] [TEST] Diagnosis for corrupted HDR consumed views --- src/video_core/host_shaders/astc_decoder.comp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/video_core/host_shaders/astc_decoder.comp b/src/video_core/host_shaders/astc_decoder.comp index ef3b2d09bd..face8f87d9 100644 --- a/src/video_core/host_shaders/astc_decoder.comp +++ b/src/video_core/host_shaders/astc_decoder.comp @@ -608,7 +608,9 @@ ivec4 BlueContract(int a, int r, int g, int b) { } bool IsHDRColorEndpointMode(uint cem) { - return cem == 2u || cem == 3u || cem == 7u || cem == 11u || cem == 14u || cem == 15u; + // return cem == 2u || cem == 3u || cem == 7u || cem == 11u || cem == 14u || cem == 15u; + // once this test is done. + return false; } // Sign-extends the low nbits of value (a 2's complement field packed into the bottom of an From 811ee522e017a875f9bdd357691515bb153bd0cd Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 5 Jul 2026 06:36:06 -0400 Subject: [PATCH 40/70] Revert "[TEST] Diagnosis for corrupted HDR consumed views" --- src/video_core/host_shaders/astc_decoder.comp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/video_core/host_shaders/astc_decoder.comp b/src/video_core/host_shaders/astc_decoder.comp index face8f87d9..ef3b2d09bd 100644 --- a/src/video_core/host_shaders/astc_decoder.comp +++ b/src/video_core/host_shaders/astc_decoder.comp @@ -608,9 +608,7 @@ ivec4 BlueContract(int a, int r, int g, int b) { } bool IsHDRColorEndpointMode(uint cem) { - // return cem == 2u || cem == 3u || cem == 7u || cem == 11u || cem == 14u || cem == 15u; - // once this test is done. - return false; + return cem == 2u || cem == 3u || cem == 7u || cem == 11u || cem == 14u || cem == 15u; } // Sign-extends the low nbits of value (a 2's complement field packed into the bottom of an From e48a8d3ff40a191b3401168dcd92e2a5bbb165b1 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 5 Jul 2026 06:36:31 -0400 Subject: [PATCH 41/70] Revert "[TEST] Test for RefreshParametter actual concurrency" --- src/video_core/engines/maxwell_3d.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 56f8e5f3e9..e7da9d9138 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -223,7 +223,9 @@ void Maxwell3D::ProcessMacro(Core::System& system, u32 method, const u32* base_s } void Maxwell3D::RefreshParametersImpl() { - // Temporary change + if (!Settings::IsGPULevelHigh()) { + return; + } size_t current_index = 0; for (auto& segment : macro_segments) { if (segment.first == 0) { From 0a0156a4848f14fb4212d488947c6d767be6884d Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 5 Jul 2026 06:53:33 -0400 Subject: [PATCH 42/70] [TEST] Removing AMD/Nvidia from HDR on ASTC decoded formats --- src/video_core/vulkan_common/vulkan_device.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index a944e7e297..5d8aeaec62 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -352,6 +352,9 @@ FN_MAX_LIMIT_LIST /// Returns true if ASTC is natively supported, including HDR-profile (non-LDR-only) bool IsOptimalAstcSupported() const { + if (IsNvidia() || IsAmd()) { + return features.features.textureCompressionASTC_LDR; + } return features.features.textureCompressionASTC_LDR && features.texture_compression_astc_hdr.textureCompressionASTC_HDR; } @@ -949,6 +952,12 @@ FN_MAX_LIMIT_LIST return properties.driver.driverID == VK_DRIVER_ID_NVIDIA_PROPRIETARY; } + bool IsAmd() const noexcept { + return properties.driver.driverID == VK_DRIVER_ID_AMD_PROPRIETARY || + properties.driver.driverID == VK_DRIVER_ID_AMD_OPEN_SOURCE || + properties.driver.driverID == VK_DRIVER_ID_MESA_RADV; + } + bool IsMoltenVK() const noexcept { return properties.driver.driverID == VK_DRIVER_ID_MOLTENVK; } From f191d7a0b756922231a17242c1e94f223453a752 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 5 Jul 2026 06:59:15 -0400 Subject: [PATCH 43/70] Revert "[TEST] Removing AMD/Nvidia from HDR on ASTC decoded formats" --- src/video_core/vulkan_common/vulkan_device.h | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 5d8aeaec62..a944e7e297 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -352,9 +352,6 @@ FN_MAX_LIMIT_LIST /// Returns true if ASTC is natively supported, including HDR-profile (non-LDR-only) bool IsOptimalAstcSupported() const { - if (IsNvidia() || IsAmd()) { - return features.features.textureCompressionASTC_LDR; - } return features.features.textureCompressionASTC_LDR && features.texture_compression_astc_hdr.textureCompressionASTC_HDR; } @@ -952,12 +949,6 @@ FN_MAX_LIMIT_LIST return properties.driver.driverID == VK_DRIVER_ID_NVIDIA_PROPRIETARY; } - bool IsAmd() const noexcept { - return properties.driver.driverID == VK_DRIVER_ID_AMD_PROPRIETARY || - properties.driver.driverID == VK_DRIVER_ID_AMD_OPEN_SOURCE || - properties.driver.driverID == VK_DRIVER_ID_MESA_RADV; - } - bool IsMoltenVK() const noexcept { return properties.driver.driverID == VK_DRIVER_ID_MOLTENVK; } From b0ccfb2c2a2230241883ce670f74331b0b94930a Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 5 Jul 2026 07:10:41 -0400 Subject: [PATCH 44/70] [TEST] Remove sRGB curve convertion --- src/video_core/host_shaders/astc_decoder.comp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/video_core/host_shaders/astc_decoder.comp b/src/video_core/host_shaders/astc_decoder.comp index ef3b2d09bd..94027e9cb3 100644 --- a/src/video_core/host_shaders/astc_decoder.comp +++ b/src/video_core/host_shaders/astc_decoder.comp @@ -1406,10 +1406,8 @@ void DecompressBlock(ivec3 coord) { vec4((C0 * (uvec4(64) - weight_vec) + C1 * weight_vec + uvec4(32)) / 64); p = Cf / 65535.0f; #ifdef VULKAN - // Destination is always linear RGBA16F on this path; apply the sRGB curve - // ourselves since there's no sRGB-tagged format left to do it automatically. - // p is (A,R,G,B); alpha (p.x) is never gamma-encoded. - if (is_srgb != 0u) { + // if (is_srgb != 0u) + if (false) { p.yzw = SRGBToLinear(p.yzw); } #endif From bfdc50ee769e82ebf10d4f966292463df6a3c643 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 5 Jul 2026 07:20:59 -0400 Subject: [PATCH 45/70] [TEST] Refactored SRGBToLinear function --- src/video_core/host_shaders/astc_decoder.comp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/video_core/host_shaders/astc_decoder.comp b/src/video_core/host_shaders/astc_decoder.comp index 94027e9cb3..c02d027cf1 100644 --- a/src/video_core/host_shaders/astc_decoder.comp +++ b/src/video_core/host_shaders/astc_decoder.comp @@ -140,12 +140,13 @@ uvec4 ReplicateByteTo16(uvec4 value) { // sRGB EOTF (decode: encoded value -> linear). Only used on the Vulkan path, where the // destination image is a linear float format with no format-level sRGB tag of its own. -float SRGBToLinear(float c) { - return (c <= 0.04045) ? (c / 12.92) : pow((c + 0.055) / 1.055, 2.4); -} - +// Vectorized (single vec3 overload, no per-component scalar ternary) to match the style +// already used elsewhere in this file (see the HDR Mt piecewise selection) rather than the +// float/vec3 overload pair this replaces. vec3 SRGBToLinear(vec3 c) { - return vec3(SRGBToLinear(c.x), SRGBToLinear(c.y), SRGBToLinear(c.z)); + const vec3 lo = c / 12.92; + const vec3 hi = pow(max((c + 0.055) / 1.055, vec3(0.0)), vec3(2.4)); + return mix(hi, lo, lessThanEqual(c, vec3(0.04045))); } uint ReplicateBitTo7(uint value) { @@ -1406,8 +1407,7 @@ void DecompressBlock(ivec3 coord) { vec4((C0 * (uvec4(64) - weight_vec) + C1 * weight_vec + uvec4(32)) / 64); p = Cf / 65535.0f; #ifdef VULKAN - // if (is_srgb != 0u) - if (false) { + if (is_srgb != 0u) { p.yzw = SRGBToLinear(p.yzw); } #endif From a7d274e5bd39e6be8c66e52896d76e3de2effb86 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 5 Jul 2026 20:46:33 -0400 Subject: [PATCH 46/70] [TEST] Adjusting sRGB formula --- src/video_core/host_shaders/astc_decoder.comp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/video_core/host_shaders/astc_decoder.comp b/src/video_core/host_shaders/astc_decoder.comp index c02d027cf1..39eec4128c 100644 --- a/src/video_core/host_shaders/astc_decoder.comp +++ b/src/video_core/host_shaders/astc_decoder.comp @@ -1407,9 +1407,8 @@ void DecompressBlock(ivec3 coord) { vec4((C0 * (uvec4(64) - weight_vec) + C1 * weight_vec + uvec4(32)) / 64); p = Cf / 65535.0f; #ifdef VULKAN - if (is_srgb != 0u) { - p.yzw = SRGBToLinear(p.yzw); - } + // Another test + p.yzw = mix(p.yzw, SRGBToLinear(p.yzw), bvec3(is_srgb != 0u)); #endif } From 8890b8eb2ad6772b4af69a62720c859a330935fa Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 5 Jul 2026 23:07:57 -0400 Subject: [PATCH 47/70] [TEST] Removal of the 7 push constant + fixated value --- src/video_core/host_shaders/astc_decoder.comp | 6 ++---- src/video_core/renderer_vulkan/vk_compute_pass.cpp | 5 ++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/video_core/host_shaders/astc_decoder.comp b/src/video_core/host_shaders/astc_decoder.comp index 39eec4128c..b6270fd067 100644 --- a/src/video_core/host_shaders/astc_decoder.comp +++ b/src/video_core/host_shaders/astc_decoder.comp @@ -33,7 +33,6 @@ UNIFORM(3) uint block_size; UNIFORM(4) uint x_shift; UNIFORM(5) uint block_height; UNIFORM(6) uint block_height_mask; -UNIFORM(7) uint is_srgb; END_PUSH_CONSTANTS struct EncodingData { @@ -1407,8 +1406,7 @@ void DecompressBlock(ivec3 coord) { vec4((C0 * (uvec4(64) - weight_vec) + C1 * weight_vec + uvec4(32)) / 64); p = Cf / 65535.0f; #ifdef VULKAN - // Another test - p.yzw = mix(p.yzw, SRGBToLinear(p.yzw), bvec3(is_srgb != 0u)); + p.yzw = mix(p.yzw, SRGBToLinear(p.yzw), bvec3((block_height_mask & 0x80000000u) != 0u)); #endif } @@ -1438,7 +1436,7 @@ void main() { uint offset = 0; offset += pos.z * layer_stride; offset += (block_y >> block_height) * block_size; - offset += (block_y & block_height_mask) << GOB_SIZE_SHIFT; + offset += (block_y & (block_height_mask & 0x7FFFFFFFu)) << GOB_SIZE_SHIFT; offset += (pos.x >> GOB_SIZE_X_SHIFT) << x_shift; offset += swizzle; diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index 5246b284ca..a6ec98d224 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -221,7 +221,6 @@ struct AstcPushConstants { u32 x_shift; u32 block_height; u32 block_height_mask; - u32 is_srgb; }; struct QueriesPrefixScanPushConstants { @@ -622,8 +621,8 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map, .block_size = params.block_size, .x_shift = params.x_shift, .block_height = params.block_height, - .block_height_mask = params.block_height_mask, - .is_srgb = is_srgb ? 1u : 0u, + .block_height_mask = + params.block_height_mask | (is_srgb ? 0x8000'0000u : 0u), }; const VkDescriptorSet set = descriptor_allocator.Commit(); device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); From 7307518e4411776ecd855bfb8dc28f4d8490a1f9 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 5 Jul 2026 23:47:22 -0400 Subject: [PATCH 48/70] [vulkan] Re-wiring older ASTC decoding path + new helper function on wider ASTC textures --- src/video_core/host_shaders/astc_decoder.comp | 18 ++---------- .../renderer_vulkan/vk_compute_pass.cpp | 6 ++-- .../renderer_vulkan/vk_texture_cache.cpp | 28 +++++++++---------- .../renderer_vulkan/vk_texture_cache.h | 2 +- 4 files changed, 19 insertions(+), 35 deletions(-) diff --git a/src/video_core/host_shaders/astc_decoder.comp b/src/video_core/host_shaders/astc_decoder.comp index b6270fd067..ba201d328d 100644 --- a/src/video_core/host_shaders/astc_decoder.comp +++ b/src/video_core/host_shaders/astc_decoder.comp @@ -44,7 +44,7 @@ layout(binding = BINDING_INPUT_BUFFER, std430) readonly restrict buffer InputBuf }; #ifdef VULKAN -layout(binding = BINDING_OUTPUT_IMAGE, rgba32f) uniform writeonly restrict image2DArray dest_image; +layout(binding = BINDING_OUTPUT_IMAGE) uniform writeonly restrict image2DArray dest_image; #else layout(binding = BINDING_OUTPUT_IMAGE, rgba8) uniform writeonly restrict image2DArray dest_image; #endif @@ -137,17 +137,6 @@ uvec4 ReplicateByteTo16(uvec4 value) { return value * 0x101; } -// sRGB EOTF (decode: encoded value -> linear). Only used on the Vulkan path, where the -// destination image is a linear float format with no format-level sRGB tag of its own. -// Vectorized (single vec3 overload, no per-component scalar ternary) to match the style -// already used elsewhere in this file (see the HDR Mt piecewise selection) rather than the -// float/vec3 overload pair this replaces. -vec3 SRGBToLinear(vec3 c) { - const vec3 lo = c / 12.92; - const vec3 hi = pow(max((c + 0.055) / 1.055, vec3(0.0)), vec3(2.4)); - return mix(hi, lo, lessThanEqual(c, vec3(0.04045))); -} - uint ReplicateBitTo7(uint value) { return value * 127; } @@ -1405,9 +1394,6 @@ void DecompressBlock(ivec3 coord) { const vec4 Cf = vec4((C0 * (uvec4(64) - weight_vec) + C1 * weight_vec + uvec4(32)) / 64); p = Cf / 65535.0f; -#ifdef VULKAN - p.yzw = mix(p.yzw, SRGBToLinear(p.yzw), bvec3((block_height_mask & 0x80000000u) != 0u)); -#endif } #ifdef VULKAN @@ -1436,7 +1422,7 @@ void main() { uint offset = 0; offset += pos.z * layer_stride; offset += (block_y >> block_height) * block_size; - offset += (block_y & (block_height_mask & 0x7FFFFFFFu)) << GOB_SIZE_SHIFT; + offset += (block_y & block_height_mask) << GOB_SIZE_SHIFT; offset += (pos.x >> GOB_SIZE_X_SHIFT) << x_shift; offset += swizzle; diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index a6ec98d224..f361c34f44 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -564,7 +564,6 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map, VideoCore::Surface::DefaultBlockWidth(image.info.format), VideoCore::Surface::DefaultBlockHeight(image.info.format), }; - const bool is_srgb = VideoCore::Surface::IsPixelFormatSRGB(image.info.format); scheduler.RequestOutsideRenderPassOperationContext(); const VkPipeline vk_pipeline = *pipeline; const VkImageAspectFlags aspect_mask = image.AspectMask(); @@ -614,15 +613,14 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map, ASSERT(params.destination == (std::array{0, 0, 0})); ASSERT(params.bytes_per_block_log2 == 4); scheduler.Record([this, num_dispatches_x, num_dispatches_y, num_dispatches_z, block_dims, - params, descriptor_data, is_srgb](vk::CommandBuffer cmdbuf) { + params, descriptor_data](vk::CommandBuffer cmdbuf) { const AstcPushConstants uniforms{ .blocks_dims = block_dims, .layer_stride = params.layer_stride, .block_size = params.block_size, .x_shift = params.x_shift, .block_height = params.block_height, - .block_height_mask = - params.block_height_mask | (is_srgb ? 0x8000'0000u : 0u), + .block_height_mask = params.block_height_mask, }; const VkDescriptorSet set = descriptor_allocator.Commit(); device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 29ec1a5127..f789c989d8 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -129,11 +129,6 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { return usage; } -// Whether an ASTC texture will be decoded via the GPU compute pass (ASTCDecoderPass / -// astc_decoder.comp) rather than the CPU ConvertImage path. Must stay the single source of -// truth for this decision: both the destination image's real format (below) and the -// AcceleratedUpload flag (Image::Image()) key off of it, and they must never disagree -- -// the compute shader's declared storage image format has to match what was actually allocated. [[nodiscard]] bool WillUseAcceleratedAstcDecode(const Device& device, const ImageInfo& info) { if (!IsPixelFormatASTC(info.format) || device.IsOptimalAstcSupported()) { return false; @@ -146,13 +141,16 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { info.size.depth == 1; } +[[nodiscard]] bool WillUseWidenedAstcFormat(const Device& device, const ImageInfo& info) { + return WillUseAcceleratedAstcDecode(device, info) && + !VideoCore::Surface::IsPixelFormatSRGB(info.format); +} + [[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info, std::optional format_override = {}) { auto format_info = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, info.format); if (format_override) { - // Accelerated ASTC HDR decode target: real (unclamped) values need a linear float - // format, not whatever the guest ASTC pixel format would normally map to. format_info.format = *format_override; format_info.attachable = false; format_info.storage = true; @@ -1586,10 +1584,10 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu : VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime_.scheduler}, runtime{&runtime_}, original_image(MakeImage(runtime_.device, runtime_.memory_allocator, info, - WillUseAcceleratedAstcDecode(runtime_.device, info) + WillUseWidenedAstcFormat(runtime_.device, info) ? std::span{} : runtime->ViewFormats(info.format), - WillUseAcceleratedAstcDecode(runtime_.device, info) + WillUseWidenedAstcFormat(runtime_.device, info) ? std::make_optional(VK_FORMAT_R32G32B32A32_SFLOAT) : std::nullopt)), aspect_mask(ImageAspectMask(info.format)) { @@ -1622,7 +1620,7 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu Settings::values.astc_recompression.GetValue() == Settings::AstcRecompression::Uncompressed) { const auto& device = runtime->device.GetLogical(); - const VkFormat storage_format = WillUseAcceleratedAstcDecode(runtime->device, info) + const VkFormat storage_format = WillUseWidenedAstcFormat(runtime->device, info) ? VK_FORMAT_R32G32B32A32_SFLOAT : VK_FORMAT_A8B8G8R8_UNORM_PACK32; for (s32 level = 0; level < info.resources.levels; ++level) { @@ -1979,7 +1977,9 @@ VkImageView Image::StorageImageView(s32 level) noexcept { auto format_info = MaxwellToVK::SurfaceFormat(runtime->device, FormatType::Optimal, true, info.format); if (WillUseAcceleratedAstcDecode(runtime->device, info)) { - format_info.format = VK_FORMAT_R32G32B32A32_SFLOAT; + format_info.format = WillUseWidenedAstcFormat(runtime->device, info) + ? VK_FORMAT_R32G32B32A32_SFLOAT + : VK_FORMAT_A8B8G8R8_UNORM_PACK32; } view = MakeStorageView(runtime->device.GetLogical(), level, *(this->*current_image), format_info.format); @@ -2147,9 +2147,9 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI SanitizeDepthStencilSwizzle(swizzle, device->SupportsDepthStencilSwizzleOne()); } } - uses_accelerated_astc_decode = WillUseAcceleratedAstcDecode(*device, image.info); + uses_widened_astc_format = WillUseWidenedAstcFormat(*device, image.info); auto format_info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format); - if (uses_accelerated_astc_decode) { + if (uses_widened_astc_format) { format_info.format = VK_FORMAT_R32G32B32A32_SFLOAT; } const VkImageUsageFlags requested_view_usage = ImageUsageFlags(format_info, format); @@ -2287,7 +2287,7 @@ VkImageView ImageView::StorageView(Shader::TextureType texture_type, if (image_format == Shader::ImageFormat::Typeless) { if (!typeless_storage_view) { auto info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format); - if (uses_accelerated_astc_decode) { + if (uses_widened_astc_format) { info.format = VK_FORMAT_R32G32B32A32_SFLOAT; } typeless_storage_view = MakeView(info.format, VK_IMAGE_ASPECT_COLOR_BIT); diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 0d11c71a42..027143fdbc 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -387,7 +387,7 @@ private: VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT; u32 buffer_size = 0; - bool uses_accelerated_astc_decode = false; + bool uses_widened_astc_format = false; }; class ImageAlloc : public VideoCommon::ImageAllocBase {}; From 4bf8828df16191eaaec85c8cddf115ab392ea033 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Mon, 6 Jul 2026 02:10:21 -0400 Subject: [PATCH 49/70] [TEST] Debugger GPU unswizzle shaders --- .../features/settings/model/BooleanSetting.kt | 3 + .../settings/model/view/SettingsItem.kt | 21 ++ .../settings/ui/SettingsFragmentPresenter.kt | 3 + .../app/src/main/res/values/strings.xml | 6 + src/common/settings.h | 10 + .../block_linear_unswizzle_3d_bcn.comp | 9 +- .../renderer_vulkan/renderer_vulkan.cpp | 1 + .../renderer_vulkan/vk_compute_pass.cpp | 260 +++++++++++++++++- .../renderer_vulkan/vk_compute_pass.h | 52 ++++ .../renderer_vulkan/vk_texture_cache.cpp | 143 +++++++++- .../renderer_vulkan/vk_texture_cache.h | 23 ++ src/video_core/vulkan_common/vulkan_device.h | 15 + 12 files changed, 522 insertions(+), 24 deletions(-) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index ad983bd749..e87c502342 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt @@ -39,6 +39,9 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { RENDERER_VERTEX_INPUT_DYNAMIC_STATE("vertex_input_dynamic_state"), RENDERER_SAMPLE_SHADING("sample_shading"), GPU_UNSWIZZLE_ENABLED("gpu_unswizzle_enabled"), + GPU_COMPUTE_UNSWIZZLE_2D("gpu_compute_unswizzle_2d"), + GPU_COMPUTE_UNSWIZZLE_3D("gpu_compute_unswizzle_3d"), + GPU_COMPUTE_UNSWIZZLE_PITCH("gpu_compute_unswizzle_pitch"), PICTURE_IN_PICTURE("picture_in_picture"), USE_CUSTOM_RTC("custom_rtc_enabled"), BLACK_BACKGROUNDS("black_backgrounds"), diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index 67cf392fef..6f6282aa30 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -724,6 +724,27 @@ abstract class SettingsItem( chunkSizeValuesId = R.array.gpuSwizzleChunkValues ) ) + put( + SwitchSetting( + BooleanSetting.GPU_COMPUTE_UNSWIZZLE_2D, + titleId = R.string.gpu_compute_unswizzle_2d, + descriptionId = R.string.gpu_compute_unswizzle_2d_description + ) + ) + put( + SwitchSetting( + BooleanSetting.GPU_COMPUTE_UNSWIZZLE_3D, + titleId = R.string.gpu_compute_unswizzle_3d, + descriptionId = R.string.gpu_compute_unswizzle_3d_description + ) + ) + put( + SwitchSetting( + BooleanSetting.GPU_COMPUTE_UNSWIZZLE_PITCH, + titleId = R.string.gpu_compute_unswizzle_pitch, + descriptionId = R.string.gpu_compute_unswizzle_pitch_description + ) + ) put( SingleChoiceSetting( IntSetting.FAST_CPU_TIME, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 7b7762520d..70d52c2121 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -308,6 +308,9 @@ class SettingsFragmentPresenter( add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key) add(BooleanSetting.RENDERER_ASYNC_PRESENTATION.key) add(SettingsItem.GPU_UNSWIZZLE_COMBINED) + add(BooleanSetting.GPU_COMPUTE_UNSWIZZLE_2D.key) + add(BooleanSetting.GPU_COMPUTE_UNSWIZZLE_3D.key) + add(BooleanSetting.GPU_COMPUTE_UNSWIZZLE_PITCH.key) add(HeaderSetting(R.string.extensions)) diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index c67c2f279f..89e2f0b0ad 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -527,6 +527,12 @@ GPU Unswizzle Settings Configure GPU-based texture unswizzling parameters or disable it entirely. Adjust these settings to balance performance and texture loading quality. Enable GPU Unswizzle + GPU Compute Unswizzle (2D) + Unswizzles block-linear 2D textures with a compute shader instead of the CPU. Disable to upload 2D textures through the CPU path. + GPU Compute Unswizzle (3D) + Unswizzles block-linear 3D textures with a compute shader instead of the CPU. Disable to upload 3D textures through the CPU path. + GPU Compute Unswizzle (Pitch) + Unswizzles pitch-linear textures with a compute shader instead of the CPU. Disable to upload linear textures through the CPU path. Disabled GPU Unswizzle Max Texture Size Sets the maximum size (MB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. diff --git a/src/common/settings.h b/src/common/settings.h index b80d6a5ec7..8a290e526d 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -605,6 +605,16 @@ struct Values { SwitchableSetting gpu_unswizzle_enabled{linkage, false, "gpu_unswizzle_enabled", Category::RendererHacks}; + SwitchableSetting gpu_compute_unswizzle_2d{linkage, true, "gpu_compute_unswizzle_2d", + Category::RendererHacks}; + + SwitchableSetting gpu_compute_unswizzle_3d{linkage, true, "gpu_compute_unswizzle_3d", + Category::RendererHacks}; + + SwitchableSetting gpu_compute_unswizzle_pitch{linkage, true, + "gpu_compute_unswizzle_pitch", + Category::RendererHacks}; + SwitchableSetting dyna_state{linkage, #if defined(__ANDROID__) ExtendedDynamicState::Disabled, diff --git a/src/video_core/host_shaders/block_linear_unswizzle_3d_bcn.comp b/src/video_core/host_shaders/block_linear_unswizzle_3d_bcn.comp index e084837f25..0f6d1ad059 100644 --- a/src/video_core/host_shaders/block_linear_unswizzle_3d_bcn.comp +++ b/src/video_core/host_shaders/block_linear_unswizzle_3d_bcn.comp @@ -1,12 +1,10 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #version 430 #ifdef VULKAN - #extension GL_EXT_shader_16bit_storage : require - #extension GL_EXT_shader_8bit_storage : require - #define HAS_EXTENDED_TYPES 1 + #define HAS_EXTENDED_TYPES 0 #define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants { #define END_PUSH_CONSTANTS }; #define UNIFORM(n) @@ -42,9 +40,6 @@ layout(push_constant) uniform PushConstants { uint block_depth; // Offset 48 uint block_depth_mask; // Offset 52 - int _pad; // Offset 56 - - ivec3 destination; // Offset 60 } pc; #else BEGIN_PUSH_CONSTANTS diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index b0a7d0c579..88e2895598 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -169,6 +169,7 @@ try } RendererVulkan::~RendererVulkan() { + scheduler.WaitWorker(); scheduler.RegisterOnSubmit([] {}); void(device.GetLogical().WaitIdle()); } diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index f361c34f44..243bc1af5e 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -24,7 +24,10 @@ #include "video_core/host_shaders/resolve_conditional_render_comp_spv.h" #include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h" #include "video_core/host_shaders/vulkan_uint8_comp_spv.h" +#include "video_core/host_shaders/block_linear_unswizzle_2d_comp_spv.h" +#include "video_core/host_shaders/block_linear_unswizzle_3d_comp_spv.h" #include "video_core/host_shaders/block_linear_unswizzle_3d_bcn_comp_spv.h" +#include "video_core/host_shaders/pitch_unswizzle_comp_spv.h" #include "video_core/renderer_vulkan/vk_compute_pass.h" #include "video_core/surface.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" @@ -233,6 +236,26 @@ struct QueriesPrefixScanPushConstants { struct ConditionalRenderingResolvePushConstants { u32 compare_to_zero; }; + +struct BlockLinear3DImagePushConstants { + alignas(16) std::array origin; + alignas(16) std::array destination; + u32 bytes_per_block_log2; + u32 slice_size; + u32 block_size; + u32 x_shift; + u32 block_height; + u32 block_height_mask; + u32 block_depth; + u32 block_depth_mask; +}; + +struct PitchUnswizzlePushConstants { + std::array origin; + std::array destination; + u32 bytes_per_block; + u32 pitch; +}; } // Anonymous namespace ComputePass::ComputePass(const Device& device_, Scheduler& scheduler, DescriptorPool& descriptor_pool, @@ -654,6 +677,235 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map, scheduler.Finish(); } +namespace { + +void RecordUnswizzleImageEntry(vk::CommandBuffer cmdbuf, VkPipeline pipeline, VkImage image, + VkImageAspectFlags aspect_mask, bool is_initialized) { + const VkImageMemoryBarrier image_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = static_cast(is_initialized ? VK_ACCESS_SHADER_WRITE_BIT + : VK_ACCESS_NONE), + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, + .oldLayout = is_initialized ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + cmdbuf.PipelineBarrier(is_initialized ? vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER + : VkPipelineStageFlags(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT), + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, image_barrier); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); +} + +void RecordUnswizzleImageExit(vk::CommandBuffer cmdbuf, VkImage image, + VkImageAspectFlags aspect_mask) { + const VkImageMemoryBarrier image_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | + VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, 0, image_barrier); +} +} // Anonymous namespace + +BlockLinearUnswizzleImage2DPass::BlockLinearUnswizzleImage2DPass( + const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, + StagingBufferPool& staging_buffer_pool_, + ComputePassDescriptorQueue& compute_pass_descriptor_queue_) + : ComputePass(device_, scheduler_, descriptor_pool_, ASTC_DESCRIPTOR_SET_BINDINGS, + ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY, ASTC_BANK_INFO, + COMPUTE_PUSH_CONSTANT_RANGE, + BLOCK_LINEAR_UNSWIZZLE_2D_COMP_SPV), + scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_}, + compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {} + +BlockLinearUnswizzleImage2DPass::~BlockLinearUnswizzleImage2DPass() = default; + +void BlockLinearUnswizzleImage2DPass::Unswizzle( + Image& image, const StagingBufferRef& map, + std::span swizzles) { + using namespace VideoCommon::Accelerated; + scheduler.RequestOutsideRenderPassOperationContext(); + const VkPipeline vk_pipeline = *pipeline; + const VkImageAspectFlags aspect_mask = image.AspectMask(); + const VkImage vk_image = image.Handle(); + const bool is_initialized = image.ExchangeInitialization(); + scheduler.Record([vk_pipeline, vk_image, aspect_mask, is_initialized](vk::CommandBuffer cmdbuf) { + RecordUnswizzleImageEntry(cmdbuf, vk_pipeline, vk_image, aspect_mask, is_initialized); + }); + for (const VideoCommon::SwizzleParameters& swizzle : swizzles) { + const size_t input_offset = swizzle.buffer_offset + map.offset; + const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 32U); + const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 32U); + const u32 num_dispatches_z = image.info.resources.layers; + + compute_pass_descriptor_queue.Acquire(scheduler, 2); + compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset, + image.guest_size_bytes - swizzle.buffer_offset); + compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level)); + const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; + + const auto params = MakeBlockLinearSwizzle2DParams(swizzle, image.info); + scheduler.Record([this, num_dispatches_x, num_dispatches_y, num_dispatches_z, params, + descriptor_data](vk::CommandBuffer cmdbuf) { + const VkDescriptorSet set = descriptor_allocator.Commit(); + device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {}); + cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, params); + cmdbuf.Dispatch(num_dispatches_x, num_dispatches_y, num_dispatches_z); + }); + } + scheduler.Record([vk_image, aspect_mask](vk::CommandBuffer cmdbuf) { + RecordUnswizzleImageExit(cmdbuf, vk_image, aspect_mask); + }); + scheduler.Finish(); +} + +BlockLinearUnswizzleImage3DPass::BlockLinearUnswizzleImage3DPass( + const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, + StagingBufferPool& staging_buffer_pool_, + ComputePassDescriptorQueue& compute_pass_descriptor_queue_) + : ComputePass(device_, scheduler_, descriptor_pool_, ASTC_DESCRIPTOR_SET_BINDINGS, + ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY, ASTC_BANK_INFO, + COMPUTE_PUSH_CONSTANT_RANGE, + BLOCK_LINEAR_UNSWIZZLE_3D_COMP_SPV), + scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_}, + compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {} + +BlockLinearUnswizzleImage3DPass::~BlockLinearUnswizzleImage3DPass() = default; + +void BlockLinearUnswizzleImage3DPass::Unswizzle( + Image& image, const StagingBufferRef& map, + std::span swizzles) { + using namespace VideoCommon::Accelerated; + scheduler.RequestOutsideRenderPassOperationContext(); + const VkPipeline vk_pipeline = *pipeline; + const VkImageAspectFlags aspect_mask = image.AspectMask(); + const VkImage vk_image = image.Handle(); + const bool is_initialized = image.ExchangeInitialization(); + scheduler.Record([vk_pipeline, vk_image, aspect_mask, is_initialized](vk::CommandBuffer cmdbuf) { + RecordUnswizzleImageEntry(cmdbuf, vk_pipeline, vk_image, aspect_mask, is_initialized); + }); + for (const VideoCommon::SwizzleParameters& swizzle : swizzles) { + const size_t input_offset = swizzle.buffer_offset + map.offset; + const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 16U); + const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 8U); + const u32 num_dispatches_z = Common::DivCeil(swizzle.num_tiles.depth, 8U); + + compute_pass_descriptor_queue.Acquire(scheduler, 2); + compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset, + image.guest_size_bytes - swizzle.buffer_offset); + compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level)); + const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; + + const auto p = MakeBlockLinearSwizzle3DParams(swizzle, image.info); + const BlockLinear3DImagePushConstants params{ + .origin = p.origin, + .destination = p.destination, + .bytes_per_block_log2 = p.bytes_per_block_log2, + .slice_size = p.slice_size, + .block_size = p.block_size, + .x_shift = p.x_shift, + .block_height = p.block_height, + .block_height_mask = p.block_height_mask, + .block_depth = p.block_depth, + .block_depth_mask = p.block_depth_mask, + }; + scheduler.Record([this, num_dispatches_x, num_dispatches_y, num_dispatches_z, params, + descriptor_data](vk::CommandBuffer cmdbuf) { + const VkDescriptorSet set = descriptor_allocator.Commit(); + device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {}); + cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, params); + cmdbuf.Dispatch(num_dispatches_x, num_dispatches_y, num_dispatches_z); + }); + } + scheduler.Record([vk_image, aspect_mask](vk::CommandBuffer cmdbuf) { + RecordUnswizzleImageExit(cmdbuf, vk_image, aspect_mask); + }); + scheduler.Finish(); +} + +PitchUnswizzlePass::PitchUnswizzlePass(const Device& device_, Scheduler& scheduler_, + DescriptorPool& descriptor_pool_, + StagingBufferPool& staging_buffer_pool_, + ComputePassDescriptorQueue& compute_pass_descriptor_queue_) + : ComputePass(device_, scheduler_, descriptor_pool_, ASTC_DESCRIPTOR_SET_BINDINGS, + ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY, ASTC_BANK_INFO, + COMPUTE_PUSH_CONSTANT_RANGE, + PITCH_UNSWIZZLE_COMP_SPV), + scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_}, + compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {} + +PitchUnswizzlePass::~PitchUnswizzlePass() = default; + +void PitchUnswizzlePass::Unswizzle(Image& image, const StagingBufferRef& map, + std::span swizzles) { + scheduler.RequestOutsideRenderPassOperationContext(); + const VkPipeline vk_pipeline = *pipeline; + const VkImageAspectFlags aspect_mask = image.AspectMask(); + const VkImage vk_image = image.Handle(); + const bool is_initialized = image.ExchangeInitialization(); + scheduler.Record([vk_pipeline, vk_image, aspect_mask, is_initialized](vk::CommandBuffer cmdbuf) { + RecordUnswizzleImageEntry(cmdbuf, vk_pipeline, vk_image, aspect_mask, is_initialized); + }); + const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(image.info.format); + for (const VideoCommon::SwizzleParameters& swizzle : swizzles) { + const size_t input_offset = swizzle.buffer_offset + map.offset; + const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 32U); + const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 32U); + + compute_pass_descriptor_queue.Acquire(scheduler, 2); + compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset, + image.guest_size_bytes - swizzle.buffer_offset); + compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level)); + const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; + + const PitchUnswizzlePushConstants params{ + .origin = {0, 0}, + .destination = {0, 0}, + .bytes_per_block = bytes_per_block, + .pitch = image.info.pitch, + }; + scheduler.Record([this, num_dispatches_x, num_dispatches_y, params, + descriptor_data](vk::CommandBuffer cmdbuf) { + const VkDescriptorSet set = descriptor_allocator.Commit(); + device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {}); + cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, params); + cmdbuf.Dispatch(num_dispatches_x, num_dispatches_y, 1); + }); + } + scheduler.Record([vk_image, aspect_mask](vk::CommandBuffer cmdbuf) { + RecordUnswizzleImageExit(cmdbuf, vk_image, aspect_mask); + }); + scheduler.Finish(); +} + constexpr u32 BL3D_BINDING_INPUT_BUFFER = 0; constexpr u32 BL3D_BINDING_OUTPUT_BUFFER = 1; @@ -671,10 +923,6 @@ struct alignas(16) BlockLinearUnswizzle3DPushConstants { u32 block_depth; // Offset 48 u32 block_depth_mask; // Offset 52 - s32 _pad; // Offset 56 - - s32 destination[3]; // Offset 60 - s32 _pad_end; // Offset 72 }; static_assert(sizeof(BlockLinearUnswizzle3DPushConstants) <= 128); @@ -781,10 +1029,6 @@ void BlockLinearUnswizzle3DPass::UnswizzleChunk( pc.origin[1] = params.origin[1]; pc.origin[2] = z_start; // Current chunk's Z start - pc.destination[0] = params.destination[0]; - pc.destination[1] = params.destination[1]; - pc.destination[2] = 0; // Shader writes to start of output buffer - pc.bytes_per_block_log2 = params.bytes_per_block_log2; pc.slice_size = params.slice_size; pc.block_size = params.block_size; diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.h b/src/video_core/renderer_vulkan/vk_compute_pass.h index cb213dae7e..34aa762ff9 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.h +++ b/src/video_core/renderer_vulkan/vk_compute_pass.h @@ -25,6 +25,7 @@ struct SwizzleParameters; namespace Vulkan { +using VideoCommon::Accelerated::BlockLinearSwizzle2DParams; using VideoCommon::Accelerated::BlockLinearSwizzle3DParams; class Device; @@ -165,6 +166,57 @@ private: }; +class BlockLinearUnswizzleImage2DPass final : public ComputePass { +public: + explicit BlockLinearUnswizzleImage2DPass( + const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, + StagingBufferPool& staging_buffer_pool_, + ComputePassDescriptorQueue& compute_pass_descriptor_queue_); + ~BlockLinearUnswizzleImage2DPass(); + + void Unswizzle(Image& image, const StagingBufferRef& map, + std::span swizzles); + +private: + Scheduler& scheduler; + StagingBufferPool& staging_buffer_pool; + ComputePassDescriptorQueue& compute_pass_descriptor_queue; +}; + +class BlockLinearUnswizzleImage3DPass final : public ComputePass { +public: + explicit BlockLinearUnswizzleImage3DPass( + const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, + StagingBufferPool& staging_buffer_pool_, + ComputePassDescriptorQueue& compute_pass_descriptor_queue_); + ~BlockLinearUnswizzleImage3DPass(); + + void Unswizzle(Image& image, const StagingBufferRef& map, + std::span swizzles); + +private: + Scheduler& scheduler; + StagingBufferPool& staging_buffer_pool; + ComputePassDescriptorQueue& compute_pass_descriptor_queue; +}; + +class PitchUnswizzlePass final : public ComputePass { +public: + explicit PitchUnswizzlePass(const Device& device_, Scheduler& scheduler_, + DescriptorPool& descriptor_pool_, + StagingBufferPool& staging_buffer_pool_, + ComputePassDescriptorQueue& compute_pass_descriptor_queue_); + ~PitchUnswizzlePass(); + + void Unswizzle(Image& image, const StagingBufferRef& map, + std::span swizzles); + +private: + Scheduler& scheduler; + StagingBufferPool& staging_buffer_pool; + ComputePassDescriptorQueue& compute_pass_descriptor_queue; +}; + class MSAACopyPass final : public ComputePass { public: explicit MSAACopyPass(const Device& device_, Scheduler& scheduler_, diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index f789c989d8..305cec454a 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -6,7 +6,9 @@ #include #include +#include #include +#include #include #include #include @@ -220,8 +222,50 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { return allocator.CreateImage(image_ci); } +[[nodiscard]] VkFormat UnswizzleStorageFormat(u32 bytes_per_block) { + switch (bytes_per_block) { + case 1: + return VK_FORMAT_R8_UINT; + case 2: + return VK_FORMAT_R16_UINT; + case 4: + return VK_FORMAT_R32_UINT; + case 8: + return VK_FORMAT_R32G32_UINT; + case 16: + return VK_FORMAT_R32G32B32A32_UINT; + default: + ASSERT_MSG(false, "Invalid bytes_per_block={} for accelerated unswizzle", bytes_per_block); + return VK_FORMAT_R32_UINT; + } +} + +[[nodiscard]] bool IsUnswizzleStorageFormatSupported(const Device& device, u32 bytes_per_block) { + switch (bytes_per_block) { + case 1: + if (!device.IsStorageBuffer8BitAccessSupported()) { + return false; + } + break; + case 2: + if (!device.IsStorageBuffer16BitAccessSupported()) { + return false; + } + break; + case 4: + case 8: + case 16: + break; + default: + return false; + } + return device.IsFormatSupported(UnswizzleStorageFormat(bytes_per_block), + VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT, FormatType::Optimal); +} + [[nodiscard]] vk::ImageView MakeStorageView(const vk::Device& device, u32 level, VkImage image, - VkFormat format) { + VkFormat format, + VkImageViewType view_type = VK_IMAGE_VIEW_TYPE_2D_ARRAY) { static constexpr VkImageViewUsageCreateInfo storage_image_view_usage_create_info{ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO, .pNext = nullptr, @@ -232,7 +276,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { .pNext = &storage_image_view_usage_create_info, .flags = 0, .image = image, - .viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY, + .viewType = view_type, .format = format, .components{ .r = VK_COMPONENT_SWIZZLE_IDENTITY, @@ -915,10 +959,56 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched if (!device.IsKhrImageFormatListSupported()) { return; } + + bool enable_2d = Settings::values.gpu_compute_unswizzle_2d.GetValue(); + bool enable_3d = Settings::values.gpu_compute_unswizzle_3d.GetValue(); + bool enable_pitch = Settings::values.gpu_compute_unswizzle_pitch.GetValue(); + if (const char* const unswizzle_env = std::getenv("GPU_COMPUTE_UNSWIZZLE")) { + const std::string_view selection{unswizzle_env}; + if (selection == "0" || selection == "none") { + enable_2d = enable_3d = enable_pitch = false; + } else if (selection != "1" && selection != "all") { + enable_2d = selection.find("2d") != std::string_view::npos; + enable_3d = selection.find("3d") != std::string_view::npos; + enable_pitch = selection.find("pitch") != std::string_view::npos; + } + LOG_INFO(Render_Vulkan, "GPU_COMPUTE_UNSWIZZLE={} (2d={}, 3d={}, pitch={})", selection, + enable_2d, enable_3d, enable_pitch); + } + const bool unswizzle_caps_ok = device.IsStorageBuffer8BitAccessSupported() && + device.IsStorageBuffer16BitAccessSupported() && + device.IsFormatlessImageStoreSupported(); + if (!unswizzle_caps_ok) { + LOG_INFO(Render_Vulkan, + "GPU image unswizzle passes unsupported (8bit={}, 16bit={}, formatless={})", + device.IsStorageBuffer8BitAccessSupported(), + device.IsStorageBuffer16BitAccessSupported(), + device.IsFormatlessImageStoreSupported()); + } else { + if (enable_2d) { + bl_unswizzle_2d_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, + compute_pass_descriptor_queue); + } + if (enable_3d) { + bl_unswizzle_3d_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, + compute_pass_descriptor_queue); + } + if (enable_pitch) { + pitch_unswizzle_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, + compute_pass_descriptor_queue); + } + } for (size_t index_a = 0; index_a < VideoCore::Surface::MaxPixelFormat; index_a++) { const auto image_format = static_cast(index_a); if (IsPixelFormatASTC(image_format) && !device.IsOptimalAstcSupported()) { view_formats[index_a].push_back(VK_FORMAT_A8B8G8R8_UNORM_PACK32); + } else if (HasImageUnswizzlePasses() && !IsPixelFormatBCn(image_format) && + VideoCore::Surface::GetFormatType(image_format) == + VideoCore::Surface::SurfaceType::ColorTexture) { + const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(image_format); + if (IsUnswizzleStorageFormatSupported(device, bytes_per_block)) { + view_formats[index_a].push_back(UnswizzleStorageFormat(bytes_per_block)); + } } for (size_t index_b = 0; index_b < VideoCore::Surface::MaxPixelFormat; index_b++) { const auto view_format = static_cast(index_b); @@ -1611,6 +1701,14 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu flags |= VideoCommon::ImageFlagBits::Converted; flags |= VideoCommon::ImageFlagBits::CostlyLoad; } + if (runtime->HasUnswizzlePassFor(info.type) && !IsPixelFormatASTC(info.format) && + !IsPixelFormatBCn(info.format) && info.num_samples == 1 && + VideoCore::Surface::GetFormatType(info.format) == + VideoCore::Surface::SurfaceType::ColorTexture) { + if (IsUnswizzleStorageFormatSupported(runtime->device, BytesPerBlock(info.format))) { + flags |= VideoCommon::ImageFlagBits::AcceleratedUpload; + } + } if (runtime->device.HasDebuggingToolAttached()) { original_image.SetObjectNameEXT(VideoCommon::Name(*this).c_str()); } @@ -1976,13 +2074,20 @@ VkImageView Image::StorageImageView(s32 level) noexcept { if (!view) { auto format_info = MaxwellToVK::SurfaceFormat(runtime->device, FormatType::Optimal, true, info.format); + VkImageViewType view_type = VK_IMAGE_VIEW_TYPE_2D_ARRAY; if (WillUseAcceleratedAstcDecode(runtime->device, info)) { format_info.format = WillUseWidenedAstcFormat(runtime->device, info) ? VK_FORMAT_R32G32B32A32_SFLOAT : VK_FORMAT_A8B8G8R8_UNORM_PACK32; + } else if (True(flags & ImageFlagBits::AcceleratedUpload) && + !IsPixelFormatASTC(info.format) && !IsPixelFormatBCn(info.format)) { + format_info.format = UnswizzleStorageFormat(BytesPerBlock(info.format)); + view_type = info.type == ImageType::e3D ? VK_IMAGE_VIEW_TYPE_3D + : info.type == ImageType::Linear ? VK_IMAGE_VIEW_TYPE_2D + : VK_IMAGE_VIEW_TYPE_2D_ARRAY; } view = MakeStorageView(runtime->device.GetLogical(), level, *(this->*current_image), - format_info.format); + format_info.format, view_type); } return *view; } @@ -2516,16 +2621,36 @@ void TextureCacheRuntime::AccelerateImageUpload( return astc_decoder_pass->Assemble(image, map, swizzles); } - if (!Settings::values.gpu_unswizzle_enabled.GetValue() || !bl3d_unswizzle_pass) { - if (IsPixelFormatBCn(image.info.format) && image.info.type == ImageType::e3D) { - ASSERT(false && "GPU unswizzle is disabled for BCn 3D texture"); + if (IsPixelFormatBCn(image.info.format)) { + if (Settings::values.gpu_unswizzle_enabled.GetValue() && bl3d_unswizzle_pass && + image.info.type == ImageType::e3D && image.info.resources.levels == 1 && + image.info.resources.layers == 1) { + return bl3d_unswizzle_pass->Unswizzle(image, map, swizzles, z_start, z_count); } - ASSERT(false); + ASSERT(false && "GPU unswizzle is disabled for BCn 3D texture"); return; } - if (bl3d_unswizzle_pass && IsPixelFormatBCn(image.info.format) && image.info.type == ImageType::e3D && image.info.resources.levels == 1 && image.info.resources.layers == 1) { - return bl3d_unswizzle_pass->Unswizzle(image, map, swizzles, z_start, z_count); + if (HasImageUnswizzlePasses()) { + switch (image.info.type) { + case ImageType::e2D: + if (bl_unswizzle_2d_pass) { + return bl_unswizzle_2d_pass->Unswizzle(image, map, swizzles); + } + break; + case ImageType::e3D: + if (bl_unswizzle_3d_pass) { + return bl_unswizzle_3d_pass->Unswizzle(image, map, swizzles); + } + break; + case ImageType::Linear: + if (pitch_unswizzle_pass) { + return pitch_unswizzle_pass->Unswizzle(image, map, swizzles); + } + break; + default: + break; + } } ASSERT(false); diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 027143fdbc..9e36561417 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -130,7 +130,30 @@ public: std::optional astc_decoder_pass; std::optional bl3d_unswizzle_pass; + std::optional bl_unswizzle_2d_pass; + std::optional bl_unswizzle_3d_pass; + std::optional pitch_unswizzle_pass; std::optional msaa_copy_pass; + + /// True when the buffer->image unswizzle pass for this image type is active. + bool HasUnswizzlePassFor(VideoCommon::ImageType type) const noexcept { + switch (type) { + case VideoCommon::ImageType::e2D: + return bl_unswizzle_2d_pass.has_value(); + case VideoCommon::ImageType::e3D: + return bl_unswizzle_3d_pass.has_value(); + case VideoCommon::ImageType::Linear: + return pitch_unswizzle_pass.has_value(); + default: + return false; + } + } + + /// True when any buffer->image unswizzle pass is active on this device. + bool HasImageUnswizzlePasses() const noexcept { + return bl_unswizzle_2d_pass.has_value() || bl_unswizzle_3d_pass.has_value() || + pitch_unswizzle_pass.has_value(); + } const Settings::ResolutionScalingInfo& resolution; std::array, VideoCore::Surface::MaxPixelFormat> view_formats; diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index a944e7e297..709fc49a6b 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -400,6 +400,16 @@ FN_MAX_LIMIT_LIST return features.bit16_storage.uniformAndStorageBuffer16BitAccess; } + /// Returns true if the device supports reading 8-bit values from a storage buffer. + bool IsStorageBuffer8BitAccessSupported() const { + return features.bit8_storage.storageBuffer8BitAccess; + } + + /// Returns true if the device supports reading 16-bit values from a storage buffer. + bool IsStorageBuffer16BitAccessSupported() const { + return features.bit16_storage.storageBuffer16BitAccess; + } + /// Returns true if the device supports binding multisample images as storage images. bool IsStorageImageMultisampleSupported() const { return features.features.shaderStorageImageMultisample; @@ -430,6 +440,11 @@ FN_MAX_LIMIT_LIST return features.features.shaderStorageImageReadWithoutFormat; } + /// Returns true if formatless image store is supported. + bool IsFormatlessImageStoreSupported() const { + return features.features.shaderStorageImageWriteWithoutFormat; + } + /// Returns true if shader int64 is supported. bool IsShaderInt64Supported() const { return features.features.shaderInt64; From ace66343ceea1c965185d1d9960102712153b148 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Mon, 6 Jul 2026 03:07:48 -0400 Subject: [PATCH 50/70] [TEST] Force checks on subgroups --- .../backend/spirv/emit_spirv.cpp | 2 +- .../backend/spirv/emit_spirv_warp.cpp | 42 ++++++++++++++++++- .../backend/spirv/spirv_emit_context.cpp | 9 ++-- src/shader_recompiler/profile.h | 7 ++++ .../renderer_vulkan/vk_pipeline_cache.cpp | 15 +++++++ .../renderer_vulkan/vk_texture_cache.cpp | 9 +++- .../vulkan_common/vulkan_device.cpp | 2 + src/video_core/vulkan_common/vulkan_device.h | 5 +++ 8 files changed, 84 insertions(+), 7 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index f73fb233cb..7ab2bd4adf 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -435,7 +435,7 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct } if ((info.uses_subgroup_vote || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles) && - profile.support_vote) { + profile.support_vote && profile.SupportsSubgroupStage(ctx.stage)) { ctx.AddCapability(spv::Capability::GroupNonUniformBallot); ctx.AddCapability(spv::Capability::GroupNonUniformShuffle); if (!profile.warp_size_potentially_larger_than_guest) { diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp index 77ff8c5731..242426ec08 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -10,7 +13,14 @@ Id SubgroupScope(EmitContext& ctx) { return ctx.Const(static_cast(spv::Scope::Subgroup)); } +bool StageSupportsSubgroups(EmitContext& ctx) { + return ctx.profile.SupportsSubgroupStage(ctx.stage); +} + Id GetThreadId(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.u32_zero_value; + } return ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id); } @@ -68,6 +78,9 @@ Id GetMaxThreadId(EmitContext& ctx, Id thread_id, Id clamp, Id segmentation_mask } Id SelectValue(EmitContext& ctx, Id in_range, Id value, Id src_thread_id) { + if (!StageSupportsSubgroups(ctx)) { + return value; + } return ctx.OpSelect( ctx.U32[1], in_range, ctx.OpGroupNonUniformShuffle(ctx.U32[1], SubgroupScope(ctx), value, src_thread_id), value); @@ -89,6 +102,9 @@ Id EmitLaneId(EmitContext& ctx) { } Id EmitVoteAll(EmitContext& ctx, Id pred) { + if (!StageSupportsSubgroups(ctx)) { + return pred; + } if (!ctx.profile.warp_size_potentially_larger_than_guest) { return ctx.OpGroupNonUniformAll(ctx.U1, SubgroupScope(ctx), pred); } @@ -102,6 +118,9 @@ Id EmitVoteAll(EmitContext& ctx, Id pred) { } Id EmitVoteAny(EmitContext& ctx, Id pred) { + if (!StageSupportsSubgroups(ctx)) { + return pred; + } if (!ctx.profile.warp_size_potentially_larger_than_guest) { return ctx.OpGroupNonUniformAny(ctx.U1, SubgroupScope(ctx), pred); } @@ -115,6 +134,9 @@ Id EmitVoteAny(EmitContext& ctx, Id pred) { } Id EmitVoteEqual(EmitContext& ctx, Id pred) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.true_value; + } if (!ctx.profile.warp_size_potentially_larger_than_guest) { return ctx.OpGroupNonUniformAllEqual(ctx.U1, SubgroupScope(ctx), pred); } @@ -129,6 +151,9 @@ Id EmitVoteEqual(EmitContext& ctx, Id pred) { } Id EmitSubgroupBallot(EmitContext& ctx, Id pred) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.OpSelect(ctx.U32[1], pred, ctx.Const(1u), ctx.u32_zero_value); + } const Id ballot{ctx.OpGroupNonUniformBallot(ctx.U32[4], SubgroupScope(ctx), pred)}; if (!ctx.profile.warp_size_potentially_larger_than_guest) { return ctx.OpCompositeExtract(ctx.U32[1], ballot, 0U); @@ -137,22 +162,37 @@ Id EmitSubgroupBallot(EmitContext& ctx, Id pred) { } Id EmitSubgroupEqMask(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.Const(1u); + } return LoadMask(ctx, ctx.subgroup_mask_eq); } Id EmitSubgroupLtMask(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.u32_zero_value; + } return LoadMask(ctx, ctx.subgroup_mask_lt); } Id EmitSubgroupLeMask(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.Const(1u); + } return LoadMask(ctx, ctx.subgroup_mask_le); } Id EmitSubgroupGtMask(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.u32_zero_value; + } return LoadMask(ctx, ctx.subgroup_mask_gt); } Id EmitSubgroupGeMask(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.Const(1u); + } return LoadMask(ctx, ctx.subgroup_mask_ge); } @@ -222,7 +262,7 @@ Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id Id EmitFSwizzleAdd(EmitContext& ctx, Id op_a, Id op_b, Id swizzle) { const Id three{ctx.Const(3U)}; - Id mask{ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id)}; + Id mask{GetThreadId(ctx)}; mask = ctx.OpBitwiseAnd(ctx.U32[1], mask, three); mask = ctx.OpShiftLeftLogical(ctx.U32[1], mask, ctx.Const(1U)); mask = ctx.OpShiftRightLogical(ctx.U32[1], swizzle, mask); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index b1bed263a6..eaa363a080 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -1447,7 +1447,7 @@ void EmitContext::DefineInputs(const IR::Program& program) { if (info.uses_is_helper_invocation) { is_helper_invocation = DefineInput(*this, U1, false, spv::BuiltIn::HelperInvocation); } - if (info.uses_subgroup_mask) { + if (info.uses_subgroup_mask && profile.SupportsSubgroupStage(stage)) { subgroup_mask_eq = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupEqMaskKHR); subgroup_mask_lt = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLtMaskKHR); subgroup_mask_le = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLeMaskKHR); @@ -1461,9 +1461,10 @@ void EmitContext::DefineInputs(const IR::Program& program) { Decorate(subgroup_mask_ge, spv::Decoration::Flat); } } - if (info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles || - (profile.warp_size_potentially_larger_than_guest && - (info.uses_subgroup_vote || info.uses_subgroup_mask))) { + if ((info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles || + (profile.warp_size_potentially_larger_than_guest && + (info.uses_subgroup_vote || info.uses_subgroup_mask))) && + profile.SupportsSubgroupStage(stage)) { AddCapability(spv::Capability::GroupNonUniform); subgroup_local_invocation_id = DefineInput(*this, U32[1], false, spv::BuiltIn::SubgroupLocalInvocationId); diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h index e95778a0de..0197a5f1d7 100644 --- a/src/shader_recompiler/profile.h +++ b/src/shader_recompiler/profile.h @@ -10,6 +10,8 @@ namespace Shader { +enum class Stage : u32; + struct Profile { u32 supported_spirv{0x00010000}; bool unified_descriptor_binding{}; @@ -32,6 +34,7 @@ struct Profile { bool support_fp64_signed_zero_nan_preserve{}; bool support_explicit_workgroup_layout{}; bool support_vote{}; + u32 supported_subgroup_stages{0x7F}; bool support_viewport_index_layer_non_geometry{}; bool support_viewport_mask{}; bool support_typeless_image_loads{}; @@ -95,6 +98,10 @@ struct Profile { u64 min_ssbo_alignment{}; u32 max_user_clip_distances{}; + + bool SupportsSubgroupStage(Stage stage) const { + return (supported_subgroup_stages & (1u << static_cast(stage))) != 0; + } }; } // namespace Shader diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 7fd87b17cd..669d736218 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -353,6 +353,20 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, serialization_thread(1, "VkPipelineSerialization") { const auto& float_control{device.FloatControlProperties()}; const VkDriverId driver_id{device.GetDriverID()}; + const VkShaderStageFlags subgroup_stages{device.GetSubgroupSupportedStages()}; + const auto subgroup_stage_bit{[subgroup_stages](VkShaderStageFlags flag, Shader::Stage stage) { + return (subgroup_stages & flag) != 0 ? (1u << static_cast(stage)) : 0u; + }}; + const u32 supported_subgroup_stages{ + subgroup_stage_bit(VK_SHADER_STAGE_VERTEX_BIT, Shader::Stage::VertexA) | + subgroup_stage_bit(VK_SHADER_STAGE_VERTEX_BIT, Shader::Stage::VertexB) | + subgroup_stage_bit(VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, + Shader::Stage::TessellationControl) | + subgroup_stage_bit(VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, + Shader::Stage::TessellationEval) | + subgroup_stage_bit(VK_SHADER_STAGE_GEOMETRY_BIT, Shader::Stage::Geometry) | + subgroup_stage_bit(VK_SHADER_STAGE_FRAGMENT_BIT, Shader::Stage::Fragment) | + subgroup_stage_bit(VK_SHADER_STAGE_COMPUTE_BIT, Shader::Stage::Compute)}; profile = Shader::Profile{ .supported_spirv = device.SupportedSpirvVersion(), .unified_descriptor_binding = true, @@ -382,6 +396,7 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, float_control.shaderSignedZeroInfNanPreserveFloat64 != VK_FALSE, .support_explicit_workgroup_layout = device.IsKhrWorkgroupMemoryExplicitLayoutSupported(), .support_vote = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_VOTE_BIT), + .supported_subgroup_stages = supported_subgroup_stages, .support_viewport_index_layer_non_geometry = device.IsExtShaderViewportIndexLayerSupported(), .support_viewport_mask = device.IsNvViewportArray2Supported(), diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 305cec454a..bc71038029 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -998,11 +998,18 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched compute_pass_descriptor_queue); } } + // Log which unswizzle passes are active, for debugging purposes. + LOG_INFO(Render_Vulkan, "GPU compute unswizzle passes active: 2d={}, 3d={}, pitch={}", + bl_unswizzle_2d_pass.has_value(), bl_unswizzle_3d_pass.has_value(), + pitch_unswizzle_pass.has_value()); for (size_t index_a = 0; index_a < VideoCore::Surface::MaxPixelFormat; index_a++) { const auto image_format = static_cast(index_a); if (IsPixelFormatASTC(image_format) && !device.IsOptimalAstcSupported()) { view_formats[index_a].push_back(VK_FORMAT_A8B8G8R8_UNORM_PACK32); - } else if (HasImageUnswizzlePasses() && !IsPixelFormatBCn(image_format) && + } else if (HasImageUnswizzlePasses() && !IsPixelFormatASTC(image_format) && + !IsPixelFormatBCn(image_format) && + VideoCore::Surface::DefaultBlockWidth(image_format) == 1 && + VideoCore::Surface::DefaultBlockHeight(image_format) == 1 && VideoCore::Surface::GetFormatType(image_format) == VideoCore::Surface::SurfaceType::ColorTexture) { const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(image_format); diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 00b30637ad..093cb9d87d 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -505,6 +505,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken CustomBorderColor."); RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); + RemoveExtensionFeature(extensions.border_color_swizzle, features.border_color_swizzle, + VK_EXT_BORDER_COLOR_SWIZZLE_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader float controls."); RemoveExtension(extensions.shader_float_controls, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader atomic int64."); diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 709fc49a6b..1333fcc58b 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -430,6 +430,11 @@ FN_MAX_LIMIT_LIST return properties.subgroup_properties.supportedOperations & feature; } + /// Returns the shader stages that support subgroup operations. + VkShaderStageFlags GetSubgroupSupportedStages() const { + return properties.subgroup_properties.supportedStages; + } + /// Returns the maximum number of push descriptors. u32 MaxPushDescriptors() const { return properties.push_descriptor.maxPushDescriptors; From ca9ff1642f9cd5ce65ed9a67345e13d5c9d88707 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Mon, 6 Jul 2026 03:16:54 -0400 Subject: [PATCH 51/70] Revert "[TEST] Force checks on subgroups" --- .../backend/spirv/emit_spirv.cpp | 2 +- .../backend/spirv/emit_spirv_warp.cpp | 42 +------------------ .../backend/spirv/spirv_emit_context.cpp | 9 ++-- src/shader_recompiler/profile.h | 7 ---- .../renderer_vulkan/vk_pipeline_cache.cpp | 15 ------- .../renderer_vulkan/vk_texture_cache.cpp | 9 +--- .../vulkan_common/vulkan_device.cpp | 2 - src/video_core/vulkan_common/vulkan_device.h | 5 --- 8 files changed, 7 insertions(+), 84 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index 7ab2bd4adf..f73fb233cb 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -435,7 +435,7 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct } if ((info.uses_subgroup_vote || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles) && - profile.support_vote && profile.SupportsSubgroupStage(ctx.stage)) { + profile.support_vote) { ctx.AddCapability(spv::Capability::GroupNonUniformBallot); ctx.AddCapability(spv::Capability::GroupNonUniformShuffle); if (!profile.warp_size_potentially_larger_than_guest) { diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp index 242426ec08..77ff8c5731 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -13,14 +10,7 @@ Id SubgroupScope(EmitContext& ctx) { return ctx.Const(static_cast(spv::Scope::Subgroup)); } -bool StageSupportsSubgroups(EmitContext& ctx) { - return ctx.profile.SupportsSubgroupStage(ctx.stage); -} - Id GetThreadId(EmitContext& ctx) { - if (!StageSupportsSubgroups(ctx)) { - return ctx.u32_zero_value; - } return ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id); } @@ -78,9 +68,6 @@ Id GetMaxThreadId(EmitContext& ctx, Id thread_id, Id clamp, Id segmentation_mask } Id SelectValue(EmitContext& ctx, Id in_range, Id value, Id src_thread_id) { - if (!StageSupportsSubgroups(ctx)) { - return value; - } return ctx.OpSelect( ctx.U32[1], in_range, ctx.OpGroupNonUniformShuffle(ctx.U32[1], SubgroupScope(ctx), value, src_thread_id), value); @@ -102,9 +89,6 @@ Id EmitLaneId(EmitContext& ctx) { } Id EmitVoteAll(EmitContext& ctx, Id pred) { - if (!StageSupportsSubgroups(ctx)) { - return pred; - } if (!ctx.profile.warp_size_potentially_larger_than_guest) { return ctx.OpGroupNonUniformAll(ctx.U1, SubgroupScope(ctx), pred); } @@ -118,9 +102,6 @@ Id EmitVoteAll(EmitContext& ctx, Id pred) { } Id EmitVoteAny(EmitContext& ctx, Id pred) { - if (!StageSupportsSubgroups(ctx)) { - return pred; - } if (!ctx.profile.warp_size_potentially_larger_than_guest) { return ctx.OpGroupNonUniformAny(ctx.U1, SubgroupScope(ctx), pred); } @@ -134,9 +115,6 @@ Id EmitVoteAny(EmitContext& ctx, Id pred) { } Id EmitVoteEqual(EmitContext& ctx, Id pred) { - if (!StageSupportsSubgroups(ctx)) { - return ctx.true_value; - } if (!ctx.profile.warp_size_potentially_larger_than_guest) { return ctx.OpGroupNonUniformAllEqual(ctx.U1, SubgroupScope(ctx), pred); } @@ -151,9 +129,6 @@ Id EmitVoteEqual(EmitContext& ctx, Id pred) { } Id EmitSubgroupBallot(EmitContext& ctx, Id pred) { - if (!StageSupportsSubgroups(ctx)) { - return ctx.OpSelect(ctx.U32[1], pred, ctx.Const(1u), ctx.u32_zero_value); - } const Id ballot{ctx.OpGroupNonUniformBallot(ctx.U32[4], SubgroupScope(ctx), pred)}; if (!ctx.profile.warp_size_potentially_larger_than_guest) { return ctx.OpCompositeExtract(ctx.U32[1], ballot, 0U); @@ -162,37 +137,22 @@ Id EmitSubgroupBallot(EmitContext& ctx, Id pred) { } Id EmitSubgroupEqMask(EmitContext& ctx) { - if (!StageSupportsSubgroups(ctx)) { - return ctx.Const(1u); - } return LoadMask(ctx, ctx.subgroup_mask_eq); } Id EmitSubgroupLtMask(EmitContext& ctx) { - if (!StageSupportsSubgroups(ctx)) { - return ctx.u32_zero_value; - } return LoadMask(ctx, ctx.subgroup_mask_lt); } Id EmitSubgroupLeMask(EmitContext& ctx) { - if (!StageSupportsSubgroups(ctx)) { - return ctx.Const(1u); - } return LoadMask(ctx, ctx.subgroup_mask_le); } Id EmitSubgroupGtMask(EmitContext& ctx) { - if (!StageSupportsSubgroups(ctx)) { - return ctx.u32_zero_value; - } return LoadMask(ctx, ctx.subgroup_mask_gt); } Id EmitSubgroupGeMask(EmitContext& ctx) { - if (!StageSupportsSubgroups(ctx)) { - return ctx.Const(1u); - } return LoadMask(ctx, ctx.subgroup_mask_ge); } @@ -262,7 +222,7 @@ Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id Id EmitFSwizzleAdd(EmitContext& ctx, Id op_a, Id op_b, Id swizzle) { const Id three{ctx.Const(3U)}; - Id mask{GetThreadId(ctx)}; + Id mask{ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id)}; mask = ctx.OpBitwiseAnd(ctx.U32[1], mask, three); mask = ctx.OpShiftLeftLogical(ctx.U32[1], mask, ctx.Const(1U)); mask = ctx.OpShiftRightLogical(ctx.U32[1], swizzle, mask); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index eaa363a080..b1bed263a6 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -1447,7 +1447,7 @@ void EmitContext::DefineInputs(const IR::Program& program) { if (info.uses_is_helper_invocation) { is_helper_invocation = DefineInput(*this, U1, false, spv::BuiltIn::HelperInvocation); } - if (info.uses_subgroup_mask && profile.SupportsSubgroupStage(stage)) { + if (info.uses_subgroup_mask) { subgroup_mask_eq = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupEqMaskKHR); subgroup_mask_lt = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLtMaskKHR); subgroup_mask_le = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLeMaskKHR); @@ -1461,10 +1461,9 @@ void EmitContext::DefineInputs(const IR::Program& program) { Decorate(subgroup_mask_ge, spv::Decoration::Flat); } } - if ((info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles || - (profile.warp_size_potentially_larger_than_guest && - (info.uses_subgroup_vote || info.uses_subgroup_mask))) && - profile.SupportsSubgroupStage(stage)) { + if (info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles || + (profile.warp_size_potentially_larger_than_guest && + (info.uses_subgroup_vote || info.uses_subgroup_mask))) { AddCapability(spv::Capability::GroupNonUniform); subgroup_local_invocation_id = DefineInput(*this, U32[1], false, spv::BuiltIn::SubgroupLocalInvocationId); diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h index 0197a5f1d7..e95778a0de 100644 --- a/src/shader_recompiler/profile.h +++ b/src/shader_recompiler/profile.h @@ -10,8 +10,6 @@ namespace Shader { -enum class Stage : u32; - struct Profile { u32 supported_spirv{0x00010000}; bool unified_descriptor_binding{}; @@ -34,7 +32,6 @@ struct Profile { bool support_fp64_signed_zero_nan_preserve{}; bool support_explicit_workgroup_layout{}; bool support_vote{}; - u32 supported_subgroup_stages{0x7F}; bool support_viewport_index_layer_non_geometry{}; bool support_viewport_mask{}; bool support_typeless_image_loads{}; @@ -98,10 +95,6 @@ struct Profile { u64 min_ssbo_alignment{}; u32 max_user_clip_distances{}; - - bool SupportsSubgroupStage(Stage stage) const { - return (supported_subgroup_stages & (1u << static_cast(stage))) != 0; - } }; } // namespace Shader diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 669d736218..7fd87b17cd 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -353,20 +353,6 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, serialization_thread(1, "VkPipelineSerialization") { const auto& float_control{device.FloatControlProperties()}; const VkDriverId driver_id{device.GetDriverID()}; - const VkShaderStageFlags subgroup_stages{device.GetSubgroupSupportedStages()}; - const auto subgroup_stage_bit{[subgroup_stages](VkShaderStageFlags flag, Shader::Stage stage) { - return (subgroup_stages & flag) != 0 ? (1u << static_cast(stage)) : 0u; - }}; - const u32 supported_subgroup_stages{ - subgroup_stage_bit(VK_SHADER_STAGE_VERTEX_BIT, Shader::Stage::VertexA) | - subgroup_stage_bit(VK_SHADER_STAGE_VERTEX_BIT, Shader::Stage::VertexB) | - subgroup_stage_bit(VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, - Shader::Stage::TessellationControl) | - subgroup_stage_bit(VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, - Shader::Stage::TessellationEval) | - subgroup_stage_bit(VK_SHADER_STAGE_GEOMETRY_BIT, Shader::Stage::Geometry) | - subgroup_stage_bit(VK_SHADER_STAGE_FRAGMENT_BIT, Shader::Stage::Fragment) | - subgroup_stage_bit(VK_SHADER_STAGE_COMPUTE_BIT, Shader::Stage::Compute)}; profile = Shader::Profile{ .supported_spirv = device.SupportedSpirvVersion(), .unified_descriptor_binding = true, @@ -396,7 +382,6 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, float_control.shaderSignedZeroInfNanPreserveFloat64 != VK_FALSE, .support_explicit_workgroup_layout = device.IsKhrWorkgroupMemoryExplicitLayoutSupported(), .support_vote = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_VOTE_BIT), - .supported_subgroup_stages = supported_subgroup_stages, .support_viewport_index_layer_non_geometry = device.IsExtShaderViewportIndexLayerSupported(), .support_viewport_mask = device.IsNvViewportArray2Supported(), diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index bc71038029..305cec454a 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -998,18 +998,11 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched compute_pass_descriptor_queue); } } - // Log which unswizzle passes are active, for debugging purposes. - LOG_INFO(Render_Vulkan, "GPU compute unswizzle passes active: 2d={}, 3d={}, pitch={}", - bl_unswizzle_2d_pass.has_value(), bl_unswizzle_3d_pass.has_value(), - pitch_unswizzle_pass.has_value()); for (size_t index_a = 0; index_a < VideoCore::Surface::MaxPixelFormat; index_a++) { const auto image_format = static_cast(index_a); if (IsPixelFormatASTC(image_format) && !device.IsOptimalAstcSupported()) { view_formats[index_a].push_back(VK_FORMAT_A8B8G8R8_UNORM_PACK32); - } else if (HasImageUnswizzlePasses() && !IsPixelFormatASTC(image_format) && - !IsPixelFormatBCn(image_format) && - VideoCore::Surface::DefaultBlockWidth(image_format) == 1 && - VideoCore::Surface::DefaultBlockHeight(image_format) == 1 && + } else if (HasImageUnswizzlePasses() && !IsPixelFormatBCn(image_format) && VideoCore::Surface::GetFormatType(image_format) == VideoCore::Surface::SurfaceType::ColorTexture) { const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(image_format); diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 093cb9d87d..00b30637ad 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -505,8 +505,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken CustomBorderColor."); RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); - RemoveExtensionFeature(extensions.border_color_swizzle, features.border_color_swizzle, - VK_EXT_BORDER_COLOR_SWIZZLE_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader float controls."); RemoveExtension(extensions.shader_float_controls, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader atomic int64."); diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 1333fcc58b..709fc49a6b 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -430,11 +430,6 @@ FN_MAX_LIMIT_LIST return properties.subgroup_properties.supportedOperations & feature; } - /// Returns the shader stages that support subgroup operations. - VkShaderStageFlags GetSubgroupSupportedStages() const { - return properties.subgroup_properties.supportedStages; - } - /// Returns the maximum number of push descriptors. u32 MaxPushDescriptors() const { return properties.push_descriptor.maxPushDescriptors; From 62e8666eee4b054849209be3331a4c8f9e1f8739 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Mon, 6 Jul 2026 03:19:32 -0400 Subject: [PATCH 52/70] Revert "[TEST] Debugger GPU unswizzle shaders" --- .../features/settings/model/BooleanSetting.kt | 3 - .../settings/model/view/SettingsItem.kt | 21 -- .../settings/ui/SettingsFragmentPresenter.kt | 3 - .../app/src/main/res/values/strings.xml | 6 - src/common/settings.h | 10 - .../block_linear_unswizzle_3d_bcn.comp | 9 +- .../renderer_vulkan/renderer_vulkan.cpp | 1 - .../renderer_vulkan/vk_compute_pass.cpp | 260 +----------------- .../renderer_vulkan/vk_compute_pass.h | 52 ---- .../renderer_vulkan/vk_texture_cache.cpp | 143 +--------- .../renderer_vulkan/vk_texture_cache.h | 23 -- src/video_core/vulkan_common/vulkan_device.h | 15 - 12 files changed, 24 insertions(+), 522 deletions(-) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index e87c502342..ad983bd749 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt @@ -39,9 +39,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { RENDERER_VERTEX_INPUT_DYNAMIC_STATE("vertex_input_dynamic_state"), RENDERER_SAMPLE_SHADING("sample_shading"), GPU_UNSWIZZLE_ENABLED("gpu_unswizzle_enabled"), - GPU_COMPUTE_UNSWIZZLE_2D("gpu_compute_unswizzle_2d"), - GPU_COMPUTE_UNSWIZZLE_3D("gpu_compute_unswizzle_3d"), - GPU_COMPUTE_UNSWIZZLE_PITCH("gpu_compute_unswizzle_pitch"), PICTURE_IN_PICTURE("picture_in_picture"), USE_CUSTOM_RTC("custom_rtc_enabled"), BLACK_BACKGROUNDS("black_backgrounds"), diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index 6f6282aa30..67cf392fef 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -724,27 +724,6 @@ abstract class SettingsItem( chunkSizeValuesId = R.array.gpuSwizzleChunkValues ) ) - put( - SwitchSetting( - BooleanSetting.GPU_COMPUTE_UNSWIZZLE_2D, - titleId = R.string.gpu_compute_unswizzle_2d, - descriptionId = R.string.gpu_compute_unswizzle_2d_description - ) - ) - put( - SwitchSetting( - BooleanSetting.GPU_COMPUTE_UNSWIZZLE_3D, - titleId = R.string.gpu_compute_unswizzle_3d, - descriptionId = R.string.gpu_compute_unswizzle_3d_description - ) - ) - put( - SwitchSetting( - BooleanSetting.GPU_COMPUTE_UNSWIZZLE_PITCH, - titleId = R.string.gpu_compute_unswizzle_pitch, - descriptionId = R.string.gpu_compute_unswizzle_pitch_description - ) - ) put( SingleChoiceSetting( IntSetting.FAST_CPU_TIME, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 70d52c2121..7b7762520d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -308,9 +308,6 @@ class SettingsFragmentPresenter( add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key) add(BooleanSetting.RENDERER_ASYNC_PRESENTATION.key) add(SettingsItem.GPU_UNSWIZZLE_COMBINED) - add(BooleanSetting.GPU_COMPUTE_UNSWIZZLE_2D.key) - add(BooleanSetting.GPU_COMPUTE_UNSWIZZLE_3D.key) - add(BooleanSetting.GPU_COMPUTE_UNSWIZZLE_PITCH.key) add(HeaderSetting(R.string.extensions)) diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 89e2f0b0ad..c67c2f279f 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -527,12 +527,6 @@ GPU Unswizzle Settings Configure GPU-based texture unswizzling parameters or disable it entirely. Adjust these settings to balance performance and texture loading quality. Enable GPU Unswizzle - GPU Compute Unswizzle (2D) - Unswizzles block-linear 2D textures with a compute shader instead of the CPU. Disable to upload 2D textures through the CPU path. - GPU Compute Unswizzle (3D) - Unswizzles block-linear 3D textures with a compute shader instead of the CPU. Disable to upload 3D textures through the CPU path. - GPU Compute Unswizzle (Pitch) - Unswizzles pitch-linear textures with a compute shader instead of the CPU. Disable to upload linear textures through the CPU path. Disabled GPU Unswizzle Max Texture Size Sets the maximum size (MB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. diff --git a/src/common/settings.h b/src/common/settings.h index 8a290e526d..b80d6a5ec7 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -605,16 +605,6 @@ struct Values { SwitchableSetting gpu_unswizzle_enabled{linkage, false, "gpu_unswizzle_enabled", Category::RendererHacks}; - SwitchableSetting gpu_compute_unswizzle_2d{linkage, true, "gpu_compute_unswizzle_2d", - Category::RendererHacks}; - - SwitchableSetting gpu_compute_unswizzle_3d{linkage, true, "gpu_compute_unswizzle_3d", - Category::RendererHacks}; - - SwitchableSetting gpu_compute_unswizzle_pitch{linkage, true, - "gpu_compute_unswizzle_pitch", - Category::RendererHacks}; - SwitchableSetting dyna_state{linkage, #if defined(__ANDROID__) ExtendedDynamicState::Disabled, diff --git a/src/video_core/host_shaders/block_linear_unswizzle_3d_bcn.comp b/src/video_core/host_shaders/block_linear_unswizzle_3d_bcn.comp index 0f6d1ad059..e084837f25 100644 --- a/src/video_core/host_shaders/block_linear_unswizzle_3d_bcn.comp +++ b/src/video_core/host_shaders/block_linear_unswizzle_3d_bcn.comp @@ -1,10 +1,12 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #version 430 #ifdef VULKAN - #define HAS_EXTENDED_TYPES 0 + #extension GL_EXT_shader_16bit_storage : require + #extension GL_EXT_shader_8bit_storage : require + #define HAS_EXTENDED_TYPES 1 #define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants { #define END_PUSH_CONSTANTS }; #define UNIFORM(n) @@ -40,6 +42,9 @@ layout(push_constant) uniform PushConstants { uint block_depth; // Offset 48 uint block_depth_mask; // Offset 52 + int _pad; // Offset 56 + + ivec3 destination; // Offset 60 } pc; #else BEGIN_PUSH_CONSTANTS diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 88e2895598..b0a7d0c579 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -169,7 +169,6 @@ try } RendererVulkan::~RendererVulkan() { - scheduler.WaitWorker(); scheduler.RegisterOnSubmit([] {}); void(device.GetLogical().WaitIdle()); } diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index 243bc1af5e..f361c34f44 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -24,10 +24,7 @@ #include "video_core/host_shaders/resolve_conditional_render_comp_spv.h" #include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h" #include "video_core/host_shaders/vulkan_uint8_comp_spv.h" -#include "video_core/host_shaders/block_linear_unswizzle_2d_comp_spv.h" -#include "video_core/host_shaders/block_linear_unswizzle_3d_comp_spv.h" #include "video_core/host_shaders/block_linear_unswizzle_3d_bcn_comp_spv.h" -#include "video_core/host_shaders/pitch_unswizzle_comp_spv.h" #include "video_core/renderer_vulkan/vk_compute_pass.h" #include "video_core/surface.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" @@ -236,26 +233,6 @@ struct QueriesPrefixScanPushConstants { struct ConditionalRenderingResolvePushConstants { u32 compare_to_zero; }; - -struct BlockLinear3DImagePushConstants { - alignas(16) std::array origin; - alignas(16) std::array destination; - u32 bytes_per_block_log2; - u32 slice_size; - u32 block_size; - u32 x_shift; - u32 block_height; - u32 block_height_mask; - u32 block_depth; - u32 block_depth_mask; -}; - -struct PitchUnswizzlePushConstants { - std::array origin; - std::array destination; - u32 bytes_per_block; - u32 pitch; -}; } // Anonymous namespace ComputePass::ComputePass(const Device& device_, Scheduler& scheduler, DescriptorPool& descriptor_pool, @@ -677,235 +654,6 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map, scheduler.Finish(); } -namespace { - -void RecordUnswizzleImageEntry(vk::CommandBuffer cmdbuf, VkPipeline pipeline, VkImage image, - VkImageAspectFlags aspect_mask, bool is_initialized) { - const VkImageMemoryBarrier image_barrier{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .pNext = nullptr, - .srcAccessMask = static_cast(is_initialized ? VK_ACCESS_SHADER_WRITE_BIT - : VK_ACCESS_NONE), - .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, - .oldLayout = is_initialized ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED, - .newLayout = VK_IMAGE_LAYOUT_GENERAL, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = image, - .subresourceRange{ - .aspectMask = aspect_mask, - .baseMipLevel = 0, - .levelCount = VK_REMAINING_MIP_LEVELS, - .baseArrayLayer = 0, - .layerCount = VK_REMAINING_ARRAY_LAYERS, - }, - }; - cmdbuf.PipelineBarrier(is_initialized ? vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER - : VkPipelineStageFlags(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT), - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, image_barrier); - cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); -} - -void RecordUnswizzleImageExit(vk::CommandBuffer cmdbuf, VkImage image, - VkImageAspectFlags aspect_mask) { - const VkImageMemoryBarrier image_barrier{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .pNext = nullptr, - .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, - .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | - VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, - .oldLayout = VK_IMAGE_LAYOUT_GENERAL, - .newLayout = VK_IMAGE_LAYOUT_GENERAL, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = image, - .subresourceRange{ - .aspectMask = aspect_mask, - .baseMipLevel = 0, - .levelCount = VK_REMAINING_MIP_LEVELS, - .baseArrayLayer = 0, - .layerCount = VK_REMAINING_ARRAY_LAYERS, - }, - }; - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, 0, image_barrier); -} -} // Anonymous namespace - -BlockLinearUnswizzleImage2DPass::BlockLinearUnswizzleImage2DPass( - const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, - StagingBufferPool& staging_buffer_pool_, - ComputePassDescriptorQueue& compute_pass_descriptor_queue_) - : ComputePass(device_, scheduler_, descriptor_pool_, ASTC_DESCRIPTOR_SET_BINDINGS, - ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY, ASTC_BANK_INFO, - COMPUTE_PUSH_CONSTANT_RANGE, - BLOCK_LINEAR_UNSWIZZLE_2D_COMP_SPV), - scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_}, - compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {} - -BlockLinearUnswizzleImage2DPass::~BlockLinearUnswizzleImage2DPass() = default; - -void BlockLinearUnswizzleImage2DPass::Unswizzle( - Image& image, const StagingBufferRef& map, - std::span swizzles) { - using namespace VideoCommon::Accelerated; - scheduler.RequestOutsideRenderPassOperationContext(); - const VkPipeline vk_pipeline = *pipeline; - const VkImageAspectFlags aspect_mask = image.AspectMask(); - const VkImage vk_image = image.Handle(); - const bool is_initialized = image.ExchangeInitialization(); - scheduler.Record([vk_pipeline, vk_image, aspect_mask, is_initialized](vk::CommandBuffer cmdbuf) { - RecordUnswizzleImageEntry(cmdbuf, vk_pipeline, vk_image, aspect_mask, is_initialized); - }); - for (const VideoCommon::SwizzleParameters& swizzle : swizzles) { - const size_t input_offset = swizzle.buffer_offset + map.offset; - const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 32U); - const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 32U); - const u32 num_dispatches_z = image.info.resources.layers; - - compute_pass_descriptor_queue.Acquire(scheduler, 2); - compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset, - image.guest_size_bytes - swizzle.buffer_offset); - compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level)); - const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; - - const auto params = MakeBlockLinearSwizzle2DParams(swizzle, image.info); - scheduler.Record([this, num_dispatches_x, num_dispatches_y, num_dispatches_z, params, - descriptor_data](vk::CommandBuffer cmdbuf) { - const VkDescriptorSet set = descriptor_allocator.Commit(); - device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); - cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {}); - cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, params); - cmdbuf.Dispatch(num_dispatches_x, num_dispatches_y, num_dispatches_z); - }); - } - scheduler.Record([vk_image, aspect_mask](vk::CommandBuffer cmdbuf) { - RecordUnswizzleImageExit(cmdbuf, vk_image, aspect_mask); - }); - scheduler.Finish(); -} - -BlockLinearUnswizzleImage3DPass::BlockLinearUnswizzleImage3DPass( - const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, - StagingBufferPool& staging_buffer_pool_, - ComputePassDescriptorQueue& compute_pass_descriptor_queue_) - : ComputePass(device_, scheduler_, descriptor_pool_, ASTC_DESCRIPTOR_SET_BINDINGS, - ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY, ASTC_BANK_INFO, - COMPUTE_PUSH_CONSTANT_RANGE, - BLOCK_LINEAR_UNSWIZZLE_3D_COMP_SPV), - scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_}, - compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {} - -BlockLinearUnswizzleImage3DPass::~BlockLinearUnswizzleImage3DPass() = default; - -void BlockLinearUnswizzleImage3DPass::Unswizzle( - Image& image, const StagingBufferRef& map, - std::span swizzles) { - using namespace VideoCommon::Accelerated; - scheduler.RequestOutsideRenderPassOperationContext(); - const VkPipeline vk_pipeline = *pipeline; - const VkImageAspectFlags aspect_mask = image.AspectMask(); - const VkImage vk_image = image.Handle(); - const bool is_initialized = image.ExchangeInitialization(); - scheduler.Record([vk_pipeline, vk_image, aspect_mask, is_initialized](vk::CommandBuffer cmdbuf) { - RecordUnswizzleImageEntry(cmdbuf, vk_pipeline, vk_image, aspect_mask, is_initialized); - }); - for (const VideoCommon::SwizzleParameters& swizzle : swizzles) { - const size_t input_offset = swizzle.buffer_offset + map.offset; - const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 16U); - const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 8U); - const u32 num_dispatches_z = Common::DivCeil(swizzle.num_tiles.depth, 8U); - - compute_pass_descriptor_queue.Acquire(scheduler, 2); - compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset, - image.guest_size_bytes - swizzle.buffer_offset); - compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level)); - const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; - - const auto p = MakeBlockLinearSwizzle3DParams(swizzle, image.info); - const BlockLinear3DImagePushConstants params{ - .origin = p.origin, - .destination = p.destination, - .bytes_per_block_log2 = p.bytes_per_block_log2, - .slice_size = p.slice_size, - .block_size = p.block_size, - .x_shift = p.x_shift, - .block_height = p.block_height, - .block_height_mask = p.block_height_mask, - .block_depth = p.block_depth, - .block_depth_mask = p.block_depth_mask, - }; - scheduler.Record([this, num_dispatches_x, num_dispatches_y, num_dispatches_z, params, - descriptor_data](vk::CommandBuffer cmdbuf) { - const VkDescriptorSet set = descriptor_allocator.Commit(); - device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); - cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {}); - cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, params); - cmdbuf.Dispatch(num_dispatches_x, num_dispatches_y, num_dispatches_z); - }); - } - scheduler.Record([vk_image, aspect_mask](vk::CommandBuffer cmdbuf) { - RecordUnswizzleImageExit(cmdbuf, vk_image, aspect_mask); - }); - scheduler.Finish(); -} - -PitchUnswizzlePass::PitchUnswizzlePass(const Device& device_, Scheduler& scheduler_, - DescriptorPool& descriptor_pool_, - StagingBufferPool& staging_buffer_pool_, - ComputePassDescriptorQueue& compute_pass_descriptor_queue_) - : ComputePass(device_, scheduler_, descriptor_pool_, ASTC_DESCRIPTOR_SET_BINDINGS, - ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY, ASTC_BANK_INFO, - COMPUTE_PUSH_CONSTANT_RANGE, - PITCH_UNSWIZZLE_COMP_SPV), - scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_}, - compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {} - -PitchUnswizzlePass::~PitchUnswizzlePass() = default; - -void PitchUnswizzlePass::Unswizzle(Image& image, const StagingBufferRef& map, - std::span swizzles) { - scheduler.RequestOutsideRenderPassOperationContext(); - const VkPipeline vk_pipeline = *pipeline; - const VkImageAspectFlags aspect_mask = image.AspectMask(); - const VkImage vk_image = image.Handle(); - const bool is_initialized = image.ExchangeInitialization(); - scheduler.Record([vk_pipeline, vk_image, aspect_mask, is_initialized](vk::CommandBuffer cmdbuf) { - RecordUnswizzleImageEntry(cmdbuf, vk_pipeline, vk_image, aspect_mask, is_initialized); - }); - const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(image.info.format); - for (const VideoCommon::SwizzleParameters& swizzle : swizzles) { - const size_t input_offset = swizzle.buffer_offset + map.offset; - const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 32U); - const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 32U); - - compute_pass_descriptor_queue.Acquire(scheduler, 2); - compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset, - image.guest_size_bytes - swizzle.buffer_offset); - compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level)); - const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; - - const PitchUnswizzlePushConstants params{ - .origin = {0, 0}, - .destination = {0, 0}, - .bytes_per_block = bytes_per_block, - .pitch = image.info.pitch, - }; - scheduler.Record([this, num_dispatches_x, num_dispatches_y, params, - descriptor_data](vk::CommandBuffer cmdbuf) { - const VkDescriptorSet set = descriptor_allocator.Commit(); - device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); - cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {}); - cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, params); - cmdbuf.Dispatch(num_dispatches_x, num_dispatches_y, 1); - }); - } - scheduler.Record([vk_image, aspect_mask](vk::CommandBuffer cmdbuf) { - RecordUnswizzleImageExit(cmdbuf, vk_image, aspect_mask); - }); - scheduler.Finish(); -} - constexpr u32 BL3D_BINDING_INPUT_BUFFER = 0; constexpr u32 BL3D_BINDING_OUTPUT_BUFFER = 1; @@ -923,6 +671,10 @@ struct alignas(16) BlockLinearUnswizzle3DPushConstants { u32 block_depth; // Offset 48 u32 block_depth_mask; // Offset 52 + s32 _pad; // Offset 56 + + s32 destination[3]; // Offset 60 + s32 _pad_end; // Offset 72 }; static_assert(sizeof(BlockLinearUnswizzle3DPushConstants) <= 128); @@ -1029,6 +781,10 @@ void BlockLinearUnswizzle3DPass::UnswizzleChunk( pc.origin[1] = params.origin[1]; pc.origin[2] = z_start; // Current chunk's Z start + pc.destination[0] = params.destination[0]; + pc.destination[1] = params.destination[1]; + pc.destination[2] = 0; // Shader writes to start of output buffer + pc.bytes_per_block_log2 = params.bytes_per_block_log2; pc.slice_size = params.slice_size; pc.block_size = params.block_size; diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.h b/src/video_core/renderer_vulkan/vk_compute_pass.h index 34aa762ff9..cb213dae7e 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.h +++ b/src/video_core/renderer_vulkan/vk_compute_pass.h @@ -25,7 +25,6 @@ struct SwizzleParameters; namespace Vulkan { -using VideoCommon::Accelerated::BlockLinearSwizzle2DParams; using VideoCommon::Accelerated::BlockLinearSwizzle3DParams; class Device; @@ -166,57 +165,6 @@ private: }; -class BlockLinearUnswizzleImage2DPass final : public ComputePass { -public: - explicit BlockLinearUnswizzleImage2DPass( - const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, - StagingBufferPool& staging_buffer_pool_, - ComputePassDescriptorQueue& compute_pass_descriptor_queue_); - ~BlockLinearUnswizzleImage2DPass(); - - void Unswizzle(Image& image, const StagingBufferRef& map, - std::span swizzles); - -private: - Scheduler& scheduler; - StagingBufferPool& staging_buffer_pool; - ComputePassDescriptorQueue& compute_pass_descriptor_queue; -}; - -class BlockLinearUnswizzleImage3DPass final : public ComputePass { -public: - explicit BlockLinearUnswizzleImage3DPass( - const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, - StagingBufferPool& staging_buffer_pool_, - ComputePassDescriptorQueue& compute_pass_descriptor_queue_); - ~BlockLinearUnswizzleImage3DPass(); - - void Unswizzle(Image& image, const StagingBufferRef& map, - std::span swizzles); - -private: - Scheduler& scheduler; - StagingBufferPool& staging_buffer_pool; - ComputePassDescriptorQueue& compute_pass_descriptor_queue; -}; - -class PitchUnswizzlePass final : public ComputePass { -public: - explicit PitchUnswizzlePass(const Device& device_, Scheduler& scheduler_, - DescriptorPool& descriptor_pool_, - StagingBufferPool& staging_buffer_pool_, - ComputePassDescriptorQueue& compute_pass_descriptor_queue_); - ~PitchUnswizzlePass(); - - void Unswizzle(Image& image, const StagingBufferRef& map, - std::span swizzles); - -private: - Scheduler& scheduler; - StagingBufferPool& staging_buffer_pool; - ComputePassDescriptorQueue& compute_pass_descriptor_queue; -}; - class MSAACopyPass final : public ComputePass { public: explicit MSAACopyPass(const Device& device_, Scheduler& scheduler_, diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 305cec454a..f789c989d8 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -6,9 +6,7 @@ #include #include -#include #include -#include #include #include #include @@ -222,50 +220,8 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { return allocator.CreateImage(image_ci); } -[[nodiscard]] VkFormat UnswizzleStorageFormat(u32 bytes_per_block) { - switch (bytes_per_block) { - case 1: - return VK_FORMAT_R8_UINT; - case 2: - return VK_FORMAT_R16_UINT; - case 4: - return VK_FORMAT_R32_UINT; - case 8: - return VK_FORMAT_R32G32_UINT; - case 16: - return VK_FORMAT_R32G32B32A32_UINT; - default: - ASSERT_MSG(false, "Invalid bytes_per_block={} for accelerated unswizzle", bytes_per_block); - return VK_FORMAT_R32_UINT; - } -} - -[[nodiscard]] bool IsUnswizzleStorageFormatSupported(const Device& device, u32 bytes_per_block) { - switch (bytes_per_block) { - case 1: - if (!device.IsStorageBuffer8BitAccessSupported()) { - return false; - } - break; - case 2: - if (!device.IsStorageBuffer16BitAccessSupported()) { - return false; - } - break; - case 4: - case 8: - case 16: - break; - default: - return false; - } - return device.IsFormatSupported(UnswizzleStorageFormat(bytes_per_block), - VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT, FormatType::Optimal); -} - [[nodiscard]] vk::ImageView MakeStorageView(const vk::Device& device, u32 level, VkImage image, - VkFormat format, - VkImageViewType view_type = VK_IMAGE_VIEW_TYPE_2D_ARRAY) { + VkFormat format) { static constexpr VkImageViewUsageCreateInfo storage_image_view_usage_create_info{ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO, .pNext = nullptr, @@ -276,7 +232,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { .pNext = &storage_image_view_usage_create_info, .flags = 0, .image = image, - .viewType = view_type, + .viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY, .format = format, .components{ .r = VK_COMPONENT_SWIZZLE_IDENTITY, @@ -959,56 +915,10 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched if (!device.IsKhrImageFormatListSupported()) { return; } - - bool enable_2d = Settings::values.gpu_compute_unswizzle_2d.GetValue(); - bool enable_3d = Settings::values.gpu_compute_unswizzle_3d.GetValue(); - bool enable_pitch = Settings::values.gpu_compute_unswizzle_pitch.GetValue(); - if (const char* const unswizzle_env = std::getenv("GPU_COMPUTE_UNSWIZZLE")) { - const std::string_view selection{unswizzle_env}; - if (selection == "0" || selection == "none") { - enable_2d = enable_3d = enable_pitch = false; - } else if (selection != "1" && selection != "all") { - enable_2d = selection.find("2d") != std::string_view::npos; - enable_3d = selection.find("3d") != std::string_view::npos; - enable_pitch = selection.find("pitch") != std::string_view::npos; - } - LOG_INFO(Render_Vulkan, "GPU_COMPUTE_UNSWIZZLE={} (2d={}, 3d={}, pitch={})", selection, - enable_2d, enable_3d, enable_pitch); - } - const bool unswizzle_caps_ok = device.IsStorageBuffer8BitAccessSupported() && - device.IsStorageBuffer16BitAccessSupported() && - device.IsFormatlessImageStoreSupported(); - if (!unswizzle_caps_ok) { - LOG_INFO(Render_Vulkan, - "GPU image unswizzle passes unsupported (8bit={}, 16bit={}, formatless={})", - device.IsStorageBuffer8BitAccessSupported(), - device.IsStorageBuffer16BitAccessSupported(), - device.IsFormatlessImageStoreSupported()); - } else { - if (enable_2d) { - bl_unswizzle_2d_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, - compute_pass_descriptor_queue); - } - if (enable_3d) { - bl_unswizzle_3d_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, - compute_pass_descriptor_queue); - } - if (enable_pitch) { - pitch_unswizzle_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, - compute_pass_descriptor_queue); - } - } for (size_t index_a = 0; index_a < VideoCore::Surface::MaxPixelFormat; index_a++) { const auto image_format = static_cast(index_a); if (IsPixelFormatASTC(image_format) && !device.IsOptimalAstcSupported()) { view_formats[index_a].push_back(VK_FORMAT_A8B8G8R8_UNORM_PACK32); - } else if (HasImageUnswizzlePasses() && !IsPixelFormatBCn(image_format) && - VideoCore::Surface::GetFormatType(image_format) == - VideoCore::Surface::SurfaceType::ColorTexture) { - const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(image_format); - if (IsUnswizzleStorageFormatSupported(device, bytes_per_block)) { - view_formats[index_a].push_back(UnswizzleStorageFormat(bytes_per_block)); - } } for (size_t index_b = 0; index_b < VideoCore::Surface::MaxPixelFormat; index_b++) { const auto view_format = static_cast(index_b); @@ -1701,14 +1611,6 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu flags |= VideoCommon::ImageFlagBits::Converted; flags |= VideoCommon::ImageFlagBits::CostlyLoad; } - if (runtime->HasUnswizzlePassFor(info.type) && !IsPixelFormatASTC(info.format) && - !IsPixelFormatBCn(info.format) && info.num_samples == 1 && - VideoCore::Surface::GetFormatType(info.format) == - VideoCore::Surface::SurfaceType::ColorTexture) { - if (IsUnswizzleStorageFormatSupported(runtime->device, BytesPerBlock(info.format))) { - flags |= VideoCommon::ImageFlagBits::AcceleratedUpload; - } - } if (runtime->device.HasDebuggingToolAttached()) { original_image.SetObjectNameEXT(VideoCommon::Name(*this).c_str()); } @@ -2074,20 +1976,13 @@ VkImageView Image::StorageImageView(s32 level) noexcept { if (!view) { auto format_info = MaxwellToVK::SurfaceFormat(runtime->device, FormatType::Optimal, true, info.format); - VkImageViewType view_type = VK_IMAGE_VIEW_TYPE_2D_ARRAY; if (WillUseAcceleratedAstcDecode(runtime->device, info)) { format_info.format = WillUseWidenedAstcFormat(runtime->device, info) ? VK_FORMAT_R32G32B32A32_SFLOAT : VK_FORMAT_A8B8G8R8_UNORM_PACK32; - } else if (True(flags & ImageFlagBits::AcceleratedUpload) && - !IsPixelFormatASTC(info.format) && !IsPixelFormatBCn(info.format)) { - format_info.format = UnswizzleStorageFormat(BytesPerBlock(info.format)); - view_type = info.type == ImageType::e3D ? VK_IMAGE_VIEW_TYPE_3D - : info.type == ImageType::Linear ? VK_IMAGE_VIEW_TYPE_2D - : VK_IMAGE_VIEW_TYPE_2D_ARRAY; } view = MakeStorageView(runtime->device.GetLogical(), level, *(this->*current_image), - format_info.format, view_type); + format_info.format); } return *view; } @@ -2621,36 +2516,16 @@ void TextureCacheRuntime::AccelerateImageUpload( return astc_decoder_pass->Assemble(image, map, swizzles); } - if (IsPixelFormatBCn(image.info.format)) { - if (Settings::values.gpu_unswizzle_enabled.GetValue() && bl3d_unswizzle_pass && - image.info.type == ImageType::e3D && image.info.resources.levels == 1 && - image.info.resources.layers == 1) { - return bl3d_unswizzle_pass->Unswizzle(image, map, swizzles, z_start, z_count); + if (!Settings::values.gpu_unswizzle_enabled.GetValue() || !bl3d_unswizzle_pass) { + if (IsPixelFormatBCn(image.info.format) && image.info.type == ImageType::e3D) { + ASSERT(false && "GPU unswizzle is disabled for BCn 3D texture"); } - ASSERT(false && "GPU unswizzle is disabled for BCn 3D texture"); + ASSERT(false); return; } - if (HasImageUnswizzlePasses()) { - switch (image.info.type) { - case ImageType::e2D: - if (bl_unswizzle_2d_pass) { - return bl_unswizzle_2d_pass->Unswizzle(image, map, swizzles); - } - break; - case ImageType::e3D: - if (bl_unswizzle_3d_pass) { - return bl_unswizzle_3d_pass->Unswizzle(image, map, swizzles); - } - break; - case ImageType::Linear: - if (pitch_unswizzle_pass) { - return pitch_unswizzle_pass->Unswizzle(image, map, swizzles); - } - break; - default: - break; - } + if (bl3d_unswizzle_pass && IsPixelFormatBCn(image.info.format) && image.info.type == ImageType::e3D && image.info.resources.levels == 1 && image.info.resources.layers == 1) { + return bl3d_unswizzle_pass->Unswizzle(image, map, swizzles, z_start, z_count); } ASSERT(false); diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 9e36561417..027143fdbc 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -130,30 +130,7 @@ public: std::optional astc_decoder_pass; std::optional bl3d_unswizzle_pass; - std::optional bl_unswizzle_2d_pass; - std::optional bl_unswizzle_3d_pass; - std::optional pitch_unswizzle_pass; std::optional msaa_copy_pass; - - /// True when the buffer->image unswizzle pass for this image type is active. - bool HasUnswizzlePassFor(VideoCommon::ImageType type) const noexcept { - switch (type) { - case VideoCommon::ImageType::e2D: - return bl_unswizzle_2d_pass.has_value(); - case VideoCommon::ImageType::e3D: - return bl_unswizzle_3d_pass.has_value(); - case VideoCommon::ImageType::Linear: - return pitch_unswizzle_pass.has_value(); - default: - return false; - } - } - - /// True when any buffer->image unswizzle pass is active on this device. - bool HasImageUnswizzlePasses() const noexcept { - return bl_unswizzle_2d_pass.has_value() || bl_unswizzle_3d_pass.has_value() || - pitch_unswizzle_pass.has_value(); - } const Settings::ResolutionScalingInfo& resolution; std::array, VideoCore::Surface::MaxPixelFormat> view_formats; diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 709fc49a6b..a944e7e297 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -400,16 +400,6 @@ FN_MAX_LIMIT_LIST return features.bit16_storage.uniformAndStorageBuffer16BitAccess; } - /// Returns true if the device supports reading 8-bit values from a storage buffer. - bool IsStorageBuffer8BitAccessSupported() const { - return features.bit8_storage.storageBuffer8BitAccess; - } - - /// Returns true if the device supports reading 16-bit values from a storage buffer. - bool IsStorageBuffer16BitAccessSupported() const { - return features.bit16_storage.storageBuffer16BitAccess; - } - /// Returns true if the device supports binding multisample images as storage images. bool IsStorageImageMultisampleSupported() const { return features.features.shaderStorageImageMultisample; @@ -440,11 +430,6 @@ FN_MAX_LIMIT_LIST return features.features.shaderStorageImageReadWithoutFormat; } - /// Returns true if formatless image store is supported. - bool IsFormatlessImageStoreSupported() const { - return features.features.shaderStorageImageWriteWithoutFormat; - } - /// Returns true if shader int64 is supported. bool IsShaderInt64Supported() const { return features.features.shaderInt64; From 21fa6b5805420b9e535726b637d2d04f92023ed5 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Mon, 6 Jul 2026 03:26:03 -0400 Subject: [PATCH 53/70] [vulkan] Added checks for 8/16bits on storage buffer access --- src/video_core/vulkan_common/vulkan_device.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index a944e7e297..42e5919974 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -400,6 +400,16 @@ FN_MAX_LIMIT_LIST return features.bit16_storage.uniformAndStorageBuffer16BitAccess; } + /// Returns true if the device supports reading 8-bit values from a storage buffer. + bool IsStorageBuffer8BitAccessSupported() const { + return features.bit8_storage.storageBuffer8BitAccess; + } + + /// Returns true if the device supports reading 16-bit values from a storage buffer. + bool IsStorageBuffer16BitAccessSupported() const { + return features.bit16_storage.storageBuffer16BitAccess; + } + /// Returns true if the device supports binding multisample images as storage images. bool IsStorageImageMultisampleSupported() const { return features.features.shaderStorageImageMultisample; From 83e1a676c5252bed6e35d03a1cd6691e778ce651 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Mon, 6 Jul 2026 03:28:53 -0400 Subject: [PATCH 54/70] [vulkan, qcom] Removed border color swizzle from QCOM drivers --- src/video_core/vulkan_common/vulkan_device.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 00b30637ad..093cb9d87d 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -505,6 +505,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken CustomBorderColor."); RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); + RemoveExtensionFeature(extensions.border_color_swizzle, features.border_color_swizzle, + VK_EXT_BORDER_COLOR_SWIZZLE_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader float controls."); RemoveExtension(extensions.shader_float_controls, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader atomic int64."); From bb639961823577742d4420691e03ea11a0101211 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Mon, 6 Jul 2026 15:57:53 -0400 Subject: [PATCH 55/70] [vulkan] Subgroups gating correction --- .../backend/spirv/emit_spirv.cpp | 2 +- .../backend/spirv/emit_spirv_warp.cpp | 42 ++++++++++++++++++- .../backend/spirv/spirv_emit_context.cpp | 9 ++-- src/shader_recompiler/profile.h | 7 ++++ .../renderer_vulkan/vk_pipeline_cache.cpp | 15 +++++++ src/video_core/vulkan_common/vulkan_device.h | 4 ++ 6 files changed, 73 insertions(+), 6 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index f73fb233cb..7ab2bd4adf 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -435,7 +435,7 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct } if ((info.uses_subgroup_vote || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles) && - profile.support_vote) { + profile.support_vote && profile.SupportsSubgroupStage(ctx.stage)) { ctx.AddCapability(spv::Capability::GroupNonUniformBallot); ctx.AddCapability(spv::Capability::GroupNonUniformShuffle); if (!profile.warp_size_potentially_larger_than_guest) { diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp index 77ff8c5731..242426ec08 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -10,7 +13,14 @@ Id SubgroupScope(EmitContext& ctx) { return ctx.Const(static_cast(spv::Scope::Subgroup)); } +bool StageSupportsSubgroups(EmitContext& ctx) { + return ctx.profile.SupportsSubgroupStage(ctx.stage); +} + Id GetThreadId(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.u32_zero_value; + } return ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id); } @@ -68,6 +78,9 @@ Id GetMaxThreadId(EmitContext& ctx, Id thread_id, Id clamp, Id segmentation_mask } Id SelectValue(EmitContext& ctx, Id in_range, Id value, Id src_thread_id) { + if (!StageSupportsSubgroups(ctx)) { + return value; + } return ctx.OpSelect( ctx.U32[1], in_range, ctx.OpGroupNonUniformShuffle(ctx.U32[1], SubgroupScope(ctx), value, src_thread_id), value); @@ -89,6 +102,9 @@ Id EmitLaneId(EmitContext& ctx) { } Id EmitVoteAll(EmitContext& ctx, Id pred) { + if (!StageSupportsSubgroups(ctx)) { + return pred; + } if (!ctx.profile.warp_size_potentially_larger_than_guest) { return ctx.OpGroupNonUniformAll(ctx.U1, SubgroupScope(ctx), pred); } @@ -102,6 +118,9 @@ Id EmitVoteAll(EmitContext& ctx, Id pred) { } Id EmitVoteAny(EmitContext& ctx, Id pred) { + if (!StageSupportsSubgroups(ctx)) { + return pred; + } if (!ctx.profile.warp_size_potentially_larger_than_guest) { return ctx.OpGroupNonUniformAny(ctx.U1, SubgroupScope(ctx), pred); } @@ -115,6 +134,9 @@ Id EmitVoteAny(EmitContext& ctx, Id pred) { } Id EmitVoteEqual(EmitContext& ctx, Id pred) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.true_value; + } if (!ctx.profile.warp_size_potentially_larger_than_guest) { return ctx.OpGroupNonUniformAllEqual(ctx.U1, SubgroupScope(ctx), pred); } @@ -129,6 +151,9 @@ Id EmitVoteEqual(EmitContext& ctx, Id pred) { } Id EmitSubgroupBallot(EmitContext& ctx, Id pred) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.OpSelect(ctx.U32[1], pred, ctx.Const(1u), ctx.u32_zero_value); + } const Id ballot{ctx.OpGroupNonUniformBallot(ctx.U32[4], SubgroupScope(ctx), pred)}; if (!ctx.profile.warp_size_potentially_larger_than_guest) { return ctx.OpCompositeExtract(ctx.U32[1], ballot, 0U); @@ -137,22 +162,37 @@ Id EmitSubgroupBallot(EmitContext& ctx, Id pred) { } Id EmitSubgroupEqMask(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.Const(1u); + } return LoadMask(ctx, ctx.subgroup_mask_eq); } Id EmitSubgroupLtMask(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.u32_zero_value; + } return LoadMask(ctx, ctx.subgroup_mask_lt); } Id EmitSubgroupLeMask(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.Const(1u); + } return LoadMask(ctx, ctx.subgroup_mask_le); } Id EmitSubgroupGtMask(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.u32_zero_value; + } return LoadMask(ctx, ctx.subgroup_mask_gt); } Id EmitSubgroupGeMask(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.Const(1u); + } return LoadMask(ctx, ctx.subgroup_mask_ge); } @@ -222,7 +262,7 @@ Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id Id EmitFSwizzleAdd(EmitContext& ctx, Id op_a, Id op_b, Id swizzle) { const Id three{ctx.Const(3U)}; - Id mask{ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id)}; + Id mask{GetThreadId(ctx)}; mask = ctx.OpBitwiseAnd(ctx.U32[1], mask, three); mask = ctx.OpShiftLeftLogical(ctx.U32[1], mask, ctx.Const(1U)); mask = ctx.OpShiftRightLogical(ctx.U32[1], swizzle, mask); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index b1bed263a6..eaa363a080 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -1447,7 +1447,7 @@ void EmitContext::DefineInputs(const IR::Program& program) { if (info.uses_is_helper_invocation) { is_helper_invocation = DefineInput(*this, U1, false, spv::BuiltIn::HelperInvocation); } - if (info.uses_subgroup_mask) { + if (info.uses_subgroup_mask && profile.SupportsSubgroupStage(stage)) { subgroup_mask_eq = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupEqMaskKHR); subgroup_mask_lt = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLtMaskKHR); subgroup_mask_le = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLeMaskKHR); @@ -1461,9 +1461,10 @@ void EmitContext::DefineInputs(const IR::Program& program) { Decorate(subgroup_mask_ge, spv::Decoration::Flat); } } - if (info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles || - (profile.warp_size_potentially_larger_than_guest && - (info.uses_subgroup_vote || info.uses_subgroup_mask))) { + if ((info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles || + (profile.warp_size_potentially_larger_than_guest && + (info.uses_subgroup_vote || info.uses_subgroup_mask))) && + profile.SupportsSubgroupStage(stage)) { AddCapability(spv::Capability::GroupNonUniform); subgroup_local_invocation_id = DefineInput(*this, U32[1], false, spv::BuiltIn::SubgroupLocalInvocationId); diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h index e95778a0de..0197a5f1d7 100644 --- a/src/shader_recompiler/profile.h +++ b/src/shader_recompiler/profile.h @@ -10,6 +10,8 @@ namespace Shader { +enum class Stage : u32; + struct Profile { u32 supported_spirv{0x00010000}; bool unified_descriptor_binding{}; @@ -32,6 +34,7 @@ struct Profile { bool support_fp64_signed_zero_nan_preserve{}; bool support_explicit_workgroup_layout{}; bool support_vote{}; + u32 supported_subgroup_stages{0x7F}; bool support_viewport_index_layer_non_geometry{}; bool support_viewport_mask{}; bool support_typeless_image_loads{}; @@ -95,6 +98,10 @@ struct Profile { u64 min_ssbo_alignment{}; u32 max_user_clip_distances{}; + + bool SupportsSubgroupStage(Stage stage) const { + return (supported_subgroup_stages & (1u << static_cast(stage))) != 0; + } }; } // namespace Shader diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 7fd87b17cd..669d736218 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -353,6 +353,20 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, serialization_thread(1, "VkPipelineSerialization") { const auto& float_control{device.FloatControlProperties()}; const VkDriverId driver_id{device.GetDriverID()}; + const VkShaderStageFlags subgroup_stages{device.GetSubgroupSupportedStages()}; + const auto subgroup_stage_bit{[subgroup_stages](VkShaderStageFlags flag, Shader::Stage stage) { + return (subgroup_stages & flag) != 0 ? (1u << static_cast(stage)) : 0u; + }}; + const u32 supported_subgroup_stages{ + subgroup_stage_bit(VK_SHADER_STAGE_VERTEX_BIT, Shader::Stage::VertexA) | + subgroup_stage_bit(VK_SHADER_STAGE_VERTEX_BIT, Shader::Stage::VertexB) | + subgroup_stage_bit(VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, + Shader::Stage::TessellationControl) | + subgroup_stage_bit(VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, + Shader::Stage::TessellationEval) | + subgroup_stage_bit(VK_SHADER_STAGE_GEOMETRY_BIT, Shader::Stage::Geometry) | + subgroup_stage_bit(VK_SHADER_STAGE_FRAGMENT_BIT, Shader::Stage::Fragment) | + subgroup_stage_bit(VK_SHADER_STAGE_COMPUTE_BIT, Shader::Stage::Compute)}; profile = Shader::Profile{ .supported_spirv = device.SupportedSpirvVersion(), .unified_descriptor_binding = true, @@ -382,6 +396,7 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, float_control.shaderSignedZeroInfNanPreserveFloat64 != VK_FALSE, .support_explicit_workgroup_layout = device.IsKhrWorkgroupMemoryExplicitLayoutSupported(), .support_vote = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_VOTE_BIT), + .supported_subgroup_stages = supported_subgroup_stages, .support_viewport_index_layer_non_geometry = device.IsExtShaderViewportIndexLayerSupported(), .support_viewport_mask = device.IsNvViewportArray2Supported(), diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 42e5919974..0938fb4850 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -430,6 +430,10 @@ FN_MAX_LIMIT_LIST return properties.subgroup_properties.supportedOperations & feature; } + VkShaderStageFlags GetSubgroupSupportedStages() const { + return properties.subgroup_properties.supportedStages; + } + /// Returns the maximum number of push descriptors. u32 MaxPushDescriptors() const { return properties.push_descriptor.maxPushDescriptors; From 06580cc6f9ca4bdf82a1b407be51078b3993fc3f Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Mon, 6 Jul 2026 19:55:19 -0400 Subject: [PATCH 56/70] [vulkan, qcom] Updated list of removed features on QCOM drivers --- src/video_core/vulkan_common/vulkan_device.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 093cb9d87d..bdd205d22a 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -502,11 +502,15 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR if (is_qualcomm) { LOG_WARNING(Render_Vulkan, "Qualcomm drivers require scaled vertex format emulation"); must_emulate_scaled_formats = true; - LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken CustomBorderColor."); + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken custom border color."); RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken border color swizzle."); RemoveExtensionFeature(extensions.border_color_swizzle, features.border_color_swizzle, VK_EXT_BORDER_COLOR_SWIZZLE_EXTENSION_NAME); + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken color write enable."); + RemoveExtensionFeature(extensions.color_write_enable, features.color_write_enable, + VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader float controls."); RemoveExtension(extensions.shader_float_controls, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader atomic int64."); From 0b17ea180498873487d3e1daf1f85f2302cc574d Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Mon, 6 Jul 2026 20:47:56 -0400 Subject: [PATCH 57/70] [TEST] Remove fp16 from qcom drivers --- src/video_core/vulkan_common/vulkan_device.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index bdd205d22a..33bedaca56 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -513,6 +513,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader float controls."); RemoveExtension(extensions.shader_float_controls, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader float16."); + RemoveExtension(extensions.shader_float16, VK_KHR_SHADER_FLOAT16_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader atomic int64."); RemoveExtensionFeature(extensions.shader_atomic_int64, features.shader_atomic_int64, VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME); From 0e1e0d5664d08150c44aa5fecc6b39877f38d923 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Mon, 6 Jul 2026 21:29:23 -0400 Subject: [PATCH 58/70] Fix build --- src/video_core/vulkan_common/vulkan_device.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 33bedaca56..8b6cdab89a 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -514,7 +514,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader float controls."); RemoveExtension(extensions.shader_float_controls, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader float16."); - RemoveExtension(extensions.shader_float16, VK_KHR_SHADER_FLOAT16_EXTENSION_NAME); + RemoveExtensionFeature(extensions.shader_float16_int8, features.shader_float16_int8, + VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader atomic int64."); RemoveExtensionFeature(extensions.shader_atomic_int64, features.shader_atomic_int64, VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME); From b54be98731cc8e5a21f284bac50a807164e5b440 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Tue, 7 Jul 2026 15:49:29 -0400 Subject: [PATCH 59/70] [TEST] Returned shaderFloat16 to QCOM --- src/video_core/vulkan_common/vulkan_device.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 8b6cdab89a..bdd205d22a 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -513,9 +513,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader float controls."); RemoveExtension(extensions.shader_float_controls, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); - LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader float16."); - RemoveExtensionFeature(extensions.shader_float16_int8, features.shader_float16_int8, - VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader atomic int64."); RemoveExtensionFeature(extensions.shader_atomic_int64, features.shader_atomic_int64, VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME); From b270bae970115b637402fe510f7464bd16f3034a Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Tue, 7 Jul 2026 16:02:35 -0400 Subject: [PATCH 60/70] [TEST] Late catch on accelerated image upload path --- src/video_core/renderer_vulkan/vk_compute_pass.cpp | 6 ++---- src/video_core/renderer_vulkan/vk_texture_cache.cpp | 10 ++++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index f361c34f44..edc0283d9a 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -746,10 +746,8 @@ void BlockLinearUnswizzle3DPass::Unswizzle( const u32 MAX_BATCH_SLICES = (std::min)(z_count, image.info.size.depth); - if (!image.has_compute_unswizzle_buffer) { - // Allocate exactly what this batch needs - image.AllocateComputeUnswizzleBuffer(MAX_BATCH_SLICES); - } + // Allocate or grow to cover this batch's slice count + image.AllocateComputeUnswizzleBuffer(MAX_BATCH_SLICES); ASSERT(swizzles.size() == 1); const auto& sw = swizzles[0]; diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index f789c989d8..b96a8f1eff 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -1635,9 +1635,6 @@ Image::Image(const VideoCommon::NullImageParams& params) : VideoCommon::ImageBas Image::~Image() = default; void Image::AllocateComputeUnswizzleBuffer(u32 max_slices) { - if (has_compute_unswizzle_buffer) - return; - using VideoCore::Surface::BytesPerBlock; const u32 block_bytes = BytesPerBlock(info.format); // 8 for BC1, 16 for BC6H @@ -1654,7 +1651,12 @@ void Image::AllocateComputeUnswizzleBuffer(u32 max_slices) { static_cast(blocks_y) * static_cast(blocks_z); - compute_unswizzle_buffer_size = block_count * block_bytes; + const VkDeviceSize required_size = block_count * block_bytes; + if (has_compute_unswizzle_buffer && required_size <= compute_unswizzle_buffer_size) { + return; + } + + compute_unswizzle_buffer_size = required_size; VkBufferCreateInfo ci{ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, From eb1060381aa962e91b36e8f3798e336f77d6e96b Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Tue, 7 Jul 2026 17:21:20 -0400 Subject: [PATCH 61/70] [TEST] Return of early compute shader change --- .../renderer_vulkan/vk_compute_pass.cpp | 323 ++++++++++++++++++ .../renderer_vulkan/vk_compute_pass.h | 51 +++ .../renderer_vulkan/vk_texture_cache.cpp | 73 +++- .../renderer_vulkan/vk_texture_cache.h | 3 + 4 files changed, 444 insertions(+), 6 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index edc0283d9a..979f119278 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -25,6 +25,9 @@ #include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h" #include "video_core/host_shaders/vulkan_uint8_comp_spv.h" #include "video_core/host_shaders/block_linear_unswizzle_3d_bcn_comp_spv.h" +#include "video_core/host_shaders/block_linear_unswizzle_2d_comp_spv.h" +#include "video_core/host_shaders/block_linear_unswizzle_3d_comp_spv.h" +#include "video_core/host_shaders/pitch_unswizzle_comp_spv.h" #include "video_core/renderer_vulkan/vk_compute_pass.h" #include "video_core/surface.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" @@ -233,6 +236,26 @@ struct QueriesPrefixScanPushConstants { struct ConditionalRenderingResolvePushConstants { u32 compare_to_zero; }; + +struct BlockLinear3DImagePushConstants { + alignas(16) std::array origin; + alignas(16) std::array destination; + u32 bytes_per_block_log2; + u32 slice_size; + u32 block_size; + u32 x_shift; + u32 block_height; + u32 block_height_mask; + u32 block_depth; + u32 block_depth_mask; +}; + +struct PitchUnswizzlePushConstants { + std::array origin; + std::array destination; + u32 bytes_per_block; + u32 pitch; +}; } // Anonymous namespace ComputePass::ComputePass(const Device& device_, Scheduler& scheduler, DescriptorPool& descriptor_pool, @@ -654,6 +677,306 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map, scheduler.Finish(); } +BlockLinearUnswizzleImage2DPass::BlockLinearUnswizzleImage2DPass( + const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, + StagingBufferPool& staging_buffer_pool_, + ComputePassDescriptorQueue& compute_pass_descriptor_queue_) + : ComputePass(device_, scheduler_, descriptor_pool_, ASTC_DESCRIPTOR_SET_BINDINGS, + ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY, ASTC_BANK_INFO, + COMPUTE_PUSH_CONSTANT_RANGE, + BLOCK_LINEAR_UNSWIZZLE_2D_COMP_SPV), + scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_}, + compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {} + +BlockLinearUnswizzleImage2DPass::~BlockLinearUnswizzleImage2DPass() = default; + +void BlockLinearUnswizzleImage2DPass::Unswizzle( + Image& image, const StagingBufferRef& map, + std::span swizzles) { + using namespace VideoCommon::Accelerated; + scheduler.RequestOutsideRenderPassOperationContext(); + const VkPipeline vk_pipeline = *pipeline; + const VkImageAspectFlags aspect_mask = image.AspectMask(); + const VkImage vk_image = image.Handle(); + const bool is_initialized = image.ExchangeInitialization(); + scheduler.Record([vk_pipeline, vk_image, aspect_mask, + is_initialized](vk::CommandBuffer cmdbuf) { + const VkImageMemoryBarrier image_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = static_cast(is_initialized ? VK_ACCESS_SHADER_WRITE_BIT + : VK_ACCESS_NONE), + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, + .oldLayout = is_initialized ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = vk_image, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + cmdbuf.PipelineBarrier(is_initialized ? vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER + : VkPipelineStageFlags(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT), + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, image_barrier); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, vk_pipeline); + }); + for (const VideoCommon::SwizzleParameters& swizzle : swizzles) { + const size_t input_offset = swizzle.buffer_offset + map.offset; + const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 32U); + const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 32U); + const u32 num_dispatches_z = image.info.resources.layers; + + compute_pass_descriptor_queue.Acquire(scheduler, 2); + compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset, + image.guest_size_bytes - swizzle.buffer_offset); + compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level)); + const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; + + const auto params = MakeBlockLinearSwizzle2DParams(swizzle, image.info); + scheduler.Record([this, num_dispatches_x, num_dispatches_y, num_dispatches_z, params, + descriptor_data](vk::CommandBuffer cmdbuf) { + const VkDescriptorSet set = descriptor_allocator.Commit(); + device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {}); + cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, params); + cmdbuf.Dispatch(num_dispatches_x, num_dispatches_y, num_dispatches_z); + }); + } + scheduler.Record([vk_image, aspect_mask](vk::CommandBuffer cmdbuf) { + const VkImageMemoryBarrier image_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = vk_image, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, image_barrier); + }); + scheduler.Finish(); +} + +BlockLinearUnswizzleImage3DPass::BlockLinearUnswizzleImage3DPass( + const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, + StagingBufferPool& staging_buffer_pool_, + ComputePassDescriptorQueue& compute_pass_descriptor_queue_) + : ComputePass(device_, scheduler_, descriptor_pool_, ASTC_DESCRIPTOR_SET_BINDINGS, + ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY, ASTC_BANK_INFO, + COMPUTE_PUSH_CONSTANT_RANGE, + BLOCK_LINEAR_UNSWIZZLE_3D_COMP_SPV), + scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_}, + compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {} + +BlockLinearUnswizzleImage3DPass::~BlockLinearUnswizzleImage3DPass() = default; + +void BlockLinearUnswizzleImage3DPass::Unswizzle( + Image& image, const StagingBufferRef& map, + std::span swizzles) { + using namespace VideoCommon::Accelerated; + scheduler.RequestOutsideRenderPassOperationContext(); + const VkPipeline vk_pipeline = *pipeline; + const VkImageAspectFlags aspect_mask = image.AspectMask(); + const VkImage vk_image = image.Handle(); + const bool is_initialized = image.ExchangeInitialization(); + scheduler.Record([vk_pipeline, vk_image, aspect_mask, + is_initialized](vk::CommandBuffer cmdbuf) { + const VkImageMemoryBarrier image_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = static_cast(is_initialized ? VK_ACCESS_SHADER_WRITE_BIT + : VK_ACCESS_NONE), + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, + .oldLayout = is_initialized ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = vk_image, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + cmdbuf.PipelineBarrier(is_initialized ? vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER + : VkPipelineStageFlags(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT), + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, image_barrier); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, vk_pipeline); + }); + for (const VideoCommon::SwizzleParameters& swizzle : swizzles) { + const size_t input_offset = swizzle.buffer_offset + map.offset; + const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 16U); + const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 8U); + const u32 num_dispatches_z = Common::DivCeil(swizzle.num_tiles.depth, 8U); + + compute_pass_descriptor_queue.Acquire(scheduler, 2); + compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset, + image.guest_size_bytes - swizzle.buffer_offset); + compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level)); + const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; + + const auto p = MakeBlockLinearSwizzle3DParams(swizzle, image.info); + const BlockLinear3DImagePushConstants params{ + .origin = p.origin, + .destination = p.destination, + .bytes_per_block_log2 = p.bytes_per_block_log2, + .slice_size = p.slice_size, + .block_size = p.block_size, + .x_shift = p.x_shift, + .block_height = p.block_height, + .block_height_mask = p.block_height_mask, + .block_depth = p.block_depth, + .block_depth_mask = p.block_depth_mask, + }; + scheduler.Record([this, num_dispatches_x, num_dispatches_y, num_dispatches_z, params, + descriptor_data](vk::CommandBuffer cmdbuf) { + const VkDescriptorSet set = descriptor_allocator.Commit(); + device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {}); + cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, params); + cmdbuf.Dispatch(num_dispatches_x, num_dispatches_y, num_dispatches_z); + }); + } + scheduler.Record([vk_image, aspect_mask](vk::CommandBuffer cmdbuf) { + const VkImageMemoryBarrier image_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = vk_image, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, image_barrier); + }); + scheduler.Finish(); +} + +PitchUnswizzlePass::PitchUnswizzlePass(const Device& device_, Scheduler& scheduler_, + DescriptorPool& descriptor_pool_, + StagingBufferPool& staging_buffer_pool_, + ComputePassDescriptorQueue& compute_pass_descriptor_queue_) + : ComputePass(device_, scheduler_, descriptor_pool_, ASTC_DESCRIPTOR_SET_BINDINGS, + ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY, ASTC_BANK_INFO, + COMPUTE_PUSH_CONSTANT_RANGE, + PITCH_UNSWIZZLE_COMP_SPV), + scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_}, + compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {} + +PitchUnswizzlePass::~PitchUnswizzlePass() = default; + +void PitchUnswizzlePass::Unswizzle(Image& image, const StagingBufferRef& map, + std::span swizzles) { + scheduler.RequestOutsideRenderPassOperationContext(); + const VkPipeline vk_pipeline = *pipeline; + const VkImageAspectFlags aspect_mask = image.AspectMask(); + const VkImage vk_image = image.Handle(); + const bool is_initialized = image.ExchangeInitialization(); + scheduler.Record([vk_pipeline, vk_image, aspect_mask, + is_initialized](vk::CommandBuffer cmdbuf) { + const VkImageMemoryBarrier image_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = static_cast(is_initialized ? VK_ACCESS_SHADER_WRITE_BIT + : VK_ACCESS_NONE), + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, + .oldLayout = is_initialized ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = vk_image, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + cmdbuf.PipelineBarrier(is_initialized ? vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER + : VkPipelineStageFlags(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT), + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, image_barrier); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, vk_pipeline); + }); + const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(image.info.format); + for (const VideoCommon::SwizzleParameters& swizzle : swizzles) { + const size_t input_offset = swizzle.buffer_offset + map.offset; + const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 32U); + const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 32U); + + compute_pass_descriptor_queue.Acquire(scheduler, 2); + compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset, + image.guest_size_bytes - swizzle.buffer_offset); + compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level)); + const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; + + const PitchUnswizzlePushConstants params{ + .origin = {0, 0}, + .destination = {0, 0}, + .bytes_per_block = bytes_per_block, + .pitch = image.info.pitch, + }; + scheduler.Record([this, num_dispatches_x, num_dispatches_y, params, + descriptor_data](vk::CommandBuffer cmdbuf) { + const VkDescriptorSet set = descriptor_allocator.Commit(); + device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {}); + cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, params); + cmdbuf.Dispatch(num_dispatches_x, num_dispatches_y, 1); + }); + } + scheduler.Record([vk_image, aspect_mask](vk::CommandBuffer cmdbuf) { + const VkImageMemoryBarrier image_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = vk_image, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, image_barrier); + }); + scheduler.Finish(); +} + constexpr u32 BL3D_BINDING_INPUT_BUFFER = 0; constexpr u32 BL3D_BINDING_OUTPUT_BUFFER = 1; diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.h b/src/video_core/renderer_vulkan/vk_compute_pass.h index cb213dae7e..dcef5ad014 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.h +++ b/src/video_core/renderer_vulkan/vk_compute_pass.h @@ -25,6 +25,7 @@ struct SwizzleParameters; namespace Vulkan { +using VideoCommon::Accelerated::BlockLinearSwizzle2DParams; using VideoCommon::Accelerated::BlockLinearSwizzle3DParams; class Device; @@ -164,6 +165,56 @@ private: ComputePassDescriptorQueue& compute_pass_descriptor_queue; }; +class BlockLinearUnswizzleImage2DPass final : public ComputePass { +public: + explicit BlockLinearUnswizzleImage2DPass(const Device& device_, Scheduler& scheduler_, + DescriptorPool& descriptor_pool_, + StagingBufferPool& staging_buffer_pool_, + ComputePassDescriptorQueue& compute_pass_descriptor_queue_); + ~BlockLinearUnswizzleImage2DPass(); + + void Unswizzle(Image& image, const StagingBufferRef& map, + std::span swizzles); + +private: + Scheduler& scheduler; + StagingBufferPool& staging_buffer_pool; + ComputePassDescriptorQueue& compute_pass_descriptor_queue; +}; + +class BlockLinearUnswizzleImage3DPass final : public ComputePass { +public: + explicit BlockLinearUnswizzleImage3DPass(const Device& device_, Scheduler& scheduler_, + DescriptorPool& descriptor_pool_, + StagingBufferPool& staging_buffer_pool_, + ComputePassDescriptorQueue& compute_pass_descriptor_queue_); + ~BlockLinearUnswizzleImage3DPass(); + + void Unswizzle(Image& image, const StagingBufferRef& map, + std::span swizzles); + +private: + Scheduler& scheduler; + StagingBufferPool& staging_buffer_pool; + ComputePassDescriptorQueue& compute_pass_descriptor_queue; +}; + +class PitchUnswizzlePass final : public ComputePass { +public: + explicit PitchUnswizzlePass(const Device& device_, Scheduler& scheduler_, + DescriptorPool& descriptor_pool_, + StagingBufferPool& staging_buffer_pool_, + ComputePassDescriptorQueue& compute_pass_descriptor_queue_); + ~PitchUnswizzlePass(); + + void Unswizzle(Image& image, const StagingBufferRef& map, + std::span swizzles); + +private: + Scheduler& scheduler; + StagingBufferPool& staging_buffer_pool; + ComputePassDescriptorQueue& compute_pass_descriptor_queue; +}; class MSAACopyPass final : public ComputePass { public: diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index b96a8f1eff..729b17d722 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -220,6 +220,29 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { return allocator.CreateImage(image_ci); } +[[nodiscard]] VkFormat UnswizzleStorageFormat(u32 bytes_per_block) { + switch (bytes_per_block) { + case 1: + return VK_FORMAT_R8_UINT; + case 2: + return VK_FORMAT_R16_UINT; + case 4: + return VK_FORMAT_R32_UINT; + case 8: + return VK_FORMAT_R32G32_UINT; + case 16: + return VK_FORMAT_R32G32B32A32_UINT; + default: + ASSERT_MSG(false, "Invalid bytes_per_block={} for accelerated unswizzle", bytes_per_block); + return VK_FORMAT_R32_UINT; + } +} + +[[nodiscard]] bool IsUnswizzleStorageFormatSupported(u32 bytes_per_block) { + return bytes_per_block == 1 || bytes_per_block == 2 || bytes_per_block == 4 || + bytes_per_block == 8 || bytes_per_block == 16; +} + [[nodiscard]] vk::ImageView MakeStorageView(const vk::Device& device, u32 level, VkImage image, VkFormat format) { static constexpr VkImageViewUsageCreateInfo storage_image_view_usage_create_info{ @@ -912,6 +935,12 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched if (device.IsStorageImageMultisampleSupported()) { msaa_copy_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, compute_pass_descriptor_queue); } + bl_unswizzle_2d_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, + compute_pass_descriptor_queue); + bl_unswizzle_3d_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, + compute_pass_descriptor_queue); + pitch_unswizzle_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, + compute_pass_descriptor_queue); if (!device.IsKhrImageFormatListSupported()) { return; } @@ -919,6 +948,14 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched const auto image_format = static_cast(index_a); if (IsPixelFormatASTC(image_format) && !device.IsOptimalAstcSupported()) { view_formats[index_a].push_back(VK_FORMAT_A8B8G8R8_UNORM_PACK32); + } else if (!IsPixelFormatASTC(image_format) && !IsPixelFormatBCn(image_format)) { + // Generic block-linear/pitch accelerated unswizzle needs an alternate UINT + // storage view registered up-front so MakeImage() enables mutable format + + // storage usage for these images (see Image::Image / storage_image_views below). + const u32 bpp = VideoCore::Surface::BytesPerBlock(image_format); + if (IsUnswizzleStorageFormatSupported(bpp)) { + view_formats[index_a].push_back(UnswizzleStorageFormat(bpp)); + } } for (size_t index_b = 0; index_b < VideoCore::Surface::MaxPixelFormat; index_b++) { const auto view_format = static_cast(index_b); @@ -1611,6 +1648,15 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu flags |= VideoCommon::ImageFlagBits::Converted; flags |= VideoCommon::ImageFlagBits::CostlyLoad; } + if (!IsPixelFormatASTC(info.format) && !IsPixelFormatBCn(info.format)) { + if (info.type == ImageType::e2D || info.type == ImageType::e3D) { + flags |= VideoCommon::ImageFlagBits::AcceleratedUpload; + } else if (info.type == ImageType::Linear) { + if (IsUnswizzleStorageFormatSupported(VideoCore::Surface::BytesPerBlock(info.format))) { + flags |= VideoCommon::ImageFlagBits::AcceleratedUpload; + } + } + } if (runtime->device.HasDebuggingToolAttached()) { original_image.SetObjectNameEXT(VideoCommon::Name(*this).c_str()); } @@ -1627,6 +1673,13 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu storage_image_views[level] = MakeStorageView(device, level, *original_image, storage_format); } + } else if (True(flags & VideoCommon::ImageFlagBits::AcceleratedUpload)) { + const auto& device = runtime->device.GetLogical(); + const VkFormat storage_format = + UnswizzleStorageFormat(VideoCore::Surface::BytesPerBlock(info.format)); + for (s32 level = 0; level < info.resources.levels; ++level) { + storage_image_views[level] = MakeStorageView(device, level, *original_image, storage_format); + } } } @@ -2518,16 +2571,24 @@ void TextureCacheRuntime::AccelerateImageUpload( return astc_decoder_pass->Assemble(image, map, swizzles); } - if (!Settings::values.gpu_unswizzle_enabled.GetValue() || !bl3d_unswizzle_pass) { - if (IsPixelFormatBCn(image.info.format) && image.info.type == ImageType::e3D) { - ASSERT(false && "GPU unswizzle is disabled for BCn 3D texture"); + if (IsPixelFormatBCn(image.info.format)) { + if (Settings::values.gpu_unswizzle_enabled.GetValue() && bl3d_unswizzle_pass && + image.info.type == ImageType::e3D && image.info.resources.levels == 1 && + image.info.resources.layers == 1) { + return bl3d_unswizzle_pass->Unswizzle(image, map, swizzles, z_start, z_count); } - ASSERT(false); + ASSERT(false && "GPU unswizzle is disabled for BCn 3D texture"); return; } - if (bl3d_unswizzle_pass && IsPixelFormatBCn(image.info.format) && image.info.type == ImageType::e3D && image.info.resources.levels == 1 && image.info.resources.layers == 1) { - return bl3d_unswizzle_pass->Unswizzle(image, map, swizzles, z_start, z_count); + if (image.info.type == ImageType::e2D) { + return bl_unswizzle_2d_pass->Unswizzle(image, map, swizzles); + } + if (image.info.type == ImageType::e3D) { + return bl_unswizzle_3d_pass->Unswizzle(image, map, swizzles); + } + if (image.info.type == ImageType::Linear) { + return pitch_unswizzle_pass->Unswizzle(image, map, swizzles); } ASSERT(false); diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 027143fdbc..a666775516 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -130,6 +130,9 @@ public: std::optional astc_decoder_pass; std::optional bl3d_unswizzle_pass; + std::optional bl_unswizzle_2d_pass; + std::optional bl_unswizzle_3d_pass; + std::optional pitch_unswizzle_pass; std::optional msaa_copy_pass; const Settings::ResolutionScalingInfo& resolution; std::array, VideoCore::Surface::MaxPixelFormat> view_formats; From 18a9d0762b33b81e87c9f66a64eee7c92ee7004d Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Tue, 7 Jul 2026 18:30:31 -0400 Subject: [PATCH 62/70] Revert "[TEST] Return of early compute shader change" --- .../renderer_vulkan/vk_compute_pass.cpp | 323 ------------------ .../renderer_vulkan/vk_compute_pass.h | 51 --- .../renderer_vulkan/vk_texture_cache.cpp | 73 +--- .../renderer_vulkan/vk_texture_cache.h | 3 - 4 files changed, 6 insertions(+), 444 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index 979f119278..edc0283d9a 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -25,9 +25,6 @@ #include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h" #include "video_core/host_shaders/vulkan_uint8_comp_spv.h" #include "video_core/host_shaders/block_linear_unswizzle_3d_bcn_comp_spv.h" -#include "video_core/host_shaders/block_linear_unswizzle_2d_comp_spv.h" -#include "video_core/host_shaders/block_linear_unswizzle_3d_comp_spv.h" -#include "video_core/host_shaders/pitch_unswizzle_comp_spv.h" #include "video_core/renderer_vulkan/vk_compute_pass.h" #include "video_core/surface.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" @@ -236,26 +233,6 @@ struct QueriesPrefixScanPushConstants { struct ConditionalRenderingResolvePushConstants { u32 compare_to_zero; }; - -struct BlockLinear3DImagePushConstants { - alignas(16) std::array origin; - alignas(16) std::array destination; - u32 bytes_per_block_log2; - u32 slice_size; - u32 block_size; - u32 x_shift; - u32 block_height; - u32 block_height_mask; - u32 block_depth; - u32 block_depth_mask; -}; - -struct PitchUnswizzlePushConstants { - std::array origin; - std::array destination; - u32 bytes_per_block; - u32 pitch; -}; } // Anonymous namespace ComputePass::ComputePass(const Device& device_, Scheduler& scheduler, DescriptorPool& descriptor_pool, @@ -677,306 +654,6 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map, scheduler.Finish(); } -BlockLinearUnswizzleImage2DPass::BlockLinearUnswizzleImage2DPass( - const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, - StagingBufferPool& staging_buffer_pool_, - ComputePassDescriptorQueue& compute_pass_descriptor_queue_) - : ComputePass(device_, scheduler_, descriptor_pool_, ASTC_DESCRIPTOR_SET_BINDINGS, - ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY, ASTC_BANK_INFO, - COMPUTE_PUSH_CONSTANT_RANGE, - BLOCK_LINEAR_UNSWIZZLE_2D_COMP_SPV), - scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_}, - compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {} - -BlockLinearUnswizzleImage2DPass::~BlockLinearUnswizzleImage2DPass() = default; - -void BlockLinearUnswizzleImage2DPass::Unswizzle( - Image& image, const StagingBufferRef& map, - std::span swizzles) { - using namespace VideoCommon::Accelerated; - scheduler.RequestOutsideRenderPassOperationContext(); - const VkPipeline vk_pipeline = *pipeline; - const VkImageAspectFlags aspect_mask = image.AspectMask(); - const VkImage vk_image = image.Handle(); - const bool is_initialized = image.ExchangeInitialization(); - scheduler.Record([vk_pipeline, vk_image, aspect_mask, - is_initialized](vk::CommandBuffer cmdbuf) { - const VkImageMemoryBarrier image_barrier{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .pNext = nullptr, - .srcAccessMask = static_cast(is_initialized ? VK_ACCESS_SHADER_WRITE_BIT - : VK_ACCESS_NONE), - .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, - .oldLayout = is_initialized ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED, - .newLayout = VK_IMAGE_LAYOUT_GENERAL, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = vk_image, - .subresourceRange{ - .aspectMask = aspect_mask, - .baseMipLevel = 0, - .levelCount = VK_REMAINING_MIP_LEVELS, - .baseArrayLayer = 0, - .layerCount = VK_REMAINING_ARRAY_LAYERS, - }, - }; - cmdbuf.PipelineBarrier(is_initialized ? vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER - : VkPipelineStageFlags(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT), - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, image_barrier); - cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, vk_pipeline); - }); - for (const VideoCommon::SwizzleParameters& swizzle : swizzles) { - const size_t input_offset = swizzle.buffer_offset + map.offset; - const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 32U); - const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 32U); - const u32 num_dispatches_z = image.info.resources.layers; - - compute_pass_descriptor_queue.Acquire(scheduler, 2); - compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset, - image.guest_size_bytes - swizzle.buffer_offset); - compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level)); - const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; - - const auto params = MakeBlockLinearSwizzle2DParams(swizzle, image.info); - scheduler.Record([this, num_dispatches_x, num_dispatches_y, num_dispatches_z, params, - descriptor_data](vk::CommandBuffer cmdbuf) { - const VkDescriptorSet set = descriptor_allocator.Commit(); - device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); - cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {}); - cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, params); - cmdbuf.Dispatch(num_dispatches_x, num_dispatches_y, num_dispatches_z); - }); - } - scheduler.Record([vk_image, aspect_mask](vk::CommandBuffer cmdbuf) { - const VkImageMemoryBarrier image_barrier{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .pNext = nullptr, - .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, - .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, - .oldLayout = VK_IMAGE_LAYOUT_GENERAL, - .newLayout = VK_IMAGE_LAYOUT_GENERAL, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = vk_image, - .subresourceRange{ - .aspectMask = aspect_mask, - .baseMipLevel = 0, - .levelCount = VK_REMAINING_MIP_LEVELS, - .baseArrayLayer = 0, - .layerCount = VK_REMAINING_ARRAY_LAYERS, - }, - }; - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, image_barrier); - }); - scheduler.Finish(); -} - -BlockLinearUnswizzleImage3DPass::BlockLinearUnswizzleImage3DPass( - const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, - StagingBufferPool& staging_buffer_pool_, - ComputePassDescriptorQueue& compute_pass_descriptor_queue_) - : ComputePass(device_, scheduler_, descriptor_pool_, ASTC_DESCRIPTOR_SET_BINDINGS, - ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY, ASTC_BANK_INFO, - COMPUTE_PUSH_CONSTANT_RANGE, - BLOCK_LINEAR_UNSWIZZLE_3D_COMP_SPV), - scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_}, - compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {} - -BlockLinearUnswizzleImage3DPass::~BlockLinearUnswizzleImage3DPass() = default; - -void BlockLinearUnswizzleImage3DPass::Unswizzle( - Image& image, const StagingBufferRef& map, - std::span swizzles) { - using namespace VideoCommon::Accelerated; - scheduler.RequestOutsideRenderPassOperationContext(); - const VkPipeline vk_pipeline = *pipeline; - const VkImageAspectFlags aspect_mask = image.AspectMask(); - const VkImage vk_image = image.Handle(); - const bool is_initialized = image.ExchangeInitialization(); - scheduler.Record([vk_pipeline, vk_image, aspect_mask, - is_initialized](vk::CommandBuffer cmdbuf) { - const VkImageMemoryBarrier image_barrier{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .pNext = nullptr, - .srcAccessMask = static_cast(is_initialized ? VK_ACCESS_SHADER_WRITE_BIT - : VK_ACCESS_NONE), - .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, - .oldLayout = is_initialized ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED, - .newLayout = VK_IMAGE_LAYOUT_GENERAL, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = vk_image, - .subresourceRange{ - .aspectMask = aspect_mask, - .baseMipLevel = 0, - .levelCount = VK_REMAINING_MIP_LEVELS, - .baseArrayLayer = 0, - .layerCount = VK_REMAINING_ARRAY_LAYERS, - }, - }; - cmdbuf.PipelineBarrier(is_initialized ? vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER - : VkPipelineStageFlags(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT), - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, image_barrier); - cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, vk_pipeline); - }); - for (const VideoCommon::SwizzleParameters& swizzle : swizzles) { - const size_t input_offset = swizzle.buffer_offset + map.offset; - const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 16U); - const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 8U); - const u32 num_dispatches_z = Common::DivCeil(swizzle.num_tiles.depth, 8U); - - compute_pass_descriptor_queue.Acquire(scheduler, 2); - compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset, - image.guest_size_bytes - swizzle.buffer_offset); - compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level)); - const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; - - const auto p = MakeBlockLinearSwizzle3DParams(swizzle, image.info); - const BlockLinear3DImagePushConstants params{ - .origin = p.origin, - .destination = p.destination, - .bytes_per_block_log2 = p.bytes_per_block_log2, - .slice_size = p.slice_size, - .block_size = p.block_size, - .x_shift = p.x_shift, - .block_height = p.block_height, - .block_height_mask = p.block_height_mask, - .block_depth = p.block_depth, - .block_depth_mask = p.block_depth_mask, - }; - scheduler.Record([this, num_dispatches_x, num_dispatches_y, num_dispatches_z, params, - descriptor_data](vk::CommandBuffer cmdbuf) { - const VkDescriptorSet set = descriptor_allocator.Commit(); - device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); - cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {}); - cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, params); - cmdbuf.Dispatch(num_dispatches_x, num_dispatches_y, num_dispatches_z); - }); - } - scheduler.Record([vk_image, aspect_mask](vk::CommandBuffer cmdbuf) { - const VkImageMemoryBarrier image_barrier{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .pNext = nullptr, - .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, - .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, - .oldLayout = VK_IMAGE_LAYOUT_GENERAL, - .newLayout = VK_IMAGE_LAYOUT_GENERAL, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = vk_image, - .subresourceRange{ - .aspectMask = aspect_mask, - .baseMipLevel = 0, - .levelCount = VK_REMAINING_MIP_LEVELS, - .baseArrayLayer = 0, - .layerCount = VK_REMAINING_ARRAY_LAYERS, - }, - }; - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, image_barrier); - }); - scheduler.Finish(); -} - -PitchUnswizzlePass::PitchUnswizzlePass(const Device& device_, Scheduler& scheduler_, - DescriptorPool& descriptor_pool_, - StagingBufferPool& staging_buffer_pool_, - ComputePassDescriptorQueue& compute_pass_descriptor_queue_) - : ComputePass(device_, scheduler_, descriptor_pool_, ASTC_DESCRIPTOR_SET_BINDINGS, - ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY, ASTC_BANK_INFO, - COMPUTE_PUSH_CONSTANT_RANGE, - PITCH_UNSWIZZLE_COMP_SPV), - scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_}, - compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {} - -PitchUnswizzlePass::~PitchUnswizzlePass() = default; - -void PitchUnswizzlePass::Unswizzle(Image& image, const StagingBufferRef& map, - std::span swizzles) { - scheduler.RequestOutsideRenderPassOperationContext(); - const VkPipeline vk_pipeline = *pipeline; - const VkImageAspectFlags aspect_mask = image.AspectMask(); - const VkImage vk_image = image.Handle(); - const bool is_initialized = image.ExchangeInitialization(); - scheduler.Record([vk_pipeline, vk_image, aspect_mask, - is_initialized](vk::CommandBuffer cmdbuf) { - const VkImageMemoryBarrier image_barrier{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .pNext = nullptr, - .srcAccessMask = static_cast(is_initialized ? VK_ACCESS_SHADER_WRITE_BIT - : VK_ACCESS_NONE), - .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, - .oldLayout = is_initialized ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED, - .newLayout = VK_IMAGE_LAYOUT_GENERAL, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = vk_image, - .subresourceRange{ - .aspectMask = aspect_mask, - .baseMipLevel = 0, - .levelCount = VK_REMAINING_MIP_LEVELS, - .baseArrayLayer = 0, - .layerCount = VK_REMAINING_ARRAY_LAYERS, - }, - }; - cmdbuf.PipelineBarrier(is_initialized ? vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER - : VkPipelineStageFlags(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT), - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, image_barrier); - cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, vk_pipeline); - }); - const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(image.info.format); - for (const VideoCommon::SwizzleParameters& swizzle : swizzles) { - const size_t input_offset = swizzle.buffer_offset + map.offset; - const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 32U); - const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 32U); - - compute_pass_descriptor_queue.Acquire(scheduler, 2); - compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset, - image.guest_size_bytes - swizzle.buffer_offset); - compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level)); - const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; - - const PitchUnswizzlePushConstants params{ - .origin = {0, 0}, - .destination = {0, 0}, - .bytes_per_block = bytes_per_block, - .pitch = image.info.pitch, - }; - scheduler.Record([this, num_dispatches_x, num_dispatches_y, params, - descriptor_data](vk::CommandBuffer cmdbuf) { - const VkDescriptorSet set = descriptor_allocator.Commit(); - device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); - cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {}); - cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, params); - cmdbuf.Dispatch(num_dispatches_x, num_dispatches_y, 1); - }); - } - scheduler.Record([vk_image, aspect_mask](vk::CommandBuffer cmdbuf) { - const VkImageMemoryBarrier image_barrier{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .pNext = nullptr, - .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, - .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, - .oldLayout = VK_IMAGE_LAYOUT_GENERAL, - .newLayout = VK_IMAGE_LAYOUT_GENERAL, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = vk_image, - .subresourceRange{ - .aspectMask = aspect_mask, - .baseMipLevel = 0, - .levelCount = VK_REMAINING_MIP_LEVELS, - .baseArrayLayer = 0, - .layerCount = VK_REMAINING_ARRAY_LAYERS, - }, - }; - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, image_barrier); - }); - scheduler.Finish(); -} - constexpr u32 BL3D_BINDING_INPUT_BUFFER = 0; constexpr u32 BL3D_BINDING_OUTPUT_BUFFER = 1; diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.h b/src/video_core/renderer_vulkan/vk_compute_pass.h index dcef5ad014..cb213dae7e 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.h +++ b/src/video_core/renderer_vulkan/vk_compute_pass.h @@ -25,7 +25,6 @@ struct SwizzleParameters; namespace Vulkan { -using VideoCommon::Accelerated::BlockLinearSwizzle2DParams; using VideoCommon::Accelerated::BlockLinearSwizzle3DParams; class Device; @@ -165,56 +164,6 @@ private: ComputePassDescriptorQueue& compute_pass_descriptor_queue; }; -class BlockLinearUnswizzleImage2DPass final : public ComputePass { -public: - explicit BlockLinearUnswizzleImage2DPass(const Device& device_, Scheduler& scheduler_, - DescriptorPool& descriptor_pool_, - StagingBufferPool& staging_buffer_pool_, - ComputePassDescriptorQueue& compute_pass_descriptor_queue_); - ~BlockLinearUnswizzleImage2DPass(); - - void Unswizzle(Image& image, const StagingBufferRef& map, - std::span swizzles); - -private: - Scheduler& scheduler; - StagingBufferPool& staging_buffer_pool; - ComputePassDescriptorQueue& compute_pass_descriptor_queue; -}; - -class BlockLinearUnswizzleImage3DPass final : public ComputePass { -public: - explicit BlockLinearUnswizzleImage3DPass(const Device& device_, Scheduler& scheduler_, - DescriptorPool& descriptor_pool_, - StagingBufferPool& staging_buffer_pool_, - ComputePassDescriptorQueue& compute_pass_descriptor_queue_); - ~BlockLinearUnswizzleImage3DPass(); - - void Unswizzle(Image& image, const StagingBufferRef& map, - std::span swizzles); - -private: - Scheduler& scheduler; - StagingBufferPool& staging_buffer_pool; - ComputePassDescriptorQueue& compute_pass_descriptor_queue; -}; - -class PitchUnswizzlePass final : public ComputePass { -public: - explicit PitchUnswizzlePass(const Device& device_, Scheduler& scheduler_, - DescriptorPool& descriptor_pool_, - StagingBufferPool& staging_buffer_pool_, - ComputePassDescriptorQueue& compute_pass_descriptor_queue_); - ~PitchUnswizzlePass(); - - void Unswizzle(Image& image, const StagingBufferRef& map, - std::span swizzles); - -private: - Scheduler& scheduler; - StagingBufferPool& staging_buffer_pool; - ComputePassDescriptorQueue& compute_pass_descriptor_queue; -}; class MSAACopyPass final : public ComputePass { public: diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 729b17d722..b96a8f1eff 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -220,29 +220,6 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { return allocator.CreateImage(image_ci); } -[[nodiscard]] VkFormat UnswizzleStorageFormat(u32 bytes_per_block) { - switch (bytes_per_block) { - case 1: - return VK_FORMAT_R8_UINT; - case 2: - return VK_FORMAT_R16_UINT; - case 4: - return VK_FORMAT_R32_UINT; - case 8: - return VK_FORMAT_R32G32_UINT; - case 16: - return VK_FORMAT_R32G32B32A32_UINT; - default: - ASSERT_MSG(false, "Invalid bytes_per_block={} for accelerated unswizzle", bytes_per_block); - return VK_FORMAT_R32_UINT; - } -} - -[[nodiscard]] bool IsUnswizzleStorageFormatSupported(u32 bytes_per_block) { - return bytes_per_block == 1 || bytes_per_block == 2 || bytes_per_block == 4 || - bytes_per_block == 8 || bytes_per_block == 16; -} - [[nodiscard]] vk::ImageView MakeStorageView(const vk::Device& device, u32 level, VkImage image, VkFormat format) { static constexpr VkImageViewUsageCreateInfo storage_image_view_usage_create_info{ @@ -935,12 +912,6 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched if (device.IsStorageImageMultisampleSupported()) { msaa_copy_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, compute_pass_descriptor_queue); } - bl_unswizzle_2d_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, - compute_pass_descriptor_queue); - bl_unswizzle_3d_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, - compute_pass_descriptor_queue); - pitch_unswizzle_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, - compute_pass_descriptor_queue); if (!device.IsKhrImageFormatListSupported()) { return; } @@ -948,14 +919,6 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched const auto image_format = static_cast(index_a); if (IsPixelFormatASTC(image_format) && !device.IsOptimalAstcSupported()) { view_formats[index_a].push_back(VK_FORMAT_A8B8G8R8_UNORM_PACK32); - } else if (!IsPixelFormatASTC(image_format) && !IsPixelFormatBCn(image_format)) { - // Generic block-linear/pitch accelerated unswizzle needs an alternate UINT - // storage view registered up-front so MakeImage() enables mutable format + - // storage usage for these images (see Image::Image / storage_image_views below). - const u32 bpp = VideoCore::Surface::BytesPerBlock(image_format); - if (IsUnswizzleStorageFormatSupported(bpp)) { - view_formats[index_a].push_back(UnswizzleStorageFormat(bpp)); - } } for (size_t index_b = 0; index_b < VideoCore::Surface::MaxPixelFormat; index_b++) { const auto view_format = static_cast(index_b); @@ -1648,15 +1611,6 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu flags |= VideoCommon::ImageFlagBits::Converted; flags |= VideoCommon::ImageFlagBits::CostlyLoad; } - if (!IsPixelFormatASTC(info.format) && !IsPixelFormatBCn(info.format)) { - if (info.type == ImageType::e2D || info.type == ImageType::e3D) { - flags |= VideoCommon::ImageFlagBits::AcceleratedUpload; - } else if (info.type == ImageType::Linear) { - if (IsUnswizzleStorageFormatSupported(VideoCore::Surface::BytesPerBlock(info.format))) { - flags |= VideoCommon::ImageFlagBits::AcceleratedUpload; - } - } - } if (runtime->device.HasDebuggingToolAttached()) { original_image.SetObjectNameEXT(VideoCommon::Name(*this).c_str()); } @@ -1673,13 +1627,6 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu storage_image_views[level] = MakeStorageView(device, level, *original_image, storage_format); } - } else if (True(flags & VideoCommon::ImageFlagBits::AcceleratedUpload)) { - const auto& device = runtime->device.GetLogical(); - const VkFormat storage_format = - UnswizzleStorageFormat(VideoCore::Surface::BytesPerBlock(info.format)); - for (s32 level = 0; level < info.resources.levels; ++level) { - storage_image_views[level] = MakeStorageView(device, level, *original_image, storage_format); - } } } @@ -2571,24 +2518,16 @@ void TextureCacheRuntime::AccelerateImageUpload( return astc_decoder_pass->Assemble(image, map, swizzles); } - if (IsPixelFormatBCn(image.info.format)) { - if (Settings::values.gpu_unswizzle_enabled.GetValue() && bl3d_unswizzle_pass && - image.info.type == ImageType::e3D && image.info.resources.levels == 1 && - image.info.resources.layers == 1) { - return bl3d_unswizzle_pass->Unswizzle(image, map, swizzles, z_start, z_count); + if (!Settings::values.gpu_unswizzle_enabled.GetValue() || !bl3d_unswizzle_pass) { + if (IsPixelFormatBCn(image.info.format) && image.info.type == ImageType::e3D) { + ASSERT(false && "GPU unswizzle is disabled for BCn 3D texture"); } - ASSERT(false && "GPU unswizzle is disabled for BCn 3D texture"); + ASSERT(false); return; } - if (image.info.type == ImageType::e2D) { - return bl_unswizzle_2d_pass->Unswizzle(image, map, swizzles); - } - if (image.info.type == ImageType::e3D) { - return bl_unswizzle_3d_pass->Unswizzle(image, map, swizzles); - } - if (image.info.type == ImageType::Linear) { - return pitch_unswizzle_pass->Unswizzle(image, map, swizzles); + if (bl3d_unswizzle_pass && IsPixelFormatBCn(image.info.format) && image.info.type == ImageType::e3D && image.info.resources.levels == 1 && image.info.resources.layers == 1) { + return bl3d_unswizzle_pass->Unswizzle(image, map, swizzles, z_start, z_count); } ASSERT(false); diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index a666775516..027143fdbc 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -130,9 +130,6 @@ public: std::optional astc_decoder_pass; std::optional bl3d_unswizzle_pass; - std::optional bl_unswizzle_2d_pass; - std::optional bl_unswizzle_3d_pass; - std::optional pitch_unswizzle_pass; std::optional msaa_copy_pass; const Settings::ResolutionScalingInfo& resolution; std::array, VideoCore::Surface::MaxPixelFormat> view_formats; From 6a3f885e466653cb8093f1558f1dfd1839e6b9b6 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Tue, 7 Jul 2026 18:41:45 -0400 Subject: [PATCH 63/70] [vulkan] 2D-Array (StorageView) mistmatch handling --- .../renderer_vulkan/vk_texture_cache.cpp | 25 +++++++++++++++---- .../renderer_vulkan/vk_texture_cache.h | 3 ++- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index b96a8f1eff..7f340a6ea8 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -2292,7 +2292,7 @@ VkImageView ImageView::StorageView(Shader::TextureType texture_type, if (uses_widened_astc_format) { info.format = VK_FORMAT_R32G32B32A32_SFLOAT; } - typeless_storage_view = MakeView(info.format, VK_IMAGE_ASPECT_COLOR_BIT); + typeless_storage_view = MakeView(info.format, VK_IMAGE_ASPECT_COLOR_BIT, texture_type); } return *typeless_storage_view; } @@ -2303,7 +2303,7 @@ VkImageView ImageView::StorageView(Shader::TextureType texture_type, auto& views{is_signed ? storage_views->signeds : storage_views->unsigneds}; auto& view{views[size_t(texture_type)]}; if (!view) - view = MakeView(Format(image_format), VK_IMAGE_ASPECT_COLOR_BIT); + view = MakeView(Format(image_format), VK_IMAGE_ASPECT_COLOR_BIT, texture_type); return *view; } return VK_NULL_HANDLE; @@ -2313,13 +2313,28 @@ bool ImageView::IsRescaled() const noexcept { return (*slot_images)[image_id].IsRescaled(); } -vk::ImageView ImageView::MakeView(VkFormat vk_format, VkImageAspectFlags aspect_mask) { +vk::ImageView ImageView::MakeView(VkFormat vk_format, VkImageAspectFlags aspect_mask, + std::optional texture_type) { + VkImageViewType view_type = ImageViewType(type); + VkImageSubresourceRange subresource_range = MakeSubresourceRange(aspect_mask, range); + if (texture_type) { + view_type = ImageViewType(*texture_type); + switch (view_type) { + case VK_IMAGE_VIEW_TYPE_1D_ARRAY: + case VK_IMAGE_VIEW_TYPE_2D_ARRAY: + case VK_IMAGE_VIEW_TYPE_CUBE_ARRAY: + break; + default: + subresource_range.layerCount = 1; + break; + } + } return device->GetLogical().CreateImageView({ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .pNext = nullptr, .flags = 0, .image = image_handle, - .viewType = ImageViewType(type), + .viewType = view_type, .format = vk_format, .components{ .r = VK_COMPONENT_SWIZZLE_IDENTITY, @@ -2327,7 +2342,7 @@ vk::ImageView ImageView::MakeView(VkFormat vk_format, VkImageAspectFlags aspect_ .b = VK_COMPONENT_SWIZZLE_IDENTITY, .a = VK_COMPONENT_SWIZZLE_IDENTITY, }, - .subresourceRange = MakeSubresourceRange(aspect_mask, range), + .subresourceRange = subresource_range, }); } diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 027143fdbc..d826d36bb8 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -370,7 +370,8 @@ private: std::array unsigneds; }; - [[nodiscard]] vk::ImageView MakeView(VkFormat vk_format, VkImageAspectFlags aspect_mask); + [[nodiscard]] vk::ImageView MakeView(VkFormat vk_format, VkImageAspectFlags aspect_mask, + std::optional texture_type = std::nullopt); const Device* device = nullptr; const SlotVector* slot_images = nullptr; From a1b7cd51b44d72b024294e95799d1edfe22ae17e Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Tue, 7 Jul 2026 19:01:42 -0400 Subject: [PATCH 64/70] [android] Initial implementation for MediaCodec --- externals/ffmpeg/CMakeLists.txt | 5 +++++ src/common/android/id_cache.cpp | 4 ++++ src/common/android/id_cache.h | 1 + src/video_core/host1x/ffmpeg.cpp | 13 +++++++++++++ 4 files changed, 23 insertions(+) diff --git a/externals/ffmpeg/CMakeLists.txt b/externals/ffmpeg/CMakeLists.txt index 7908e0a619..0baff02c84 100644 --- a/externals/ffmpeg/CMakeLists.txt +++ b/externals/ffmpeg/CMakeLists.txt @@ -59,6 +59,11 @@ endif() if (PLATFORM_PS4 OR PLATFORM_MANAGARM) # Doesn't support VA-API, don't go thru the embarrassment of trying to enable it list(APPEND FFmpeg_HWACCEL_FLAGS --disable-vaapi) +elseif (ANDROID) + list(APPEND FFmpeg_HWACCEL_FLAGS + --enable-mediacodec + --enable-jni + ) elseif (UNIX AND NOT DEFINED FFmpeg_IS_CROSS_COMPILING AND NOT ANDROID) find_package(PkgConfig REQUIRED) pkg_check_modules(LIBVA libva) diff --git a/src/common/android/id_cache.cpp b/src/common/android/id_cache.cpp index 76af1e0fb2..a56dbc199b 100644 --- a/src/common/android/id_cache.cpp +++ b/src/common/android/id_cache.cpp @@ -123,6 +123,10 @@ namespace Common::Android { return owned.env; } + JavaVM *GetJavaVM() { + return s_java_vm; + } + jclass GetNativeLibraryClass() { return s_native_library_class; } diff --git a/src/common/android/id_cache.h b/src/common/android/id_cache.h index 6b48f471b7..2ae2cdc4cb 100644 --- a/src/common/android/id_cache.h +++ b/src/common/android/id_cache.h @@ -12,6 +12,7 @@ namespace Common::Android { JNIEnv* GetEnvForThread(); +JavaVM* GetJavaVM(); /** * Starts a new thread to run JNI. Intended to be used when you must run JNI from a fiber. diff --git a/src/video_core/host1x/ffmpeg.cpp b/src/video_core/host1x/ffmpeg.cpp index 88df6695d8..36364ba3c2 100644 --- a/src/video_core/host1x/ffmpeg.cpp +++ b/src/video_core/host1x/ffmpeg.cpp @@ -19,8 +19,16 @@ extern "C" { #endif #include + +#if defined(__ANDROID__) +#include +#endif } +#if defined(__ANDROID__) +#include "common/android/id_cache.h" +#endif + namespace FFmpeg { namespace { @@ -290,6 +298,11 @@ bool DecodeApi::Initialize(Tegra::Host1x::NvdecCommon::VideoCodec codec) { // Enable GPU decoding if requested. if (Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::Gpu) { +#if defined(__ANDROID__) + static const int jni_vm_result = + av_jni_set_java_vm(Common::Android::GetJavaVM(), nullptr); + (void)jni_vm_result; +#endif m_hardware_context.emplace(); m_hardware_context->InitializeForDecoder(*m_decoder_context, *m_decoder); } From d1f41a91ec144c59eb51b2e17741283f56b26bb9 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Tue, 7 Jul 2026 20:08:10 -0400 Subject: [PATCH 65/70] [android] Verifying the actual flag required on hardware access --- src/android/app/src/main/jni/CMakeLists.txt | 2 +- src/android/app/src/main/jni/native.cpp | 11 ++ src/common/android/id_cache.cpp | 7 +- src/common/android/id_cache.h | 3 +- src/video_core/host1x/codecs/decoder.cpp | 78 +++++---- src/video_core/host1x/codecs/decoder.h | 8 + src/video_core/host1x/codecs/h264.cpp | 4 + src/video_core/host1x/codecs/vp8.cpp | 1 + src/video_core/host1x/codecs/vp9.cpp | 1 + src/video_core/host1x/ffmpeg.cpp | 168 +++++++++++++++++--- src/video_core/host1x/ffmpeg.h | 29 +++- src/video_core/host1x/nvdec.cpp | 1 + src/video_core/host1x/vic.cpp | 9 +- src/video_core/host1x/vic.h | 1 + 14 files changed, 252 insertions(+), 71 deletions(-) diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt index b6fe2d7722..7013691765 100644 --- a/src/android/app/src/main/jni/CMakeLists.txt +++ b/src/android/app/src/main/jni/CMakeLists.txt @@ -27,7 +27,7 @@ if (ARCHITECTURE_arm64) target_link_libraries(yuzu-android PRIVATE adrenotools) endif() -target_link_libraries(yuzu-android PRIVATE OpenSSL::SSL cpp-jwt::cpp-jwt) +target_link_libraries(yuzu-android PRIVATE ${FFmpeg_LIBRARIES} OpenSSL::SSL cpp-jwt::cpp-jwt) if (ENABLE_UPDATE_CHECKER) target_compile_definitions(yuzu-android PUBLIC ENABLE_UPDATE_CHECKER) endif() diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index abfc428919..b686dc9658 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -36,6 +36,10 @@ #include #include +extern "C" { +#include +} + #include "common/android/multiplayer/multiplayer.h" #include "common/android/android_common.h" #include "common/android/id_cache.h" @@ -680,6 +684,13 @@ const char* fallback_cpu_detection() { } // namespace +extern "C" { +jint InitFFmpegOnLoad(JavaVM* vm) { + av_jni_set_java_vm(vm, nullptr); + return 0; +} +} + extern "C" { void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceChanged(JNIEnv* env, jobject instance, diff --git a/src/common/android/id_cache.cpp b/src/common/android/id_cache.cpp index a56dbc199b..0fb358f6e4 100644 --- a/src/common/android/id_cache.cpp +++ b/src/common/android/id_cache.cpp @@ -123,10 +123,6 @@ namespace Common::Android { return owned.env; } - JavaVM *GetJavaVM() { - return s_java_vm; - } - jclass GetNativeLibraryClass() { return s_native_library_class; } @@ -431,8 +427,11 @@ namespace Common::Android { extern "C" { #endif + jint InitFFmpegOnLoad(JavaVM *vm); + jint JNI_OnLoad(JavaVM *vm, void *reserved) { s_java_vm = vm; + InitFFmpegOnLoad(vm); JNIEnv *env; if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION) != JNI_OK) diff --git a/src/common/android/id_cache.h b/src/common/android/id_cache.h index 2ae2cdc4cb..6e47acb746 100644 --- a/src/common/android/id_cache.h +++ b/src/common/android/id_cache.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once @@ -12,7 +12,6 @@ namespace Common::Android { JNIEnv* GetEnvForThread(); -JavaVM* GetJavaVM(); /** * Starts a new thread to run JNI. Intended to be used when you must run JNI from a fiber. diff --git a/src/video_core/host1x/codecs/decoder.cpp b/src/video_core/host1x/codecs/decoder.cpp index c75059db6f..86b859532d 100644 --- a/src/video_core/host1x/codecs/decoder.cpp +++ b/src/video_core/host1x/codecs/decoder.cpp @@ -20,58 +20,54 @@ Decoder::Decoder(Host1x::Host1x& host1x_, s32 id_, const Host1x::NvdecCommon::Nv Decoder::~Decoder() = default; +void Decoder::SetFrameDimensions(s32 width, s32 height) { + if (width <= 0 || height <= 0) { + frame_dimensions.reset(); + return; + } + frame_dimensions = FFmpeg::FrameDimensions{width, height}; +} + void Decoder::Decode() { if (!initialized) { return; } const auto packet_data = ComposeFrame(); - // Send assembled bitstream to decoder. - if (!decode_api.SendPacket(packet_data)) { - return; - } - - // Only receive/store visible frames. - if (vp9_hidden_frame) { - return; - } - - // Receive output frames from decoder. - auto frame = decode_api.ReceiveFrame(); - - if (!frame) { - return; - } - - if (IsInterlaced()) { - auto [luma_top, luma_bottom, chroma_top, chroma_bottom] = GetInterlacedOffsets(); - auto frame_copy = frame; - - if (!frame.get()) { - LOG_ERROR(HW_GPU, - "Nvdec {} failed to decode interlaced frame for top {:#X} bottom 0x{:X}", id, - luma_top, luma_bottom); - } - - if (UsingDecodeOrder()) { - host1x.frame_queue.PushDecodeOrder(id, luma_top, std::move(frame)); - host1x.frame_queue.PushDecodeOrder(id, luma_bottom, std::move(frame_copy)); - } else { - host1x.frame_queue.PushPresentOrder(id, luma_top, std::move(frame)); - host1x.frame_queue.PushPresentOrder(id, luma_bottom, std::move(frame_copy)); - } + FFmpeg::FrameOffsets offsets{}; + offsets.hidden = vp9_hidden_frame; + offsets.interlaced = IsInterlaced(); + if (offsets.interlaced) { + std::tie(offsets.luma, offsets.luma_bottom, std::ignore, std::ignore) = + GetInterlacedOffsets(); } else { - auto [luma_offset, chroma_offset] = GetProgressiveOffsets(); + std::tie(offsets.luma, std::ignore) = GetProgressiveOffsets(); + } - if (!frame.get()) { - LOG_ERROR(HW_GPU, "Nvdec {} failed to decode progressive frame for luma {:#X}", id, - luma_offset); - } + // Send assembled bitstream to decoder. + if (!decode_api.SendPacket(packet_data, offsets, GetFrameDimensions())) { + return; + } + auto push = [&](u64 luma, std::shared_ptr frame) { if (UsingDecodeOrder()) { - host1x.frame_queue.PushDecodeOrder(id, luma_offset, std::move(frame)); + host1x.frame_queue.PushDecodeOrder(id, luma, std::move(frame)); } else { - host1x.frame_queue.PushPresentOrder(id, luma_offset, std::move(frame)); + host1x.frame_queue.PushPresentOrder(id, luma, std::move(frame)); + } + }; + + while (auto result = decode_api.ReceiveFrame()) { + auto& [frame, frame_offsets] = *result; + if (frame_offsets.hidden || !frame) { + continue; + } + if (frame_offsets.interlaced) { + auto frame_copy = frame; + push(frame_offsets.luma, std::move(frame)); + push(frame_offsets.luma_bottom, std::move(frame_copy)); + } else { + push(frame_offsets.luma, std::move(frame)); } } } diff --git a/src/video_core/host1x/codecs/decoder.h b/src/video_core/host1x/codecs/decoder.h index 9fca89aa40..2cc6d8dce6 100644 --- a/src/video_core/host1x/codecs/decoder.h +++ b/src/video_core/host1x/codecs/decoder.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -46,12 +47,19 @@ protected: virtual std::tuple GetInterlacedOffsets() = 0; virtual bool IsInterlaced() = 0; + void SetFrameDimensions(s32 width, s32 height); + + std::optional GetFrameDimensions() const { + return frame_dimensions; + } + FFmpeg::DecodeApi decode_api; Host1x::Host1x& host1x; const Host1x::NvdecCommon::NvdecRegisters& regs; s32 id; bool initialized : 1 = false; bool vp9_hidden_frame : 1 = false; + std::optional frame_dimensions; }; } // namespace Tegra diff --git a/src/video_core/host1x/codecs/h264.cpp b/src/video_core/host1x/codecs/h264.cpp index f439ac3828..e3a6f5d182 100644 --- a/src/video_core/host1x/codecs/h264.cpp +++ b/src/video_core/host1x/codecs/h264.cpp @@ -52,6 +52,10 @@ bool H264::IsInterlaced() { std::span H264::ComposeFrame() { host1x.gmmu_manager.ReadBlock(regs.picture_info_offset.Address(), ¤t_context, sizeof(H264DecoderContext)); + const auto& params = current_context.h264_parameter_set; + SetFrameDimensions(static_cast(params.pic_width_in_mbs) * 16, + static_cast(params.frame_height_in_mbs) * 16); + const s64 frame_number = current_context.h264_parameter_set.frame_number.Value(); if (!is_first_frame && frame_number != 0) { frame_scratch.resize_destructive(current_context.stream_len); diff --git a/src/video_core/host1x/codecs/vp8.cpp b/src/video_core/host1x/codecs/vp8.cpp index 00fe6e4499..33d781816a 100644 --- a/src/video_core/host1x/codecs/vp8.cpp +++ b/src/video_core/host1x/codecs/vp8.cpp @@ -35,6 +35,7 @@ std::tuple VP8::GetInterlacedOffsets() { std::span VP8::ComposeFrame() { host1x.gmmu_manager.ReadBlock(regs.picture_info_offset.Address(), ¤t_context, sizeof(VP8PictureInfo)); + SetFrameDimensions(current_context.frame_width, current_context.frame_height); const bool is_key_frame = current_context.key_frame == 1u; const auto bitstream_size = size_t(current_context.vld_buffer_size); diff --git a/src/video_core/host1x/codecs/vp9.cpp b/src/video_core/host1x/codecs/vp9.cpp index 1085e08939..dcc6261e5c 100644 --- a/src/video_core/host1x/codecs/vp9.cpp +++ b/src/video_core/host1x/codecs/vp9.cpp @@ -841,6 +841,7 @@ std::span VP9::ComposeFrame() { { Vp9FrameContainer curr_frame = GetCurrentFrame(); current_frame_info = curr_frame.info; + SetFrameDimensions(current_frame_info.frame_size.width, current_frame_info.frame_size.height); bitstream = std::move(curr_frame.bit_stream); } // The uncompressed header routine sets PrevProb parameters needed for the compressed header diff --git a/src/video_core/host1x/ffmpeg.cpp b/src/video_core/host1x/ffmpeg.cpp index 36364ba3c2..b5635a2293 100644 --- a/src/video_core/host1x/ffmpeg.cpp +++ b/src/video_core/host1x/ffmpeg.cpp @@ -4,6 +4,10 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include + #include "common/assert.h" #include "common/logging.h" #include "common/scope_exit.h" @@ -19,16 +23,8 @@ extern "C" { #endif #include - -#if defined(__ANDROID__) -#include -#endif } -#if defined(__ANDROID__) -#include "common/android/id_cache.h" -#endif - namespace FFmpeg { namespace { @@ -92,6 +88,52 @@ std::string AVError(int errnum) { return errbuf; } +#if defined(__ANDROID__) +size_t FindNalStartCode(std::span data, size_t i) { + const size_t n = data.size(); + if (i + 3 < n && data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 0 && data[i + 3] == 1) { + return 4; + } + if (i + 2 < n && data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1) { + return 3; + } + return 0; +} + +std::vector ExtractH264ParameterSetExtradata(std::span packet) { + std::vector extradata; + const size_t size = packet.size(); + size_t i = 0; + while (i < size) { + const size_t sc = FindNalStartCode(packet, i); + if (sc == 0) { + ++i; + continue; + } + const size_t nal_start = i + sc; + if (nal_start >= size) { + break; + } + const u8 nal_type = packet[nal_start] & 0x1F; + + size_t j = nal_start + 1; + while (j < size && FindNalStartCode(packet, j) == 0) { + ++j; + } + + if (nal_type == 7 || nal_type == 8) { + constexpr u8 start[4] = {0, 0, 0, 1}; + extradata.insert(extradata.end(), start, start + sizeof(start)); + extradata.insert(extradata.end(), packet.begin() + nal_start, packet.begin() + j); + } else if (nal_type == 1 || nal_type == 5) { + break; + } + i = j; + } + return extradata; +} +#endif + } Packet::Packet(std::span data) { @@ -126,7 +168,24 @@ Decoder::Decoder(Tegra::Host1x::NvdecCommon::VideoCodec codec) { return AV_CODEC_ID_NONE; } }(); - m_codec = avcodec_find_decoder(av_codec); + +#if defined(__ANDROID__) + if (Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::Gpu) { + const char* mc_name = nullptr; + switch (av_codec) { + case AV_CODEC_ID_H264: mc_name = "h264_mediacodec"; break; + case AV_CODEC_ID_VP8: mc_name = "vp8_mediacodec"; break; + case AV_CODEC_ID_VP9: mc_name = "vp9_mediacodec"; break; + default: break; + } + if (mc_name) { + m_codec = avcodec_find_decoder_by_name(mc_name); + } + } +#endif + if (!m_codec) { + m_codec = avcodec_find_decoder(av_codec); + } } bool Decoder::SupportsDecodingOnDevice(AVPixelFormat* out_pix_fmt, AVHWDeviceType type) const { @@ -222,6 +281,8 @@ DecoderContext::DecoderContext(const Decoder& decoder) : m_decoder{decoder} { av_opt_set(m_codec_context->priv_data, "tune", "zerolatency", 0); m_codec_context->thread_count = 0; m_codec_context->thread_type &= ~FF_THREAD_FRAME; + m_codec_context->flags |= AV_CODEC_FLAG_LOW_DELAY; + m_codec_context->flags2 |= AV_CODEC_FLAG2_FAST; } DecoderContext::~DecoderContext() { @@ -235,7 +296,19 @@ void DecoderContext::InitializeHardwareDecoder(const HardwareContext& context, A m_codec_context->pix_fmt = hw_pix_fmt; } -bool DecoderContext::OpenContext(const Decoder& decoder) { +bool DecoderContext::OpenContext(const Decoder& decoder, std::span extradata) { + if (!extradata.empty()) { + av_freep(&m_codec_context->extradata); + m_codec_context->extradata = static_cast( + av_mallocz(extradata.size() + AV_INPUT_BUFFER_PADDING_SIZE)); + if (!m_codec_context->extradata) { + LOG_ERROR(HW_GPU, "Failed to allocate extradata"); + return false; + } + std::memcpy(m_codec_context->extradata, extradata.data(), extradata.size()); + m_codec_context->extradata_size = static_cast(extradata.size()); + } + if (const int ret = avcodec_open2(m_codec_context, decoder.GetCodec(), nullptr); ret < 0) { LOG_ERROR(HW_GPU, "avcodec_open2 error: {}", AVError(ret)); return false; @@ -289,6 +362,13 @@ void DecodeApi::Reset() { m_hardware_context.reset(); m_decoder_context.reset(); m_decoder.reset(); + m_opened = false; + m_defer_android_mediacodec_open = false; + m_needs_h264_extradata = false; + m_next_pts = 0; + while (!m_pending_offsets.empty()) { + m_pending_offsets.pop(); + } } bool DecodeApi::Initialize(Tegra::Host1x::NvdecCommon::VideoCodec codec) { @@ -298,32 +378,82 @@ bool DecodeApi::Initialize(Tegra::Host1x::NvdecCommon::VideoCodec codec) { // Enable GPU decoding if requested. if (Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::Gpu) { -#if defined(__ANDROID__) - static const int jni_vm_result = - av_jni_set_java_vm(Common::Android::GetJavaVM(), nullptr); - (void)jni_vm_result; -#endif m_hardware_context.emplace(); m_hardware_context->InitializeForDecoder(*m_decoder_context, *m_decoder); } +#if defined(__ANDROID__) + const std::string_view decoder_name = m_decoder->GetCodec() ? m_decoder->GetCodec()->name : ""; + + m_defer_android_mediacodec_open = decoder_name == "h264_mediacodec" || + decoder_name == "vp8_mediacodec" || + decoder_name == "vp9_mediacodec"; + + m_needs_h264_extradata = decoder_name == "h264_mediacodec"; + if (m_defer_android_mediacodec_open) { + return true; + } +#endif + // Open the decoder context. if (!m_decoder_context->OpenContext(*m_decoder)) { this->Reset(); return false; } + m_opened = true; return true; } -bool DecodeApi::SendPacket(std::span packet_data) { +bool DecodeApi::SendPacket(std::span packet_data, const FrameOffsets& offsets, + std::optional dimensions) { + if (!m_opened) { + std::vector extradata; +#if defined(__ANDROID__) + if (m_defer_android_mediacodec_open) { + if (!dimensions) { + return true; + } + + auto* ctx = m_decoder_context->GetCodecContext(); + ctx->width = dimensions->width; + ctx->height = dimensions->height; + ctx->coded_width = dimensions->width; + ctx->coded_height = dimensions->height; + } + + if (m_needs_h264_extradata) { + extradata = ExtractH264ParameterSetExtradata(packet_data); + if (extradata.empty()) { + return true; + } + } +#endif + if (!m_decoder_context->OpenContext(*m_decoder, extradata)) { + this->Reset(); + return false; + } + m_opened = true; + } + m_pending_offsets.push(offsets); FFmpeg::Packet packet(packet_data); + packet.GetPacket()->pts = m_next_pts; + packet.GetPacket()->dts = m_next_pts; + ++m_next_pts; return m_decoder_context->SendPacket(packet); } -std::shared_ptr DecodeApi::ReceiveFrame() { - // Receive raw frame from decoder. - return m_decoder_context->ReceiveFrame(); +std::optional DecodeApi::ReceiveFrame() { + auto frame = m_decoder_context->ReceiveFrame(); + if (!frame) { + return std::nullopt; + } + FrameOffsets offsets{}; + if (!m_pending_offsets.empty()) { + offsets = m_pending_offsets.front(); + m_pending_offsets.pop(); + } + return DecodedFrame{std::move(frame), offsets}; } } diff --git a/src/video_core/host1x/ffmpeg.h b/src/video_core/host1x/ffmpeg.h index fdb6908bb6..00cd3f5d4d 100644 --- a/src/video_core/host1x/ffmpeg.h +++ b/src/video_core/host1x/ffmpeg.h @@ -179,7 +179,7 @@ public: ~DecoderContext(); void InitializeHardwareDecoder(const HardwareContext& context, AVPixelFormat hw_pix_fmt); - bool OpenContext(const Decoder& decoder); + bool OpenContext(const Decoder& decoder, std::span extradata = {}); bool SendPacket(const Packet& packet); std::shared_ptr ReceiveFrame(); @@ -198,6 +198,18 @@ private: bool m_decode_order{}; }; +struct FrameOffsets { + bool interlaced{}; + bool hidden{}; + u64 luma{}; + u64 luma_bottom{}; +}; + +struct FrameDimensions { + s32 width{}; + s32 height{}; +}; + class DecodeApi { public: YUZU_NON_COPYABLE(DecodeApi); @@ -213,13 +225,24 @@ public: return m_decoder_context->UsingDecodeOrder(); } - bool SendPacket(std::span packet_data); - std::shared_ptr ReceiveFrame(); + bool SendPacket(std::span packet_data, const FrameOffsets& offsets, + std::optional dimensions = std::nullopt); + + struct DecodedFrame { + std::shared_ptr frame; + FrameOffsets offsets; + }; + std::optional ReceiveFrame(); private: std::optional m_decoder; std::optional m_decoder_context; std::optional m_hardware_context; + bool m_opened{}; + bool m_defer_android_mediacodec_open{}; + bool m_needs_h264_extradata{}; + s64 m_next_pts{}; + std::queue m_pending_offsets; }; } // namespace FFmpeg diff --git a/src/video_core/host1x/nvdec.cpp b/src/video_core/host1x/nvdec.cpp index f2e5c358d8..5dbc6a417e 100644 --- a/src/video_core/host1x/nvdec.cpp +++ b/src/video_core/host1x/nvdec.cpp @@ -31,6 +31,7 @@ Nvdec::Nvdec(Host1x& host1x_, s32 id_, u32 syncpt) Nvdec::~Nvdec() { LOG_INFO(HW_GPU, "Destroying nvdec {}", id); + host1x.frame_queue.Close(id); } void Nvdec::ProcessMethod(u32 method, u32 argument) { diff --git a/src/video_core/host1x/vic.cpp b/src/video_core/host1x/vic.cpp index f7830a3a45..6514508947 100644 --- a/src/video_core/host1x/vic.cpp +++ b/src/video_core/host1x/vic.cpp @@ -118,6 +118,7 @@ void Vic::Execute() noexcept { output_surface.resize(output_width * output_height); if (Settings::values.nvdec_emulation.GetValue() != Settings::NvdecEmulation::Off) { + bool decoded_frame = false; for (size_t i = 0; i < config.slot_structs.size(); i++) { if (auto& slot_config = config.slot_structs[i]; slot_config.config.slot_enable) { auto const luma_offset = regs.surfaces[i][SurfaceIndex::Current].luma.Address(); @@ -136,11 +137,17 @@ void Vic::Execute() noexcept { break; } Blend(config, slot_config, config.output_surface_config.out_pixel_format); + decoded_frame = true; } else { - LOG_ERROR(HW_GPU, "Vic {} failed to get frame with offset {:#X}", id, luma_offset); + LOG_TRACE(HW_GPU, "Vic {} failed to get frame with offset {:#X}", id, luma_offset); } } } + if (decoded_frame) { + has_decoded_frame = true; + } else if (!has_decoded_frame) { + return; + } } else { // Fill the frame with black, as otherwise they can have random data and be very glitchy. std::fill(output_surface.begin(), output_surface.end(), Pixel{}); diff --git a/src/video_core/host1x/vic.h b/src/video_core/host1x/vic.h index 23f3f66c18..18eff3382f 100644 --- a/src/video_core/host1x/vic.h +++ b/src/video_core/host1x/vic.h @@ -628,6 +628,7 @@ private: s32 id; s32 nvdec_id{-1}; + bool has_decoded_frame{}; u32 syncpoint; }; From 6df0089950b289fd3b30da410c792b5c818bc2e0 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Tue, 7 Jul 2026 20:13:56 -0400 Subject: [PATCH 66/70] [vic] Quick change on VP9 offsets + ifdef encoding flags for Android --- src/video_core/host1x/codecs/decoder.cpp | 2 +- src/video_core/host1x/ffmpeg.cpp | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/video_core/host1x/codecs/decoder.cpp b/src/video_core/host1x/codecs/decoder.cpp index 86b859532d..1d6d3e8af6 100644 --- a/src/video_core/host1x/codecs/decoder.cpp +++ b/src/video_core/host1x/codecs/decoder.cpp @@ -59,7 +59,7 @@ void Decoder::Decode() { while (auto result = decode_api.ReceiveFrame()) { auto& [frame, frame_offsets] = *result; - if (frame_offsets.hidden || !frame) { + if (!frame) { continue; } if (frame_offsets.interlaced) { diff --git a/src/video_core/host1x/ffmpeg.cpp b/src/video_core/host1x/ffmpeg.cpp index b5635a2293..90b1ad7a9f 100644 --- a/src/video_core/host1x/ffmpeg.cpp +++ b/src/video_core/host1x/ffmpeg.cpp @@ -281,8 +281,10 @@ DecoderContext::DecoderContext(const Decoder& decoder) : m_decoder{decoder} { av_opt_set(m_codec_context->priv_data, "tune", "zerolatency", 0); m_codec_context->thread_count = 0; m_codec_context->thread_type &= ~FF_THREAD_FRAME; +#if defined(__ANDROID__) m_codec_context->flags |= AV_CODEC_FLAG_LOW_DELAY; m_codec_context->flags2 |= AV_CODEC_FLAG2_FAST; +#endif } DecoderContext::~DecoderContext() { @@ -435,7 +437,9 @@ bool DecodeApi::SendPacket(std::span packet_data, const FrameOffsets& } m_opened = true; } - m_pending_offsets.push(offsets); + if (!offsets.hidden) { + m_pending_offsets.push(offsets); + } FFmpeg::Packet packet(packet_data); packet.GetPacket()->pts = m_next_pts; packet.GetPacket()->dts = m_next_pts; From 059d902ca30ad5a88e9bf4e78a9c1d0ebbdad7ec Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Tue, 7 Jul 2026 20:39:28 -0400 Subject: [PATCH 67/70] [android] Fix on missing transfer frames --- src/video_core/host1x/ffmpeg.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/video_core/host1x/ffmpeg.cpp b/src/video_core/host1x/ffmpeg.cpp index 90b1ad7a9f..a7ffac293d 100644 --- a/src/video_core/host1x/ffmpeg.cpp +++ b/src/video_core/host1x/ffmpeg.cpp @@ -378,19 +378,23 @@ bool DecodeApi::Initialize(Tegra::Host1x::NvdecCommon::VideoCodec codec) { m_decoder.emplace(codec); m_decoder_context.emplace(*m_decoder); + bool is_mediacodec = false; +#if defined(__ANDROID__) + const std::string_view decoder_name = m_decoder->GetCodec() ? m_decoder->GetCodec()->name : ""; + is_mediacodec = decoder_name == "h264_mediacodec" || + decoder_name == "vp8_mediacodec" || + decoder_name == "vp9_mediacodec"; +#endif + // Enable GPU decoding if requested. - if (Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::Gpu) { + if (!is_mediacodec && + Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::Gpu) { m_hardware_context.emplace(); m_hardware_context->InitializeForDecoder(*m_decoder_context, *m_decoder); } #if defined(__ANDROID__) - const std::string_view decoder_name = m_decoder->GetCodec() ? m_decoder->GetCodec()->name : ""; - - m_defer_android_mediacodec_open = decoder_name == "h264_mediacodec" || - decoder_name == "vp8_mediacodec" || - decoder_name == "vp9_mediacodec"; - + m_defer_android_mediacodec_open = is_mediacodec; m_needs_h264_extradata = decoder_name == "h264_mediacodec"; if (m_defer_android_mediacodec_open) { return true; From c57fdebdf0651215c9cc4850b792bed2abdb43e4 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Tue, 7 Jul 2026 22:27:19 -0400 Subject: [PATCH 68/70] [vic] Update logging for codec used --- src/video_core/host1x/ffmpeg.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/video_core/host1x/ffmpeg.cpp b/src/video_core/host1x/ffmpeg.cpp index a7ffac293d..783259ccfc 100644 --- a/src/video_core/host1x/ffmpeg.cpp +++ b/src/video_core/host1x/ffmpeg.cpp @@ -316,9 +316,7 @@ bool DecoderContext::OpenContext(const Decoder& decoder, std::span ext return false; } - if (!m_codec_context->hw_device_ctx) { - LOG_INFO(HW_GPU, "Using FFmpeg CPU decoder"); - } + LOG_INFO(HW_GPU, "Using decoder {}", decoder.GetCodec()->name); return true; } From 940cc8caef3fe4fd690e2b410c43c2fddacf344c Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Tue, 7 Jul 2026 23:21:41 -0400 Subject: [PATCH 69/70] [vulkan] Add EAC formats on the query format table --- src/video_core/vulkan_common/vulkan_device.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index bdd205d22a..44acda5dcf 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -300,6 +300,10 @@ ankerl::unordered_dense::map GetFormatProperties(v VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK, VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK, VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK, + VK_FORMAT_EAC_R11_UNORM_BLOCK, + VK_FORMAT_EAC_R11_SNORM_BLOCK, + VK_FORMAT_EAC_R11G11_UNORM_BLOCK, + VK_FORMAT_EAC_R11G11_SNORM_BLOCK, }; ankerl::unordered_dense::map format_properties; for (const auto format : formats) { From a8c6189d531adc38e8bbd461d0bfedcb06bc3127 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Tue, 7 Jul 2026 23:26:53 -0400 Subject: [PATCH 70/70] [vulkan] Restored Presync + ConditionalRendering workaround for QCOM drivers --- src/video_core/renderer_vulkan/vk_query_cache.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp index 935f8b79fb..e396bb8685 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp @@ -236,7 +236,8 @@ public: } PauseCounter(); const auto driver_id = device.GetDriverID(); - if (driver_id == VK_DRIVER_ID_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP) { + if (driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || + driver_id == VK_DRIVER_ID_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP) { pending_sync.clear(); sync_values_stash.clear(); return; @@ -1520,7 +1521,7 @@ bool QueryCacheRuntime::HostConditionalRenderingCompareValues(VideoCommon::Looku auto driver_id = impl->device.GetDriverID(); const bool is_gpu_high = Settings::IsGPULevelHigh(); - if ((!is_gpu_high && driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) || driver_id == VK_DRIVER_ID_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP) { + if ((!is_gpu_high && driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) || driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || driver_id == VK_DRIVER_ID_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP) { EndHostConditionalRendering(); return true; }