From 609865ab2e6e9551f908612fdf1e314c3989bc00 Mon Sep 17 00:00:00 2001 From: utzcoz Date: Sat, 5 Sep 2026 00:33:02 +0800 Subject: [PATCH 1/2] vulkan: give the guest a device local memory type on unified memory With system blobs, host visible memory is shared memory imported as a host pointer, and Metal cannot bind a tiled image to that. A device that reports one unified memory type, as kosmickrisp does, then has nowhere to put an image, and kk_image_plane_bind asserts on the first one. Add a guest only type in that case: device local, first in the list so that it is what an image is given, allocating from the same host type without host visible emulation. Memory requirements include it wherever the host type is allowed. MoltenVK reports a device local only type of its own, so nothing changes there. --- .../vk_emulated_physical_device_memory.cpp | 53 ++++++++++++++++ .../vk_emulated_physical_device_memory.h | 5 ++ ..._emulated_physical_device_memory_tests.cpp | 61 +++++++++++++++++++ 3 files changed, 119 insertions(+) diff --git a/host/vulkan/vk_emulated_physical_device_memory.cpp b/host/vulkan/vk_emulated_physical_device_memory.cpp index 0006d0a7e..2a0fe6892 100644 --- a/host/vulkan/vk_emulated_physical_device_memory.cpp +++ b/host/vulkan/vk_emulated_physical_device_memory.cpp @@ -42,6 +42,42 @@ EmulatedPhysicalDeviceMemoryProperties::EmulatedPhysicalDeviceMemoryProperties( } mGuestColorBufferMemoryTypeIndex = hostColorBufferMemoryTypeIndex; + // With system blobs, host visible memory is shared memory imported as a host pointer, and + // a tiled image cannot be bound to that. A device whose every memory type is host visible + // leaves images nowhere else to go, so the guest gets a device local only type that + // allocates from the same host type without the sharing. It goes first: a strict subset + // of flags has to precede its superset, which is also what makes it the type an image + // is given. + if (features.SystemBlob.enabled() && mHostMemoryProperties.memoryTypeCount > 0 && + mHostMemoryProperties.memoryTypeCount < VK_MAX_MEMORY_TYPES) { + bool allHostVisible = true; + uint32_t hostDeviceLocalIndex = 0; + for (uint32_t i = 0; i < mHostMemoryProperties.memoryTypeCount; i++) { + const VkMemoryPropertyFlags flags = mHostMemoryProperties.memoryTypes[i].propertyFlags; + if (!(flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) { + allHostVisible = false; + } + if ((flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) && hostDeviceLocalIndex == 0) { + hostDeviceLocalIndex = i; + } + } + if (allHostVisible) { + for (uint32_t i = mGuestMemoryProperties.memoryTypeCount; i > 0; i--) { + mGuestMemoryProperties.memoryTypes[i] = mGuestMemoryProperties.memoryTypes[i - 1]; + mGuestToHostMemoryTypeIndexMap[i] = i - 1; + mHostToGuestMemoryTypeIndexMap[i - 1] = i; + } + mGuestMemoryProperties.memoryTypes[0] = VkMemoryType{ + .propertyFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + .heapIndex = mHostMemoryProperties.memoryTypes[hostDeviceLocalIndex].heapIndex, + }; + mGuestMemoryProperties.memoryTypeCount++; + mGuestToHostMemoryTypeIndexMap[0] = hostDeviceLocalIndex; + mGuestDeviceOnlyMemoryTypeIndex = 0; + mGuestColorBufferMemoryTypeIndex = hostColorBufferMemoryTypeIndex + 1; + } + } + // Limit max safe memory heap size if the VulkanMaxSafeHeapSize feature is set to a non-zero // value. const uint64_t maxSafeHeapSizeLimit = features.VulkanMaxSafeHeapSize.getValue().value_or(0); @@ -179,6 +215,15 @@ EmulatedPhysicalDeviceMemoryProperties::getHostMemoryInfoFromGuestMemoryTypeInde return std::nullopt; } + // The device only type is the host type with its host visibility withheld, so that an + // allocation from it is not given host visible emulation. + if (mGuestDeviceOnlyMemoryTypeIndex == guestMemoryTypeIndex) { + return HostMemoryInfo{ + .index = hostMemoryTypeIndex, + .memoryType = mGuestMemoryProperties.memoryTypes[guestMemoryTypeIndex], + }; + } + return getHostMemoryInfoFromHostMemoryTypeIndex(hostMemoryTypeIndex); } @@ -201,6 +246,14 @@ void EmulatedPhysicalDeviceMemoryProperties::transformToGuestMemoryRequirements( guestMemoryTypeBits |= (1u << guestMemoryTypeIndex); } + if (mGuestDeviceOnlyMemoryTypeIndex) { + const uint32_t hostMemoryTypeIndex = + mGuestToHostMemoryTypeIndexMap[*mGuestDeviceOnlyMemoryTypeIndex]; + if (hostMemoryTypeBits & (1u << hostMemoryTypeIndex)) { + guestMemoryTypeBits |= (1u << *mGuestDeviceOnlyMemoryTypeIndex); + } + } + memoryRequirements->memoryTypeBits = guestMemoryTypeBits; } diff --git a/host/vulkan/vk_emulated_physical_device_memory.h b/host/vulkan/vk_emulated_physical_device_memory.h index 9d344dc5f..87d85b411 100644 --- a/host/vulkan/vk_emulated_physical_device_memory.h +++ b/host/vulkan/vk_emulated_physical_device_memory.h @@ -68,6 +68,11 @@ class EmulatedPhysicalDeviceMemoryProperties { // try to import host ColorBuffer allocations // (e.g. vkGetAndroidHardwareBufferPropertiesANDROID()). uint32_t mGuestColorBufferMemoryTypeIndex; + + // A guest only memory type that allocates from a host visible host type without host + // visible emulation, so that images have somewhere to go on a device whose every memory + // type is host visible. + std::optional mGuestDeviceOnlyMemoryTypeIndex; }; } // namespace vk diff --git a/host/vulkan/vk_emulated_physical_device_memory_tests.cpp b/host/vulkan/vk_emulated_physical_device_memory_tests.cpp index cb8167f5b..b3d881b56 100644 --- a/host/vulkan/vk_emulated_physical_device_memory_tests.cpp +++ b/host/vulkan/vk_emulated_physical_device_memory_tests.cpp @@ -116,6 +116,67 @@ TEST(VkGuestMemoryUtilsTest, Passthrough) { EqsVkPhysicalDeviceMemoryProperties(hostMemoryProperties)); } +TEST(VkGuestMemoryUtilsTest, SystemBlobDeviceOnlyTypeWhenEverythingIsHostVisible) { + // A unified memory device, as Metal reports it. + const VkPhysicalDeviceMemoryProperties hostMemoryProperties = { + .memoryTypeCount = 1, + .memoryTypes = + { + { + .propertyFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | + VK_MEMORY_PROPERTY_HOST_CACHED_BIT, + .heapIndex = 0, + }, + }, + .memoryHeapCount = 1, + .memoryHeaps = + { + { + .size = 0x1000000, + .flags = VK_MEMORY_HEAP_DEVICE_LOCAL_BIT, + }, + }, + }; + + gfxstream::host::FeatureSet features; + features.SystemBlob.setEnabled(true); + features.GlDirectMem.setEnabled(true); + + EmulatedPhysicalDeviceMemoryProperties helper(hostMemoryProperties, 0, features); + + // The guest sees a device local only type first, then the host type. + VkPhysicalDeviceMemoryProperties expectedGuestMemoryProperties = hostMemoryProperties; + expectedGuestMemoryProperties.memoryTypeCount = 2; + expectedGuestMemoryProperties.memoryTypes[0] = { + .propertyFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + .heapIndex = 0, + }; + expectedGuestMemoryProperties.memoryTypes[1] = hostMemoryProperties.memoryTypes[0]; + EXPECT_THAT(helper.getGuestMemoryProperties(), + EqsVkPhysicalDeviceMemoryProperties(expectedGuestMemoryProperties)); + + // Both allocate from the one host type; the device only one is not host visible. + EXPECT_THAT(helper.getHostMemoryInfoFromGuestMemoryTypeIndex(0), + Optional(EqsHostMemoryInfo(EmulatedPhysicalDeviceMemoryProperties::HostMemoryInfo{ + .index = 0, + .memoryType = expectedGuestMemoryProperties.memoryTypes[0], + }))); + EXPECT_THAT(helper.getHostMemoryInfoFromGuestMemoryTypeIndex(1), + Optional(EqsHostMemoryInfo(EmulatedPhysicalDeviceMemoryProperties::HostMemoryInfo{ + .index = 0, + .memoryType = hostMemoryProperties.memoryTypes[0], + }))); + + // Anything the host type can hold, either guest type can. + VkMemoryRequirements requirements = {.memoryTypeBits = 0b1}; + helper.transformToGuestMemoryRequirements(&requirements); + EXPECT_EQ(requirements.memoryTypeBits, 0b11u); + + EXPECT_EQ(helper.getGuestColorBufferMemoryTypeIndex(), 1u); +} + TEST(VkGuestMemoryUtilsTest, ReserveAHardwareBuffer) { const VkPhysicalDeviceMemoryProperties hostMemoryProperties = { .memoryTypeCount = 2, From eba0d6145a62931e4759720b1264cdbf95110eb8 Mon Sep 17 00:00:00 2001 From: utzcoz Date: Sat, 5 Sep 2026 00:33:02 +0800 Subject: [PATCH 2/2] base: keep shared memory names from outliving the server on Apple System blob memory is named shared-memory-vk-N with a per process counter and opened without O_EXCL. A server that is killed leaves its objects behind, the next one reuses the names, and on macOS an object that already has a size cannot be resized: ftruncate fails with EINVAL and vkAllocateMemory fails. Put the pid in the name, and on Apple unlink the object as soon as it exists; the descriptor keeps it alive and is what the guest is handed. --- common/base/SharedMemory_posix.cpp | 6 ++++++ host/vulkan/vk_decoder_global_state.cpp | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/common/base/SharedMemory_posix.cpp b/common/base/SharedMemory_posix.cpp index efd971c4a..b8eea914a 100644 --- a/common/base/SharedMemory_posix.cpp +++ b/common/base/SharedMemory_posix.cpp @@ -136,6 +136,12 @@ int SharedMemory::openInternal(int oflag, int mode, bool doMapping) { } if (oflag & O_CREAT) { +#if defined(__APPLE__) + // The descriptor keeps the object alive; the name is only a leak if the process dies. + if (mShareType == ShareType::SHARED_MEMORY) { + shm_unlink(mName.c_str()); + } +#endif if (HANDLE_EINTR(fstat(mFd, &sb)) == -1) { err = -errno; close(); diff --git a/host/vulkan/vk_decoder_global_state.cpp b/host/vulkan/vk_decoder_global_state.cpp index 14665afcd..0871ef225 100644 --- a/host/vulkan/vk_decoder_global_state.cpp +++ b/host/vulkan/vk_decoder_global_state.cpp @@ -6716,7 +6716,10 @@ class VkDecoderGlobalState::Impl { static_cast(alignedSize)); } localAllocInfo.allocationSize = alignedSize; - auto memory = SharedMemory("shared-memory-vk-" + std::to_string(sUniqueShmemId++), + // The name is per process: a server that was killed can leave its objects + // behind, and a name that already has a size cannot be resized. + auto memory = SharedMemory("shared-memory-vk-" + std::to_string(getpid()) + "-" + + std::to_string(sUniqueShmemId++), localAllocInfo.allocationSize); if (m_vkEmulation->getFeatures().VulkanAllocateHostVisibleAsUdmabuf.enabled()) {