From b24d8b39122bdcd3d25058b740fc2b4cb09e2ad9 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 17 Jul 2026 01:13:15 -0400 Subject: [PATCH 01/26] [TEST] Hunting down recursive mutex 1 (cherry picked from commit ab92e5fa52b04a3edb77a304d43bd7dd887e70c3) --- .../renderer_vulkan/vk_query_cache.cpp | 5 ++--- src/video_core/renderer_vulkan/vk_rasterizer.cpp | 16 +++++++++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp index 709f65dc25..8d352f3b73 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp @@ -236,8 +236,7 @@ public: } PauseCounter(); const auto driver_id = device.GetDriverID(); - if (driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || - driver_id == VK_DRIVER_ID_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP) { + if (driver_id == VK_DRIVER_ID_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP) { pending_sync.clear(); sync_values_stash.clear(); return; @@ -1521,7 +1520,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_QUALCOMM_PROPRIETARY || 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_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP) { EndHostConditionalRendering(); return true; } diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 720f3b868c..77a4e3d9a7 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -241,11 +241,17 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) { if (!pipeline) { return; } - std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; - // update engine as channel may be different. - pipeline->SetEngine(maxwell3d, gpu_memory); - if (!pipeline->Configure(is_indexed)) - return; + { + // Only resource binding/upload needs the cache locks held. The query-cache calls and the + // deferred draw recording below acquire their own locks, so keeping them outside this scope + // avoids re-entrant locking of buffer_cache.mutex and shrinks the serialized region. + std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; + // update engine as channel may be different. + pipeline->SetEngine(maxwell3d, gpu_memory); + if (!pipeline->Configure(is_indexed)) { + return; + } + } UpdateDynamicStates(); From a8f4fdd20f51883bc76b66ea2fd48f98a106d031 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 17 Jul 2026 01:55:28 -0400 Subject: [PATCH 02/26] [TEST] Hunting down recursive mutex 2 (cherry picked from commit f53235779358969c81a5d106eae470552ee8541e) --- src/video_core/renderer_vulkan/vk_query_cache.cpp | 5 +++-- src/video_core/renderer_vulkan/vk_rasterizer.cpp | 4 ---- src/video_core/texture_cache/texture_cache_base.h | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp index 8d352f3b73..709f65dc25 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; } diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 77a4e3d9a7..2093617c69 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -242,11 +242,7 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) { return; } { - // Only resource binding/upload needs the cache locks held. The query-cache calls and the - // deferred draw recording below acquire their own locks, so keeping them outside this scope - // avoids re-entrant locking of buffer_cache.mutex and shrinks the serialized region. std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; - // update engine as channel may be different. pipeline->SetEngine(maxwell3d, gpu_memory); if (!pipeline->Configure(is_indexed)) { return; diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index fbc2bb4cf7..abad222eba 100644 --- a/src/video_core/texture_cache/texture_cache_base.h +++ b/src/video_core/texture_cache/texture_cache_base.h @@ -257,7 +257,7 @@ public: /// Prepare an image to be used void PrepareImage(ImageId image_id, bool is_modification, bool invalidate); - std::recursive_mutex mutex; + std::mutex mutex; private: /// Iterate over all page indices in a range From 65e45f0a9aa552c4b9032b22e841eda7ee8b71cf Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 17 Jul 2026 02:15:10 -0400 Subject: [PATCH 03/26] [TEST] Hunting down recursive mutex 3 (cherry picked from commit 933f79af95a99213b1954bf215fc10f487f48549) --- src/video_core/buffer_cache/buffer_cache_base.h | 2 +- src/video_core/renderer_opengl/gl_rasterizer.cpp | 2 +- src/video_core/renderer_vulkan/vk_rasterizer.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index 14ab3e6ebc..3cddde6cf5 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -321,7 +321,7 @@ public: } while (channel_state->has_deleted_buffers); } - std::recursive_mutex mutex; + std::mutex mutex; Runtime& runtime; bool any_buffer_uploaded = false; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 1d77d28c46..c0bb388930 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -716,7 +716,7 @@ void RasterizerOpenGL::AccelerateInlineToMemory(GPUVAddr address, size_t copy_si } gpu_memory->WriteBlockUnsafe(address, memory.data(), copy_size); { - std::unique_lock lock{buffer_cache.mutex}; + std::scoped_lock lock{buffer_cache.mutex}; if (!buffer_cache.InlineMemory(*cpu_addr, copy_size, memory)) { buffer_cache.WriteMemory(*cpu_addr, copy_size); } diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 2093617c69..ec9e4208b5 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -925,7 +925,7 @@ void RasterizerVulkan::AccelerateInlineToMemory(GPUVAddr address, size_t copy_si } gpu_memory->WriteBlockUnsafe(address, memory.data(), copy_size); { - std::unique_lock lock{buffer_cache.mutex}; + std::scoped_lock lock{buffer_cache.mutex}; if (!buffer_cache.InlineMemory(*cpu_addr, copy_size, memory)) { buffer_cache.WriteMemory(*cpu_addr, copy_size); } From aec1658697796a01ce8541a86f9f2d383bde815d Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 17 Jul 2026 02:32:14 -0400 Subject: [PATCH 04/26] [TEST] Hunting down recursive mutex 4 (cherry picked from commit 84490a7d6ff6db85530a59382ee43bb341ce4029) --- .../yuzu_emu/features/settings/model/view/SettingsItem.kt | 2 +- src/android/app/src/main/jni/android_settings.h | 2 +- src/video_core/buffer_cache/buffer_cache_base.h | 2 +- src/video_core/renderer_opengl/gl_rasterizer.cpp | 2 +- src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | 8 +++----- src/video_core/renderer_vulkan/vk_rasterizer.cpp | 2 +- 6 files changed, 8 insertions(+), 10 deletions(-) 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 7c6063988f..644c2f751f 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 @@ -594,7 +594,7 @@ abstract class SettingsItem( IntSetting.ANDROID_PIPELINE_WORKERS, titleId = R.string.pipeline_worker_cores, descriptionId = R.string.pipeline_worker_cores_description, - min = 4, + min = 1, max = 8, units = "cores" ) diff --git a/src/android/app/src/main/jni/android_settings.h b/src/android/app/src/main/jni/android_settings.h index 304ff62f07..3aba766a1b 100644 --- a/src/android/app/src/main/jni/android_settings.h +++ b/src/android/app/src/main/jni/android_settings.h @@ -147,7 +147,7 @@ namespace AndroidSettings { &show_performance_overlay}; - Settings::Setting pipeline_worker_count{linkage, 4, "pipeline_worker_count", + Settings::Setting pipeline_worker_count{linkage, 0, "pipeline_worker_count", Settings::Category::Android, Settings::Specialization::Default, true, diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index 3cddde6cf5..14ab3e6ebc 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -321,7 +321,7 @@ public: } while (channel_state->has_deleted_buffers); } - std::mutex mutex; + std::recursive_mutex mutex; Runtime& runtime; bool any_buffer_uploaded = false; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index c0bb388930..1d77d28c46 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -716,7 +716,7 @@ void RasterizerOpenGL::AccelerateInlineToMemory(GPUVAddr address, size_t copy_si } gpu_memory->WriteBlockUnsafe(address, memory.data(), copy_size); { - std::scoped_lock lock{buffer_cache.mutex}; + std::unique_lock lock{buffer_cache.mutex}; if (!buffer_cache.InlineMemory(*cpu_addr, copy_size, memory)) { buffer_cache.WriteMemory(*cpu_addr, copy_size); } diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 0ff4e52c54..92d16c4d67 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -305,12 +305,10 @@ size_t GetTotalPipelineWorkers() { std::max(static_cast(std::thread::hardware_concurrency()), 2ULL) - 1ULL; #ifdef __ANDROID__ const int configured = AndroidSettings::values.pipeline_worker_count.GetValue(); - const int clamped = std::clamp(configured, 4, 8); - const size_t desired = static_cast(clamped); - if (desired == 0) { - return 1ULL; + if (configured <= 0) { + return max_core_threads; } - return std::min(max_core_threads, desired); + return std::min(max_core_threads, static_cast(configured)); #else return max_core_threads; #endif diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index ec9e4208b5..2093617c69 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -925,7 +925,7 @@ void RasterizerVulkan::AccelerateInlineToMemory(GPUVAddr address, size_t copy_si } gpu_memory->WriteBlockUnsafe(address, memory.data(), copy_size); { - std::scoped_lock lock{buffer_cache.mutex}; + std::unique_lock lock{buffer_cache.mutex}; if (!buffer_cache.InlineMemory(*cpu_addr, copy_size, memory)) { buffer_cache.WriteMemory(*cpu_addr, copy_size); } From e0a742277ab8690eb212875451d884bca1f72413 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 17 Jul 2026 03:00:53 -0400 Subject: [PATCH 05/26] [TEST] Hunting down recursive mutex 5 (cherry picked from commit a484e6c34b9d22c042a490310ec6de27b0c44655) --- src/android/app/src/main/jni/android_settings.h | 2 +- src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/android/app/src/main/jni/android_settings.h b/src/android/app/src/main/jni/android_settings.h index 3aba766a1b..3eaa377b20 100644 --- a/src/android/app/src/main/jni/android_settings.h +++ b/src/android/app/src/main/jni/android_settings.h @@ -147,7 +147,7 @@ namespace AndroidSettings { &show_performance_overlay}; - Settings::Setting pipeline_worker_count{linkage, 0, "pipeline_worker_count", + Settings::Setting pipeline_worker_count{linkage, 2, "pipeline_worker_count", Settings::Category::Android, Settings::Specialization::Default, true, diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 92d16c4d67..8e60368406 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -305,10 +305,8 @@ size_t GetTotalPipelineWorkers() { std::max(static_cast(std::thread::hardware_concurrency()), 2ULL) - 1ULL; #ifdef __ANDROID__ const int configured = AndroidSettings::values.pipeline_worker_count.GetValue(); - if (configured <= 0) { - return max_core_threads; - } - return std::min(max_core_threads, static_cast(configured)); + const size_t desired = static_cast(std::max(configured, 1)); + return std::min(max_core_threads, desired); #else return max_core_threads; #endif From f3bd7aa402ed83d476ff47c3a5199102d82ab988 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 17 Jul 2026 03:13:55 -0400 Subject: [PATCH 06/26] [TEST] Hunting down recursive mutex 6 (cherry picked from commit eb32b8766a3777e75e3afc53568240f970475002) --- src/video_core/buffer_cache/buffer_cache.h | 66 ++++++++++++++++++- .../renderer_opengl/gl_rasterizer.cpp | 1 - .../renderer_vulkan/vk_rasterizer.cpp | 1 - 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index e40aad1fb5..cf5b29b2b1 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -175,9 +175,71 @@ std::optional BufferCache

::GetFlushArea(DA template void BufferCache

::DownloadMemory(DAddr device_addr, u64 size) { - ForEachBufferInRange(device_addr, size, [&](BufferId, Buffer& buffer) { - DownloadBufferMemory(buffer, device_addr, size); + if constexpr (!USE_MEMORY_MAPS) { + std::scoped_lock lock{mutex}; + ForEachBufferInRange(device_addr, size, [&](BufferId, Buffer& buffer) { + DownloadBufferMemory(buffer, device_addr, size); + }); + return; + } + + boost::container::small_vector, 8> downloads; + u64 total_size_bytes = 0; + u64 largest_copy = 0; + + std::unique_lock lock{mutex}; + ForEachBufferInRange(device_addr, size, [&](BufferId buffer_id, Buffer& buffer) { + memory_tracker.ForEachDownloadRangeAndClear( + device_addr, size, [&](u64 device_addr_out, u64 range_size) { + const DAddr buffer_addr = buffer.CpuAddr(); + const auto add_download = [&](DAddr start, DAddr end) { + const u64 new_offset = start - buffer_addr; + const u64 new_size = end - start; + downloads.push_back({ + BufferCopy{ + .src_offset = new_offset, + .dst_offset = total_size_bytes, + .size = new_size, + }, + buffer_id, + }); + constexpr u64 align = 64ULL; + constexpr u64 mask = ~(align - 1ULL); + total_size_bytes += (new_size + align - 1) & mask; + largest_copy = (std::max)(largest_copy, new_size); + }; + gpu_modified_ranges.ForEachInRange(device_addr_out, range_size, add_download); + ClearDownload(device_addr_out, range_size); + gpu_modified_ranges.Subtract(device_addr_out, range_size); + }); }); + if (total_size_bytes == 0) { + return; + } + + auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes); + boost::container::small_vector writebacks; + runtime.PreCopyBarrier(); + for (auto& [copy, buffer_id] : downloads) { + copy.dst_offset += download_staging.offset; + Buffer& buffer = slot_buffers[buffer_id]; + buffer.MarkUsage(copy.src_offset, copy.size); + const std::array copies{copy}; + runtime.CopyBuffer(download_staging.buffer, buffer, copies, false); + BufferCopy writeback{copy}; + writeback.src_offset = static_cast(buffer.CpuAddr()) + copy.src_offset; + writebacks.push_back(writeback); + } + runtime.PostCopyBarrier(); + lock.unlock(); + + runtime.Finish(); + const u8* const base = download_staging.mapped_span.data(); + for (const BufferCopy& writeback : writebacks) { + const u64 staging_offset = writeback.dst_offset - download_staging.offset; + device_memory.WriteBlockUnsafe(static_cast(writeback.src_offset), + base + staging_offset, writeback.size); + } } template diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 1d77d28c46..e660f75dae 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -485,7 +485,6 @@ void RasterizerOpenGL::FlushRegion(DAddr addr, u64 size, VideoCommon::CacheType texture_cache.DownloadMemory(addr, size); } if ((True(which & VideoCommon::CacheType::BufferCache))) { - std::scoped_lock lock{buffer_cache.mutex}; buffer_cache.DownloadMemory(addr, size); } if ((True(which & VideoCommon::CacheType::QueryCache))) { diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 2093617c69..6d9eed9d2e 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -675,7 +675,6 @@ void RasterizerVulkan::FlushRegion(DAddr addr, u64 size, VideoCommon::CacheType texture_cache.DownloadMemory(addr, size); } if ((True(which & VideoCommon::CacheType::BufferCache))) { - std::scoped_lock lock{buffer_cache.mutex}; buffer_cache.DownloadMemory(addr, size); } if ((True(which & VideoCommon::CacheType::QueryCache))) { From 0b1c69bc39d0f7753b90bdea2b66dd9d533b67ca Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 17 Jul 2026 03:56:02 -0400 Subject: [PATCH 07/26] [TEST] Remove unnecessary submit (cherry picked from commit 7ed7e5e31dad9392722dc1818f76169d51c63b69) --- src/video_core/fence_manager.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/video_core/fence_manager.h b/src/video_core/fence_manager.h index 16f19bf009..a4242994a4 100644 --- a/src/video_core/fence_manager.h +++ b/src/video_core/fence_manager.h @@ -91,9 +91,6 @@ public: func(); } fences.push(std::move(new_fence)); - if (should_flush) { - rasterizer.FlushCommands(); - } if constexpr (can_async_check) { guard.unlock(); cv.notify_all(); From cece80d688013d015bb0d6c3faeff4a1b1c52ff4 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 17 Jul 2026 07:28:30 -0400 Subject: [PATCH 08/26] [TEST] Remove unnecessary memory upload (cherry picked from commit e81d170458eb2bb78e4524961391f12da66c61ff) --- src/video_core/buffer_cache/buffer_cache.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index cf5b29b2b1..a3dd157fa6 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -276,7 +276,7 @@ bool BufferCache

::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am auto& src_buffer = slot_buffers[buffer_a]; auto& dest_buffer = slot_buffers[buffer_b]; SynchronizeBuffer(src_buffer, *cpu_src_address, static_cast(amount)); - SynchronizeBuffer(dest_buffer, *cpu_dest_address, static_cast(amount)); + memory_tracker.UnmarkRegionAsCpuModified(*cpu_dest_address, static_cast(amount)); std::array copies{BufferCopy{ .src_offset = src_buffer.Offset(*cpu_src_address), .dst_offset = dest_buffer.Offset(*cpu_dest_address), From 3c2f29810135607b5ef8f4067bac7581c0644f7d Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 17 Jul 2026 07:52:45 -0400 Subject: [PATCH 09/26] [TEST] Adjustments on CommandPools + ResetQueryPool (cherry picked from commit e8b1dc7c0bb55bd1cbc9c99db601a25f2b5c220b) --- .../renderer_vulkan/vk_command_pool.cpp | 44 ++++++++++++++----- .../renderer_vulkan/vk_command_pool.h | 17 ++++--- .../vulkan_common/vulkan_wrapper.cpp | 1 + src/video_core/vulkan_common/vulkan_wrapper.h | 5 +++ 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_command_pool.cpp b/src/video_core/renderer_vulkan/vk_command_pool.cpp index d0dbf7ca54..7ba35e0e20 100644 --- a/src/video_core/renderer_vulkan/vk_command_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_command_pool.cpp @@ -1,9 +1,13 @@ +// 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 #include #include "video_core/renderer_vulkan/vk_command_pool.h" +#include "video_core/renderer_vulkan/vk_master_semaphore.h" #include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_wrapper.h" @@ -14,32 +18,52 @@ constexpr size_t COMMAND_BUFFER_POOL_SIZE = 4; struct CommandPool::Pool { vk::CommandPool handle; vk::CommandBuffers cmdbufs; + u64 tick; }; CommandPool::CommandPool(MasterSemaphore& master_semaphore_, const Device& device_) - : ResourcePool(master_semaphore_, COMMAND_BUFFER_POOL_SIZE), device{device_} {} + : master_semaphore{master_semaphore_}, device{device_} {} CommandPool::~CommandPool() = default; -void CommandPool::Allocate(size_t begin, size_t end) { - // Command buffers are going to be committed, recorded, executed every single usage cycle. - // They are also going to be reset when committed. +void CommandPool::AllocatePool() { Pool& pool = pools.emplace_back(); pool.handle = device.GetLogical().CreateCommandPool({ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .pNext = nullptr, - .flags = - VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT, .queueFamilyIndex = device.GetGraphicsFamily(), }); pool.cmdbufs = pool.handle.Allocate(COMMAND_BUFFER_POOL_SIZE); + pool.tick = 0; +} + +void CommandPool::AcquirePool() { + if (!pools.empty()) { + master_semaphore.Refresh(); + const u64 gpu_tick = master_semaphore.KnownGpuTick(); + for (size_t i = 0; i < pools.size(); ++i) { + const size_t candidate = (current_pool + 1 + i) % pools.size(); + if (gpu_tick >= pools[candidate].tick) { + current_pool = candidate; + current_index = 0; + pools[current_pool].handle.Reset(); + return; + } + } + } + AllocatePool(); + current_pool = pools.size() - 1; + current_index = 0; } VkCommandBuffer CommandPool::Commit() { - const size_t index = CommitResource(); - const auto pool_index = index / COMMAND_BUFFER_POOL_SIZE; - const auto sub_index = index % COMMAND_BUFFER_POOL_SIZE; - return pools[pool_index].cmdbufs[sub_index]; + if (pools.empty() || current_index >= COMMAND_BUFFER_POOL_SIZE) { + AcquirePool(); + } + Pool& pool = pools[current_pool]; + pool.tick = master_semaphore.CurrentTick(); + return pool.cmdbufs[current_index++]; } } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_command_pool.h b/src/video_core/renderer_vulkan/vk_command_pool.h index ec1647f012..362ec0610d 100644 --- a/src/video_core/renderer_vulkan/vk_command_pool.h +++ b/src/video_core/renderer_vulkan/vk_command_pool.h @@ -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 @@ -6,7 +9,7 @@ #include #include -#include "video_core/renderer_vulkan/vk_resource_pool.h" +#include "common/common_types.h" #include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { @@ -14,20 +17,24 @@ namespace Vulkan { class Device; class MasterSemaphore; -class CommandPool final : public ResourcePool { +class CommandPool final { public: explicit CommandPool(MasterSemaphore& master_semaphore_, const Device& device_); - ~CommandPool() override; - - void Allocate(size_t begin, size_t end) override; + ~CommandPool(); VkCommandBuffer Commit(); private: struct Pool; + void AllocatePool(); + void AcquirePool(); + + MasterSemaphore& master_semaphore; const Device& device; std::vector pools; + size_t current_pool = 0; + size_t current_index = 0; }; } // namespace Vulkan diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp index 4943705859..c497110d8e 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.cpp +++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp @@ -230,6 +230,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkMapMemory); X(vkQueueSubmit); X(vkQueueSubmit2); + X(vkResetCommandPool); X(vkResetFences); X(vkResetQueryPool); X(vkSetDebugUtilsObjectNameEXT); diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h index ef975e2bda..fcdcd61e24 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.h +++ b/src/video_core/vulkan_common/vulkan_wrapper.h @@ -346,6 +346,7 @@ struct DeviceDispatch : InstanceDispatch { PFN_vkMapMemory vkMapMemory{}; PFN_vkQueueSubmit vkQueueSubmit{}; PFN_vkQueueSubmit2 vkQueueSubmit2{}; + PFN_vkResetCommandPool vkResetCommandPool{}; PFN_vkResetFences vkResetFences{}; PFN_vkResetQueryPool vkResetQueryPool{}; PFN_vkSetDebugUtilsObjectNameEXT vkSetDebugUtilsObjectNameEXT{}; @@ -925,6 +926,10 @@ public: CommandBuffers Allocate(std::size_t num_buffers, VkCommandBufferLevel level = VK_COMMAND_BUFFER_LEVEL_PRIMARY) const; + void Reset(VkCommandPoolResetFlags flags = 0) const { + Check(dld->vkResetCommandPool(owner, handle, flags)); + } + /// Set object name. void SetObjectNameEXT(const char* name) const; }; From 43ed5cf9b5adcb566a6495d81b1cd6c9c811a704 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 17 Jul 2026 17:44:24 -0400 Subject: [PATCH 10/26] [TEST] Hunting down recursive mutex 7 (cherry picked from commit 4956bc86c379a08de0bf3c433c15858e868bfbdb) --- src/video_core/buffer_cache/buffer_cache.h | 58 +++++++++++++--------- src/video_core/fence_manager.h | 4 +- 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index a3dd157fa6..b6d0852b4e 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -735,32 +735,44 @@ void BufferCache

::PopAsyncFlushes() { template void BufferCache

::PopAsyncBuffers() { - if (async_buffers.empty()) { - return; - } - if (!async_buffers.front().has_value()) { + struct Writeback { + DAddr addr; + const u8* src; + u64 size; + }; + boost::container::small_vector writebacks; + { + std::scoped_lock lock{mutex}; + if (async_buffers.empty()) { + return; + } + if (!async_buffers.front().has_value()) { + async_buffers.pop_front(); + return; + } + auto& downloads = pending_downloads.front(); + auto& async_buffer = async_buffers.front(); + const u8* base = async_buffer->mapped_span.data(); + const size_t base_offset = async_buffer->offset; + for (const auto& copy : downloads) { + const DAddr device_addr = static_cast(copy.src_offset); + const u64 dst_offset = copy.dst_offset - base_offset; + const u8* read_mapped_memory = base + dst_offset; + async_downloads.ForEachInRange(device_addr, copy.size, [&](DAddr start, DAddr end, s32) { + writebacks.push_back( + {start, &read_mapped_memory[start - device_addr], end - start}); + }); + async_downloads.Subtract(device_addr, copy.size, [&](DAddr start, DAddr end) { + gpu_modified_ranges.Subtract(start, end - start); + }); + } + async_buffers_death_ring.emplace_back(*async_buffer); async_buffers.pop_front(); - return; + pending_downloads.pop_front(); } - auto& downloads = pending_downloads.front(); - auto& async_buffer = async_buffers.front(); - u8* base = async_buffer->mapped_span.data(); - const size_t base_offset = async_buffer->offset; - for (const auto& copy : downloads) { - const DAddr device_addr = static_cast(copy.src_offset); - const u64 dst_offset = copy.dst_offset - base_offset; - const u8* read_mapped_memory = base + dst_offset; - async_downloads.ForEachInRange(device_addr, copy.size, [&](DAddr start, DAddr end, s32) { - device_memory.WriteBlockUnsafe(start, &read_mapped_memory[start - device_addr], - end - start); - }); - async_downloads.Subtract(device_addr, copy.size, [&](DAddr start, DAddr end) { - gpu_modified_ranges.Subtract(start, end - start); - }); + for (const auto& wb : writebacks) { + device_memory.WriteBlockUnsafe(wb.addr, wb.src, wb.size); } - async_buffers_death_ring.emplace_back(*async_buffer); - async_buffers.pop_front(); - pending_downloads.pop_front(); } template diff --git a/src/video_core/fence_manager.h b/src/video_core/fence_manager.h index a4242994a4..7980786662 100644 --- a/src/video_core/fence_manager.h +++ b/src/video_core/fence_manager.h @@ -235,10 +235,10 @@ private: void PopAsyncFlushes() { { - std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; + std::scoped_lock lock{texture_cache.mutex}; texture_cache.PopAsyncFlushes(); - buffer_cache.PopAsyncFlushes(); } + buffer_cache.PopAsyncFlushes(); query_cache.PopAsyncFlushes(); } From 1b01f5c93abca9ec806182d122be751f912ee8f7 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 17 Jul 2026 19:56:16 -0400 Subject: [PATCH 11/26] [TEST] Coalesce NCE fault write. (cherry picked from commit eec29b83f3179871f8eab8d936180bab2e55f554) --- src/core/arm/nce/arm_nce.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/arm/nce/arm_nce.cpp b/src/core/arm/nce/arm_nce.cpp index 63d5638fce..b181839db8 100644 --- a/src/core/arm/nce/arm_nce.cpp +++ b/src/core/arm/nce/arm_nce.cpp @@ -157,6 +157,8 @@ bool ArmNce::HandleGuestAlignmentFault(GuestContext* guest_ctx, void* raw_info, return HandleFailedGuestFault(guest_ctx, raw_info, raw_context); } +constexpr size_t NCE_WRITE_FAULT_CLUSTER_PAGES = 4; + bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) { auto* info = static_cast(raw_info); @@ -165,7 +167,7 @@ bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, voi const Common::ProcessAddress addr = (reinterpret_cast(info->si_addr) & ~Memory::YUZU_PAGEMASK); auto& memory = guest_ctx->parent->m_running_thread->GetOwnerProcess()->GetMemory(); - if (memory.InvalidateNCE(addr, Memory::YUZU_PAGESIZE)) { + if (memory.InvalidateNCE(addr, Memory::YUZU_PAGESIZE * NCE_WRITE_FAULT_CLUSTER_PAGES)) { // We handled the access successfully and are returning to guest code. return true; } From 8d96b2e894a6c17127f66aaa1652a1e0d4807903 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 17 Jul 2026 20:15:19 -0400 Subject: [PATCH 12/26] [TEST] Caching for texture + pages on NCE (cherry picked from commit 9c313fb787a93d59f351879ec44a80db854ce330) --- src/core/device_memory_manager.h | 5 ++++ src/core/device_memory_manager.inc | 23 +++++++++++++++++++ .../renderer_vulkan/vk_rasterizer.cpp | 5 +++- src/video_core/texture_cache/texture_cache.h | 5 ++++ 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/core/device_memory_manager.h b/src/core/device_memory_manager.h index 3d97fdcc5c..a66ca4aaae 100644 --- a/src/core/device_memory_manager.h +++ b/src/core/device_memory_manager.h @@ -126,6 +126,10 @@ public: // New batch API to update multiple ranges with a single lock acquisition. void UpdatePagesCachedBatch(std::span> ranges, s32 delta); + void UpdateTexturePagesCount(DAddr addr, size_t size, s32 delta); + + [[nodiscard]] bool IsRegionTextureCached(DAddr addr, size_t size) const noexcept; + private: struct TranslationEntry { DAddr guest_page{}; @@ -234,6 +238,7 @@ private: (1ULL << (device_virtual_bits - page_bits)) / subentries; using CachedPages = std::array; std::unique_ptr cached_pages; + std::unique_ptr texture_cached_pages; Common::RangeMutex counter_guard; std::mutex mapping_guard; diff --git a/src/core/device_memory_manager.inc b/src/core/device_memory_manager.inc index 43a480cfef..15e52b1511 100644 --- a/src/core/device_memory_manager.inc +++ b/src/core/device_memory_manager.inc @@ -177,6 +177,7 @@ DeviceMemoryManager::DeviceMemoryManager(const DeviceMemory& device_memo { impl = std::make_unique>(); cached_pages = std::make_unique(); + texture_cached_pages = std::make_unique(); const size_t total_virtual = device_as_size >> Memory::YUZU_PAGEBITS; for (size_t i = 0; i < total_virtual; i++) { @@ -625,6 +626,28 @@ void DeviceMemoryManager::UpdatePagesCachedCount(DAddr addr, size_t size UpdatePagesCachedCountNoLock(addr, size, delta); } +template +void DeviceMemoryManager::UpdateTexturePagesCount(DAddr addr, size_t size, s32 delta) { + Common::ScopedRangeLock lk(counter_guard, addr, size); + const size_t page_end = Common::DivCeil(addr + size, Memory::YUZU_PAGESIZE); + for (size_t page = addr >> Memory::YUZU_PAGEBITS; page != page_end; ++page) { + CounterAtomicType& count = texture_cached_pages->at(page >> subentries_shift).Count(page); + count.fetch_add(static_cast(delta), std::memory_order_release); + } +} + +template +bool DeviceMemoryManager::IsRegionTextureCached(DAddr addr, size_t size) const noexcept { + const size_t page_end = Common::DivCeil(addr + size, Memory::YUZU_PAGESIZE); + for (size_t page = addr >> Memory::YUZU_PAGEBITS; page != page_end; ++page) { + if (texture_cached_pages->at(page >> subentries_shift).Count(page).load( + std::memory_order_acquire) != 0) { + return true; + } + } + return false; +} + template void DeviceMemoryManager::UpdatePagesCachedBatch(std::span> ranges, s32 delta) { if (ranges.empty()) { diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 6d9eed9d2e..c5ceebf6cb 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -772,12 +772,15 @@ bool RasterizerVulkan::OnCPUWrite(DAddr addr, u64 size) { return false; } +static constexpr bool ENABLE_TEXTURE_CACHE_INVALIDATION_SKIP = true; + void RasterizerVulkan::OnCacheInvalidation(DAddr addr, u64 size) { if (addr == 0 || size == 0) { return; } - { + if (!ENABLE_TEXTURE_CACHE_INVALIDATION_SKIP || + device_memory.IsRegionTextureCached(addr, size)) { std::scoped_lock lock{texture_cache.mutex}; texture_cache.WriteMemory(addr, size); } diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 2cfdb94359..1989511dd4 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -2308,6 +2308,7 @@ void TextureCache

::TrackImage(ImageBase& image, ImageId image_id) { if (False(image.flags & ImageFlagBits::Sparse)) { if (image.cpu_addr < ~(1ULL << 40)) { device_memory.UpdatePagesCachedCount(image.cpu_addr, image.guest_size_bytes, 1); + device_memory.UpdateTexturePagesCount(image.cpu_addr, image.guest_size_bytes, 1); } return; } @@ -2320,12 +2321,14 @@ void TextureCache

::TrackImage(ImageBase& image, ImageId image_id) { const DAddr cpu_addr = map.cpu_addr; const std::size_t size = map.size; device_memory.UpdatePagesCachedCount(cpu_addr, size, 1); + device_memory.UpdateTexturePagesCount(cpu_addr, size, 1); } return; } ForEachSparseSegment(image, [this]([[maybe_unused]] GPUVAddr gpu_addr, DAddr cpu_addr, size_t size) { device_memory.UpdatePagesCachedCount(cpu_addr, size, 1); + device_memory.UpdateTexturePagesCount(cpu_addr, size, 1); }); } @@ -2336,6 +2339,7 @@ void TextureCache

::UntrackImage(ImageBase& image, ImageId image_id) { if (False(image.flags & ImageFlagBits::Sparse)) { if (image.cpu_addr < ~(1ULL << 40)) { device_memory.UpdatePagesCachedCount(image.cpu_addr, image.guest_size_bytes, -1); + device_memory.UpdateTexturePagesCount(image.cpu_addr, image.guest_size_bytes, -1); } return; } @@ -2348,6 +2352,7 @@ void TextureCache

::UntrackImage(ImageBase& image, ImageId image_id) { const DAddr cpu_addr = map.cpu_addr; const std::size_t size = map.size; device_memory.UpdatePagesCachedCount(cpu_addr, size, -1); + device_memory.UpdateTexturePagesCount(cpu_addr, size, -1); } } From 723afa7d46eb45c5fc1290a0673b55aaa3a36d4f Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 18 Jul 2026 00:00:32 -0400 Subject: [PATCH 13/26] [TEST] Hunting down recursive mutex 8 --- src/video_core/buffer_cache/buffer_cache.h | 9 +++++++ .../buffer_cache/buffer_cache_base.h | 2 ++ .../buffer_cache/memory_tracker_base.h | 25 +++++++++++++++++++ .../renderer_vulkan/vk_rasterizer.cpp | 5 +++- 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index b6d0852b4e..ffeb1e0a7e 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -121,6 +121,15 @@ void BufferCache

::WriteMemory(DAddr device_addr, u64 size) { memory_tracker.MarkRegionAsCpuModified(device_addr, size); } +template +void BufferCache

::CpuWriteInvalidate(DAddr device_addr, u64 size) { + if (!memory_tracker.CpuMarkIfNotGpuModified(device_addr, size)) { + return; + } + std::scoped_lock lock{mutex}; + WriteMemory(device_addr, size); +} + template void BufferCache

::CachedWriteMemory(DAddr device_addr, u64 size) { const bool is_dirty = IsRegionRegistered(device_addr, size); diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index 14ab3e6ebc..3b900b95f9 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -217,6 +217,8 @@ public: void WriteMemory(DAddr device_addr, u64 size); + void CpuWriteInvalidate(DAddr device_addr, u64 size); + void CachedWriteMemory(DAddr device_addr, u64 size); bool OnCPUWrite(DAddr device_addr, u64 size); diff --git a/src/video_core/buffer_cache/memory_tracker_base.h b/src/video_core/buffer_cache/memory_tracker_base.h index 2c571271d6..887b4ff149 100644 --- a/src/video_core/buffer_cache/memory_tracker_base.h +++ b/src/video_core/buffer_cache/memory_tracker_base.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -130,12 +131,28 @@ public: } void FlushCachedWrites() noexcept { + std::scoped_lock lk{tracker_mutex}; for (auto id : cached_pages) { top_tier[id]->FlushCachedWrites(); } cached_pages.clear(); } + [[nodiscard]] bool CpuMarkIfNotGpuModified(VAddr addr, u64 size) { + std::scoped_lock lk{tracker_mutex}; + const bool gpu = IteratePagesNoLock( + addr, size, [](Manager* manager, u64 offset, size_t sz) { + return manager->IsRegionModified(Type::GPU, offset, sz); + }); + if (gpu) { + return true; + } + IteratePagesNoLock(addr, size, [](Manager* manager, u64 offset, size_t sz) { + manager->ChangeRegionState(Type::CPU, true, manager->cpu_addr + offset, sz); + }); + return false; + } + /// Call 'func' for each CPU modified range and unmark those pages as CPU modified template void ForEachUploadRange(VAddr query_cpu_range, u64 query_size, Func&& func) { @@ -162,6 +179,12 @@ public: private: template bool IteratePages(VAddr cpu_address, size_t size, Func&& func) { + std::scoped_lock lk{tracker_mutex}; + return IteratePagesNoLock(cpu_address, size, std::forward(func)); + } + + template + bool IteratePagesNoLock(VAddr cpu_address, size_t size, Func&& func) { using FuncReturn = typename std::invoke_result::type; static constexpr bool BOOL_BREAK = std::is_same_v; std::size_t remaining_size{size}; @@ -199,6 +222,7 @@ private: template std::pair IteratePairs(VAddr cpu_address, size_t size, Func&& func) { + std::scoped_lock lk{tracker_mutex}; std::size_t remaining_size{size}; std::size_t page_index{cpu_address >> HIGHER_PAGE_BITS}; u64 page_offset{cpu_address & HIGHER_PAGE_MASK}; @@ -259,6 +283,7 @@ private: std::deque free_managers; ankerl::unordered_dense::set cached_pages; DeviceTracker* device_tracker = nullptr; + std::mutex tracker_mutex; }; } // namespace VideoCommon diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index c5ceebf6cb..e7120fe8bb 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -773,6 +773,7 @@ bool RasterizerVulkan::OnCPUWrite(DAddr addr, u64 size) { } static constexpr bool ENABLE_TEXTURE_CACHE_INVALIDATION_SKIP = true; +static constexpr bool ENABLE_FINE_GRAINED_TRACKER_LOCK = true; void RasterizerVulkan::OnCacheInvalidation(DAddr addr, u64 size) { if (addr == 0 || size == 0) { @@ -784,7 +785,9 @@ void RasterizerVulkan::OnCacheInvalidation(DAddr addr, u64 size) { std::scoped_lock lock{texture_cache.mutex}; texture_cache.WriteMemory(addr, size); } - { + if (ENABLE_FINE_GRAINED_TRACKER_LOCK) { + buffer_cache.CpuWriteInvalidate(addr, size); + } else { std::scoped_lock lock{buffer_cache.mutex}; buffer_cache.WriteMemory(addr, size); } From 59ae72c97df5e512885f7e697e4605024ced523d Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 18 Jul 2026 01:16:28 -0400 Subject: [PATCH 14/26] [TEST] Review rasters on Maxwell values --- src/video_core/vulkan_common/vulkan_device.cpp | 2 +- src/video_core/vulkan_common/vulkan_device.h | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 8fd9b6e69f..2c1fe7ccaa 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -1245,7 +1245,7 @@ void Device::RemoveUnsuitableExtensions() { extensions.line_rasterization && features.line_rasterization.rectangularLines; const bool supports_conservative_raster_mode = features.extended_dynamic_state3.extendedDynamicState3ConservativeRasterizationMode && - extensions.conservative_rasterization; + ENABLE_CONSERVATIVE_RASTER && extensions.conservative_rasterization; const bool supports_line_stipple_enable = features.extended_dynamic_state3.extendedDynamicState3LineStippleEnable && extensions.line_rasterization && features.line_rasterization.stippledRectangularLines; diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index c449f60324..d2d9838ca9 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -776,9 +776,11 @@ FN_MAX_LIMIT_LIST return extensions.shader_demote_to_helper_invocation; } + static constexpr bool ENABLE_CONSERVATIVE_RASTER = false; + /// Returns true if the device supports VK_EXT_conservative_rasterization. bool IsExtConservativeRasterizationSupported() const { - return extensions.conservative_rasterization; + return ENABLE_CONSERVATIVE_RASTER && extensions.conservative_rasterization; } /// Returns true if the device supports VK_EXT_provoking_vertex. From a2d24b3eb7be62fbcaf2e3f192231cf66054416b Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 18 Jul 2026 01:46:03 -0400 Subject: [PATCH 15/26] [TEST] Updated list of QCOM driver removed features --- src/video_core/vulkan_common/vulkan_device.cpp | 6 +++++- src/video_core/vulkan_common/vulkan_device.h | 4 +--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 2c1fe7ccaa..a68fce040f 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -527,6 +527,10 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR RemoveExtensionFeature(extensions.workgroup_memory_explicit_layout, features.workgroup_memory_explicit_layout, VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME); + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken conservative rasterization."); + RemoveExtensionFeature(extensions.conservative_rasterization, + features.conservative_rasterization, + VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME); #if defined(__ANDROID__) && defined(ARCHITECTURE_arm64) // BCn patching only safe on Android 9+ (API 28+). Older versions crash on driver load. @@ -1245,7 +1249,7 @@ void Device::RemoveUnsuitableExtensions() { extensions.line_rasterization && features.line_rasterization.rectangularLines; const bool supports_conservative_raster_mode = features.extended_dynamic_state3.extendedDynamicState3ConservativeRasterizationMode && - ENABLE_CONSERVATIVE_RASTER && extensions.conservative_rasterization; + extensions.conservative_rasterization; const bool supports_line_stipple_enable = features.extended_dynamic_state3.extendedDynamicState3LineStippleEnable && extensions.line_rasterization && features.line_rasterization.stippledRectangularLines; diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index d2d9838ca9..c449f60324 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -776,11 +776,9 @@ FN_MAX_LIMIT_LIST return extensions.shader_demote_to_helper_invocation; } - static constexpr bool ENABLE_CONSERVATIVE_RASTER = false; - /// Returns true if the device supports VK_EXT_conservative_rasterization. bool IsExtConservativeRasterizationSupported() const { - return ENABLE_CONSERVATIVE_RASTER && extensions.conservative_rasterization; + return extensions.conservative_rasterization; } /// Returns true if the device supports VK_EXT_provoking_vertex. From c394eef32eb51441311c0c03e11218a7bd64730c Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 18 Jul 2026 02:11:33 -0400 Subject: [PATCH 16/26] [TEST] Depth clip bugs + tessellation --- .../renderer_vulkan/vk_pipeline_cache.cpp | 8 +++++++- src/video_core/vulkan_common/vulkan_device.cpp | 15 +++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 8e60368406..b53fa84626 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -167,6 +167,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span program } const Shader::Stage stage{program.stage}; const bool has_geometry{key.unique_hashes[4] != 0 && !programs[4].is_geometry_passthrough}; + const bool has_tessellation{key.unique_hashes[3] != 0}; const bool gl_ndc{key.state.ndc_minus_one_to_one != 0}; const float point_size{std::bit_cast(key.state.point_size)}; switch (stage) { @@ -185,7 +186,9 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span program LOG_WARNING(Render_Vulkan, "XFB requested in pipeline key but device lacks VK_EXT_transform_feedback; ignoring XFB decorations"); } } - info.convert_depth_mode = gl_ndc; + if (!has_tessellation) { + info.convert_depth_mode = gl_ndc; + } } if (key.state.dynamic_vertex_input) { for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) { @@ -224,6 +227,9 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span program ASSERT(false); return Shader::TessSpacing::Equal; }(); + if (!has_geometry) { + info.convert_depth_mode = gl_ndc; + } break; case Shader::Stage::Geometry: if (program.output_topology == Shader::OutputTopology::PointList) { diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index a68fce040f..7e36206fa8 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -517,12 +517,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 atomic int64."); - RemoveExtensionFeature(extensions.shader_atomic_int64, features.shader_atomic_int64, - VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME); - features.shader_atomic_int64.shaderBufferInt64Atomics = false; - features.shader_atomic_int64.shaderSharedInt64Atomics = false; - features.features.shaderInt64 = false; LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken workgroup memory explicit layout."); RemoveExtensionFeature(extensions.workgroup_memory_explicit_layout, features.workgroup_memory_explicit_layout, @@ -531,6 +525,15 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR RemoveExtensionFeature(extensions.conservative_rasterization, features.conservative_rasterization, VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME); + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken depth clip control."); + RemoveExtensionFeature(extensions.depth_clip_control, features.depth_clip_control, + VK_EXT_DEPTH_CLIP_CONTROL_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; + features.shader_atomic_int64.shaderSharedInt64Atomics = false; + features.features.shaderInt64 = false; #if defined(__ANDROID__) && defined(ARCHITECTURE_arm64) // BCn patching only safe on Android 9+ (API 28+). Older versions crash on driver load. From 142eeb0b8a3baf3421bc788ec7cfb447b99021d9 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 18 Jul 2026 02:20:32 -0400 Subject: [PATCH 17/26] fix build --- src/video_core/vulkan_common/vulkan_device.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 7e36206fa8..6bf4d589ed 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -522,9 +522,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR features.workgroup_memory_explicit_layout, VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken conservative rasterization."); - RemoveExtensionFeature(extensions.conservative_rasterization, - features.conservative_rasterization, - VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME); + RemoveExtension(extensions.conservative_rasterization, + VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken depth clip control."); RemoveExtensionFeature(extensions.depth_clip_control, features.depth_clip_control, VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME); From 787f80e05c981dd233e8159448fd8bb178f6b52f Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 18 Jul 2026 14:49:00 -0400 Subject: [PATCH 18/26] [TEST] Extend coalescing to more cpu regions --- src/video_core/buffer_cache/buffer_cache.h | 3 ++ .../buffer_cache/memory_tracker_base.h | 23 +++++++++++++- src/video_core/buffer_cache/word_manager.h | 30 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index ffeb1e0a7e..641e38934b 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -1721,6 +1721,9 @@ void BufferCache

::TouchBuffer(Buffer& buffer, BufferId buffer_id) noexcept { template bool BufferCache

::SynchronizeBuffer(Buffer& buffer, DAddr device_addr, u32 size) { + if (!memory_tracker.HasCpuModifiedCheap(device_addr, size)) { + return true; + } upload_copies.clear(); u64 total_size_bytes = 0; u64 largest_copy = 0; diff --git a/src/video_core/buffer_cache/memory_tracker_base.h b/src/video_core/buffer_cache/memory_tracker_base.h index 887b4ff149..5ffa3b1542 100644 --- a/src/video_core/buffer_cache/memory_tracker_base.h +++ b/src/video_core/buffer_cache/memory_tracker_base.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include #include @@ -50,6 +51,25 @@ public: }); } + [[nodiscard]] bool HasCpuModifiedCheap(VAddr query_cpu_addr, u64 query_size) noexcept { + std::size_t remaining_size{query_size}; + std::size_t page_index{query_cpu_addr >> HIGHER_PAGE_BITS}; + u64 page_offset{query_cpu_addr & HIGHER_PAGE_MASK}; + while (remaining_size > 0) { + const std::size_t copy_amount{ + std::min(HIGHER_PAGE_SIZE - page_offset, remaining_size)}; + const Manager* manager = + std::atomic_ref(top_tier[page_index]).load(std::memory_order_acquire); + if (manager == nullptr || manager->CpuModifiedPageCount() != 0) { + return true; + } + page_index++; + page_offset = 0; + remaining_size -= copy_amount; + } + return false; + } + /// Returns true if a region has been modified from the CPU [[nodiscard]] bool IsRegionCpuModified(VAddr query_cpu_addr, u64 query_size) noexcept { return IteratePages(query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) { @@ -260,7 +280,8 @@ private: void CreateRegion(std::size_t page_index) { const VAddr base_cpu_addr = page_index << HIGHER_PAGE_BITS; - top_tier[page_index] = GetNewManager(base_cpu_addr); + std::atomic_ref(top_tier[page_index]) + .store(GetNewManager(base_cpu_addr), std::memory_order_release); } Manager* GetNewManager(VAddr base_cpu_address) { diff --git a/src/video_core/buffer_cache/word_manager.h b/src/video_core/buffer_cache/word_manager.h index 76d608bbe0..2e8717f195 100644 --- a/src/video_core/buffer_cache/word_manager.h +++ b/src/video_core/buffer_cache/word_manager.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include #include @@ -48,6 +49,11 @@ struct WordManager { u64 const last_word = (~u64{0} << shift) >> shift; heap[num_words * size_t(Type::CPU) + num_words - 1] = last_word; heap[num_words * size_t(Type::Untracked) + num_words - 1] = last_word; + u32 cpu_pages = 0; + for (size_t i = 0; i < num_words; ++i) { + cpu_pages += static_cast(std::popcount(heap[num_words * size_t(Type::CPU) + i])); + } + cpu_modified_pages.store(cpu_pages, std::memory_order_relaxed); } explicit WordManager() = default; @@ -120,10 +126,15 @@ struct WordManager { [[maybe_unused]] std::span untracked_words = Span(Type::Untracked); [[maybe_unused]] std::span cached_words = Span(Type::CachedCPU); std::vector> ranges; + s64 cpu_delta = 0; IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) { if (type == Type::CPU || type == Type::CachedCPU) { CollectChangedRanges(!enable, index, untracked_words[index], mask, ranges); } + if (type == Type::CPU) { + const u64 old = state_words[index]; + cpu_delta += enable ? std::popcount(~old & mask) : -std::popcount(old & mask); + } if (enable) { state_words[index] |= mask; if (type == Type::CPU || type == Type::CachedCPU) @@ -138,6 +149,9 @@ struct WordManager { untracked_words[index] &= ~mask; } }); + if (cpu_delta != 0) { + cpu_modified_pages.fetch_add(static_cast(cpu_delta), std::memory_order_release); + } if (!ranges.empty()) { ApplyCollectedRanges(ranges, (!enable) ? 1 : -1); } @@ -165,6 +179,7 @@ struct WordManager { (pending_pointer - pending_offset) * BYTES_PER_PAGE); }; std::vector> ranges; + s64 cpu_delta = 0; IterateWords(offset, size, [&](size_t index, u64 mask) { if (type == Type::GPU) mask &= ~untracked_words[index]; @@ -173,6 +188,8 @@ struct WordManager { if (type == Type::CPU || type == Type::CachedCPU) { CollectChangedRanges(true, index, untracked_words[index], mask, ranges); } + if (type == Type::CPU) + cpu_delta -= std::popcount(word); state_words[index] &= ~mask; if (type == Type::CPU || type == Type::CachedCPU) untracked_words[index] &= ~mask; @@ -194,6 +211,9 @@ struct WordManager { } }); }); + if (cpu_delta != 0) { + cpu_modified_pages.fetch_add(static_cast(cpu_delta), std::memory_order_release); + } if (pending) { release(); } @@ -248,13 +268,18 @@ struct WordManager { auto const untracked_words = Span(Type::Untracked); auto const cpu_words = Span(Type::CPU); std::vector> ranges; + s64 cpu_delta = 0; for (u64 word_index = 0; word_index < num_words; ++word_index) { const u64 cached_bits = cached_words[word_index]; CollectChangedRanges(false, word_index, untracked_words[word_index], cached_bits, ranges); + cpu_delta += std::popcount(~cpu_words[word_index] & cached_bits); untracked_words[word_index] |= cached_bits; cpu_words[word_index] |= cached_bits; cached_words[word_index] = 0; } + if (cpu_delta != 0) { + cpu_modified_pages.fetch_add(static_cast(cpu_delta), std::memory_order_release); + } if (!ranges.empty()) { ApplyCollectedRanges(ranges, -1); } @@ -319,9 +344,14 @@ struct WordManager { return std::span(heap.data() + num_words * size_t(type), num_words); } + [[nodiscard]] u32 CpuModifiedPageCount() const noexcept { + return cpu_modified_pages.load(std::memory_order_acquire); + } + std::array heap = {}; DeviceTracker* tracker = nullptr; VAddr cpu_addr = 0; + std::atomic cpu_modified_pages{0}; }; } // namespace VideoCommon From dfb2fa717e1b50af18b35ec692ea32fa7f1f83af Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 18 Jul 2026 16:51:03 -0400 Subject: [PATCH 19/26] I despite you clang --- .../buffer_cache/memory_tracker_base.h | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/video_core/buffer_cache/memory_tracker_base.h b/src/video_core/buffer_cache/memory_tracker_base.h index 5ffa3b1542..4c9e978484 100644 --- a/src/video_core/buffer_cache/memory_tracker_base.h +++ b/src/video_core/buffer_cache/memory_tracker_base.h @@ -58,8 +58,7 @@ public: while (remaining_size > 0) { const std::size_t copy_amount{ std::min(HIGHER_PAGE_SIZE - page_offset, remaining_size)}; - const Manager* manager = - std::atomic_ref(top_tier[page_index]).load(std::memory_order_acquire); + const Manager* manager = top_tier[page_index].load(std::memory_order_acquire); if (manager == nullptr || manager->CpuModifiedPageCount() != 0) { return true; } @@ -153,7 +152,7 @@ public: void FlushCachedWrites() noexcept { std::scoped_lock lk{tracker_mutex}; for (auto id : cached_pages) { - top_tier[id]->FlushCachedWrites(); + top_tier[id].load(std::memory_order_relaxed)->FlushCachedWrites(); } cached_pages.clear(); } @@ -213,7 +212,7 @@ private: while (remaining_size > 0) { const std::size_t copy_amount{ std::min(HIGHER_PAGE_SIZE - page_offset, remaining_size)}; - auto* manager{top_tier[page_index]}; + auto* manager{top_tier[page_index].load(std::memory_order_relaxed)}; if (manager) { if constexpr (BOOL_BREAK) { if (func(manager, page_offset, copy_amount)) { @@ -224,7 +223,7 @@ private: } } else if constexpr (create_region_on_fail) { CreateRegion(page_index); - manager = top_tier[page_index]; + manager = top_tier[page_index].load(std::memory_order_relaxed); if constexpr (BOOL_BREAK) { if (func(manager, page_offset, copy_amount)) { return true; @@ -251,7 +250,7 @@ private: while (remaining_size > 0) { const std::size_t copy_amount{ std::min(HIGHER_PAGE_SIZE - page_offset, remaining_size)}; - auto* manager{top_tier[page_index]}; + auto* manager{top_tier[page_index].load(std::memory_order_relaxed)}; const auto execute = [&] { auto [new_begin, new_end] = func(manager, page_offset, copy_amount); if (new_begin != 0 || new_end != 0) { @@ -264,7 +263,7 @@ private: execute(); } else if constexpr (create_region_on_fail) { CreateRegion(page_index); - manager = top_tier[page_index]; + manager = top_tier[page_index].load(std::memory_order_relaxed); execute(); } page_index++; @@ -280,8 +279,7 @@ private: void CreateRegion(std::size_t page_index) { const VAddr base_cpu_addr = page_index << HIGHER_PAGE_BITS; - std::atomic_ref(top_tier[page_index]) - .store(GetNewManager(base_cpu_addr), std::memory_order_release); + top_tier[page_index].store(GetNewManager(base_cpu_addr), std::memory_order_release); } Manager* GetNewManager(VAddr base_cpu_address) { @@ -299,7 +297,7 @@ private: return new_manager; } - std::array top_tier{}; + std::array, NUM_HIGH_PAGES> top_tier{}; std::deque> manager_pool; std::deque free_managers; ankerl::unordered_dense::set cached_pages; From 20e6c163a62316647b67a2f4298a4bd8e9bd3ee8 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 18 Jul 2026 17:49:22 -0400 Subject: [PATCH 20/26] [TEST] Extend coalescing to more gpu to cpu regions --- src/video_core/buffer_cache/buffer_cache.h | 8 +++++++- .../buffer_cache/buffer_cache_base.h | 2 ++ .../buffer_cache/memory_tracker_base.h | 18 ++++++++++++++++++ src/video_core/buffer_cache/word_manager.h | 18 ++++++++++++++++++ .../renderer_vulkan/vk_rasterizer.cpp | 8 +++++--- 5 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 641e38934b..7dbecb4fe1 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -791,6 +791,11 @@ bool BufferCache

::IsRegionGpuModified(DAddr addr, size_t size) { return is_dirty; } +template +bool BufferCache

::IsRegionGpuModifiedCheap(DAddr addr, size_t size) { + return memory_tracker.IsRegionGpuModifiedCheap(addr, size); +} + template bool BufferCache

::IsRegionRegistered(DAddr addr, size_t size) { const DAddr end_addr = addr + size; @@ -1025,7 +1030,8 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 }(); const bool use_fast_buffer = needs_alignment_stream || (has_host_buffer && size <= channel_state->uniform_buffer_skip_cache_size - && !memory_tracker.IsRegionGpuModified(device_addr, size)); + && !(memory_tracker.IsRegionGpuModifiedCheap(device_addr, size) + && memory_tracker.IsRegionGpuModified(device_addr, size))); if (use_fast_buffer) { if constexpr (IS_OPENGL) { if (runtime.HasFastBufferSubData()) { diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index 3b900b95f9..622f222f2b 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -300,6 +300,8 @@ public: /// Return true when a CPU region is modified from the GPU [[nodiscard]] bool IsRegionGpuModified(DAddr addr, size_t size); + [[nodiscard]] bool IsRegionGpuModifiedCheap(DAddr addr, size_t size); + /// Return true when a region is registered on the cache [[nodiscard]] bool IsRegionRegistered(DAddr addr, size_t size); diff --git a/src/video_core/buffer_cache/memory_tracker_base.h b/src/video_core/buffer_cache/memory_tracker_base.h index 4c9e978484..329c7e890d 100644 --- a/src/video_core/buffer_cache/memory_tracker_base.h +++ b/src/video_core/buffer_cache/memory_tracker_base.h @@ -69,6 +69,24 @@ public: return false; } + [[nodiscard]] bool IsRegionGpuModifiedCheap(VAddr query_cpu_addr, u64 query_size) noexcept { + std::size_t remaining_size{query_size}; + std::size_t page_index{query_cpu_addr >> HIGHER_PAGE_BITS}; + u64 page_offset{query_cpu_addr & HIGHER_PAGE_MASK}; + while (remaining_size > 0) { + const std::size_t copy_amount{ + std::min(HIGHER_PAGE_SIZE - page_offset, remaining_size)}; + const Manager* manager = top_tier[page_index].load(std::memory_order_acquire); + if (manager != nullptr && manager->GpuModifiedPageCount() != 0) { + return true; + } + page_index++; + page_offset = 0; + remaining_size -= copy_amount; + } + return false; + } + /// Returns true if a region has been modified from the CPU [[nodiscard]] bool IsRegionCpuModified(VAddr query_cpu_addr, u64 query_size) noexcept { return IteratePages(query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) { diff --git a/src/video_core/buffer_cache/word_manager.h b/src/video_core/buffer_cache/word_manager.h index 2e8717f195..0779adbd35 100644 --- a/src/video_core/buffer_cache/word_manager.h +++ b/src/video_core/buffer_cache/word_manager.h @@ -127,6 +127,7 @@ struct WordManager { [[maybe_unused]] std::span cached_words = Span(Type::CachedCPU); std::vector> ranges; s64 cpu_delta = 0; + s64 gpu_delta = 0; IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) { if (type == Type::CPU || type == Type::CachedCPU) { CollectChangedRanges(!enable, index, untracked_words[index], mask, ranges); @@ -134,6 +135,9 @@ struct WordManager { if (type == Type::CPU) { const u64 old = state_words[index]; cpu_delta += enable ? std::popcount(~old & mask) : -std::popcount(old & mask); + } else if (type == Type::GPU) { + const u64 old = state_words[index]; + gpu_delta += enable ? std::popcount(~old & mask) : -std::popcount(old & mask); } if (enable) { state_words[index] |= mask; @@ -152,6 +156,9 @@ struct WordManager { if (cpu_delta != 0) { cpu_modified_pages.fetch_add(static_cast(cpu_delta), std::memory_order_release); } + if (gpu_delta != 0) { + gpu_modified_pages.fetch_add(static_cast(gpu_delta), std::memory_order_release); + } if (!ranges.empty()) { ApplyCollectedRanges(ranges, (!enable) ? 1 : -1); } @@ -180,6 +187,7 @@ struct WordManager { }; std::vector> ranges; s64 cpu_delta = 0; + s64 gpu_delta = 0; IterateWords(offset, size, [&](size_t index, u64 mask) { if (type == Type::GPU) mask &= ~untracked_words[index]; @@ -190,6 +198,8 @@ struct WordManager { } if (type == Type::CPU) cpu_delta -= std::popcount(word); + else if (type == Type::GPU) + gpu_delta -= std::popcount(word); state_words[index] &= ~mask; if (type == Type::CPU || type == Type::CachedCPU) untracked_words[index] &= ~mask; @@ -214,6 +224,9 @@ struct WordManager { if (cpu_delta != 0) { cpu_modified_pages.fetch_add(static_cast(cpu_delta), std::memory_order_release); } + if (gpu_delta != 0) { + gpu_modified_pages.fetch_add(static_cast(gpu_delta), std::memory_order_release); + } if (pending) { release(); } @@ -348,10 +361,15 @@ struct WordManager { return cpu_modified_pages.load(std::memory_order_acquire); } + [[nodiscard]] u32 GpuModifiedPageCount() const noexcept { + return gpu_modified_pages.load(std::memory_order_acquire); + } + std::array heap = {}; DeviceTracker* tracker = nullptr; VAddr cpu_addr = 0; std::atomic cpu_modified_pages{0}; + std::atomic gpu_modified_pages{0}; }; } // namespace VideoCommon diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index e7120fe8bb..b6664f93a4 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -684,9 +684,11 @@ void RasterizerVulkan::FlushRegion(DAddr addr, u64 size, VideoCommon::CacheType bool RasterizerVulkan::MustFlushRegion(DAddr addr, u64 size, VideoCommon::CacheType which) { if ((True(which & VideoCommon::CacheType::BufferCache))) { - std::scoped_lock lock{buffer_cache.mutex}; - if (buffer_cache.IsRegionGpuModified(addr, size)) { - return true; + if (buffer_cache.IsRegionGpuModifiedCheap(addr, size)) { + std::scoped_lock lock{buffer_cache.mutex}; + if (buffer_cache.IsRegionGpuModified(addr, size)) { + return true; + } } } if (!Settings::IsGPULevelHigh()) { From cf76977977584674f797f974a544ee6833ec6684 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 18 Jul 2026 21:28:17 -0400 Subject: [PATCH 21/26] Revert "[TEST] Extend coalescing to more gpu to cpu regions" --- src/video_core/buffer_cache/buffer_cache.h | 8 +------- .../buffer_cache/buffer_cache_base.h | 2 -- .../buffer_cache/memory_tracker_base.h | 18 ------------------ src/video_core/buffer_cache/word_manager.h | 18 ------------------ .../renderer_vulkan/vk_rasterizer.cpp | 8 +++----- 5 files changed, 4 insertions(+), 50 deletions(-) diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 7dbecb4fe1..641e38934b 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -791,11 +791,6 @@ bool BufferCache

::IsRegionGpuModified(DAddr addr, size_t size) { return is_dirty; } -template -bool BufferCache

::IsRegionGpuModifiedCheap(DAddr addr, size_t size) { - return memory_tracker.IsRegionGpuModifiedCheap(addr, size); -} - template bool BufferCache

::IsRegionRegistered(DAddr addr, size_t size) { const DAddr end_addr = addr + size; @@ -1030,8 +1025,7 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 }(); const bool use_fast_buffer = needs_alignment_stream || (has_host_buffer && size <= channel_state->uniform_buffer_skip_cache_size - && !(memory_tracker.IsRegionGpuModifiedCheap(device_addr, size) - && memory_tracker.IsRegionGpuModified(device_addr, size))); + && !memory_tracker.IsRegionGpuModified(device_addr, size)); if (use_fast_buffer) { if constexpr (IS_OPENGL) { if (runtime.HasFastBufferSubData()) { diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index 622f222f2b..3b900b95f9 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -300,8 +300,6 @@ public: /// Return true when a CPU region is modified from the GPU [[nodiscard]] bool IsRegionGpuModified(DAddr addr, size_t size); - [[nodiscard]] bool IsRegionGpuModifiedCheap(DAddr addr, size_t size); - /// Return true when a region is registered on the cache [[nodiscard]] bool IsRegionRegistered(DAddr addr, size_t size); diff --git a/src/video_core/buffer_cache/memory_tracker_base.h b/src/video_core/buffer_cache/memory_tracker_base.h index 329c7e890d..4c9e978484 100644 --- a/src/video_core/buffer_cache/memory_tracker_base.h +++ b/src/video_core/buffer_cache/memory_tracker_base.h @@ -69,24 +69,6 @@ public: return false; } - [[nodiscard]] bool IsRegionGpuModifiedCheap(VAddr query_cpu_addr, u64 query_size) noexcept { - std::size_t remaining_size{query_size}; - std::size_t page_index{query_cpu_addr >> HIGHER_PAGE_BITS}; - u64 page_offset{query_cpu_addr & HIGHER_PAGE_MASK}; - while (remaining_size > 0) { - const std::size_t copy_amount{ - std::min(HIGHER_PAGE_SIZE - page_offset, remaining_size)}; - const Manager* manager = top_tier[page_index].load(std::memory_order_acquire); - if (manager != nullptr && manager->GpuModifiedPageCount() != 0) { - return true; - } - page_index++; - page_offset = 0; - remaining_size -= copy_amount; - } - return false; - } - /// Returns true if a region has been modified from the CPU [[nodiscard]] bool IsRegionCpuModified(VAddr query_cpu_addr, u64 query_size) noexcept { return IteratePages(query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) { diff --git a/src/video_core/buffer_cache/word_manager.h b/src/video_core/buffer_cache/word_manager.h index 0779adbd35..2e8717f195 100644 --- a/src/video_core/buffer_cache/word_manager.h +++ b/src/video_core/buffer_cache/word_manager.h @@ -127,7 +127,6 @@ struct WordManager { [[maybe_unused]] std::span cached_words = Span(Type::CachedCPU); std::vector> ranges; s64 cpu_delta = 0; - s64 gpu_delta = 0; IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) { if (type == Type::CPU || type == Type::CachedCPU) { CollectChangedRanges(!enable, index, untracked_words[index], mask, ranges); @@ -135,9 +134,6 @@ struct WordManager { if (type == Type::CPU) { const u64 old = state_words[index]; cpu_delta += enable ? std::popcount(~old & mask) : -std::popcount(old & mask); - } else if (type == Type::GPU) { - const u64 old = state_words[index]; - gpu_delta += enable ? std::popcount(~old & mask) : -std::popcount(old & mask); } if (enable) { state_words[index] |= mask; @@ -156,9 +152,6 @@ struct WordManager { if (cpu_delta != 0) { cpu_modified_pages.fetch_add(static_cast(cpu_delta), std::memory_order_release); } - if (gpu_delta != 0) { - gpu_modified_pages.fetch_add(static_cast(gpu_delta), std::memory_order_release); - } if (!ranges.empty()) { ApplyCollectedRanges(ranges, (!enable) ? 1 : -1); } @@ -187,7 +180,6 @@ struct WordManager { }; std::vector> ranges; s64 cpu_delta = 0; - s64 gpu_delta = 0; IterateWords(offset, size, [&](size_t index, u64 mask) { if (type == Type::GPU) mask &= ~untracked_words[index]; @@ -198,8 +190,6 @@ struct WordManager { } if (type == Type::CPU) cpu_delta -= std::popcount(word); - else if (type == Type::GPU) - gpu_delta -= std::popcount(word); state_words[index] &= ~mask; if (type == Type::CPU || type == Type::CachedCPU) untracked_words[index] &= ~mask; @@ -224,9 +214,6 @@ struct WordManager { if (cpu_delta != 0) { cpu_modified_pages.fetch_add(static_cast(cpu_delta), std::memory_order_release); } - if (gpu_delta != 0) { - gpu_modified_pages.fetch_add(static_cast(gpu_delta), std::memory_order_release); - } if (pending) { release(); } @@ -361,15 +348,10 @@ struct WordManager { return cpu_modified_pages.load(std::memory_order_acquire); } - [[nodiscard]] u32 GpuModifiedPageCount() const noexcept { - return gpu_modified_pages.load(std::memory_order_acquire); - } - std::array heap = {}; DeviceTracker* tracker = nullptr; VAddr cpu_addr = 0; std::atomic cpu_modified_pages{0}; - std::atomic gpu_modified_pages{0}; }; } // namespace VideoCommon diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index b6664f93a4..e7120fe8bb 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -684,11 +684,9 @@ void RasterizerVulkan::FlushRegion(DAddr addr, u64 size, VideoCommon::CacheType bool RasterizerVulkan::MustFlushRegion(DAddr addr, u64 size, VideoCommon::CacheType which) { if ((True(which & VideoCommon::CacheType::BufferCache))) { - if (buffer_cache.IsRegionGpuModifiedCheap(addr, size)) { - std::scoped_lock lock{buffer_cache.mutex}; - if (buffer_cache.IsRegionGpuModified(addr, size)) { - return true; - } + std::scoped_lock lock{buffer_cache.mutex}; + if (buffer_cache.IsRegionGpuModified(addr, size)) { + return true; } } if (!Settings::IsGPULevelHigh()) { From db708d2ea0a922f202202d517960165181c6ff76 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 18 Jul 2026 21:54:18 -0400 Subject: [PATCH 22/26] [TEST] Remove certain annoying finish on compute pass KEEEEEEEEEEEEEEELLLLLLLLLLEEEEEEEEEEEEEEEEEEEEEEEEEER --- src/video_core/renderer_vulkan/vk_compute_pass.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index 80c434dc8f..594dba4e13 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -613,7 +613,6 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map, 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; From 1330abd2b6eff876b1d4bf2e75b946cc06d95268 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 18 Jul 2026 23:45:01 -0400 Subject: [PATCH 23/26] [TEST] Changes on the ASTC enconding path --- src/video_core/host_shaders/astc_decoder.comp | 96 +++++++++++++------ 1 file changed, 65 insertions(+), 31 deletions(-) diff --git a/src/video_core/host_shaders/astc_decoder.comp b/src/video_core/host_shaders/astc_decoder.comp index ce03e589ad..8fe6abc472 100644 --- a/src/video_core/host_shaders/astc_decoder.comp +++ b/src/video_core/host_shaders/astc_decoder.comp @@ -964,35 +964,70 @@ void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode, ui } uint UnquantizeTexelWeight(EncodingData val) { - uint encoding = Encoding(val), bitlen = NumBits(val), bitval = BitValue(val); - if (encoding == JUST_BITS) { - return (bitlen >= 1 && bitlen <= 5) - ? uint(floor(0.5f + float(bitval) * 64.0f / float((1 << bitlen) - 1))) - : FastReplicateTo6(bitval, bitlen); - } else if (encoding == TRIT || encoding == QUINT) { - uint B = 0, C = 0, D = 0; - uint b_mask = (0x3100 >> (bitlen * 4)) & 0xf; - uint b = (bitval >> 1) & b_mask; + const uint encoding = Encoding(val); + const uint bitlen = NumBits(val); + const uint bitval = BitValue(val); + const uint A = ReplicateBitTo7((bitval & 1)); + uint B = 0, C = 0, D = 0; + uint result = 0; + const uint bitlen_0_results[5] = {0, 16, 32, 48, 64}; + switch (encoding) { + case JUST_BITS: + return FastReplicateTo6(bitval, bitlen); + case TRIT: { D = QuintTritValue(val); - if (encoding == TRIT) { - switch (bitlen) { - case 0: return D * 32; //0,32,64 - case 1: C = 50; break; - case 2: C = 23; B = (b << 6) | (b << 2) | b; break; - case 3: C = 11; B = (b << 5) | b; break; - } - } else if (encoding == QUINT) { - switch (bitlen) { - case 0: return D * 16; //0, 16, 32, 48, 64 - case 1: C = 28; break; - case 2: C = 13; B = (b << 6) | (b << 1); break; - } + switch (bitlen) { + case 0: + return bitlen_0_results[D * 2]; + case 1: { + C = 50; + break; } - uint A = ReplicateBitTo7(bitval & 1); - uint res = (A & 0x20) | (((D * C + B) ^ A) >> 2); - return res + (res > 32 ? 1 : 0); + case 2: { + C = 23; + const uint b = (bitval >> 1) & 1; + B = (b << 6) | (b << 2) | b; + break; + } + case 3: { + C = 11; + const uint cb = (bitval >> 1) & 3; + B = (cb << 5) | cb; + break; + } + default: + break; + } + break; } - return 0; + case QUINT: { + D = QuintTritValue(val); + switch (bitlen) { + case 0: + return bitlen_0_results[D]; + case 1: { + C = 28; + break; + } + case 2: { + C = 13; + const uint b = (bitval >> 1) & 1; + B = (b << 6) | (b << 1); + break; + } + } + break; + } + } + if (encoding != JUST_BITS && bitlen > 0) { + result = D * C + B; + result ^= A; + result = (A & 0x20) | (result >> 2); + } + if (result > 32) { + result += 1; + } + return result; } void UnquantizeTexelWeights(uvec2 size, bool is_dual_plane) { @@ -1394,11 +1429,10 @@ void DecompressBlock(ivec3 coord) { } uint SwizzleOffset(uvec2 pos) { - return ((pos.x & 32u) << 3u) | - ((pos.y & 6u) << 5u) | - ((pos.x & 16u) << 1u) | - ((pos.y & 1u) << 4u) | - (pos.x & 15u); + const uint x = pos.x; + const uint y = pos.y; + return ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + + ((x % 32) / 16) * 32 + (y % 2) * 16 + (x % 16); } void main() { From f67a300a69488717cd70e57ee693121a42dde10c Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 19 Jul 2026 01:00:09 -0400 Subject: [PATCH 24/26] [TEST] ASTC to fragment --- src/video_core/host_shaders/CMakeLists.txt | 2 + src/video_core/host_shaders/astc_decoder.frag | 1449 +++++++++++++++++ src/video_core/host_shaders/astc_decoder.vert | 19 + .../renderer_vulkan/vk_compute_pass.cpp | 393 +++++ .../renderer_vulkan/vk_compute_pass.h | 41 + .../renderer_vulkan/vk_texture_cache.cpp | 14 + .../renderer_vulkan/vk_texture_cache.h | 1 + 7 files changed, 1919 insertions(+) create mode 100644 src/video_core/host_shaders/astc_decoder.frag create mode 100644 src/video_core/host_shaders/astc_decoder.vert diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt index 0d342c463d..59cd9efe4d 100644 --- a/src/video_core/host_shaders/CMakeLists.txt +++ b/src/video_core/host_shaders/CMakeLists.txt @@ -15,6 +15,8 @@ set(GLSL_INCLUDES set(SHADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/astc_decoder.comp + ${CMAKE_CURRENT_SOURCE_DIR}/astc_decoder.frag + ${CMAKE_CURRENT_SOURCE_DIR}/astc_decoder.vert ${CMAKE_CURRENT_SOURCE_DIR}/blit_color_float.frag ${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_2d.comp ${CMAKE_CURRENT_SOURCE_DIR}/blit_color_msaa.frag diff --git a/src/video_core/host_shaders/astc_decoder.frag b/src/video_core/host_shaders/astc_decoder.frag new file mode 100644 index 0000000000..64fecfb557 --- /dev/null +++ b/src/video_core/host_shaders/astc_decoder.frag @@ -0,0 +1,1449 @@ +// 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 + +#version 450 + +#ifdef VULKAN + +#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants { +#define END_PUSH_CONSTANTS }; +#define UNIFORM(n) +#define BINDING_INPUT_BUFFER 0 +#define BINDING_OUTPUT_IMAGE 1 + +#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv + +#define BEGIN_PUSH_CONSTANTS +#define END_PUSH_CONSTANTS +#define UNIFORM(n) layout(location = n) uniform +#define BINDING_INPUT_BUFFER 0 +#define BINDING_OUTPUT_IMAGE 0 + +#endif + +BEGIN_PUSH_CONSTANTS +UNIFORM(1) uvec2 block_dims; +UNIFORM(2) uint layer_stride; +UNIFORM(3) uint block_size; +UNIFORM(4) uint x_shift; +UNIFORM(5) uint block_height; +UNIFORM(6) uint block_height_mask; +UNIFORM(7) uint dest_layer; +END_PUSH_CONSTANTS + +struct EncodingData { + uint data; +}; + +layout(binding = BINDING_INPUT_BUFFER, std430) readonly restrict buffer InputBufferU32 { + uvec4 astc_data[]; +}; + +layout(location = 0) out vec4 frag_color; + +const uint GOB_SIZE_X_SHIFT = 6; +const uint GOB_SIZE_Y_SHIFT = 3; +const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT; + +const uint BYTES_PER_BLOCK_LOG2 = 4; + +const uint JUST_BITS = 0u; +const uint QUINT = 1u; +const uint TRIT = 2u; + +// ASTC Encodings data, sorted in ascending order based on their BitLength value +// (see GetBitLength() function) +const uint encoding_values[22] = uint[]( + (JUST_BITS), (JUST_BITS | (1u << 8u)), (TRIT), (JUST_BITS | (2u << 8u)), + (QUINT), (TRIT | (1u << 8u)), (JUST_BITS | (3u << 8u)), (QUINT | (1u << 8u)), + (TRIT | (2u << 8u)), (JUST_BITS | (4u << 8u)), (QUINT | (2u << 8u)), (TRIT | (3u << 8u)), + (JUST_BITS | (5u << 8u)), (QUINT | (3u << 8u)), (TRIT | (4u << 8u)), (JUST_BITS | (6u << 8u)), + (QUINT | (4u << 8u)), (TRIT | (5u << 8u)), (JUST_BITS | (7u << 8u)), (QUINT | (5u << 8u)), + (TRIT | (6u << 8u)), (JUST_BITS | (8u << 8u))); + +// Input ASTC texture globals +int total_bitsread = 0; +uvec4 local_buff; +ivec2 g_sub; + +// Color data globals +uvec4 color_endpoint_data; +int color_bitsread = 0; + +// Global "vector" to be pushed into when decoding +// At most will require BLOCK_WIDTH x BLOCK_HEIGHT in single plane mode +// At most will require BLOCK_WIDTH x BLOCK_HEIGHT x 2 in dual plane mode +// So the maximum would be 144 (12 x 12) elements, x 2 for two planes +#define DIVCEIL(number, divisor) (number + divisor - 1) / divisor +#define ARRAY_NUM_ELEMENTS 144 +#define VECTOR_ARRAY_SIZE DIVCEIL(ARRAY_NUM_ELEMENTS * 2, 4) +uint result_vector[ARRAY_NUM_ELEMENTS * 2]; + +int result_index = 0; +uint result_vector_max_index; +bool result_limit_reached = false; + +// EncodingData helpers +uint Encoding(EncodingData val) { + return bitfieldExtract(val.data, 0, 8); +} +uint NumBits(EncodingData val) { + return bitfieldExtract(val.data, 8, 8); +} +uint BitValue(EncodingData val) { + return bitfieldExtract(val.data, 16, 8); +} +uint QuintTritValue(EncodingData val) { + return bitfieldExtract(val.data, 24, 8); +} + +void Encoding(inout EncodingData val, uint v) { + val.data = bitfieldInsert(val.data, v, 0, 8); +} +void NumBits(inout EncodingData val, uint v) { + val.data = bitfieldInsert(val.data, v, 8, 8); +} +void BitValue(inout EncodingData val, uint v) { + val.data = bitfieldInsert(val.data, v, 16, 8); +} +void QuintTritValue(inout EncodingData val, uint v) { + val.data = bitfieldInsert(val.data, v, 24, 8); +} + +EncodingData CreateEncodingData(uint encoding, uint num_bits, uint bit_val, uint quint_trit_val) { + return EncodingData(((encoding) << 0u) | ((num_bits) << 8u) | + ((bit_val) << 16u) | ((quint_trit_val) << 24u)); +} + + +void ResultEmplaceBack(EncodingData val) { + if (result_index >= result_vector_max_index) { + // Alert callers to avoid decoding more than needed by this phase + result_limit_reached = true; + return; + } + result_vector[result_index] = val.data; + ++result_index; +} + +uvec4 ReplicateByteTo16(uvec4 value) { + return value * 0x101; +} + +uint ReplicateBitTo7(uint value) { + return value * 127; +} + +uint ReplicateBitTo9(uint value) { + return value * 511; +} + +uint ReplicateBits(uint value, uint num_bits, uint to_bit) { + if (value == 0 || num_bits == 0) { + return 0; + } + if (num_bits >= to_bit) { + return value; + } + const uint v = value & uint((1 << num_bits) - 1); + uint res = v; + uint reslen = num_bits; + while (reslen < to_bit) { + const uint num_dst_bits_to_shift_up = min(num_bits, to_bit - reslen); + const uint num_src_bits_to_shift_down = num_bits - num_dst_bits_to_shift_up; + + res <<= num_dst_bits_to_shift_up; + res |= (v >> num_src_bits_to_shift_down); + reslen += num_bits; + } + return res; +} + +uint FastReplicateTo8(uint value, uint num_bits) { + return ReplicateBits(value, num_bits, 8); +} + +uint FastReplicateTo6(uint value, uint num_bits) { + return ReplicateBits(value, num_bits, 6); +} + +uint Div3Floor(uint v) { + return (v * 0x5556) >> 16; +} + +uint Div3Ceil(uint v) { + return Div3Floor(v + 2); +} + +uint Div5Floor(uint v) { + return (v * 0x3334) >> 16; +} + +uint Div5Ceil(uint v) { + return Div5Floor(v + 4); +} + +uint Hash52(uint p) { + p ^= p >> 15; + p -= p << 17; + p += p << 7; + p += p << 4; + p ^= p >> 5; + p += p << 16; + p ^= p >> 7; + p ^= p >> 3; + p ^= p << 6; + p ^= p >> 17; + return p; +} + +uint Select2DPartition(uint seed, uint x, uint y, uint partition_count) { + if ((block_dims.y * block_dims.x) < 32) { + x <<= 1; + y <<= 1; + } + + seed += (partition_count - 1) * 1024; + + const uint rnum = Hash52(uint(seed)); + uint seed1 = uint(rnum & 0xF); + uint seed2 = uint((rnum >> 4) & 0xF); + uint seed3 = uint((rnum >> 8) & 0xF); + uint seed4 = uint((rnum >> 12) & 0xF); + uint seed5 = uint((rnum >> 16) & 0xF); + uint seed6 = uint((rnum >> 20) & 0xF); + uint seed7 = uint((rnum >> 24) & 0xF); + uint seed8 = uint((rnum >> 28) & 0xF); + + seed1 = (seed1 * seed1); + seed2 = (seed2 * seed2); + seed3 = (seed3 * seed3); + seed4 = (seed4 * seed4); + seed5 = (seed5 * seed5); + seed6 = (seed6 * seed6); + seed7 = (seed7 * seed7); + seed8 = (seed8 * seed8); + + uint sh1, sh2; + if ((seed & 1) > 0) { + sh1 = (seed & 2) > 0 ? 4 : 5; + sh2 = (partition_count == 3) ? 6 : 5; + } else { + sh1 = (partition_count == 3) ? 6 : 5; + sh2 = (seed & 2) > 0 ? 4 : 5; + } + seed1 >>= sh1; + seed2 >>= sh2; + seed3 >>= sh1; + seed4 >>= sh2; + seed5 >>= sh1; + seed6 >>= sh2; + seed7 >>= sh1; + seed8 >>= sh2; + + uint a = seed1 * x + seed2 * y + (rnum >> 14); + uint b = seed3 * x + seed4 * y + (rnum >> 10); + uint c = seed5 * x + seed6 * y + (rnum >> 6); + uint d = seed7 * x + seed8 * y + (rnum >> 2); + + a &= 0x3F; + b &= 0x3F; + c &= 0x3F; + d &= 0x3F; + + if (partition_count < 4) { + d = 0; + } + if (partition_count < 3) { + c = 0; + } + + if (a >= b && a >= c && a >= d) { + return 0; + } else if (b >= c && b >= d) { + return 1; + } else if (c >= d) { + return 2; + } else { + return 3; + } +} + +uint ExtractBits(uvec4 payload, int offset, int bits) { + if (bits <= 0) { + return 0; + } + if (bits > 32) { + return 0; + } + const int last_offset = offset + bits - 1; + const int shifted_offset = offset >> 5; + if ((last_offset >> 5) == shifted_offset) { + return bitfieldExtract(payload[shifted_offset], offset & 31, bits); + } + const int first_bits = 32 - (offset & 31); + const int result_first = int(bitfieldExtract(payload[shifted_offset], offset & 31, first_bits)); + const int result_second = int(bitfieldExtract(payload[shifted_offset + 1], 0, bits - first_bits)); + return result_first | (result_second << first_bits); +} + +uint StreamBits(uint num_bits) { + const int int_bits = int(num_bits); + const uint ret = ExtractBits(local_buff, total_bitsread, int_bits); + total_bitsread += int_bits; + return ret; +} + +void SkipBits(uint num_bits) { + const int int_bits = int(num_bits); + total_bitsread += int_bits; +} + +uint StreamColorBits(uint num_bits) { + const int int_bits = int(num_bits); + const uint ret = ExtractBits(color_endpoint_data, color_bitsread, int_bits); + color_bitsread += int_bits; + return ret; +} + +EncodingData GetEncodingFromVector(uint index) { + const uint data = result_vector[index]; + return EncodingData(data); +} + +// Returns the number of bits required to encode n_vals values. +uint GetBitLength(uint n_vals, uint encoding_index) { + const EncodingData encoding_value = EncodingData(encoding_values[encoding_index]); + const uint encoding = Encoding(encoding_value); + uint total_bits = NumBits(encoding_value) * n_vals; + if (encoding == TRIT) { + total_bits += Div5Ceil(n_vals * 8); + } else if (encoding == QUINT) { + total_bits += Div3Ceil(n_vals * 7); + } + return total_bits; +} + +uint GetNumWeightValues(uvec2 size, bool dual_plane) { + uint n_vals = size.x * size.y; + if (dual_plane) { + n_vals *= 2; + } + return n_vals; +} + +uint GetPackedBitSize(uvec2 size, bool dual_plane, uint max_weight) { + const uint n_vals = GetNumWeightValues(size, dual_plane); + return GetBitLength(n_vals, max_weight); +} + +uint BitsBracket(uint bits, uint pos) { + return ((bits >> pos) & 1); +} + +uint BitsOp(uint bits, uint start, uint end) { + const uint mask = (1 << (end - start + 1)) - 1; + return ((bits >> start) & mask); +} + +void DecodeQuintBlock(uint num_bits) { + uvec3 m; + uvec4 qQ; + m[0] = StreamColorBits(num_bits); + qQ.w = StreamColorBits(3); + m[1] = StreamColorBits(num_bits); + qQ.w |= StreamColorBits(2) << 3; + m[2] = StreamColorBits(num_bits); + qQ.w |= StreamColorBits(2) << 5; + if (BitsOp(qQ.w, 1, 2) == 3 && BitsOp(qQ.w, 5, 6) == 0) { + qQ.x = 4; + qQ.y = 4; + qQ.z = (BitsBracket(qQ.w, 0) << 2) | ((BitsBracket(qQ.w, 4) & ~BitsBracket(qQ.w, 0)) << 1) | + (BitsBracket(qQ.w, 3) & ~BitsBracket(qQ.w, 0)); + } else { + uint C = 0; + if (BitsOp(qQ.w, 1, 2) == 3) { + qQ.z = 4; + C = (BitsOp(qQ.w, 3, 4) << 3) | ((~BitsOp(qQ.w, 5, 6) & 3) << 1) | BitsBracket(qQ.w, 0); + } else { + qQ.z = BitsOp(qQ.w, 5, 6); + C = BitsOp(qQ.w, 0, 4); + } + if (BitsOp(C, 0, 2) == 5) { + qQ.y = 4; + qQ.x = BitsOp(C, 3, 4); + } else { + qQ.y = BitsOp(C, 3, 4); + qQ.x = BitsOp(C, 0, 2); + } + } + for (uint i = 0; i < 3; i++) { + const EncodingData val = CreateEncodingData(QUINT, num_bits, m[i], qQ[i]); + ResultEmplaceBack(val); + } +} + +void DecodeTritBlock(uint num_bits) { + uvec4 m; + uvec4 t; + uvec3 Tm5t5; + m[0] = StreamColorBits(num_bits); + Tm5t5.x = StreamColorBits(2); + m[1] = StreamColorBits(num_bits); + Tm5t5.x |= StreamColorBits(2) << 2; + m[2] = StreamColorBits(num_bits); + Tm5t5.x |= StreamColorBits(1) << 4; + m[3] = StreamColorBits(num_bits); + Tm5t5.x |= StreamColorBits(2) << 5; + Tm5t5.y = StreamColorBits(num_bits); + Tm5t5.x |= StreamColorBits(1) << 7; + uint C = 0; + if (BitsOp(Tm5t5.x, 2, 4) == 7) { + C = (BitsOp(Tm5t5.x, 5, 7) << 2) | BitsOp(Tm5t5.x, 0, 1); + Tm5t5.z = 2; + t[3] = 2; + } else { + C = BitsOp(Tm5t5.x, 0, 4); + if (BitsOp(Tm5t5.x, 5, 6) == 3) { + Tm5t5.z = 2; + t[3] = BitsBracket(Tm5t5.x, 7); + } else { + Tm5t5.z = BitsBracket(Tm5t5.x, 7); + t[3] = BitsOp(Tm5t5.x, 5, 6); + } + } + if (BitsOp(C, 0, 1) == 3) { + t[2] = 2; + t[1] = BitsBracket(C, 4); + t[0] = (BitsBracket(C, 3) << 1) | (BitsBracket(C, 2) & ~BitsBracket(C, 3)); + } else if (BitsOp(C, 2, 3) == 3) { + t[2] = 2; + t[1] = 2; + t[0] = BitsOp(C, 0, 1); + } else { + t[2] = BitsBracket(C, 4); + t[1] = BitsOp(C, 2, 3); + t[0] = (BitsBracket(C, 1) << 1) | (BitsBracket(C, 0) & ~BitsBracket(C, 1)); + } + for (uint i = 0; i < 4; i++) { + const EncodingData val = CreateEncodingData(TRIT, num_bits, m[i], t[i]); + ResultEmplaceBack(val); + } + const EncodingData val = CreateEncodingData(TRIT, num_bits, Tm5t5.y, Tm5t5.z); + ResultEmplaceBack(val); +} + +void DecodeIntegerSequence(uint max_range, uint num_values) { + EncodingData val = EncodingData(encoding_values[max_range]); + const uint encoding = Encoding(val); + const uint num_bits = NumBits(val); + uint vals_decoded = 0; + while (vals_decoded < num_values && !result_limit_reached) { + switch (encoding) { + case QUINT: + DecodeQuintBlock(num_bits); + vals_decoded += 3; + break; + case TRIT: + DecodeTritBlock(num_bits); + vals_decoded += 5; + break; + case JUST_BITS: + BitValue(val, StreamColorBits(num_bits)); + ResultEmplaceBack(val); + vals_decoded++; + break; + } + } +} + +void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits, out uint color_values[32]) { + uint num_values = 0; + for (uint i = 0; i < num_partitions; i++) { + num_values += ((modes[i] >> 2) + 1) << 1; + } + // Find the largest encoding that's within color_data_bits + // TODO(ameerj): profile with binary search + int range = 0; + while (++range < encoding_values.length()) { + const uint bit_length = GetBitLength(num_values, range); + if (bit_length > color_data_bits) { + break; + } + } + DecodeIntegerSequence(range - 1, num_values); + uint out_index = 0; + for (int itr = 0; itr < result_index; ++itr) { + if (out_index >= num_values) { + break; + } + const EncodingData val = GetEncodingFromVector(itr); + const uint encoding = Encoding(val); + const uint bitlen = NumBits(val); + const uint bitval = BitValue(val); + uint A = 0, B = 0, C = 0, D = 0; + A = ReplicateBitTo9((bitval & 1)); + switch (encoding) { + case JUST_BITS: + color_values[++out_index] = FastReplicateTo8(bitval, bitlen); + break; + case TRIT: { + D = QuintTritValue(val); + switch (bitlen) { + case 1: + C = 204; + break; + case 2: { + C = 93; + const uint b = (bitval >> 1) & 1; + B = (b << 8) | (b << 4) | (b << 2) | (b << 1); + break; + } + case 3: { + C = 44; + const uint cb = (bitval >> 1) & 3; + B = (cb << 7) | (cb << 2) | cb; + break; + } + case 4: { + C = 22; + const uint dcb = (bitval >> 1) & 7; + B = (dcb << 6) | dcb; + break; + } + case 5: { + C = 11; + const uint edcb = (bitval >> 1) & 0xF; + B = (edcb << 5) | (edcb >> 2); + break; + } + case 6: { + C = 5; + const uint fedcb = (bitval >> 1) & 0x1F; + B = (fedcb << 4) | (fedcb >> 4); + break; + } + } + break; + } + case QUINT: { + D = QuintTritValue(val); + switch (bitlen) { + case 1: + C = 113; + break; + case 2: { + C = 54; + const uint b = (bitval >> 1) & 1; + B = (b << 8) | (b << 3) | (b << 2); + break; + } + case 3: { + C = 26; + const uint cb = (bitval >> 1) & 3; + B = (cb << 7) | (cb << 1) | (cb >> 1); + break; + } + case 4: { + C = 13; + const uint dcb = (bitval >> 1) & 7; + B = (dcb << 6) | (dcb >> 1); + break; + } + case 5: { + C = 6; + const uint edcb = (bitval >> 1) & 0xF; + B = (edcb << 5) | (edcb >> 3); + break; + } + } + break; + } + } + if (encoding != JUST_BITS) { + uint T = (D * C) + B; + T ^= A; + T = (A & 0x80) | (T >> 2); + color_values[++out_index] = T; + } + } +} + +ivec2 BitTransferSigned(int a, int b) { + ivec2 transferred; + transferred.y = b >> 1; + transferred.y |= a & 0x80; + transferred.x = a >> 1; + transferred.x &= 0x3F; + if ((transferred.x & 0x20) > 0) { + transferred.x -= 0x40; + } + return transferred; +} + +uvec4 ClampByte(ivec4 color) { + return uvec4(clamp(color, 0, 255)); +} + +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; +} + +int SignExtend(int value, uint nbits) { + int sign_bit = 1 << (nbits - 1u); + return (value ^ sign_bit) - sign_bit; +} + +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)); +} + +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) \ + uvec4 V[2]; \ + for (uint i = 0; i < N; i++) { \ + V[i / 4][i % 4] = color_values[++colvals_index]; \ + } +#define READ_INT_VALUES(N) \ + ivec4 V[2]; \ + for (uint i = 0; i < N; i++) { \ + V[i / 4][i % 4] = int(color_values[++colvals_index]); \ + } + + switch (color_endpoint_mode) { + case 0: { + READ_UINT_VALUES(2) + ep1 = uvec4(0xFF, V[0].x, V[0].x, V[0].x); + ep2 = uvec4(0xFF, V[0].y, V[0].y, V[0].y); + break; + } + case 1: { + READ_UINT_VALUES(2) + const uint L0 = (V[0].x >> 2) | (V[0].y & 0xC0); + const uint L1 = min(L0 + (V[0].y & 0x3F), 0xFFU); + ep1 = uvec4(0xFF, L0, L0, L0); + ep2 = uvec4(0xFF, L1, L1, L1); + break; + } + case 4: { + READ_UINT_VALUES(4) + ep1 = uvec4(V[0].z, V[0].x, V[0].x, V[0].x); + ep2 = uvec4(V[0].w, V[0].y, V[0].y, V[0].y); + break; + } + case 5: { + READ_INT_VALUES(4) + ivec2 transferred = BitTransferSigned(V[0].y, V[0].x); + V[0].y = transferred.x; + V[0].x = transferred.y; + transferred = BitTransferSigned(V[0].w, V[0].z); + V[0].w = transferred.x; + V[0].z = transferred.y; + ep1 = ClampByte(ivec4(V[0].z, V[0].x, V[0].x, V[0].x)); + ep2 = ClampByte(ivec4(V[0].z + V[0].w, V[0].x + V[0].y, V[0].x + V[0].y, V[0].x + V[0].y)); + break; + } + case 6: { + READ_UINT_VALUES(4) + ep1 = uvec4(0xFF, (V[0].x * V[0].w) >> 8, (V[0].y * V[0].w) >> 8, (V[0].z * V[0].w) >> 8); + ep2 = uvec4(0xFF, V[0].x, V[0].y, V[0].z); + break; + } + case 8: { + READ_UINT_VALUES(6) + if ((V[0].y + V[0].w + V[1].y) >= (V[0].x + V[0].z + V[1].x)) { + ep1 = uvec4(0xFF, V[0].x, V[0].z, V[1].x); + ep2 = uvec4(0xFF, V[0].y, V[0].w, V[1].y); + } else { + ep1 = uvec4(BlueContract(0xFF, int(V[0].y), int(V[0].w), int(V[1].y))); + ep2 = uvec4(BlueContract(0xFF, int(V[0].x), int(V[0].z), int(V[1].x))); + } + break; + } + case 9: { + READ_INT_VALUES(6) + ivec2 transferred = BitTransferSigned(V[0].y, V[0].x); + V[0].y = transferred.x; + V[0].x = transferred.y; + transferred = BitTransferSigned(V[0].w, V[0].z); + V[0].w = transferred.x; + V[0].z = transferred.y; + transferred = BitTransferSigned(V[1].y, V[1].x); + V[1].y = transferred.x; + V[1].x = transferred.y; + if ((V[0].y + V[0].w + V[1].y) >= 0) { + ep1 = ClampByte(ivec4(0xFF, V[0].x, V[0].z, V[1].x)); + ep2 = ClampByte(ivec4(0xFF, V[0].x + V[0].y, V[0].z + V[0].w, V[1].x + V[1].y)); + } else { + ep1 = ClampByte(BlueContract(0xFF, V[0].x + V[0].y, V[0].z + V[0].w, V[1].x + V[1].y)); + ep2 = ClampByte(BlueContract(0xFF, V[0].x, V[0].z, V[1].x)); + } + break; + } + case 10: { + READ_UINT_VALUES(6) + ep1 = uvec4(V[1].x, (V[0].x * V[0].w) >> 8, (V[0].y * V[0].w) >> 8, (V[0].z * V[0].w) >> 8); + ep2 = uvec4(V[1].y, V[0].x, V[0].y, V[0].z); + break; + } + case 12: { + READ_UINT_VALUES(8) + if ((V[0].y + V[0].w + V[1].y) >= (V[0].x + V[0].z + V[1].x)) { + ep1 = uvec4(V[1].z, V[0].x, V[0].z, V[1].x); + ep2 = uvec4(V[1].w, V[0].y, V[0].w, V[1].y); + } else { + ep1 = uvec4(BlueContract(int(V[1].w), int(V[0].y), int(V[0].w), int(V[1].y))); + ep2 = uvec4(BlueContract(int(V[1].z), int(V[0].x), int(V[0].z), int(V[1].x))); + } + break; + } + case 13: { + READ_INT_VALUES(8) + ivec2 transferred = BitTransferSigned(V[0].y, V[0].x); + V[0].y = transferred.x; + V[0].x = transferred.y; + transferred = BitTransferSigned(V[0].w, V[0].z); + V[0].w = transferred.x; + V[0].z = transferred.y; + + transferred = BitTransferSigned(V[1].y, V[1].x); + V[1].y = transferred.x; + V[1].x = transferred.y; + + transferred = BitTransferSigned(V[1].w, V[1].z); + V[1].w = transferred.x; + V[1].z = transferred.y; + + if ((V[0].y + V[0].w + V[1].y) >= 0) { + ep1 = ClampByte(ivec4(V[1].z, V[0].x, V[0].z, V[1].x)); + ep2 = ClampByte(ivec4(V[1].w + V[1].z, V[0].x + V[0].y, V[0].z + V[0].w, V[1].x + V[1].y)); + } else { + ep1 = ClampByte(BlueContract(V[1].z + V[1].w, V[0].x + V[0].y, V[0].z + V[0].w, V[1].x + V[1].y)); + ep2 = ClampByte(BlueContract(V[1].z, V[0].x, V[0].z, V[1].x)); + } + 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); + 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: { + ep1 = uvec4(0xFF, 0xFF, 0, 0); + ep2 = uvec4(0xFF, 0xFF, 0, 0); + break; + } + } +#undef READ_UINT_VALUES +#undef READ_INT_VALUES +} + +uint UnquantizeTexelWeight(EncodingData val) { + const uint encoding = Encoding(val); + const uint bitlen = NumBits(val); + const uint bitval = BitValue(val); + const uint A = ReplicateBitTo7((bitval & 1)); + uint B = 0, C = 0, D = 0; + uint result = 0; + const uint bitlen_0_results[5] = {0, 16, 32, 48, 64}; + switch (encoding) { + case JUST_BITS: + return FastReplicateTo6(bitval, bitlen); + case TRIT: { + D = QuintTritValue(val); + switch (bitlen) { + case 0: + return bitlen_0_results[D * 2]; + case 1: { + C = 50; + break; + } + case 2: { + C = 23; + const uint b = (bitval >> 1) & 1; + B = (b << 6) | (b << 2) | b; + break; + } + case 3: { + C = 11; + const uint cb = (bitval >> 1) & 3; + B = (cb << 5) | cb; + break; + } + default: + break; + } + break; + } + case QUINT: { + D = QuintTritValue(val); + switch (bitlen) { + case 0: + return bitlen_0_results[D]; + case 1: { + C = 28; + break; + } + case 2: { + C = 13; + const uint b = (bitval >> 1) & 1; + B = (b << 6) | (b << 1); + break; + } + } + break; + } + } + if (encoding != JUST_BITS && bitlen > 0) { + result = D * C + B; + result ^= A; + result = (A & 0x20) | (result >> 2); + } + if (result > 32) { + result += 1; + } + return result; +} + +void UnquantizeTexelWeights(uvec2 size, bool is_dual_plane) { + const uint num_planes = is_dual_plane ? 2 : 1; + const uint area = size.x * size.y; + const uint loop_count = min(result_index, area * num_planes); + for (uint itr = 0; itr < loop_count; ++itr) { + result_vector[itr] = + UnquantizeTexelWeight(GetEncodingFromVector(itr)); + } +} + +uint GetUnquantizedTexelWeight(uint offset_base, uint plane, bool is_dual_plane) { + const uint offset = is_dual_plane ? 2 * offset_base + plane : offset_base; + return result_vector[offset]; +} + +uvec4 GetUnquantizedWeightVector(uint t, uint s, uvec2 size, uint plane_index, bool is_dual_plane) { + const uint Ds = uint((block_dims.x * 0.5f + 1024) / (block_dims.x - 1)); + const uint Dt = uint((block_dims.y * 0.5f + 1024) / (block_dims.y - 1)); + const uint area = size.x * size.y; + + const uint cs = Ds * s; + const uint ct = Dt * t; + const uint gs = (cs * (size.x - 1) + 32) >> 6; + const uint gt = (ct * (size.y - 1) + 32) >> 6; + const uint js = gs >> 4; + const uint fs = gs & 0xF; + const uint jt = gt >> 4; + const uint ft = gt & 0x0F; + const uint w11 = (fs * ft + 8) >> 4; + const uint w10 = ft - w11; + const uint w01 = fs - w11; + const uint w00 = 16 - fs - ft + w11; + const uvec4 w = uvec4(w00, w01, w10, w11); + const uint v0 = jt * size.x + js; + + uvec4 p0 = uvec4(0); + uvec4 p1 = uvec4(0); + + if (v0 < area) { + const uint offset_base = v0; + p0.x = GetUnquantizedTexelWeight(offset_base, 0, is_dual_plane); + p1.x = GetUnquantizedTexelWeight(offset_base, 1, is_dual_plane); + } + if ((v0 + 1) < (area)) { + const uint offset_base = v0 + 1; + p0.y = GetUnquantizedTexelWeight(offset_base, 0, is_dual_plane); + p1.y = GetUnquantizedTexelWeight(offset_base, 1, is_dual_plane); + } + if ((v0 + size.x) < (area)) { + const uint offset_base = v0 + size.x; + p0.z = GetUnquantizedTexelWeight(offset_base, 0, is_dual_plane); + p1.z = GetUnquantizedTexelWeight(offset_base, 1, is_dual_plane); + } + if ((v0 + size.x + 1) < (area)) { + const uint offset_base = v0 + size.x + 1; + p0.w = GetUnquantizedTexelWeight(offset_base, 0, is_dual_plane); + p1.w = GetUnquantizedTexelWeight(offset_base, 1, is_dual_plane); + } + + const uint primary_weight = (uint(dot(p0, w)) + 8) >> 4; + + uvec4 weight_vec = uvec4(primary_weight); + + if (is_dual_plane) { + const uint secondary_weight = (uint(dot(p1, w)) + 8) >> 4; + for (uint c = 0; c < 4; c++) { + const bool is_secondary = ((plane_index + 1u) & 3u) == c; + weight_vec[c] = is_secondary ? secondary_weight : primary_weight; + } + } + return weight_vec; +} + +int FindLayout(uint mode) { + if ((mode & 3) != 0) { + if ((mode & 8) != 0) { + if ((mode & 4) != 0) { + if ((mode & 0x100) != 0) { + return 4; + } + return 3; + } + return 2; + } + if ((mode & 4) != 0) { + return 1; + } + return 0; + } + if ((mode & 0x100) != 0) { + if ((mode & 0x80) != 0) { + if ((mode & 0x20) != 0) { + return 8; + } + return 7; + } + return 9; + } + if ((mode & 0x80) != 0) { + return 6; + } + return 5; +} + + +void FillError(ivec3 coord) { + frag_color = vec4(0.0, 0.0, 0.0, 0.0); +} + +void FillVoidExtentLDR(ivec3 coord) { + SkipBits(52); + const uint r_u = StreamBits(16); + const uint g_u = StreamBits(16); + const uint b_u = StreamBits(16); + const uint a_u = StreamBits(16); + const float a = float(a_u) / 65535.0f; + const float r = float(r_u) / 65535.0f; + const float g = float(g_u) / 65535.0f; + const float b = float(b_u) / 65535.0f; + frag_color = vec4(r, g, b, a); +} + +bool IsError(uint mode) { + if ((mode & 0x1ff) == 0x1fc) { + if ((mode & 0x200) != 0) { + // params.void_extent_hdr = true; + return true; + } + if ((mode & 0x400) == 0 || StreamBits(1) == 0) { + return true; + } + return false; + } + if ((mode & 0xf) == 0) { + return true; + } + if ((mode & 3) == 0 && (mode & 0x1c0) == 0x1c0) { + return true; + } + return false; +} + +uvec2 DecodeBlockSize(uint mode) { + uint A, B; + switch (FindLayout(mode)) { + case 0: + A = (mode >> 5) & 0x3; + B = (mode >> 7) & 0x3; + return uvec2(B + 4, A + 2); + case 1: + A = (mode >> 5) & 0x3; + B = (mode >> 7) & 0x3; + return uvec2(B + 8, A + 2); + case 2: + A = (mode >> 5) & 0x3; + B = (mode >> 7) & 0x3; + return uvec2(A + 2, B + 8); + case 3: + A = (mode >> 5) & 0x3; + B = (mode >> 7) & 0x1; + return uvec2(A + 2, B + 6); + case 4: + A = (mode >> 5) & 0x3; + B = (mode >> 7) & 0x1; + return uvec2(B + 2, A + 2); + case 5: + A = (mode >> 5) & 0x3; + return uvec2(12, A + 2); + case 6: + A = (mode >> 5) & 0x3; + return uvec2(A + 2, 12); + case 7: + return uvec2(6, 10); + case 8: + return uvec2(10, 6); + case 9: + A = (mode >> 5) & 0x3; + B = (mode >> 9) & 0x3; + return uvec2(A + 6, B + 6); + default: + return uvec2(0); + } +} + +uint DecodeMaxWeight(uint mode) { + const uint mode_layout = FindLayout(mode); + uint weight_index = (mode & 0x10) != 0 ? 1 : 0; + if (mode_layout < 5) { + weight_index |= (mode & 0x3) << 1; + } else { + weight_index |= (mode & 0xc) >> 1; + } + weight_index -= 2; + if ((mode_layout != 9) && ((mode & 0x200) != 0)) { + weight_index += 6; + } + return weight_index + 1; +} + +void DecompressBlock(ivec3 coord) { + uint mode = StreamBits(11); + if (IsError(mode)) { + FillError(coord); + return; + } + if ((mode & 0x1ff) == 0x1fc) { + // params.void_extent_ldr = true; + FillVoidExtentLDR(coord); + return; + } + const uvec2 size_params = DecodeBlockSize(mode); + if ((size_params.x > block_dims.x) || (size_params.y > block_dims.y)) { + FillError(coord); + return; + } + const uint num_partitions = StreamBits(2) + 1; + const uint mode_layout = FindLayout(mode); + const bool dual_plane = (mode_layout != 9) && ((mode & 0x400) != 0); + if (num_partitions > 4 || (num_partitions == 4 && dual_plane)) { + FillError(coord); + return; + } + uint partition_index = 1; + uvec4 color_endpoint_mode = uvec4(0); + uint ced_pointer = 0; + uint base_cem = 0; + if (num_partitions == 1) { + color_endpoint_mode.x = StreamBits(4); + partition_index = 0; + } else { + partition_index = StreamBits(10); + base_cem = StreamBits(6); + } + const uint base_mode = base_cem & 3; + const uint max_weight = DecodeMaxWeight(mode); + const uint weight_bits = GetPackedBitSize(size_params, dual_plane, max_weight); + uint remaining_bits = 128 - weight_bits - total_bitsread; + uint extra_cem_bits = 0; + if (base_mode > 0) { + switch (num_partitions) { + case 2: + extra_cem_bits += 2; + break; + case 3: + extra_cem_bits += 5; + break; + case 4: + extra_cem_bits += 8; + break; + default: + return; + } + } + remaining_bits -= extra_cem_bits; + const uint plane_selector_bits = dual_plane ? 2 : 0; + remaining_bits -= plane_selector_bits; + if (remaining_bits > 128) { + // Bad data, more remaining bits than 4 bytes + // return early + return; + } + // Read color data... + const uint color_data_bits = remaining_bits; + while (remaining_bits > 0) { + const int nb = int(min(remaining_bits, 32U)); + const uint b = StreamBits(nb); + color_endpoint_data[ced_pointer] = uint(bitfieldExtract(b, 0, nb)); + ++ced_pointer; + remaining_bits -= nb; + } + const uint plane_index = uint(StreamBits(plane_selector_bits)); + if (base_mode > 0) { + const uint extra_cem = StreamBits(extra_cem_bits); + uint cem = (extra_cem << 6) | base_cem; + cem >>= 2; + uvec4 C = uvec4(0); + for (uint i = 0; i < num_partitions; i++) { + C[i] = (cem & 1); + cem >>= 1; + } + uvec4 M = uvec4(0); + for (uint i = 0; i < num_partitions; i++) { + M[i] = cem & 3; + cem >>= 2; + } + for (uint i = 0; i < num_partitions; i++) { + color_endpoint_mode[i] = base_mode; + if (C[i] == 0) { + --color_endpoint_mode[i]; + } + color_endpoint_mode[i] <<= 2; + color_endpoint_mode[i] |= M[i]; + } + } else if (num_partitions > 1) { + const uint cem = base_cem >> 2; + for (uint i = 0; i < num_partitions; i++) { + color_endpoint_mode[i] = cem; + } + } + + uvec4 endpoints0[4]; + uvec4 endpoints1[4]; + { + // This decode phase should at most push 32 elements into the vector + result_vector_max_index = 32; + uint color_values[32]; + uint colvals_index = 0; + DecodeColorValues(color_endpoint_mode, num_partitions, color_data_bits, color_values); + for (uint i = 0; i < num_partitions; i++) { + ComputeEndpoints(endpoints0[i], endpoints1[i], color_endpoint_mode[i], color_values, + colvals_index); + } + } + color_endpoint_data = local_buff; + color_endpoint_data = bitfieldReverse(color_endpoint_data).wzyx; + const uint clear_byte_start = (weight_bits >> 3) + 1; + + const uint byte_insert = ExtractBits(color_endpoint_data, int(clear_byte_start - 1) * 8, 8) & + uint(((1 << (weight_bits % 8)) - 1)); + const uint vec_index = (clear_byte_start - 1) >> 2; + color_endpoint_data[vec_index] = bitfieldInsert(color_endpoint_data[vec_index], byte_insert, + int((clear_byte_start - 1) % 4) * 8, 8); + for (uint i = clear_byte_start; i < 16; ++i) { + const uint idx = i >> 2; + color_endpoint_data[idx] = bitfieldInsert(color_endpoint_data[idx], 0, int(i % 4) * 8, 8); + } + + // Re-init vector variables for next decode phase + result_index = 0; + color_bitsread = 0; + result_limit_reached = false; + + // The limit for the Unquantize phase, avoids decoding more data than needed. + result_vector_max_index = size_params.x * size_params.y; + if (dual_plane) { + result_vector_max_index *= 2; + } + DecodeIntegerSequence(max_weight, GetNumWeightValues(size_params, dual_plane)); + + UnquantizeTexelWeights(size_params, dual_plane); + { + { + const uint j = uint(g_sub.y); + const uint i = uint(g_sub.x); + uint local_partition = 0; + if (num_partitions > 1) { + local_partition = Select2DPartition(partition_index, i, j, num_partitions); + } + const uint local_cem = color_endpoint_mode[local_partition]; + const uvec4 weight_vec = GetUnquantizedWeightVector(j, i, size_params, plane_index, dual_plane); + + vec4 p; + if (IsHDRColorEndpointMode(local_cem)) { + 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); + 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); + + 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 + frag_color = p.gbar; +#else + frag_color = clamp(p, 0.0f, 1.0f).gbar; +#endif + } + } +} + +uint SwizzleOffset(uvec2 pos) { + const uint x = pos.x; + const uint y = pos.y; + return ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + + ((x % 32) / 16) * 32 + (y % 2) * 16 + (x % 16); +} + +void main() { + frag_color = vec4(0.0, 0.0, 0.0, 0.0); + + const ivec2 texel = ivec2(gl_FragCoord.xy); + const uvec2 block_xy = uvec2(texel) / block_dims; + g_sub = ivec2(uvec2(texel) % block_dims); + + uvec3 pos = uvec3(block_xy, dest_layer); + pos.x <<= BYTES_PER_BLOCK_LOG2; + const uint swizzle = SwizzleOffset(pos.xy); + const uint block_y = pos.y >> GOB_SIZE_Y_SHIFT; + + 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 += (pos.x >> GOB_SIZE_X_SHIFT) << x_shift; + offset += swizzle; + + local_buff = astc_data[offset / 16]; + DecompressBlock(ivec3(0)); +} diff --git a/src/video_core/host_shaders/astc_decoder.vert b/src/video_core/host_shaders/astc_decoder.vert new file mode 100644 index 0000000000..ca92a7fcf5 --- /dev/null +++ b/src/video_core/host_shaders/astc_decoder.vert @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#version 450 + +#ifdef VULKAN +#define VERTEX_ID gl_VertexIndex +#else +#define VERTEX_ID gl_VertexID +out gl_PerVertex { + vec4 gl_Position; +}; +#endif + +void main() { + float x = float((VERTEX_ID & 1) << 2); + float y = float((VERTEX_ID & 2) << 1); + gl_Position = vec4(x - 1.0, y - 1.0, 0.0, 1.0); +} diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index 594dba4e13..02cf64636e 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -4,6 +4,7 @@ // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include #include @@ -17,6 +18,8 @@ #include "common/div_ceil.h" #include "common/vector_math.h" #include "video_core/host_shaders/astc_decoder_comp_spv.h" +#include "video_core/host_shaders/astc_decoder_frag_spv.h" +#include "video_core/host_shaders/astc_decoder_vert_spv.h" #include "video_core/host_shaders/queries_prefix_scan_sum_comp_spv.h" #include "video_core/host_shaders/queries_prefix_scan_sum_nosubgroups_comp_spv.h" #include "video_core/host_shaders/resolve_conditional_render_comp_spv.h" @@ -25,7 +28,9 @@ #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/maxwell_to_vk.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" +#include "video_core/renderer_vulkan/vk_render_pass_cache.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" #include "video_core/renderer_vulkan/vk_update_descriptor.h" @@ -615,6 +620,394 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map, }); } +namespace { +struct AstcFragPushConstants { + std::array blocks_dims; + u32 layer_stride; + u32 block_size; + u32 x_shift; + u32 block_height; + u32 block_height_mask; + u32 dest_layer; +}; + +constexpr VkDescriptorSetLayoutBinding ASTC_FRAG_BINDING{ + .binding = 0, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, + .pImmutableSamplers = nullptr, +}; + +constexpr VkDescriptorUpdateTemplateEntry ASTC_FRAG_TEMPLATE{ + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .offset = 0, + .stride = sizeof(DescriptorUpdateEntry), +}; + +constexpr DescriptorBankInfo ASTC_FRAG_BANK_INFO{ + .uniform_buffers = 0, + .storage_buffers = 1, + .texture_buffers = 0, + .image_buffers = 0, + .textures = 0, + .images = 0, + .score = 1, +}; + +constexpr VkPushConstantRange ASTC_FRAG_PUSH_CONSTANT_RANGE{ + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, + .offset = 0, + .size = static_cast(sizeof(AstcFragPushConstants)), +}; + +constexpr VkPipelineVertexInputStateCreateInfo ASTC_FRAG_VERTEX_INPUT{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .vertexBindingDescriptionCount = 0, + .pVertexBindingDescriptions = nullptr, + .vertexAttributeDescriptionCount = 0, + .pVertexAttributeDescriptions = nullptr, +}; + +constexpr VkPipelineInputAssemblyStateCreateInfo ASTC_FRAG_INPUT_ASSEMBLY{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + .primitiveRestartEnable = VK_FALSE, +}; + +constexpr VkPipelineViewportStateCreateInfo ASTC_FRAG_VIEWPORT{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .viewportCount = 1, + .pViewports = nullptr, + .scissorCount = 1, + .pScissors = nullptr, +}; + +constexpr VkPipelineRasterizationStateCreateInfo ASTC_FRAG_RASTERIZATION{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .depthClampEnable = VK_FALSE, + .rasterizerDiscardEnable = VK_FALSE, + .polygonMode = VK_POLYGON_MODE_FILL, + .cullMode = VK_CULL_MODE_NONE, + .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE, + .depthBiasEnable = VK_FALSE, + .depthBiasConstantFactor = 0.0f, + .depthBiasClamp = 0.0f, + .depthBiasSlopeFactor = 0.0f, + .lineWidth = 1.0f, +}; + +constexpr VkPipelineMultisampleStateCreateInfo ASTC_FRAG_MULTISAMPLE{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, + .sampleShadingEnable = VK_FALSE, + .minSampleShading = 0.0f, + .pSampleMask = nullptr, + .alphaToCoverageEnable = VK_FALSE, + .alphaToOneEnable = VK_FALSE, +}; + +constexpr VkPipelineColorBlendAttachmentState ASTC_FRAG_BLEND_ATTACHMENT{ + .blendEnable = VK_FALSE, + .srcColorBlendFactor = VK_BLEND_FACTOR_ONE, + .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO, + .colorBlendOp = VK_BLEND_OP_ADD, + .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, + .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, + .alphaBlendOp = VK_BLEND_OP_ADD, + .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, +}; + +constexpr std::array ASTC_FRAG_DYNAMIC_STATES{VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR}; +} // Anonymous namespace + +ASTCDecoderFragmentPass::ASTCDecoderFragmentPass( + const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, + ComputePassDescriptorQueue& compute_pass_descriptor_queue_, + RenderPassCache& render_pass_cache_) + : device{device_}, scheduler{scheduler_}, + compute_pass_descriptor_queue{compute_pass_descriptor_queue_}, + render_pass_cache{render_pass_cache_}, + descriptor_set_layout{device.GetLogical().CreateDescriptorSetLayout({ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .bindingCount = 1, + .pBindings = &ASTC_FRAG_BINDING, + })}, + descriptor_template{device.GetLogical().CreateDescriptorUpdateTemplate({ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .descriptorUpdateEntryCount = 1, + .pDescriptorUpdateEntries = &ASTC_FRAG_TEMPLATE, + .templateType = VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET, + .descriptorSetLayout = *descriptor_set_layout, + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .pipelineLayout = VK_NULL_HANDLE, + .set = 0, + })}, + pipeline_layout{device.GetLogical().CreatePipelineLayout({ + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .setLayoutCount = 1, + .pSetLayouts = descriptor_set_layout.address(), + .pushConstantRangeCount = 1, + .pPushConstantRanges = &ASTC_FRAG_PUSH_CONSTANT_RANGE, + })}, + descriptor_allocator{descriptor_pool_.Allocator(device_, scheduler_, *descriptor_set_layout, + ASTC_FRAG_BANK_INFO)}, + vertex_shader{BuildShader(device, ASTC_DECODER_VERT_SPV)}, + fragment_shader{BuildShader(device, ASTC_DECODER_FRAG_SPV)} {} + +ASTCDecoderFragmentPass::~ASTCDecoderFragmentPass() = default; + +VkPipeline ASTCDecoderFragmentPass::FindOrEmplacePipeline(VkRenderPass render_pass) { + const auto it = std::ranges::find(pipeline_keys, render_pass); + if (it != pipeline_keys.end()) { + return *pipelines[std::distance(pipeline_keys.begin(), it)]; + } + pipeline_keys.push_back(render_pass); + const std::array stages{ + VkPipelineShaderStageCreateInfo{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stage = VK_SHADER_STAGE_VERTEX_BIT, + .module = *vertex_shader, + .pName = "main", + .pSpecializationInfo = nullptr, + }, + VkPipelineShaderStageCreateInfo{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stage = VK_SHADER_STAGE_FRAGMENT_BIT, + .module = *fragment_shader, + .pName = "main", + .pSpecializationInfo = nullptr, + }, + }; + const VkPipelineColorBlendStateCreateInfo color_blend{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .logicOpEnable = VK_FALSE, + .logicOp = VK_LOGIC_OP_CLEAR, + .attachmentCount = 1, + .pAttachments = &ASTC_FRAG_BLEND_ATTACHMENT, + .blendConstants = {0.0f, 0.0f, 0.0f, 0.0f}, + }; + const VkPipelineDynamicStateCreateInfo dynamic_state{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .dynamicStateCount = static_cast(ASTC_FRAG_DYNAMIC_STATES.size()), + .pDynamicStates = ASTC_FRAG_DYNAMIC_STATES.data(), + }; + pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({ + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stageCount = static_cast(stages.size()), + .pStages = stages.data(), + .pVertexInputState = &ASTC_FRAG_VERTEX_INPUT, + .pInputAssemblyState = &ASTC_FRAG_INPUT_ASSEMBLY, + .pTessellationState = nullptr, + .pViewportState = &ASTC_FRAG_VIEWPORT, + .pRasterizationState = &ASTC_FRAG_RASTERIZATION, + .pMultisampleState = &ASTC_FRAG_MULTISAMPLE, + .pDepthStencilState = nullptr, + .pColorBlendState = &color_blend, + .pDynamicState = &dynamic_state, + .layout = *pipeline_layout, + .renderPass = render_pass, + .subpass = 0, + .basePipelineHandle = VK_NULL_HANDLE, + .basePipelineIndex = 0, + })); + return *pipelines.back(); +} + +void ASTCDecoderFragmentPass::Assemble(Image& image, const StagingBufferRef& map, + std::span swizzles, + VideoCore::Surface::PixelFormat decoded_format) { + using namespace VideoCommon::Accelerated; + while (!frame_resources.empty() && scheduler.IsFree(frame_resources.front().tick)) { + frame_resources.pop_front(); + } + const std::array block_dims{ + VideoCore::Surface::DefaultBlockWidth(image.info.format), + VideoCore::Surface::DefaultBlockHeight(image.info.format), + }; + RenderPassKey key{}; + key.color_formats.fill(VideoCore::Surface::PixelFormat::Invalid); + key.color_formats[0] = decoded_format; + key.depth_format = VideoCore::Surface::PixelFormat::Invalid; + key.samples = VK_SAMPLE_COUNT_1_BIT; + const VkRenderPass render_pass = render_pass_cache.Get(key); + const VkPipeline pipeline = FindOrEmplacePipeline(render_pass); + const VkFormat vk_format = + MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, decoded_format).format; + const VkImage vk_image = image.Handle(); + const VkImageAspectFlags aspect_mask = image.AspectMask(); + + scheduler.RequestOutsideRenderPassOperationContext(); + const bool is_initialized = image.ExchangeInitialization(); + scheduler.Record([vk_image, aspect_mask, is_initialized](vk::CommandBuffer cmdbuf) { + const VkImageMemoryBarrier barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = static_cast( + is_initialized ? VK_ACCESS_SHADER_READ_BIT : VK_ACCESS_NONE), + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .oldLayout = is_initialized ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .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_GRAPHICS_COMPUTE_TRANSFER, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, barrier); + }); + + for (const VideoCommon::SwizzleParameters& swizzle : swizzles) { + const size_t input_offset = swizzle.buffer_offset + map.offset; + const auto params = MakeBlockLinearSwizzle2DParams(swizzle, image.info); + const u32 level = swizzle.level; + const u32 width = std::max(1u, image.info.size.width >> level); + const u32 height = std::max(1u, image.info.size.height >> level); + const u32 layers = image.info.resources.layers; + for (u32 layer = 0; layer < layers; ++layer) { + vk::ImageView view = device.GetLogical().CreateImageView(VkImageViewCreateInfo{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .image = vk_image, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = vk_format, + .components{}, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = level, + .levelCount = 1, + .baseArrayLayer = layer, + .layerCount = 1, + }, + }); + vk::Framebuffer framebuffer = + device.GetLogical().CreateFramebuffer(VkFramebufferCreateInfo{ + .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .renderPass = render_pass, + .attachmentCount = 1, + .pAttachments = view.address(), + .width = width, + .height = height, + .layers = 1, + }); + const AstcFragPushConstants pc{ + .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, + .dest_layer = layer, + }; + compute_pass_descriptor_queue.Acquire(scheduler, 1); + compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset, + image.guest_size_bytes - swizzle.buffer_offset); + const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; + scheduler.Record([this, pipeline, render_pass, descriptor_data, pc, width, height, + fb = *framebuffer](vk::CommandBuffer cmdbuf) { + const VkDescriptorSet set = descriptor_allocator.Commit(); + device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); + const VkRenderPassBeginInfo begin{ + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .pNext = nullptr, + .renderPass = render_pass, + .framebuffer = fb, + .renderArea{.offset = {0, 0}, .extent = {width, height}}, + .clearValueCount = 0, + .pClearValues = nullptr, + }; + const VkViewport viewport{ + .x = 0.0f, + .y = 0.0f, + .width = static_cast(width), + .height = static_cast(height), + .minDepth = 0.0f, + .maxDepth = 1.0f, + }; + const VkRect2D scissor{.offset = {0, 0}, .extent = {width, height}}; + cmdbuf.BeginRenderPass(begin, VK_SUBPASS_CONTENTS_INLINE); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + cmdbuf.SetViewport(0, viewport); + cmdbuf.SetScissor(0, scissor); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline_layout, 0, set, + {}); + cmdbuf.PushConstants(*pipeline_layout, VK_SHADER_STAGE_FRAGMENT_BIT, pc); + cmdbuf.Draw(3, 1, 0, 0); + cmdbuf.EndRenderPass(); + }); + frame_resources.push_back(FrameResources{ + .tick = scheduler.CurrentTick(), + .view = std::move(view), + .framebuffer = std::move(framebuffer), + }); + } + } + + scheduler.Record([vk_image, aspect_mask](vk::CommandBuffer cmdbuf) { + const VkImageMemoryBarrier barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .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_COLOR_ATTACHMENT_OUTPUT_BIT, + vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, barrier); + }); + scheduler.InvalidateState(); +} + 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 3d30aa6bdc..ae0e895e42 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.h +++ b/src/video_core/renderer_vulkan/vk_compute_pass.h @@ -6,12 +6,15 @@ #pragma once +#include #include #include #include +#include #include "common/common_types.h" #include "video_core/engines/maxwell_3d.h" +#include "video_core/surface.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" #include "video_core/renderer_vulkan/vk_update_descriptor.h" #include "video_core/texture_cache/types.h" @@ -137,6 +140,44 @@ private: MemoryAllocator& memory_allocator; }; +class RenderPassCache; + +class ASTCDecoderFragmentPass final { +public: + explicit ASTCDecoderFragmentPass(const Device& device_, Scheduler& scheduler_, + DescriptorPool& descriptor_pool_, + ComputePassDescriptorQueue& compute_pass_descriptor_queue_, + RenderPassCache& render_pass_cache_); + ~ASTCDecoderFragmentPass(); + + void Assemble(Image& image, const StagingBufferRef& map, + std::span swizzles, + VideoCore::Surface::PixelFormat decoded_format); + +private: + VkPipeline FindOrEmplacePipeline(VkRenderPass render_pass); + + const Device& device; + Scheduler& scheduler; + ComputePassDescriptorQueue& compute_pass_descriptor_queue; + RenderPassCache& render_pass_cache; + vk::DescriptorSetLayout descriptor_set_layout; + vk::DescriptorUpdateTemplate descriptor_template; + vk::PipelineLayout pipeline_layout; + DescriptorAllocator descriptor_allocator; + vk::ShaderModule vertex_shader; + vk::ShaderModule fragment_shader; + std::vector pipeline_keys; + std::vector pipelines; + + struct FrameResources { + u64 tick; + vk::ImageView view; + vk::Framebuffer framebuffer; + }; + std::deque frame_resources; +}; + class BlockLinearUnswizzle3DPass final : public ComputePass { public: explicit BlockLinearUnswizzle3DPass(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 44a4227a19..fbee609de9 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -129,6 +129,9 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { if (info.storage) { usage |= VK_IMAGE_USAGE_STORAGE_BIT; } + if (IsPixelFormatASTC(format)) { + usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + } return usage; } @@ -911,6 +914,10 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched if (Settings::values.accelerate_astc.GetValue() == Settings::AstcDecodeMode::Gpu) { astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, compute_pass_descriptor_queue, memory_allocator); + if (device.IsTiler()) { + astc_decoder_fragment_pass.emplace(device, scheduler, descriptor_pool, + compute_pass_descriptor_queue, render_pass_cache); + } } if (!device.IsKhrImageFormatListSupported()) { return; @@ -2850,6 +2857,13 @@ void TextureCacheRuntime::AccelerateImageUpload( u32 z_start, u32 z_count) { if (IsPixelFormatASTC(image.info.format)) { + if (astc_decoder_fragment_pass) { + const VideoCore::Surface::PixelFormat decoded_format = + WillUseWidenedAstcFormat(device, image.info) + ? VideoCore::Surface::PixelFormat::R32G32B32A32_FLOAT + : VideoCore::Surface::PixelFormat::A8B8G8R8_UNORM; + return astc_decoder_fragment_pass->Assemble(image, map, swizzles, decoded_format); + } return astc_decoder_pass->Assemble(image, map, swizzles); } diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 99f2978cca..b0fdc79b57 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -147,6 +147,7 @@ public: BlitImageHelper& blit_image_helper; RenderPassCache& render_pass_cache; std::optional astc_decoder_pass; + std::optional astc_decoder_fragment_pass; std::optional bl3d_unswizzle_pass; const Settings::ResolutionScalingInfo& resolution; From 10e924c8cba07a69efbfc06c90b5f53d17cd5d46 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 19 Jul 2026 01:07:06 -0400 Subject: [PATCH 25/26] Fix build --- src/video_core/renderer_vulkan/vk_compute_pass.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index 02cf64636e..ac9fe147ce 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -32,6 +32,7 @@ #include "video_core/renderer_vulkan/vk_descriptor_pool.h" #include "video_core/renderer_vulkan/vk_render_pass_cache.h" #include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/renderer_vulkan/vk_shader_util.h" #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" #include "video_core/renderer_vulkan/vk_update_descriptor.h" #include "video_core/texture_cache/accelerated_swizzle.h" From 29825a3290e912bf7550ed6a7f623aee8fba48cb Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sun, 19 Jul 2026 16:16:56 -0400 Subject: [PATCH 26/26] AAaaa --- src/video_core/renderer_vulkan/vk_texture_cache.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index b0fdc79b57..dbf4da168c 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -93,7 +93,7 @@ public: void AccelerateImageUpload(Image&, const StagingBufferRef&, std::span, - u32 z_start, u32 z_count); + u32 z_start = 0, u32 z_count = 0); void InsertUploadMemoryBarrier() {}