Skip to content

vk: optionally allocate uncached host visible memory as cached - #209

Open
qiyinxi wants to merge 1 commit into
google:mainfrom
qiyinxi:vk-wc-as-cached
Open

qiyinxi wants to merge 1 commit into
google:mainfrom
qiyinxi:vk-wc-as-cached

Conversation

@qiyinxi

@qiyinxi qiyinxi commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #198.

On NVIDIA on Windows (RTX 4070 SUPER), a guest allocation that asks only for HOST_VISIBLE memory is made from the host's HOST_VISIBLE | HOST_COHERENT type. That type is not HOST_CACHED (write-combined on this driver as far as we can tell), and under WHPX the guest's writes into it through its mapping are slow: with Honkai: Star Rail's Vulkan renderer, the upload copy loop takes about a third of the render thread.

Change

A new feature, VulkanAllocateUncachedHostVisibleAsCached, off by default: a guest allocation from a host visible type that is neither host cached nor device local is made from a HOST_VISIBLE | HOST_COHERENT | HOST_CACHED type on the same heap, if there is one.

  • Only when gfxstream allocates the memory itself and maps it with vkMapMemory(); with ExternalBlob, SystemBlob, VulkanAllocateHostVisibleAsUdmabuf or VulkanAllocateHostMemory it does nothing and warns once.
  • Imported memory and blobs keep their type; a dedicated allocation is replaced only if its image or buffer allows the new type. For other allocations this assumes that resources that allow the original type also allow the replacement, which the Vulkan spec does not guarantee (it holds for the NVIDIA types we tested).
  • The guest's memory types do not change; memoryInfo.caching follows the type that was actually allocated.

It could instead use the EmulatedGuestMemoryType mapping from 8136433, so that transformToGuestMemoryRequirements() hides the type from resources that do not allow it. Happy to switch if you prefer that.

To enable it: VulkanAllocateUncachedHostVisibleAsCached:enabled in STREAM_RENDERER_PARAM_RENDERER_FEATURES. The Android Emulator also needs a featurecontrol entry and a kAemuToGfxstreamFeatureMap line in opengles.cpp; we used a local environment variable there.

