From dbb88bcf224904e9ce1aab2cabd5449ca2a0ecc1 Mon Sep 17 00:00:00 2001 From: kuba Date: Tue, 29 Sep 2026 23:07:11 +0200 Subject: [PATCH] [Vk] Refuse push descriptors on macOS for every device through one policy The guard that refuses push descriptors on MoltenVK keyed on the VK_KHR_portability_subset device extension, but that extension is not enumerated for every device: the headless device the draw replay and the device tests create took the raw driver limit, so replays on macOS read guest storage buffers past their first bytes as zero (MoltenVK reports a zero OpArrayLength for buffers bound through push descriptors). The limit now comes from one policy both the presenter and the headless device use, and macOS, whose only driver is MoltenVK, pushes none regardless of which extensions are listed. --- .../Gpu/Vulkan/VulkanPushDescriptorPolicy.cs | 51 +++++++++++++++++++ .../VulkanVideoPresenter.Device.Setup.cs | 2 +- .../Gpu/Vulkan/HeadlessVulkan.cs | 4 +- 3 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 src/SharpEmu.Libs/Gpu/Vulkan/VulkanPushDescriptorPolicy.cs diff --git a/src/SharpEmu.Libs/Gpu/Vulkan/VulkanPushDescriptorPolicy.cs b/src/SharpEmu.Libs/Gpu/Vulkan/VulkanPushDescriptorPolicy.cs new file mode 100644 index 0000000..0cec065 --- /dev/null +++ b/src/SharpEmu.Libs/Gpu/Vulkan/VulkanPushDescriptorPolicy.cs @@ -0,0 +1,51 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System.Text; +using Silk.NET.Vulkan; + +namespace SharpEmu.Libs.Gpu.Vulkan; + +// How many descriptors a set may push. MoltenVK reports a zero OpArrayLength for storage buffers +// bound through push descriptors, which turns every bounds-checked load into zero and drops every +// store, so portability (MoltenVK) devices push none. The portability-subset extension is not +// enumerated for every device, and macOS only ever has MoltenVK, so macOS pushes none outright. +// Every device the emulator or its tools create takes the limit from here. +public static unsafe class VulkanPushDescriptorPolicy +{ + private const string PortabilitySubsetExtensionName = "VK_KHR_portability_subset"; + + public static uint UsableCount(Vk vk, PhysicalDevice physical, uint reported) => + OperatingSystem.IsMacOS() || HasDeviceExtension(vk, physical, PortabilitySubsetExtensionName) ? 0 : reported; + + private static bool HasDeviceExtension(Vk vk, PhysicalDevice physical, string name) + { + uint count = 0; + if (vk.EnumerateDeviceExtensionProperties(physical, (byte*)null, &count, null) != Result.Success || count == 0) + { + return false; + } + + var properties = new ExtensionProperties[count]; + fixed (ExtensionProperties* pointer = properties) + { + if (vk.EnumerateDeviceExtensionProperties(physical, (byte*)null, &count, pointer) != Result.Success) + { + return false; + } + + var expected = Encoding.UTF8.GetBytes(name); + for (var index = 0; index < count; index++) + { + var actual = new ReadOnlySpan(pointer[index].ExtensionName, 256); + var length = actual.IndexOf((byte)0); + if (actual[..(length < 0 ? actual.Length : length)].SequenceEqual(expected)) + { + return true; + } + } + } + + return false; + } +} diff --git a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Device.Setup.cs b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Device.Setup.cs index f5c9524..08d2bd4 100644 --- a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Device.Setup.cs +++ b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Device.Setup.cs @@ -594,7 +594,7 @@ internal static unsafe partial class VulkanVideoPresenter SetNativeSubgroupCapabilities(subgroup.SubgroupSize, subgroup.SupportedStages); // MoltenVK reports a zero OpArrayLength for storage buffers bound through push // descriptors, which turns every bounds-checked load into zero and drops every store. - _maxPushDescriptors = IsDeviceExtensionAvailable(PortabilitySubsetExtensionName) ? 0 : pushDescriptorProperties.MaxPushDescriptors; + _maxPushDescriptors = Gpu.Vulkan.VulkanPushDescriptorPolicy.UsableCount(_vk, _physicalDevice, pushDescriptorProperties.MaxPushDescriptors); _noAttachmentSampleCounts = properties.Limits.FramebufferNoAttachmentsSampleCounts; _maxComputeWorkGroupCountX = properties.Limits.MaxComputeWorkGroupCount[0]; _maxComputeWorkGroupCountY = properties.Limits.MaxComputeWorkGroupCount[1]; diff --git a/tests/SharpEmu.Libs.Tests/Gpu/Vulkan/HeadlessVulkan.cs b/tests/SharpEmu.Libs.Tests/Gpu/Vulkan/HeadlessVulkan.cs index ead42a4..6d937c2 100644 --- a/tests/SharpEmu.Libs.Tests/Gpu/Vulkan/HeadlessVulkan.cs +++ b/tests/SharpEmu.Libs.Tests/Gpu/Vulkan/HeadlessVulkan.cs @@ -49,7 +49,7 @@ internal sealed unsafe class HeadlessVulkan : IDisposable "VK_KHR_push_descriptor", ]; - // The push descriptor limit of the device; the render host pushes sets that fit it. + // The push descriptor limit the render host may use on this device (none on MoltenVK). public uint MaxPushDescriptors { get @@ -57,7 +57,7 @@ internal sealed unsafe class HeadlessVulkan : IDisposable var pushDescriptors = new PhysicalDevicePushDescriptorPropertiesKHR { SType = StructureType.PhysicalDevicePushDescriptorPropertiesKhr }; var properties = new PhysicalDeviceProperties2 { SType = StructureType.PhysicalDeviceProperties2, PNext = &pushDescriptors }; Vk.GetPhysicalDeviceProperties2(Physical, &properties); - return pushDescriptors.MaxPushDescriptors; + return SharpEmu.Libs.Gpu.Vulkan.VulkanPushDescriptorPolicy.UsableCount(Vk, Physical, pushDescriptors.MaxPushDescriptors); } }