[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.
This commit is contained in:
kuba 2026-09-29 23:07:11 +02:00
parent 3e878a878f
commit dbb88bcf22
3 changed files with 54 additions and 3 deletions

View File

@ -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<byte>(pointer[index].ExtensionName, 256);
var length = actual.IndexOf((byte)0);
if (actual[..(length < 0 ? actual.Length : length)].SequenceEqual(expected))
{
return true;
}
}
}
return false;
}
}

View File

@ -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];

View File

@ -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);
}
}