From aebf6b491353c1ae2690b48141bc5e21d105cd21 Mon Sep 17 00:00:00 2001 From: nmzik Date: Sun, 19 Jul 2026 14:33:38 +0200 Subject: [PATCH] graphics: synchronize host file writes and depth clipping Map paired PS5 depth-clip state through Vulkan pipeline creation and preserve asymmetric hard failures. Prepare GPU-mapped guest ranges before kernel file reads so buffer, texture, and alias ownership remains coherent. Add focused regression coverage and update the current launch target. --- src/graphics/guest_gpu/hardwareContext.h | 5 +++ src/graphics/host_gpu/pageManager.cpp | 38 +++++++++++++++++++ src/graphics/host_gpu/pageManager.h | 2 + src/graphics/host_gpu/renderer/debug.cpp | 2 +- .../host_gpu/renderer/gpuResourceManager.cpp | 35 +++++++++++++++++ .../host_gpu/renderer/gpuResourceManager.h | 1 + .../host_gpu/renderer/pipelineCache.cpp | 5 ++- .../host_gpu/renderer/pipelineCache.h | 3 +- src/graphics/host_gpu/renderer/shaders.cpp | 2 +- .../presentation/window/vulkanWindow.cpp | 17 ++++++++- src/kernel/fileSystem.cpp | 12 ++++++ src/kernel/memory.cpp | 7 ++++ src/kernel/memory.h | 1 + tests/PageManagerTests.cpp | 31 +++++++++++++++ tests/ShaderRecompilerComputeTests.cpp | 29 ++++++++++++++ 15 files changed, 184 insertions(+), 6 deletions(-) diff --git a/src/graphics/guest_gpu/hardwareContext.h b/src/graphics/guest_gpu/hardwareContext.h index ad8307e..454c219 100644 --- a/src/graphics/guest_gpu/hardwareContext.h +++ b/src/graphics/guest_gpu/hardwareContext.h @@ -253,6 +253,11 @@ struct ClipControl { bool cull_on_clipping_error_disable = false; bool linear_attribute_clip_enable = false; bool force_viewport_index_from_vs_enable = false; + + [[nodiscard]] bool IsZClipModeRepresentable() const { + return min_z_clip_disable == max_z_clip_disable; + } + [[nodiscard]] bool IsZClipEnabled() const { return !min_z_clip_disable; } }; struct DepthControl { diff --git a/src/graphics/host_gpu/pageManager.cpp b/src/graphics/host_gpu/pageManager.cpp index 22fe94f..e2c58aa 100644 --- a/src/graphics/host_gpu/pageManager.cpp +++ b/src/graphics/host_gpu/pageManager.cpp @@ -316,6 +316,26 @@ bool PageManager::IsMapped(uint64_t vaddr, uint64_t size) const noexcept { return true; } +bool PageManager::HasAnyMapping(uint64_t vaddr, uint64_t size) const noexcept { + if (g_in_fault_resolution || vaddr == 0 || size == 0 || vaddr >= ADDRESS_SIZE || + size > ADDRESS_SIZE - vaddr) { + return false; + } + const auto end = PageEnd(vaddr, size); + for (auto page_vaddr = PageStart(vaddr); page_vaddr < end; page_vaddr += PAGE_SIZE) { + auto* region = m_impl->FindRegion(page_vaddr); + if (region == nullptr) { + continue; + } + auto& page = m_impl->GetPage(*region, page_vaddr); + SpinGuard lock(page.lock); + if (page.mappings != 0) { + return true; + } + } + return false; +} + bool PageManager::HasGpuAccess(uint64_t vaddr, uint64_t size, GpuAccess access) const noexcept { if (access != GpuAccess::Read && access != GpuAccess::Write && access != GpuAccess::ReadWrite) { FailFast("HasGpuAccess received an invalid GPU access mode"); @@ -644,4 +664,22 @@ bool PageManager::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noex return true; } +bool PageManager::HandleWriteRange(uint64_t vaddr, uint64_t size) noexcept { + if (g_in_fault_resolution || vaddr == 0 || size == 0 || vaddr >= ADDRESS_SIZE || + size > ADDRESS_SIZE - vaddr) { + return false; + } + const auto end = PageEnd(vaddr, size); + for (auto page_vaddr = PageStart(vaddr); page_vaddr < end; page_vaddr += PAGE_SIZE) { + if (!IsMapped(page_vaddr, 1)) { + continue; + } + const auto fault_vaddr = std::max(page_vaddr, vaddr); + if (!HandleFault(PageFaultAccess::Write, fault_vaddr)) { + return false; + } + } + return true; +} + } // namespace Libs::Graphics diff --git a/src/graphics/host_gpu/pageManager.h b/src/graphics/host_gpu/pageManager.h index 511eb22..cf5214f 100644 --- a/src/graphics/host_gpu/pageManager.h +++ b/src/graphics/host_gpu/pageManager.h @@ -38,6 +38,7 @@ public: [[nodiscard]] uint64_t GetPageSize() const; [[nodiscard]] bool IsTracked(uint64_t vaddr) const noexcept; [[nodiscard]] bool IsMapped(uint64_t vaddr, uint64_t size) const noexcept; + [[nodiscard]] bool HasAnyMapping(uint64_t vaddr, uint64_t size) const noexcept; [[nodiscard]] bool HasGpuAccess(uint64_t vaddr, uint64_t size, GpuAccess access) const noexcept; void UpdatePageWatchers(bool track, uint64_t vaddr, uint64_t size, @@ -46,6 +47,7 @@ public: void OnGpuUnmap(uint64_t vaddr, uint64_t size, GpuAccess access = GpuAccess::ReadWrite); [[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept; + [[nodiscard]] bool HandleWriteRange(uint64_t vaddr, uint64_t size) noexcept; private: void BeginBackingWrite(uint64_t vaddr, uint64_t size) noexcept; diff --git a/src/graphics/host_gpu/renderer/debug.cpp b/src/graphics/host_gpu/renderer/debug.cpp index 077d323..9dde2f4 100644 --- a/src/graphics/host_gpu/renderer/debug.cpp +++ b/src/graphics/host_gpu/renderer/debug.cpp @@ -687,7 +687,7 @@ static void ClipCheck(const HW::ClipControl& c) { // dx_linear_attr_clip_enable preserves linear (noperspective) attributes at clip-generated // vertices, which Vulkan provides as part of clipping and interpolation. EXIT_NOT_IMPLEMENTED(c.user_clip_planes != 0 || c.user_clip_plane_mode != 0 || - c.vertex_kill_any || c.min_z_clip_disable || c.max_z_clip_disable || + c.vertex_kill_any || !c.IsZClipModeRepresentable() || c.user_clip_plane_negate_y || c.clip_disable || c.user_clip_plane_cull_only || c.cull_on_clipping_error_disable || c.force_viewport_index_from_vs_enable); diff --git a/src/graphics/host_gpu/renderer/gpuResourceManager.cpp b/src/graphics/host_gpu/renderer/gpuResourceManager.cpp index a48a213..7fbf757 100644 --- a/src/graphics/host_gpu/renderer/gpuResourceManager.cpp +++ b/src/graphics/host_gpu/renderer/gpuResourceManager.cpp @@ -61,6 +61,41 @@ bool GpuResourceManager::HandleFault(PageFaultAccess access, uint64_t fault_vadd return m_page_manager.HandleFault(access, fault_vaddr); } +void GpuResourceManager::PrepareHostWrite(uint64_t vaddr, uint64_t size) { + if (!m_page_manager.HasAnyMapping(vaddr, size)) { + return; + } + if (LabelInCallback()) { + EXIT("unsupported host write from an asynchronous GPU label callback, addr=0x%016" PRIx64 + " size=0x%016" PRIx64 "\n", + vaddr, size); + } + const auto handle_range = [this, vaddr, size]() { + if (!m_page_manager.HandleWriteRange(vaddr, size)) { + EXIT("failed to prepare host write, addr=0x%016" PRIx64 " size=0x%016" PRIx64 + "\n", + vaddr, size); + } + }; + if (auto* cp = GraphicsRunCurrentCommandProcessor(); cp != nullptr) { + cp->BeginReadbackTransaction(); + { + ResourceMutex::FaultScope fault(m_resource_mutex); + handle_range(); + } + cp->EndReadbackTransaction(); + return; + } + if (m_resource_mutex.IsOwnedByCurrentThread()) { + EXIT("unsupported host write from a pre-owned resource transaction, addr=0x%016" PRIx64 + " size=0x%016" PRIx64 "\n", + vaddr, size); + } + GraphicsRunSubmissionLock submissions; + ResourceMutex::FaultScope fault(m_resource_mutex); + handle_range(); +} + bool GpuResourceManager::IsMapped(uint64_t vaddr, uint64_t size) const noexcept { return m_page_manager.IsMapped(vaddr, size); } diff --git a/src/graphics/host_gpu/renderer/gpuResourceManager.h b/src/graphics/host_gpu/renderer/gpuResourceManager.h index c968e20..e2d94c2 100644 --- a/src/graphics/host_gpu/renderer/gpuResourceManager.h +++ b/src/graphics/host_gpu/renderer/gpuResourceManager.h @@ -24,6 +24,7 @@ public: [[nodiscard]] TextureCache* GetTextureCache() { return &m_texture_cache; } [[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept; + void PrepareHostWrite(uint64_t vaddr, uint64_t size); [[nodiscard]] bool IsMapped(uint64_t vaddr, uint64_t size) const noexcept; void MapMemory(uint64_t vaddr, uint64_t size, GpuAccess access); void UnmapMemory(uint64_t vaddr, uint64_t size, GpuAccess access); diff --git a/src/graphics/host_gpu/renderer/pipelineCache.cpp b/src/graphics/host_gpu/renderer/pipelineCache.cpp index 26cd728..f9dd2e6 100644 --- a/src/graphics/host_gpu/renderer/pipelineCache.cpp +++ b/src/graphics/host_gpu/renderer/pipelineCache.cpp @@ -88,7 +88,10 @@ PipelineCache::GraphicsPipeline* PipelineCache::CreateGraphicsPipeline( } } - static_params.negative_one_to_one = !ctx->GetClipControl().dx_clip_space; + const auto& clip_control = ctx->GetClipControl(); + EXIT_NOT_IMPLEMENTED(!clip_control.IsZClipModeRepresentable()); + static_params.negative_one_to_one = !clip_control.dx_clip_space; + static_params.depth_clip_enable = clip_control.IsZClipEnabled(); static_params.topology = topology; static_params.samples = framebuffer->samples; static_params.sample_shading_enable = diff --git a/src/graphics/host_gpu/renderer/pipelineCache.h b/src/graphics/host_gpu/renderer/pipelineCache.h index f17ed86..3e61319 100644 --- a/src/graphics/host_gpu/renderer/pipelineCache.h +++ b/src/graphics/host_gpu/renderer/pipelineCache.h @@ -33,6 +33,7 @@ struct PipelineStaticParameters { float viewport_scale[3] = {}; float viewport_offset[3] = {}; bool negative_one_to_one = false; + bool depth_clip_enable = true; int scissor_ltrb[4] = {0}; vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList; uint32_t samples = 1; @@ -75,7 +76,7 @@ static_assert(std::is_trivially_copyable_v); static_assert(std::is_standard_layout_v); static_assert(alignof(PipelineStaticParameters) == 1); static_assert(sizeof(PipelineStaticParameters) == - sizeof(float[3]) + sizeof(float[3]) + sizeof(bool) + sizeof(int[4]) + + sizeof(float[3]) + sizeof(float[3]) + sizeof(bool) * 2 + sizeof(int[4]) + sizeof(vk::PrimitiveTopology) + sizeof(uint32_t) + sizeof(bool) * 4 + sizeof(vk::CompareOp) + sizeof(bool) + sizeof(float) * 2 + sizeof(bool) + sizeof(PipelineStencilStaticState) * 2 + sizeof(uint32_t) + diff --git a/src/graphics/host_gpu/renderer/shaders.cpp b/src/graphics/host_gpu/renderer/shaders.cpp index bac4a10..ad5ee4e 100644 --- a/src/graphics/host_gpu/renderer/shaders.cpp +++ b/src/graphics/host_gpu/renderer/shaders.cpp @@ -734,7 +734,7 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline* pipeline, vk::Rende clip_ext.sType = vk::StructureType::ePipelineRasterizationDepthClipStateCreateInfoEXT; clip_ext.pNext = nullptr; clip_ext.flags = {}; - clip_ext.depthClipEnable = VK_FALSE; + clip_ext.depthClipEnable = static_params.depth_clip_enable ? VK_TRUE : VK_FALSE; vk::PipelineRasterizationStateCreateInfo rasterizer {}; rasterizer.sType = vk::StructureType::ePipelineRasterizationStateCreateInfo; diff --git a/src/graphics/presentation/window/vulkanWindow.cpp b/src/graphics/presentation/window/vulkanWindow.cpp index a10124a..46a711a 100644 --- a/src/graphics/presentation/window/vulkanWindow.cpp +++ b/src/graphics/presentation/window/vulkanWindow.cpp @@ -289,9 +289,13 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa color_write_ext.sType = vk::StructureType::ePhysicalDeviceColorWriteEnableFeaturesEXT; color_write_ext.pNext = nullptr; + vk::PhysicalDeviceDepthClipEnableFeaturesEXT depth_clip_enable {}; + depth_clip_enable.sType = vk::StructureType::ePhysicalDeviceDepthClipEnableFeaturesEXT; + depth_clip_enable.pNext = &color_write_ext; + vk::PhysicalDeviceDepthClipControlFeaturesEXT depth_clip_control {}; depth_clip_control.sType = vk::StructureType::ePhysicalDeviceDepthClipControlFeaturesEXT; - depth_clip_control.pNext = &color_write_ext; + depth_clip_control.pNext = &depth_clip_enable; vk::PhysicalDeviceVulkan12Features features12 {}; features12.sType = vk::StructureType::ePhysicalDeviceVulkan12Features; @@ -325,6 +329,10 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa LOGF("depthClipControl is not supported\n"); skip_device = true; } + if (depth_clip_enable.depthClipEnable != VK_TRUE) { + LOGF("depthClipEnable is not supported\n"); + skip_device = true; + } if (features12.samplerMirrorClampToEdge != VK_TRUE) { LOGF("samplerMirrorClampToEdge is not supported\n"); @@ -590,9 +598,14 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, vk::Sur color_write_ext.pNext = nullptr; color_write_ext.colorWriteEnable = VK_TRUE; + vk::PhysicalDeviceDepthClipEnableFeaturesEXT depth_clip_enable {}; + depth_clip_enable.sType = vk::StructureType::ePhysicalDeviceDepthClipEnableFeaturesEXT; + depth_clip_enable.pNext = &color_write_ext; + depth_clip_enable.depthClipEnable = VK_TRUE; + vk::PhysicalDeviceDepthClipControlFeaturesEXT depth_clip_control {}; depth_clip_control.sType = vk::StructureType::ePhysicalDeviceDepthClipControlFeaturesEXT; - depth_clip_control.pNext = &color_write_ext; + depth_clip_control.pNext = &depth_clip_enable; depth_clip_control.depthClipControl = VK_TRUE; vk::PhysicalDeviceVulkan12Features features12 {}; diff --git a/src/kernel/fileSystem.cpp b/src/kernel/fileSystem.cpp index 1e17cf1..6f9a656 100644 --- a/src/kernel/fileSystem.cpp +++ b/src/kernel/fileSystem.cpp @@ -9,6 +9,7 @@ #include "common/logging/log.h" #include "common/stringUtils.h" #include "common/threads.h" +#include "kernel/memory.h" #include "libs/errno.h" #include "libs/libs.h" #include "libs/network.h" @@ -502,6 +503,11 @@ int64_t KYTY_SYSV_ABI KernelRead(int d, void* buf, size_t nbytes) { file->mutex.Lock(); bool is_invalid = file->f.IsInvalid(); + const auto pos = file->f.Tell(); + const auto file_size = file->f.Size(); + const auto remaining = pos < file_size ? file_size - pos : 0; + Memory::PrepareHostWrite(reinterpret_cast(buf), + std::min(nbytes, remaining)); uint32_t bytes_read = 0; file->f.Read(buf, static_cast(nbytes), &bytes_read); @@ -622,6 +628,12 @@ int64_t KYTY_SYSV_ABI KernelPread(int d, void* buf, size_t nbytes, int64_t offse bool is_invalid = file->f.IsInvalid(); auto pos = file->f.Tell(); + const auto file_size = file->f.Size(); + const auto remaining = static_cast(offset) < file_size + ? file_size - static_cast(offset) + : 0; + Memory::PrepareHostWrite(reinterpret_cast(buf), + std::min(nbytes, remaining)); uint32_t bytes_read = 0; file->f.Seek(offset); file->f.Read(buf, static_cast(nbytes), &bytes_read); diff --git a/src/kernel/memory.cpp b/src/kernel/memory.cpp index 695e0c9..e525cc9 100644 --- a/src/kernel/memory.cpp +++ b/src/kernel/memory.cpp @@ -882,6 +882,13 @@ void WriteBacking(uint64_t vaddr, const void* data, uint64_t size) noexcept { } } +void PrepareHostWrite(uint64_t vaddr, uint64_t size) { + if (size == 0 || Graphics::g_render_ctx == nullptr) { + return; + } + Graphics::g_render_ctx->GetGpuResources()->PrepareHostWrite(vaddr, size); +} + struct PrtAperture { uint64_t address = 0; uint64_t size = 0; diff --git a/src/kernel/memory.h b/src/kernel/memory.h index e8bc09b..a297e44 100644 --- a/src/kernel/memory.h +++ b/src/kernel/memory.h @@ -99,6 +99,7 @@ void SetFlexibleMemorySize(uint64_t size); bool TryWriteBacking(uint64_t vaddr, const void* data, uint64_t size); bool TryReadBacking(uint64_t vaddr, void* data, uint64_t size); void WriteBacking(uint64_t vaddr, const void* data, uint64_t size) noexcept; +void PrepareHostWrite(uint64_t vaddr, uint64_t size); int KYTY_SYSV_ABI KernelMapNamedFlexibleMemory(void** addr_in_out, size_t len, int prot, int flags, const char* name); diff --git a/tests/PageManagerTests.cpp b/tests/PageManagerTests.cpp index 03eceb6..48ec1a8 100644 --- a/tests/PageManagerTests.cpp +++ b/tests/PageManagerTests.cpp @@ -168,6 +168,36 @@ void TestSharedWatcherFault() { Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed"); } +void TestMappedHostWriteRange() { + FaultContext context; + PageManager manager(InvalidateFault, &context); + context.manager = &manager; + const auto page_size = manager.GetPageSize(); + auto *memory = Allocate(page_size * 3); + const auto address = reinterpret_cast(memory); + + manager.OnGpuMap(address, page_size); + manager.OnGpuMap(address + page_size * 2, page_size); + manager.UpdatePageWatchers(true, address, page_size); + manager.UpdatePageWatchers(true, address + page_size * 2, page_size); + Check(manager.HasAnyMapping(address + 16, page_size * 3 - 32), + "host-write range did not find partial GPU mappings"); + Check(!manager.IsMapped(address, page_size * 3), + "partial GPU mappings were reported as a full mapping"); + Check(manager.HandleWriteRange(address + 16, page_size * 3 - 32), + "mapped host-write range was not handled"); + Check(context.calls.load(std::memory_order_relaxed) == 2, + "host-write range did not invalidate each mapped watched page"); + Check(IsWritable(memory) && IsWritable(memory + page_size * 2), + "host-write range did not restore writable protection"); + + manager.OnGpuUnmap(address, page_size); + manager.OnGpuUnmap(address + page_size * 2, page_size); + Check(!manager.HasAnyMapping(address, page_size * 3), + "host-write range retained stale GPU mappings"); + Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed"); +} + void TestReadWriteWatcherFault() { FaultContext context; PageManager manager(InvalidateFault, &context); @@ -607,6 +637,7 @@ int main(int argc, char **argv) { } TestWatchFaultAndUnwatch(); TestSharedWatcherFault(); + TestMappedHostWriteRange(); TestReadWriteWatcherFault(); TestPermittedMappedLateFaultsResume(); TestPartialMappingUnmapPreservesTokens(); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 772425b..cd8e089 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -13869,6 +13869,30 @@ void CheckReferenceClockScale() { std::printf("[host] %-32s ok\n", "ReferenceClockScale"); } +void CheckClipControlDepthClipState() { + HW::ClipControl clip; + Require("ClipControlDepthClipState", "default", + clip.IsZClipModeRepresentable() && clip.IsZClipEnabled(), + "default paired Z clipping was not enabled"); + + clip.min_z_clip_disable = true; + Require("ClipControlDepthClipState", "asymmetric near", + !clip.IsZClipModeRepresentable(), + "asymmetric near-plane state was accepted"); + + clip.min_z_clip_disable = false; + clip.max_z_clip_disable = true; + Require("ClipControlDepthClipState", "asymmetric far", + !clip.IsZClipModeRepresentable(), + "asymmetric far-plane state was accepted"); + + clip.min_z_clip_disable = true; + Require("ClipControlDepthClipState", "both disabled", + clip.IsZClipModeRepresentable() && !clip.IsZClipEnabled(), + "paired Z-clip disable was not represented"); + std::printf("[host] %-32s ok\n", "ClipControlDepthClipState"); +} + } // namespace } // namespace Libs::Graphics @@ -13877,6 +13901,10 @@ int main(int argc, char **argv) { EnsureConfigInitialized(); TileInit(); + if (argc == 2 && std::strcmp(argv[1], "--clip-control-only") == 0) { + CheckClipControlDepthClipState(); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--reference-clock-only") == 0) { CheckReferenceClockScale(); return 0; @@ -14021,6 +14049,7 @@ int main(int argc, char **argv) { (void)argc; (void)argv; #endif + CheckClipControlDepthClipState(); CheckReferenceClockScale(); CheckEmbeddedFetchVertexOffset(); CheckEmbeddedFetchLaneSpill();