mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 04:38:30 +09:00
[Feat] (Spikes): add the standalone external-memory probe that decides the persistent-map tier
- Plan B §11 P0 requires spike B ("external memory 导出,两台设备") to run before the
persistent-map decision of §8.3 can be taken: T0 (server imports a client
allocation), T1 (server exports its own HOST_VISIBLE|HOST_COHERENT allocation)
or T2 (AcquirePersistentMap returns nullptr, making the §5.10 client-side block
push mandatory). §8.3 says the answer must be measured on the two campaign
devices, and that a platform unknown must not block the interface work.
- tools/spikes/extmem_probe/ is a self-contained NDK command-line program: it
links vulkan/EGL/GLESv3/android/log and nothing from MobileGL, is configured by
its own CMakeLists with the android toolchain file, and is deliberately absent
from the project's build graph (the root CMakeLists only pulls in
tools/trace_replay), so the default ALL target is untouched.
- Phase A enumerates VK_KHR_external_memory_fd, VK_EXT_external_memory_dma_buf,
VK_EXT_external_memory_host, VK_ANDROID_external_memory_android_hardware_buffer
and, through a headless EGL pbuffer context, GL_EXT_memory_object{,_fd},
GL_EXT_external_buffer, GL_EXT_buffer_storage, GL_OES_EGL_image_external{,_essl3}
and EGL_ANDROID_get_native_client_buffer, plus the memory-type table and the
vkGetPhysicalDeviceExternalBufferProperties verdict per handle type for the exact
buffer usage set MobileGL needs.
- Route T1 allocates a HOST_VISIBLE|HOST_COHERENT buffer memory with
VkExportMemoryAllocateInfo, writes a pattern through vkMapMemory, exports an fd
with vkGetMemoryFdKHR (opaque-fd and, where advertised, dma-buf), hands it to a
second process over SCM_RIGHTS, and has that process both mmap() the fd and
import it into its own VkDeviceMemory + vkMapMemory. Both sides write and both
sides compare, so a copy-on-import or one-directional mapping is reported as
PARTIAL rather than as success.
- Route T0 has the second process allocate an AHardwareBuffer BLOB
(CPU_READ_OFTEN|CPU_WRITE_OFTEN|GPU_DATA_BUFFER), send it with
AHardwareBuffer_sendHandleToUnixSocket, and the first process import it three
ways -- AHardwareBuffer_lock, VkDeviceMemory via
VK_ANDROID_external_memory_android_hardware_buffer, and a GL buffer via
eglGetNativeClientBufferANDROID + glBufferStorageExternalEXT mapped
persistent/coherent -- with a write-back leg the allocating process verifies.
- Route T3 covers VK_EXT_external_memory_host: a memfd-backed mmap region aligned
to minImportedHostPointerAlignment, imported through
VkImportMemoryHostPointerInfoEXT, plus the same memfd handed to another process.
- The second process is /proc/self/exe re-exec'd with --child=<route> and one end
of a socketpair on fd 3. A bare fork() is not usable: neither side's Vulkan
driver survives fork, and both sides need live Vulkan. It is also the topology
the transport actually has (§8.1, inheriting PLAN.md §11.1-§11.6: the client
spawns the server), so the probe measures the arrangement that would ship.
- The probe also builds for the host with T0 compiled out. That is not scope
creep: a negative device result is only worth something if the harness is known
to report a working route as working. Running it on lavapipe did that, and paid
for itself immediately by exposing two harness bugs that would have produced
false negatives on the devices -- (a) the child wrote through its plain mmap
before reading through the Vulkan import, so on a driver whose exported fd maps
at an offset the probe overwrote the very payload the second read compares
(lavapipe reports payloadAt=4096); reads through both mappings now precede
writes through either, and the offset is searched for and reported; (b) an
export failure on a handle type the driver never advertised as EXPORTABLE was
classified FAIL instead of UNSUPPORTED (lavapipe's dma-buf answer).
- Output is a RESULT/summary table carrying the raw driver verdicts (VkResult
names, errno, GL enums) because those codes -- not a pass/fail bit -- are what
§8.3 needs in order to pick the tier.
- Built with NDK 27.3.13750724 for arm64-v8a / android-30, RelWithDebInfo, PIE,
warning-clean; host build clang/RelWithDebInfo, warning-clean.
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
# Standalone NDK command-line probe for MobileGL disaggregation spike B
|
||||
# (plan-B §8.3 / §11 P0). Deliberately NOT part of the MobileGL build graph:
|
||||
# it links nothing from the project and is configured on its own, e.g.
|
||||
#
|
||||
# cmake -S tools/spikes/extmem_probe -B /tmp/extmem-build -G Ninja \
|
||||
# -DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake \
|
||||
# -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-30 \
|
||||
# -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||
#
|
||||
# See build_android.sh for the exact invocation used on the devices.
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(extmem_probe LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
add_executable(extmem_probe extmem_probe.cpp)
|
||||
|
||||
# PIE is the NDK default for executables; make it explicit so a stale toolchain
|
||||
# cannot produce something Android's linker refuses to run.
|
||||
target_compile_options(extmem_probe PRIVATE -fPIE -Wall -Wextra -Wno-unused-parameter)
|
||||
target_link_options(extmem_probe PRIVATE -pie)
|
||||
|
||||
if(ANDROID)
|
||||
target_link_libraries(extmem_probe PRIVATE vulkan EGL GLESv3 android log)
|
||||
else()
|
||||
# Host build: T0 (AHardwareBuffer) compiles out, T1/T3 stay. Its only purpose is
|
||||
# to validate this harness against a driver that is known to implement the
|
||||
# routes (lavapipe), so that a device-side FAIL is attributable to the device
|
||||
# driver and not to the probe. It is NOT a substitute for a device run.
|
||||
message(STATUS "extmem_probe: host build -- T1/T3 harness validation only, T0 disabled")
|
||||
target_link_libraries(extmem_probe PRIVATE vulkan EGL GLESv2)
|
||||
endif()
|
||||
@@ -0,0 +1,80 @@
|
||||
# extmem_probe — disaggregation spike B (external memory)
|
||||
|
||||
A standalone Android command-line probe that answers one question per device:
|
||||
|
||||
> Can a server-allocated `HOST_VISIBLE|HOST_COHERENT` `VkDeviceMemory` be shared
|
||||
> with another process and mapped there, and by which route?
|
||||
|
||||
This is the P0 spike that decides the `AcquirePersistentMap` tier in plan B §8.3
|
||||
(T0 = server imports a client allocation, T1 = server exports its own, T2 = give
|
||||
up and return `nullptr`). It links nothing from MobileGL and is not part of the
|
||||
project's CMake build graph.
|
||||
|
||||
## What it does
|
||||
|
||||
* **phase A — enumeration.** Vulkan device identity + memory types, and per
|
||||
handle type (`OPAQUE_FD`, `DMA_BUF`, `HOST_ALLOCATION`, `AHARDWAREBUFFER`) the
|
||||
`vkGetPhysicalDeviceExternalBufferProperties` verdict for the buffer usage
|
||||
MobileGL actually needs. Then a headless EGL pbuffer context reports
|
||||
`GL_EXT_memory_object{,_fd}`, `GL_EXT_external_buffer`, `GL_EXT_buffer_storage`,
|
||||
`GL_OES_EGL_image_external{,_essl3}` and `EGL_ANDROID_get_native_client_buffer`.
|
||||
* **T1 — server exports.** Allocates a `HOST_VISIBLE|HOST_COHERENT` buffer memory
|
||||
with `VkExportMemoryAllocateInfo`, maps it, writes a pattern, exports an fd with
|
||||
`vkGetMemoryFdKHR` (opaque-fd, then dma-buf), hands the fd to a second process
|
||||
over `SCM_RIGHTS`, and has that process (a) `mmap()` the fd and (b) import it
|
||||
into its own `VkDeviceMemory` and `vkMapMemory` it. Both sides write and both
|
||||
sides compare, so a one-directional or copy-on-import mapping is caught.
|
||||
* **T0 — server imports.** The second process allocates an `AHardwareBuffer` BLOB
|
||||
(`CPU_READ_OFTEN|CPU_WRITE_OFTEN|GPU_DATA_BUFFER`), writes a pattern under
|
||||
`AHardwareBuffer_lock`, and sends it with
|
||||
`AHardwareBuffer_sendHandleToUnixSocket`. The first process reads it back three
|
||||
ways — CPU lock, `VkDeviceMemory` imported through
|
||||
`VK_ANDROID_external_memory_android_hardware_buffer`, and a GL buffer created
|
||||
with `eglGetNativeClientBufferANDROID` + `glBufferStorageExternalEXT` mapped
|
||||
persistent/coherent — writes through each, and the allocating process verifies
|
||||
every write.
|
||||
* **T3 — host pointer import.** If `VK_EXT_external_memory_host` is advertised,
|
||||
imports a memfd-backed, alignment-corrected `mmap` region as a `VkDeviceMemory`
|
||||
and maps it; also passes the memfd to the second process for a cross-process
|
||||
round trip.
|
||||
|
||||
Process topology mirrors the target design (the client spawns the server): the
|
||||
probe re-execs `/proc/self/exe --child=<route>` and hands the child one end of a
|
||||
`socketpair` on fd 3. A bare `fork()` is not usable — neither side's Vulkan
|
||||
driver survives it, and both sides need live Vulkan.
|
||||
|
||||
## Build and run
|
||||
|
||||
```sh
|
||||
ANDROID_NDK=$HOME/android-sdk/ndk/27.3.13750724 ./build_android.sh /tmp/extmem-build
|
||||
adb -s <serial> push /tmp/extmem-build/extmem_probe /data/local/tmp/p0-extmem/
|
||||
adb -s <serial> shell /data/local/tmp/p0-extmem/extmem_probe
|
||||
```
|
||||
|
||||
There is also a host build (`cmake -S . -B <dir>` with no toolchain file). It
|
||||
compiles T0 out — `AHardwareBuffer` is Android-only — and exists for exactly one
|
||||
reason: running T1/T3 against a driver that is known to implement them
|
||||
(lavapipe: `VK_DRIVER_FILES=/usr/share/vulkan/icd.d/lvp_icd.json
|
||||
EGL_PLATFORM=surfaceless`) proves the harness reports a working route as
|
||||
working, which is what makes a device-side `FAIL` attributable to the device
|
||||
driver rather than to this program. It is not a substitute for a device run.
|
||||
|
||||
Options: `--size=BYTES` (default 65536; the payload is split into 4 KiB regions,
|
||||
one per writer), `--only-t0` / `--only-t1` / `--only-t3`.
|
||||
|
||||
Output is a per-route `RESULT <route> <status> <detail>` line stream plus a
|
||||
summary table; `status` is one of `OK`, `PARTIAL`, `UNSUPPORTED`, `FAIL`, `SKIP`.
|
||||
Driver error codes are printed verbatim (`VkResult` names, `errno`, GL enums) —
|
||||
that is the payload of the spike, so do not summarise them away.
|
||||
|
||||
Two details worth knowing when reading T1 output:
|
||||
|
||||
* the child reads through *both* the plain `mmap` and the imported
|
||||
`VkDeviceMemory` before it writes through either, because a driver whose
|
||||
exported fd maps at an offset would otherwise have its payload overwritten by
|
||||
the probe's own first write, and the second read would report a false failure;
|
||||
* when the direct compare fails, the child scans the mapping for the exporter's
|
||||
payload and reports `payloadAt=<offset>`. `payloadAt=4096` with a clean Vulkan
|
||||
import (lavapipe's answer) means the fd is shareable but its offset-0 is not
|
||||
the allocation's base — a route that only works if that offset is
|
||||
discoverable, which opaque-fd does not promise.
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
# Build the spike-B external-memory probe for arm64 Android.
|
||||
#
|
||||
# ANDROID_NDK=/path/to/ndk ./build_android.sh [build-dir]
|
||||
#
|
||||
# Produces <build-dir>/extmem_probe, a PIE arm64-v8a executable that depends
|
||||
# only on the platform (libvulkan / libEGL / libGLESv3 / libandroid / liblog).
|
||||
set -e
|
||||
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
BUILD="${1:-$HERE/build-android}"
|
||||
NDK="${ANDROID_NDK:-$HOME/android-sdk/ndk/27.3.13750724}"
|
||||
|
||||
if [ ! -f "$NDK/build/cmake/android.toolchain.cmake" ]; then
|
||||
echo "NDK not found at $NDK (set ANDROID_NDK)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cmake -S "$HERE" -B "$BUILD" -G Ninja \
|
||||
-DCMAKE_TOOLCHAIN_FILE="$NDK/build/cmake/android.toolchain.cmake" \
|
||||
-DANDROID_ABI=arm64-v8a \
|
||||
-DANDROID_PLATFORM=android-30 \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||
cmake --build "$BUILD" -j "$(nproc)"
|
||||
echo "built: $BUILD/extmem_probe"
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user