Existing features

  • VulkanEnsureCachedCoherentMemoryAvailable (Add VulkanEnsureCachedCoherentMemoryAvailable feature #64) only changes reported flags when there is no cached coherent type, so it does nothing on NVIDIA.
  • VulkanDisableCoherentMemoryAndEmulate: the game still lands on the uncached type, and the flushes made it slower, 52.0 → 45.1 fps (Guest uploads into host-visible memory are slow when it is backed by NVIDIA's write-combined type (Windows/WHPX); a cached coherent type fixes it #198).
  • VulkanAllocateHostMemory backs host visible allocations with process-private memory imported through VK_EXT_external_memory_host, so it may avoid the write-combined type as well; the Android Emulator already turns it on for NVIDIA with AEHD (b/382621412), but not with WHPX. We have no numbers for it: with -feature VulkanAllocateHostMemory, the game did not get past start-up on our build (the same crash in its render thread in both attempts), and we have not looked into whether that is related to gfxstream. If you would rather use that path here, I can look into it.

Why off by default

Write-combined memory is usually right for memory the CPU only writes; Dawn, for example, prefers a non-cached coherent type for write-only mapped buffers with BufferMapExtendedUsages (https://dawn.googlesource.com/dawn/+/9729a6380ccabcd595d415afbe83427072091dd7). We do not know why guest writes into it are this slow under WHPX. Risks:

  • GPU reads of cached coherent memory snoop the CPU caches. The guest allocates this memory in 16–32 MB blocks, so vertex and uniform data probably live there too. The emulator's 3D engine utilization stayed in the same range (11–20% without the change, 14–17% with it) while the frame rate went from about 48 fps to the 60 fps cap (below), but we could not compare at equal frame rates.
  • With the Khronos validation layer on the host there were no memory type errors (VUID-vkBindBufferMemory-memory-01035, VUID-vkBindImageMemory-memory-01047), and the same set of messages with and without the change.
  • Not tested on AMD, Intel or Linux. In the Android Emulator, AMD hosts turn on VulkanAllocateDeviceMemoryOnly by default, so non device local types are reported to the guest without any property flags, and Linux hosts with Intel or AMD GPUs turn on VulkanAllocateHostMemory, where this does nothing.

Results

Windows 11 + WHPX, NVIDIA RTX 4070 SUPER (driver 610.88; #198 was measured on 591.86), x86_64 android-34 guest, Star Rail's Vulkan renderer, cold boot and login with no further input (same spot and camera), 10 s samples:

Without the change With this commit
fps (60 fps cap) 45.9–49.9; 47.8–48.8 in a second boot 62.1–62.3
Render thread 88–92% 44–46%
Upload copy loop, share of the render thread's samples 35%; 38% in a second boot 2.7%
Emulator 3D engine utilization 11–20% 14–17%

(all samples in the loop's code range)

The render thread share with the change varied between runs: 44–46% here, and 63–72% in earlier runs of previous revisions, which are the same on this path. Every run with the change stayed well below every run without it (88–92%), and the frame rate and the copy loop share (2–3%) were consistent across them. We could not hold every host condition constant between runs (other processes, clocks, the game's own state), so read that row as a range.

Our emulator build carries other local patches, the same in both columns, including a guest libvulkan_enc fix that Star Rail's Vulkan renderer needs on this image. A local version of this change has been on by default in our builds since 2026-10-01, and we have not traced any problem to it.

Testing

  • VkGuestMemoryUtilsTest.VulkanAllocateUncachedHostVisibleAsCached (Bazel gfxstream_emulatedphysicalmemory_tests, CMake Vulkan_unittests), built on Windows with clang-cl and the Android Emulator's flags: VkGuestMemoryUtilsTest.* 12/12 pass, and the new test fails when the AMD or external memory checks are removed. vk_decoder_global_state.cpp compiles with the same flags; I have not run a full CMake or Bazel build of main.
  • The "With this commit" column and the validation run used an Android Emulator build of this commit, ported to the emulator's gfxstream branch, with the feature turned on; its log shows the game's HOST_VISIBLE allocations on the cached type and no gfxstream errors.

This change was developed with AI assistance (Claude Code; see the Assisted-by trailer). The tests above were run on my machine, and I am responsible for the change.

On NVIDIA on Windows, a guest allocation that asks only for HOST_VISIBLE
memory is made from the host's HOST_VISIBLE | HOST_COHERENT type, which
is not HOST_CACHED (write-combined on this driver, as far as we can
tell). The guest writes its uploads straight into that memory through
its mapping, and under WHPX those writes are slow: in one Unity game the
upload copy loop took about a third of the render thread, and libc
memcpy 45% of it with ANGLE (google#198).

Add the VulkanAllocateUncachedHostVisibleAsCached feature, off by
default. With it, a guest allocation from a host visible memory type
that is neither host cached nor device local is made from a
HOST_VISIBLE | HOST_COHERENT | HOST_CACHED type on the same heap
instead, if the host has one. Imported ColorBuffer and Buffer memory
and blobs keep their memory type, and a dedicated allocation gets the
replacement only if its image or buffer allows it. This assumes that
resources that allow the original memory type also allow the
replacement, which the Vulkan spec does not guarantee.

It only applies when gfxstream allocates host visible memory itself and
maps it with vkMapMemory(). With ExternalBlob, SystemBlob,
VulkanAllocateHostVisibleAsUdmabuf or VulkanAllocateHostMemory, host
visible memory keeps its memory type and a warning is logged once. The
memory types reported to the guest do not change.

Test: VkGuestMemoryUtilsTest.* (12/12), built from this tree with
      clang-cl and the Android Emulator's flags and linked against that
      build's libraries; the new test fails on the AMD device uncached
      row and the four external memory rows when those checks are
      removed.
Test: Android Emulator (Windows 11 + WHPX, RTX 4070 SUPER, x86_64
      android-34) with this change and the feature turned on: Honkai:
      Star Rail, Vulkan renderer, same spot, render thread 88-92% -> 44-46%
      (63-72% in earlier runs of previous revisions), 46-50 -> 62 fps
      (at the 60 fps cap), the upload copy loop 35% -> 2.7% of the render
      thread's samples.
Assisted-by: Claude Code:Claude-Opus-5.5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant