From e80a4cec0f0655a821b03a607d5f310563975ee2 Mon Sep 17 00:00:00 2001 From: utzcoz Date: Sat, 12 Sep 2026 23:43:08 +0800 Subject: [PATCH 1/7] vulkan: give the guest its own memory types for a device image query vkGetDeviceImageMemoryRequirements answers with the host's memory type bits unless the format is one gfxstream decompresses. The query that names a created image translates them first. Nothing notices while a guest memory type index is also a host one, which holds until a type is emulated. With one, a guest is told an image fits memory types it does not have, and asking the same question the other way answers something else. Translate on the way out on every path. --- host/vulkan/vk_decoder_global_state.cpp | 30 +++++++++++-------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/host/vulkan/vk_decoder_global_state.cpp b/host/vulkan/vk_decoder_global_state.cpp index fb37464df..7ddf47819 100644 --- a/host/vulkan/vk_decoder_global_state.cpp +++ b/host/vulkan/vk_decoder_global_state.cpp @@ -2768,13 +2768,6 @@ class VkDecoderGlobalState::Impl { GFXSTREAM_FATAL("%s: function implementation cannot be found!"); } - const VkFormat format = pInfo->pCreateInfo->format; - bool needDecompression = isEtc2(format) || isAstc(format); - if (!needDecompression) { - // No modifications needed - return; - } - std::lock_guard lock(mMutex); auto* deviceInfo = gfxstream::base::find(mDeviceInfo, device); @@ -2783,9 +2776,20 @@ class VkDecoderGlobalState::Impl { return; } - needDecompression = deviceInfo->needEmulatedDecompression(format); + auto* physicalDeviceInfo = gfxstream::base::find(mPhysdevInfo, deviceInfo->physicalDevice); + if (!physicalDeviceInfo) { + GFXSTREAM_ERROR("Failed to find physical device info for physical device:%p", + deviceInfo->physicalDevice); + return; + } + auto& physicalDeviceMemHelper = physicalDeviceInfo->memoryPropertiesHelper; + + const VkFormat format = pInfo->pCreateInfo->format; + const bool needDecompression = + (isEtc2(format) || isAstc(format)) && deviceInfo->needEmulatedDecompression(format); if (!needDecompression) { - // No modifications needed + physicalDeviceMemHelper->transformToGuestMemoryRequirements( + &pMemoryRequirements->memoryRequirements); return; } @@ -2808,14 +2812,6 @@ class VkDecoderGlobalState::Impl { pMemoryRequirements->memoryRequirements = cmpInfo.getMemoryRequirements(); cmpInfo.destroy(vk); - auto* physicalDeviceInfo = gfxstream::base::find(mPhysdevInfo, deviceInfo->physicalDevice); - if (!physicalDeviceInfo) { - GFXSTREAM_ERROR("Failed to find physical device info for physical device:%p", - deviceInfo->physicalDevice); - return; - } - - auto& physicalDeviceMemHelper = physicalDeviceInfo->memoryPropertiesHelper; physicalDeviceMemHelper->transformToGuestMemoryRequirements( &pMemoryRequirements->memoryRequirements); } From f6c6476374e68efb04b35a6f58c0a9eb2915a68e Mon Sep 17 00:00:00 2001 From: utzcoz Date: Sat, 12 Sep 2026 21:38:00 +0800 Subject: [PATCH 2/7] vulkan: align a system blob to what the driver imports A system blob's shared memory is handed to the driver as a host pointer, and a driver states the alignment it takes in minImportedHostPointerAlignment. The size is rounded to 4096 instead, which is the page size the guest is assumed to have rather than anything the importing driver asked for. On Apple silicon a page is 16KB and kosmickrisp asks for that, so an allocation whose size is a multiple of 4096 but not of 16KB is imported against the driver's stated requirement. Round up to whichever is larger. Where the two agree, which is everywhere a page is 4096, nothing changes. --- host/vulkan/vk_decoder_global_state.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/host/vulkan/vk_decoder_global_state.cpp b/host/vulkan/vk_decoder_global_state.cpp index 7ddf47819..49bf25656 100644 --- a/host/vulkan/vk_decoder_global_state.cpp +++ b/host/vulkan/vk_decoder_global_state.cpp @@ -6851,8 +6851,17 @@ class VkDecoderGlobalState::Impl { #endif } else if (m_vkEmulation->getFeatures().SystemBlob.enabled() || m_vkEmulation->getFeatures().VulkanAllocateHostVisibleAsUdmabuf.enabled()) { + // A system blob is imported as a host pointer, and a driver can ask for a + // coarser alignment than a page: Apple silicon pages are 16KB. + uint64_t blobAlignment = kPageSizeforBlob; + if (m_vkEmulation->supportsExternalMemoryHostProperties()) { + blobAlignment = std::max(blobAlignment, + m_vkEmulation->externalMemoryHostProperties() + .minImportedHostPointerAlignment); + } + // Ensure size is page-aligned. - VkDeviceSize alignedSize = ALIGN(localAllocInfo.allocationSize, kPageSizeforBlob); + VkDeviceSize alignedSize = ALIGN(localAllocInfo.allocationSize, blobAlignment); if (alignedSize != localAllocInfo.allocationSize) { GFXSTREAM_ERROR("Warning: Aligning allocation size from %llu to %llu", static_cast(localAllocInfo.allocationSize), @@ -6904,8 +6913,7 @@ class VkDecoderGlobalState::Impl { return VK_ERROR_OUT_OF_HOST_MEMORY; } mappedPtr = memory.get(); - int mappedPtrAlignment = - reinterpret_cast(mappedPtr) % kPageSizeforBlob; + int mappedPtrAlignment = reinterpret_cast(mappedPtr) % blobAlignment; if (mappedPtrAlignment != 0) { GFXSTREAM_ERROR( "Warning: Mapped shared memory pointer is not aligned to page size, " From 4167af747d22843223346e72f539eeb21af1d4f4 Mon Sep 17 00:00:00 2001 From: utzcoz Date: Sat, 5 Sep 2026 00:33:02 +0800 Subject: [PATCH 3/7] base: keep a dead server's shared memory from failing the next one 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 create with O_EXCL, unlinking and making the object again when the name is taken. What has the name then is either left over from a dead process or belongs to a live one, which keeps it through its descriptor either way; sharing it by accident is what O_CREAT alone did. --- common/base/SharedMemory_posix.cpp | 10 +++++++++- host/vulkan/vk_decoder_global_state.cpp | 3 ++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/common/base/SharedMemory_posix.cpp b/common/base/SharedMemory_posix.cpp index efd971c4a..40cf525f3 100644 --- a/common/base/SharedMemory_posix.cpp +++ b/common/base/SharedMemory_posix.cpp @@ -115,7 +115,15 @@ int SharedMemory::openInternal(int oflag, int mode, bool doMapping) { struct stat sb; if (mShareType == ShareType::SHARED_MEMORY) { #if defined(__APPLE__) - mFd = ::shm_open(mName.c_str(), oflag, mode); + // An object left behind by a dead process cannot be resized, and one another process + // still uses must not be shared by accident: create fresh, replacing whatever has the + // name. Its owner, if any, keeps it through its descriptor. + const int createFlags = (oflag & O_CREAT) ? (oflag | O_EXCL) : oflag; + mFd = ::shm_open(mName.c_str(), createFlags, mode); + if (mFd == -1 && errno == EEXIST && (oflag & O_CREAT)) { + shm_unlink(mName.c_str()); + mFd = ::shm_open(mName.c_str(), createFlags, mode); + } #elif defined(HAVE_MEMFD_CREATE) mFd = memfd_create(mName.c_str(), MFD_CLOEXEC | MFD_ALLOW_SEALING); #else diff --git a/host/vulkan/vk_decoder_global_state.cpp b/host/vulkan/vk_decoder_global_state.cpp index 49bf25656..e8a68b695 100644 --- a/host/vulkan/vk_decoder_global_state.cpp +++ b/host/vulkan/vk_decoder_global_state.cpp @@ -6868,7 +6868,8 @@ class VkDecoderGlobalState::Impl { static_cast(alignedSize)); } localAllocInfo.allocationSize = alignedSize; - auto memory = SharedMemory("shared-memory-vk-" + std::to_string(sUniqueShmemId++), + auto memory = SharedMemory("shared-memory-vk-" + std::to_string(getpid()) + "-" + + std::to_string(sUniqueShmemId++), localAllocInfo.allocationSize); if (m_vkEmulation->getFeatures().VulkanAllocateHostVisibleAsUdmabuf.enabled()) { From 3a2f902dbd5a3c977de2a112cd9316e0bd70b013 Mon Sep 17 00:00:00 2001 From: utzcoz Date: Sat, 5 Sep 2026 00:33:02 +0800 Subject: [PATCH 4/7] 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, allocating from the same host type without host visible emulation. A tiled image is offered that type alone, since the host visible one cannot hold it; a linear image may still use either. MoltenVK reports a device local only type of its own, so nothing changes there. Guest memory types are now kept as a list of structs, each naming its host type and what it is reserved for, instead of index maps in both directions. The new type is inserted where the spec's ordering puts it: a type whose flags are a strict subset of another's sits at a lower index, which on a unified memory device is the front. The AHB type is still appended, as before. Metal is what makes this the common case, so it is behind __APPLE__. --- host/vulkan/vk_decoder_global_state.cpp | 20 ++- .../vk_emulated_physical_device_memory.cpp | 132 ++++++++++++++---- .../vk_emulated_physical_device_memory.h | 22 ++- ..._emulated_physical_device_memory_tests.cpp | 48 +++++++ 4 files changed, 189 insertions(+), 33 deletions(-) diff --git a/host/vulkan/vk_decoder_global_state.cpp b/host/vulkan/vk_decoder_global_state.cpp index e8a68b695..1902ea45e 100644 --- a/host/vulkan/vk_decoder_global_state.cpp +++ b/host/vulkan/vk_decoder_global_state.cpp @@ -2788,8 +2788,8 @@ class VkDecoderGlobalState::Impl { const bool needDecompression = (isEtc2(format) || isAstc(format)) && deviceInfo->needEmulatedDecompression(format); if (!needDecompression) { - physicalDeviceMemHelper->transformToGuestMemoryRequirements( - &pMemoryRequirements->memoryRequirements); + physicalDeviceMemHelper->transformToGuestImageMemoryRequirements( + pInfo->pCreateInfo->tiling, &pMemoryRequirements->memoryRequirements); return; } @@ -2812,8 +2812,8 @@ class VkDecoderGlobalState::Impl { pMemoryRequirements->memoryRequirements = cmpInfo.getMemoryRequirements(); cmpInfo.destroy(vk); - physicalDeviceMemHelper->transformToGuestMemoryRequirements( - &pMemoryRequirements->memoryRequirements); + physicalDeviceMemHelper->transformToGuestImageMemoryRequirements( + pInfo->pCreateInfo->tiling, &pMemoryRequirements->memoryRequirements); } void destroyDeviceWithExclusiveInfo(VkDevice device, DeviceInfo& deviceInfo, @@ -5598,7 +5598,8 @@ class VkDecoderGlobalState::Impl { auto& physicalDeviceMemHelper = physicalDeviceInfo->memoryPropertiesHelper; updateImageMemoryRequirementsLocked(device, image, pMemoryRequirements); - physicalDeviceMemHelper->transformToGuestMemoryRequirements(pMemoryRequirements); + physicalDeviceMemHelper->transformToGuestImageMemoryRequirements( + imageTilingLocked(image), pMemoryRequirements); } // A driver that defers the layout also reports rowPitch=0; answer with the AHB's stride. @@ -5664,8 +5665,8 @@ class VkDecoderGlobalState::Impl { auto& physicalDeviceMemHelper = physicalDeviceInfo->memoryPropertiesHelper; updateImageMemoryRequirementsLocked(device, pInfo->image, &pMemoryRequirements->memoryRequirements); - physicalDeviceMemHelper->transformToGuestMemoryRequirements( - &pMemoryRequirements->memoryRequirements); + physicalDeviceMemHelper->transformToGuestImageMemoryRequirements( + imageTilingLocked(pInfo->image), &pMemoryRequirements->memoryRequirements); } void on_vkGetBufferMemoryRequirements(gfxstream::base::BumpPool* pool, VkSnapshotApiCallHandle, @@ -10500,6 +10501,11 @@ class VkDecoderGlobalState::Impl { return false; } + VkImageTiling imageTilingLocked(VkImage image) REQUIRES(mMutex) { + auto* imageInfo = gfxstream::base::find(mImageInfo, image); + return imageInfo ? imageInfo->imageCreateInfoShallow.tiling : VK_IMAGE_TILING_OPTIMAL; + } + void updateImageMemoryRequirementsLocked(VkDevice device, VkImage image, VkMemoryRequirements* pMemoryRequirements) REQUIRES(mMutex) { diff --git a/host/vulkan/vk_emulated_physical_device_memory.cpp b/host/vulkan/vk_emulated_physical_device_memory.cpp index 0006d0a7e..d50962cf2 100644 --- a/host/vulkan/vk_emulated_physical_device_memory.cpp +++ b/host/vulkan/vk_emulated_physical_device_memory.cpp @@ -24,7 +24,23 @@ namespace host { namespace vk { namespace { -static constexpr const uint32_t kInvalidMemoryTypeIndex = std::numeric_limits::max(); +#if defined(__APPLE__) +// The first device local type, if every type is host visible. +std::optional FindDeviceLocalMemoryTypeIfAllAreHostVisible( + const VkPhysicalDeviceMemoryProperties& memoryProperties) { + std::optional deviceLocalIndex; + for (uint32_t i = 0; i < memoryProperties.memoryTypeCount; i++) { + const VkMemoryPropertyFlags flags = memoryProperties.memoryTypes[i].propertyFlags; + if (!(flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) { + return std::nullopt; + } + if (!deviceLocalIndex && (flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) { + deviceLocalIndex = i; + } + } + return deviceLocalIndex; +} +#endif } // namespace @@ -34,12 +50,6 @@ EmulatedPhysicalDeviceMemoryProperties::EmulatedPhysicalDeviceMemoryProperties( // Start with the original host memory properties: mHostMemoryProperties = hostMemoryProperties; mGuestMemoryProperties = hostMemoryProperties; - std::fill_n(mGuestToHostMemoryTypeIndexMap, VK_MAX_MEMORY_TYPES, kInvalidMemoryTypeIndex); - std::fill_n(mHostToGuestMemoryTypeIndexMap, VK_MAX_MEMORY_TYPES, kInvalidMemoryTypeIndex); - for (uint32_t i = 0; i < mHostMemoryProperties.memoryTypeCount; i++) { - mGuestToHostMemoryTypeIndexMap[i] = i; - mHostToGuestMemoryTypeIndexMap[i] = i; - } mGuestColorBufferMemoryTypeIndex = hostColorBufferMemoryTypeIndex; // Limit max safe memory heap size if the VulkanMaxSafeHeapSize feature is set to a non-zero @@ -129,29 +139,83 @@ EmulatedPhysicalDeviceMemoryProperties::EmulatedPhysicalDeviceMemoryProperties( } } + for (uint32_t i = 0; i < mGuestMemoryProperties.memoryTypeCount; i++) { + mGuestMemoryTypes.push_back(EmulatedGuestMemoryType{ + .hostMemoryTypeIndex = i, + .memoryType = mGuestMemoryProperties.memoryTypes[i], + }); + } + +#if defined(__APPLE__) + const std::optional hostDeviceLocalIndex = + features.SystemBlob.enabled() + ? FindDeviceLocalMemoryTypeIfAllAreHostVisible(hostMemoryProperties) + : std::nullopt; + if (hostDeviceLocalIndex && mGuestMemoryTypes.size() < VK_MAX_MEMORY_TYPES) { + const VkMemoryType memoryType = { + .propertyFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + .heapIndex = hostMemoryProperties.memoryTypes[*hostDeviceLocalIndex].heapIndex, + }; + const uint32_t index = findIndexForNewMemoryType(memoryType.propertyFlags); + mGuestMemoryTypes.insert(mGuestMemoryTypes.begin() + index, + EmulatedGuestMemoryType{ + .hostMemoryTypeIndex = *hostDeviceLocalIndex, + .memoryType = memoryType, + }); + mGuestAppleSystemBlobMemoryTypeIndex = index; + if (index <= mGuestColorBufferMemoryTypeIndex) { + mGuestColorBufferMemoryTypeIndex++; + } + } +#endif + // If enabled, reserve an additional memory type for AHB backed buffers and images // so that the host can control its memory properties. This ensures that the guest // only sees `VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT` and will not try to map the // memory. if (features.VulkanUseDedicatedAhbMemoryType.enabled()) { - if (mGuestMemoryProperties.memoryTypeCount == VK_MAX_MEMORY_TYPES) { + if (mGuestMemoryTypes.size() == VK_MAX_MEMORY_TYPES) { GFXSTREAM_FATAL( "Unable to create emulated AHB memory type because VK_MAX_MEMORY_TYPES " "already in use."); } - uint32_t ahbMemoryTypeIndex = mGuestMemoryProperties.memoryTypeCount; - ++mGuestMemoryProperties.memoryTypeCount; - - VkMemoryType& ahbMemoryType = mGuestMemoryProperties.memoryTypes[ahbMemoryTypeIndex]; - ahbMemoryType.propertyFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; - ahbMemoryType.heapIndex = - mHostMemoryProperties.memoryTypes[hostColorBufferMemoryTypeIndex].heapIndex; + const VkMemoryType memoryType = { + .propertyFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + .heapIndex = + mHostMemoryProperties.memoryTypes[hostColorBufferMemoryTypeIndex].heapIndex, + }; + const uint32_t index = findIndexForNewMemoryType(memoryType.propertyFlags); + mGuestMemoryTypes.insert(mGuestMemoryTypes.begin() + index, + EmulatedGuestMemoryType{ + .hostMemoryTypeIndex = hostColorBufferMemoryTypeIndex, + .isReservedForAhbAllocations = true, + .memoryType = memoryType, + }); + mGuestColorBufferMemoryTypeIndex = index; + if (mGuestAppleSystemBlobMemoryTypeIndex && + index <= *mGuestAppleSystemBlobMemoryTypeIndex) { + ++*mGuestAppleSystemBlobMemoryTypeIndex; + } + } - mGuestToHostMemoryTypeIndexMap[ahbMemoryTypeIndex] = hostColorBufferMemoryTypeIndex; + mGuestMemoryProperties.memoryTypeCount = static_cast(mGuestMemoryTypes.size()); + for (uint32_t i = 0; i < mGuestMemoryProperties.memoryTypeCount; i++) { + mGuestMemoryProperties.memoryTypes[i] = mGuestMemoryTypes[i].memoryType; + } +} - mGuestColorBufferMemoryTypeIndex = ahbMemoryTypeIndex; +uint32_t EmulatedPhysicalDeviceMemoryProperties::findIndexForNewMemoryType( + VkMemoryPropertyFlags propertyFlags) const { + // A memory type whose flags are a strict subset of another's must come before it, see + // https://docs.vulkan.org/refpages/latest/refpages/source/VkPhysicalDeviceMemoryProperties.html + for (uint32_t i = 0; i < mGuestMemoryTypes.size(); i++) { + const VkMemoryPropertyFlags existingFlags = mGuestMemoryTypes[i].memoryType.propertyFlags; + if (existingFlags != propertyFlags && (existingFlags & propertyFlags) == propertyFlags) { + return i; + } } + return static_cast(mGuestMemoryTypes.size()); } std::optional @@ -174,9 +238,14 @@ EmulatedPhysicalDeviceMemoryProperties::getHostMemoryInfoFromGuestMemoryTypeInde return std::nullopt; } - uint32_t hostMemoryTypeIndex = mGuestToHostMemoryTypeIndexMap[guestMemoryTypeIndex]; - if (hostMemoryTypeIndex == kInvalidMemoryTypeIndex) { - return std::nullopt; + uint32_t hostMemoryTypeIndex = mGuestMemoryTypes[guestMemoryTypeIndex].hostMemoryTypeIndex; + + // The host type with its host visibility withheld, so no host visible emulation. + if (guestMemoryTypeIndex == mGuestAppleSystemBlobMemoryTypeIndex) { + return HostMemoryInfo{ + .index = hostMemoryTypeIndex, + .memoryType = mGuestMemoryProperties.memoryTypes[guestMemoryTypeIndex], + }; } return getHostMemoryInfoFromHostMemoryTypeIndex(hostMemoryTypeIndex); @@ -187,14 +256,14 @@ void EmulatedPhysicalDeviceMemoryProperties::transformToGuestMemoryRequirements( uint32_t guestMemoryTypeBits = 0; const uint32_t hostMemoryTypeBits = memoryRequirements->memoryTypeBits; - for (uint32_t hostMemoryTypeIndex = 0; - hostMemoryTypeIndex < mHostMemoryProperties.memoryTypeCount; hostMemoryTypeIndex++) { + for (uint32_t guestMemoryTypeIndex = 0; guestMemoryTypeIndex < mGuestMemoryTypes.size(); + guestMemoryTypeIndex++) { + uint32_t hostMemoryTypeIndex = mGuestMemoryTypes[guestMemoryTypeIndex].hostMemoryTypeIndex; if (!(hostMemoryTypeBits & (1u << hostMemoryTypeIndex))) { continue; } - uint32_t guestMemoryTypeIndex = mHostToGuestMemoryTypeIndexMap[hostMemoryTypeIndex]; - if (guestMemoryTypeIndex == kInvalidMemoryTypeIndex) { + if (mGuestMemoryTypes[guestMemoryTypeIndex].isReservedForAhbAllocations) { continue; } @@ -204,6 +273,21 @@ void EmulatedPhysicalDeviceMemoryProperties::transformToGuestMemoryRequirements( memoryRequirements->memoryTypeBits = guestMemoryTypeBits; } +void EmulatedPhysicalDeviceMemoryProperties::transformToGuestImageMemoryRequirements( + VkImageTiling tiling, VkMemoryRequirements* memoryRequirements) const { + transformToGuestMemoryRequirements(memoryRequirements); + if (tiling == VK_IMAGE_TILING_LINEAR || !mGuestAppleSystemBlobMemoryTypeIndex) { + return; + } + + // The type is set only when the host allows the host type behind it, so a tiled image + // is left that alone; without it, what the host allows is better than nothing. + const uint32_t tiledMemoryTypeBit = 1u << *mGuestAppleSystemBlobMemoryTypeIndex; + if (memoryRequirements->memoryTypeBits & tiledMemoryTypeBit) { + memoryRequirements->memoryTypeBits = tiledMemoryTypeBit; + } +} + void EmulatedPhysicalDeviceMemoryProperties::clampMemoryBudgetToGuestHeapSizes( VkPhysicalDeviceMemoryBudgetPropertiesEXT* budgetProps) const { if (budgetProps == nullptr) { diff --git a/host/vulkan/vk_emulated_physical_device_memory.h b/host/vulkan/vk_emulated_physical_device_memory.h index 9d344dc5f..344669bd8 100644 --- a/host/vulkan/vk_emulated_physical_device_memory.h +++ b/host/vulkan/vk_emulated_physical_device_memory.h @@ -17,6 +17,7 @@ #include #include +#include #include "gfxstream/host/features.h" @@ -54,20 +55,37 @@ class EmulatedPhysicalDeviceMemoryProperties { void transformToGuestMemoryRequirements(VkMemoryRequirements* hostMemoryRequirements) const; + // As above, but a tiled image is left only the types reserved for it. + void transformToGuestImageMemoryRequirements( + VkImageTiling tiling, VkMemoryRequirements* hostMemoryRequirements) const; + // Clamp heapBudget/heapUsage to the guest-visible heap sizes. No-op if budgetProps is null. void clampMemoryBudgetToGuestHeapSizes( VkPhysicalDeviceMemoryBudgetPropertiesEXT* budgetProps) const; private: + uint32_t findIndexForNewMemoryType(VkMemoryPropertyFlags propertyFlags) const; + VkPhysicalDeviceMemoryProperties mGuestMemoryProperties; VkPhysicalDeviceMemoryProperties mHostMemoryProperties; - uint32_t mGuestToHostMemoryTypeIndexMap[VK_MAX_MEMORY_TYPES]; - uint32_t mHostToGuestMemoryTypeIndexMap[VK_MAX_MEMORY_TYPES]; + + struct EmulatedGuestMemoryType { + uint32_t hostMemoryTypeIndex; + // Reserved for AHBs with the VulkanUseDedicatedAhbMemoryType feature. + bool isReservedForAhbAllocations = false; + // The memory properties reported to the guest. + VkMemoryType memoryType; + }; + std::vector mGuestMemoryTypes; // The memory type index reported to the guest for VkDeviceMemory requirements which would // try to import host ColorBuffer allocations // (e.g. vkGetAndroidHardwareBufferPropertiesANDROID()). uint32_t mGuestColorBufferMemoryTypeIndex; + + // The memory type index reported to the guest for tiled images on Apple with system + // blobs: device local only, so that the host never imports shared memory for them. + std::optional mGuestAppleSystemBlobMemoryTypeIndex; }; } // 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..0b3744b18 100644 --- a/host/vulkan/vk_emulated_physical_device_memory_tests.cpp +++ b/host/vulkan/vk_emulated_physical_device_memory_tests.cpp @@ -116,6 +116,54 @@ TEST(VkGuestMemoryUtilsTest, Passthrough) { EqsVkPhysicalDeviceMemoryProperties(hostMemoryProperties)); } +#if defined(__APPLE__) +TEST(VkGuestMemoryUtilsTest, SystemBlobDeviceLocalTypeWhenEverythingIsHostVisible) { + const VkPhysicalDeviceMemoryProperties hostMemoryProperties = { + .memoryTypeCount = 2, + .memoryTypes = + { + {.propertyFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT}, + {.propertyFlags = + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT}, + }, + .memoryHeapCount = 1, + .memoryHeaps = {{.size = 0x1000000, .flags = VK_MEMORY_HEAP_DEVICE_LOCAL_BIT}}, + }; + + gfxstream::host::FeatureSet features; + features.SystemBlob.setEnabled(true); + EmulatedPhysicalDeviceMemoryProperties helper(hostMemoryProperties, 1, features); + + // Device local alone is a strict subset of the second host type, so it goes before it. + VkPhysicalDeviceMemoryProperties expectedGuestMemoryProperties = hostMemoryProperties; + expectedGuestMemoryProperties.memoryTypeCount = 3; + expectedGuestMemoryProperties.memoryTypes[1] = {.propertyFlags = + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT}; + expectedGuestMemoryProperties.memoryTypes[2] = hostMemoryProperties.memoryTypes[1]; + EXPECT_THAT(helper.getGuestMemoryProperties(), + EqsVkPhysicalDeviceMemoryProperties(expectedGuestMemoryProperties)); + EXPECT_THAT(helper.getHostMemoryInfoFromGuestMemoryTypeIndex(1), + Optional(EqsHostMemoryInfo(EmulatedPhysicalDeviceMemoryProperties::HostMemoryInfo{ + .index = 1, + .memoryType = expectedGuestMemoryProperties.memoryTypes[1], + }))); + EXPECT_EQ(helper.getGuestColorBufferMemoryTypeIndex(), 2u); + + VkMemoryRequirements requirements = {.memoryTypeBits = 0b11}; + helper.transformToGuestImageMemoryRequirements(VK_IMAGE_TILING_LINEAR, &requirements); + EXPECT_EQ(requirements.memoryTypeBits, 0b111u); + + requirements = {.memoryTypeBits = 0b11}; + helper.transformToGuestImageMemoryRequirements(VK_IMAGE_TILING_OPTIMAL, &requirements); + EXPECT_EQ(requirements.memoryTypeBits, 0b010u); + + // Nothing reserved is allowed, so what the host allows stays. + requirements = {.memoryTypeBits = 0b01}; + helper.transformToGuestImageMemoryRequirements(VK_IMAGE_TILING_OPTIMAL, &requirements); + EXPECT_EQ(requirements.memoryTypeBits, 0b001u); +} +#endif + TEST(VkGuestMemoryUtilsTest, ReserveAHardwareBuffer) { const VkPhysicalDeviceMemoryProperties hostMemoryProperties = { .memoryTypeCount = 2, From 7788ca54efb56dd76d6c6b6c2016299ddd145530 Mon Sep 17 00:00:00 2001 From: utzcoz Date: Tue, 22 Sep 2026 01:47:00 +0800 Subject: [PATCH 5/7] vulkan: give the guest its own memory types for a device buffer query vkGetDeviceBufferMemoryRequirements is the one requirements query the decoder still hands to the driver directly, so with an emulated memory type its answer names host types, the way the device image query's did. Add the global state handler that translates it. The decoder reaches it once the guest's generator routes the query through global state, which is a mesa change; until the decoder is regenerated from that, it is not called. --- host/vulkan/vk_decoder_global_state.cpp | 49 +++++++++++++++++++++++++ host/vulkan/vk_decoder_global_state.h | 11 ++++++ 2 files changed, 60 insertions(+) diff --git a/host/vulkan/vk_decoder_global_state.cpp b/host/vulkan/vk_decoder_global_state.cpp index 1902ea45e..ad7e98088 100644 --- a/host/vulkan/vk_decoder_global_state.cpp +++ b/host/vulkan/vk_decoder_global_state.cpp @@ -2816,6 +2816,41 @@ class VkDecoderGlobalState::Impl { pInfo->pCreateInfo->tiling, &pMemoryRequirements->memoryRequirements); } + void on_vkGetDeviceBufferMemoryRequirements(gfxstream::base::BumpPool* pool, + VkSnapshotApiCallHandle apiCallHandle, + VkDevice boxed_device, + const VkDeviceBufferMemoryRequirements* pInfo, + VkMemoryRequirements2* pMemoryRequirements) { + auto device = unbox_VkDevice(boxed_device); + auto vk = dispatch_VkDevice(boxed_device); + + if (vk->vkGetDeviceBufferMemoryRequirements) { + vk->vkGetDeviceBufferMemoryRequirements(device, pInfo, pMemoryRequirements); + } else if (vk->vkGetDeviceBufferMemoryRequirementsKHR) { + vk->vkGetDeviceBufferMemoryRequirementsKHR(device, pInfo, pMemoryRequirements); + } else { + GFXSTREAM_FATAL("%s: function implementation cannot be found!", __func__); + } + + std::lock_guard lock(mMutex); + + auto* deviceInfo = gfxstream::base::find(mDeviceInfo, device); + if (!deviceInfo) { + GFXSTREAM_ERROR("%s: Failed to find device info for device: %p", __func__, device); + return; + } + + auto* physicalDeviceInfo = gfxstream::base::find(mPhysdevInfo, deviceInfo->physicalDevice); + if (!physicalDeviceInfo) { + GFXSTREAM_ERROR("Failed to find physical device info for physical device:%p", + deviceInfo->physicalDevice); + return; + } + + physicalDeviceInfo->memoryPropertiesHelper->transformToGuestMemoryRequirements( + &pMemoryRequirements->memoryRequirements); + } + void destroyDeviceWithExclusiveInfo(VkDevice device, DeviceInfo& deviceInfo, std::unordered_map& fenceInfos, std::unordered_map& queueInfos) { @@ -11778,6 +11813,20 @@ void VkDecoderGlobalState::on_vkGetDeviceImageMemoryRequirementsKHR( pMemoryRequirements); } +void VkDecoderGlobalState::on_vkGetDeviceBufferMemoryRequirements( + gfxstream::base::BumpPool* pool, VkSnapshotApiCallHandle apiCallHandle, VkDevice device, + const VkDeviceBufferMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements) { + mImpl->on_vkGetDeviceBufferMemoryRequirements(pool, apiCallHandle, device, pInfo, + pMemoryRequirements); +} + +void VkDecoderGlobalState::on_vkGetDeviceBufferMemoryRequirementsKHR( + gfxstream::base::BumpPool* pool, VkSnapshotApiCallHandle apiCallHandle, VkDevice device, + const VkDeviceBufferMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements) { + mImpl->on_vkGetDeviceBufferMemoryRequirements(pool, apiCallHandle, device, pInfo, + pMemoryRequirements); +} + void VkDecoderGlobalState::on_vkDestroyDevice(gfxstream::base::BumpPool* pool, VkSnapshotApiCallHandle apiCallHandle, VkDevice device, const VkAllocationCallbacks*) { diff --git a/host/vulkan/vk_decoder_global_state.h b/host/vulkan/vk_decoder_global_state.h index add118d3f..2a97a004e 100644 --- a/host/vulkan/vk_decoder_global_state.h +++ b/host/vulkan/vk_decoder_global_state.h @@ -245,6 +245,17 @@ class VkDecoderGlobalState { const VkDeviceImageMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements); + void on_vkGetDeviceBufferMemoryRequirements(gfxstream::base::BumpPool* pool, + VkSnapshotApiCallHandle apiCallHandle, + VkDevice device, + const VkDeviceBufferMemoryRequirements* pInfo, + VkMemoryRequirements2* pMemoryRequirements); + void on_vkGetDeviceBufferMemoryRequirementsKHR(gfxstream::base::BumpPool* pool, + VkSnapshotApiCallHandle apiCallHandle, + VkDevice device, + const VkDeviceBufferMemoryRequirements* pInfo, + VkMemoryRequirements2* pMemoryRequirements); + void on_vkDestroyDevice(gfxstream::base::BumpPool* pool, VkSnapshotApiCallHandle apiCallHandle, VkDevice device, const VkAllocationCallbacks* pAllocator); From 4d5095f14d939c8c6c0ce8b0ed9ed0e53c4df93c Mon Sep 17 00:00:00 2001 From: utzcoz Date: Thu, 24 Sep 2026 22:30:44 +0800 Subject: [PATCH 6/7] vulkan: regenerate the decoder for the device buffer query Regenerated with mesa's generate-gfxstream-vulkan.sh at caf81bd201a, keeping what mesa 4b40fa7ace8 changes: vkGetDeviceBufferMemoryRequirements and its KHR alias now go through global state, so the handler that gives the guest its own memory types is reached. The generator's other output since the decoder was last regenerated is left for a regeneration of its own. --- host/vulkan/vk_decoder.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/host/vulkan/vk_decoder.cpp b/host/vulkan/vk_decoder.cpp index 2de23adb7..c63e49d14 100644 --- a/host/vulkan/vk_decoder.cpp +++ b/host/vulkan/vk_decoder.cpp @@ -11747,14 +11747,12 @@ size_t VkDecoder::Impl::decode(void* buf, size_t len, IOStream* ioStream, VkDevice device; const VkDeviceBufferMemoryRequirements* pInfo; VkMemoryRequirements2* pMemoryRequirements; - // Begin non wrapped dispatchable handle unboxing for device; + // Begin global wrapped dispatchable handle unboxing for device; uint64_t cgen_var_0; memcpy((uint64_t*)&cgen_var_0, *readStreamPtrPtr, 1 * 8); *readStreamPtrPtr += 1 * 8; *(VkDevice*)&device = (VkDevice)(VkDevice)((VkDevice)(*&cgen_var_0)); - auto unboxed_device = unbox_VkDevice(device); auto vk = dispatch_VkDevice(device); - // End manual dispatchable handle unboxing for device; vkReadStream->alloc((void**)&pInfo, sizeof(const VkDeviceBufferMemoryRequirements)); reservedunmarshal_VkDeviceBufferMemoryRequirements( vkReadStream, VK_STRUCTURE_TYPE_MAX_ENUM, @@ -11780,8 +11778,8 @@ size_t VkDecoder::Impl::decode(void* buf, size_t len, IOStream* ioStream, (unsigned long long)pMemoryRequirements); } if (CC_LIKELY(vk)) { - vk->vkGetDeviceBufferMemoryRequirements(unboxed_device, pInfo, - pMemoryRequirements); + m_state->on_vkGetDeviceBufferMemoryRequirements( + &m_pool, snapshotApiCallHandle, device, pInfo, pMemoryRequirements); } vkStream->unsetHandleMapping(); if (pMemoryRequirements) { @@ -17634,14 +17632,12 @@ size_t VkDecoder::Impl::decode(void* buf, size_t len, IOStream* ioStream, VkDevice device; const VkDeviceBufferMemoryRequirements* pInfo; VkMemoryRequirements2* pMemoryRequirements; - // Begin non wrapped dispatchable handle unboxing for device; + // Begin global wrapped dispatchable handle unboxing for device; uint64_t cgen_var_0; memcpy((uint64_t*)&cgen_var_0, *readStreamPtrPtr, 1 * 8); *readStreamPtrPtr += 1 * 8; *(VkDevice*)&device = (VkDevice)(VkDevice)((VkDevice)(*&cgen_var_0)); - auto unboxed_device = unbox_VkDevice(device); auto vk = dispatch_VkDevice(device); - // End manual dispatchable handle unboxing for device; vkReadStream->alloc((void**)&pInfo, sizeof(const VkDeviceBufferMemoryRequirements)); reservedunmarshal_VkDeviceBufferMemoryRequirements( vkReadStream, VK_STRUCTURE_TYPE_MAX_ENUM, @@ -17668,8 +17664,8 @@ size_t VkDecoder::Impl::decode(void* buf, size_t len, IOStream* ioStream, (unsigned long long)pMemoryRequirements); } if (CC_LIKELY(vk)) { - vk->vkGetDeviceBufferMemoryRequirementsKHR(unboxed_device, pInfo, - pMemoryRequirements); + m_state->on_vkGetDeviceBufferMemoryRequirementsKHR( + &m_pool, snapshotApiCallHandle, device, pInfo, pMemoryRequirements); } vkStream->unsetHandleMapping(); if (pMemoryRequirements) { From ce5580eb7547c813e47e3e3beaad367e9e84919b Mon Sep 17 00:00:00 2001 From: utzcoz Date: Thu, 24 Sep 2026 22:25:06 +0800 Subject: [PATCH 7/7] ci: run the base tests on the macOS runner The shared memory code has an Apple path of its own now, and nothing ran its tests there. Let the reusable Bazel workflow take a test list, and give the macOS job the base tests, which need no graphics driver. The WorkerThread case that expects a ready future before start() is not run on any platform and fails on all of them; it is filtered out until it is fixed. --- .github/workflows/presubmit.yaml | 11 +++++++---- .github/workflows/presubmit_bazel.yml | 21 ++++++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/.github/workflows/presubmit.yaml b/.github/workflows/presubmit.yaml index 94d990daf..60eb19870 100644 --- a/.github/workflows/presubmit.yaml +++ b/.github/workflows/presubmit.yaml @@ -86,10 +86,13 @@ jobs: //host:gfxstream_backend_static //host:gfxstream_backend_shared //host:gfxstream_backend - # Nothing is run yet: this job exists to keep the macOS host build - # compiling. Tests need a graphics driver environment that macOS does not - # have here yet. - run-tests: false + # Only tests that need no graphics driver run: macOS has no driver + # environment here yet. The WorkerThread case is not run anywhere yet + # and fails on every platform, so it stays out until it is fixed. + test-targets: >- + common/base:gfxstream_common_base_tests + additional-bazel-args: >- + --test_arg=--gtest_filter=-WorkerThread.TheReturnedFutureFromEnqueueShouldBeReadyBeforeTheWorkerStarts run-gfxstream-cmake-build: runs-on: ubuntu-22.04 diff --git a/.github/workflows/presubmit_bazel.yml b/.github/workflows/presubmit_bazel.yml index 004befec5..caae4a0e0 100644 --- a/.github/workflows/presubmit_bazel.yml +++ b/.github/workflows/presubmit_bazel.yml @@ -25,6 +25,18 @@ on: type: boolean default: true + # Which tests to run. Defaults to the full suite; platforms that can only + # build part of the target graph can override this with a curated list. + test-targets: + type: string + default: >- + host:gfxstream_framebuffer_tests + host/vulkan:gfxstream_compositorvk_tests + host/vulkan:gfxstream_emulatedphysicalmemory_tests + host/vulkan:vk_common_operations_tests + host/vulkan:vk_format_utils_tests + tests/end2end:gfxstream_end2end_tests + permissions: contents: read @@ -91,16 +103,11 @@ jobs: - name: Test if: ${{ inputs.run-tests }} env: + TEST_TARGETS: ${{ inputs.test-targets }} ADDITIONAL_BAZEL_ARGS: ${{ inputs.additional-bazel-args }} run: | - bazel test \ + bazel test $TEST_TARGETS \ --disk_cache=$HOME/bazel-disk-cache \ --test_output=streamed \ --verbose_failures \ - host:gfxstream_framebuffer_tests \ - host/vulkan:gfxstream_compositorvk_tests \ - host/vulkan:gfxstream_emulatedphysicalmemory_tests \ - host/vulkan:vk_common_operations_tests \ - host/vulkan:vk_format_utils_tests \ - tests/end2end:gfxstream_end2end_tests \ $ADDITIONAL_BAZEL_ARGS