mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 05:38:31 +09:00
215 lines
11 KiB
Markdown
215 lines
11 KiB
Markdown
---
|
|
name: gl-cts-on-mobilegl
|
|
description: Run the Khronos OpenGL CTS (VK-GL-CTS glcts, KHR-GL33) against MobileGL on an Android device and compute a per-backend conformance rate. Use when measuring OpenGL 3.3 core conformance for DirectGLES or DirectVulkan, building glcts for Android arm64, porting a dEQP tcu::Platform onto MobileGL, or triaging CTS failures, crashes, and cases that hang the device.
|
|
---
|
|
|
|
# OpenGL CTS on MobileGL (Android)
|
|
|
|
## Overview
|
|
|
|
`glcts` from VK-GL-CTS is built as a **standalone arm64 executable** and run from
|
|
`adb shell`. It reaches OpenGL only through `libMobileGL.so`, which supplies both
|
|
EGL and desktop GL, so a result is unambiguously MobileGL's and never the system
|
|
GL stack's. No APK and no Activity are involved.
|
|
|
|
The port lives in this repository under `MobileGL/tools/cts/` and is copied into
|
|
a VK-GL-CTS checkout by `scripts/sync_to_cts.py`, so it survives a throwaway CTS
|
|
clone.
|
|
|
|
Set up paths first:
|
|
|
|
```sh
|
|
export MG=<path-to-MobileGL-worktree> # do builds in a worktree, not the shared tree
|
|
export CTS=<path-to-VK-GL-CTS-checkout>
|
|
export NDK="$ANDROID_HOME/ndk/27.3.13750724"
|
|
export SERIAL=<adb-device-serial>
|
|
```
|
|
|
|
## Prerequisites
|
|
|
|
- Android NDK r27 (the repo builds MobileGL with 27.3.13750724), CMake, Ninja, Python 3.
|
|
- A rooted-or-not Android device with `adb`; ~600 MB free under `/data/local/tmp`.
|
|
- **A device you can physically power-cycle.** Some cases hang the GPU hard
|
|
enough to reboot it — see "Cases that take the device down".
|
|
- On Windows, invoke `python`, not `python3`: the latter resolves to the
|
|
Microsoft Store alias stub and exits 49.
|
|
|
|
## Step 1 — build libMobileGL.so
|
|
|
|
Build in a git worktree (other agents share the main tree). A fresh worktree is
|
|
missing glslang's bundled SPIR-V Tools, which is a hard configure blocker
|
|
because `ENABLE_OPT` is forced on:
|
|
|
|
```sh
|
|
cp -r <main-tree>/3rdparty/glslang/External/* "$MG/3rdparty/glslang/External/"
|
|
./gradlew -p "$MG/android-plugin" :app:assembleTraceRelease
|
|
```
|
|
|
|
The stripped library lands in
|
|
`android-plugin/app/build/intermediates/stripped_native_libs/traceRelease/.../arm64-v8a/libMobileGL.so`.
|
|
|
|
## Step 2 — get VK-GL-CTS and its externals
|
|
|
|
Use a **release tag**, not `main`, so the mustpass list — and therefore the
|
|
reported rate — is citable:
|
|
|
|
```sh
|
|
git -C "$CTS" checkout opengl-cts-4.6.8.1
|
|
cd "$CTS" && python external/fetch_sources.py
|
|
```
|
|
|
|
## Step 3 — build glcts for Android arm64
|
|
|
|
```sh
|
|
python "$MG/tools/cts/scripts/sync_to_cts.py" "$CTS"
|
|
|
|
cmake -S "$CTS" -B build-cts-a64 -G Ninja \
|
|
-DDEQP_TARGET=mobilegl -DDEQP_TARGET_TOOLCHAIN=ndk-modern \
|
|
-DANDROID_NDK_PATH="$NDK" -DDE_ANDROID_API=26 -DANDROID_ABI=arm64-v8a \
|
|
-DCMAKE_BUILD_TYPE=Release
|
|
ninja -C build-cts-a64 glcts
|
|
"$NDK"/toolchains/llvm/prebuilt/*/bin/llvm-strip build-cts-a64/external/openglcts/modules/glcts
|
|
```
|
|
|
|
Confirm the configure output says `DE_OS = DE_OS_ANDROID`, `DE_CPU =
|
|
DE_CPU_ARM_64` and `DEQP_ANDROID_BUILD = EXE`. Two things make that work and
|
|
both are easy to get wrong:
|
|
|
|
- `DEQP_TARGET_TOOLCHAIN=ndk-modern` is required. dEQP includes `Defs.cmake`
|
|
*before* the target file, so a target cannot set `DE_OS` itself. Without the
|
|
toolchain hook the build mis-detects as `DE_OS_UNIX`/`x86_64` and dies on
|
|
`__assert_fail` (bionic has `__assert2`).
|
|
- The target sets `DEQP_ANDROID_EXE ON`. Otherwise dEQP builds the modules into
|
|
the `libdeqp.so` an APK would load and no `glcts` executable exists.
|
|
|
|
`KHR-GL33` needs no ungating — the package registry registers it unconditionally;
|
|
only the `dEQP-*` packages are `#if DE_OS != DE_OS_ANDROID`.
|
|
|
|
## Step 4 — deploy
|
|
|
|
```sh
|
|
adb -s $SERIAL shell mkdir -p /data/local/tmp/mgcts
|
|
adb -s $SERIAL push build-cts-a64/external/openglcts/modules/glcts /data/local/tmp/mgcts/
|
|
adb -s $SERIAL push build-cts-a64/external/openglcts/modules/gl_cts /data/local/tmp/mgcts/
|
|
adb -s $SERIAL push <libMobileGL.so> /data/local/tmp/mgcts/
|
|
adb -s $SERIAL shell chmod 755 /data/local/tmp/mgcts/glcts
|
|
```
|
|
|
|
## Step 5 — preflight
|
|
|
|
Never start a multi-hour run without this. It proves the device/library pair
|
|
yields a 3.3 core context and that FBO readback is correct, in about a second:
|
|
|
|
```sh
|
|
adb -s $SERIAL shell 'cd /data/local/tmp/mgcts && LD_LIBRARY_PATH=. ./mgprobe \
|
|
--backend DirectVulkan --surface imagereader --lib ./libMobileGL.so'
|
|
```
|
|
|
|
Expect `PASS ... user_fbo=ok`. `default_fb=broken` on DirectVulkan is expected
|
|
and does not gate — see below.
|
|
|
|
## Step 6 — run
|
|
|
|
```sh
|
|
python "$MG/tools/cts/scripts/run_cts.py" \
|
|
--serial $SERIAL --backend DirectGLES \
|
|
--caselist .../mustpass/gl/khronos_mustpass/main/gl33-main.txt \
|
|
--outdir runs/gles --skip-file runs/skip.txt
|
|
```
|
|
|
|
The runner re-invokes `glcts` with only the cases that have no result yet, so a
|
|
crash costs one case rather than the run. It distinguishes a crashed *case* from
|
|
a dead *device* by checking the device still answers a shell command — without
|
|
that check a dead device looks like every remaining case crashing, which yields
|
|
a completely bogus but plausible-looking conformance number. On a device reboot
|
|
it waits, re-pulls the partial `.qpa` (which survives on `/data/local/tmp`),
|
|
records the case that was open as `DeviceHang`, and quarantines it.
|
|
|
|
## Step 7 — report
|
|
|
|
```sh
|
|
python "$MG/tools/cts/scripts/qpa_report.py" runs/gles --label DirectGLES
|
|
```
|
|
|
|
Pass rate counts `Pass`, `NotSupported`, `QualityWarning`, `CompatibilityWarning`
|
|
and `Waiver` as non-failures, matching how Khronos scores a submission; the
|
|
strict rate counts only `Pass`. Quarantined and never-reached cases are reported
|
|
separately and excluded from the rates, so a partial run cannot read as a
|
|
complete one.
|
|
|
|
## Required flags, and why
|
|
|
|
| Flag | Why it is not optional |
|
|
| --- | --- |
|
|
| `--deqp-surface-type=fbo` | On DirectVulkan, `glReadPixels` from the **default framebuffer returns all zeros** with no GL error. dEQP verifies nearly everything through `glReadPixels`, so rendering to the surface scores DirectVulkan near zero for a reason unrelated to conformance. Use it for **both** backends so the two numbers stay comparable. |
|
|
| `MOBILEGL_CTS_FBO_COLOR_TEXTURE=1` | **`--deqp-surface-type=fbo` alone is not enough.** dEQP's `FboRenderContext` allocates a *renderbuffer* colour attachment, and DirectVulkan returns zeros from a renderbuffer-attached FBO too — only a *texture*-attached FBO reads back correctly. This env var (a patch to `framework/opengl/gluFboRenderContext.cpp`, off by default) switches the attachment to a texture and isolates that single defect. Measured effect: `KHR-GL33.shaders.loops.for_constant_iterations.*` goes 0/62 → 62/62, and the whole-suite DirectVulkan conformance rate goes 46.15% → 72.74%. DirectGLES is bit-identical either way (93.05%), which is the control proving the switch is neutral where readback works. |
|
|
| `--deqp-terminate-on-device-lost=disable` | Defaults to *enable*, which calls `glGetGraphicsResetStatus()` after every case. That is GL 4.5 / `KHR_robustness`, absent from GL 3.3 core, so the pointer is null and the process segfaults on the first case. Desktop drivers expose the extension, which is why upstream never trips on it. |
|
|
|
|
## Cases that take the device down
|
|
|
|
Some cases hang the GPU hard enough that the device reboots or stops answering
|
|
adb entirely. Keep them in a `--skip-file`, and expect to find more:
|
|
|
|
- `KHR-GL33.clip_distance.functional` — wedged an Adreno 750 tablet; it rebooted
|
|
and then stopped responding to adb altogether.
|
|
- `KHR-GL33.framebuffer_blit.multisampled_to_singlesampled_blit_color_config_test`
|
|
— rebooted an Adreno 830 phone after 862 cases, on DirectGLES.
|
|
- `KHR-GL33.framebuffer_blit.multisampled_to_singlesampled_blit_depth_config_test`
|
|
— same, on both backends (found and quarantined automatically by the runner).
|
|
- `KHR-GL33.texture_repeat_mode.rgb565_11x131_0_clamp_to_edge` — on DirectVulkan.
|
|
|
|
The whole `framebuffer_blit.multisampled_to_singlesampled_*` family is suspect;
|
|
treat a new variant as a device-hang candidate rather than a normal failure.
|
|
|
|
When a run dies, pull `/data/local/tmp/mgcts/chunk.qpa` — it survives the reboot,
|
|
and the last `#beginTestCaseResult` with no matching `#endTestCaseResult` names
|
|
the case that did it.
|
|
|
|
## Reference results
|
|
|
|
`opengl-cts-4.6.8.1`, KHR-GL33 mustpass (`gl33-main.txt`, 9886 cases), Adreno 830
|
|
/ Android 15, MobileGL `dev`@199164c2, 9884 measured / 0 unrun / 2 quarantined.
|
|
Conformance rate = Pass + NotSupported, as Khronos scores a submission.
|
|
|
|
| backend | conformance | strict Pass | Fail | Crash | InternalError | DeviceHang |
|
|
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
|
|
| DirectGLES | **93.05%** | 85.94% | 679 | 1 | 6 | 1 |
|
|
| DirectVulkan (texture FBO) | **72.74%** | 65.71% | 2394 | 294 | 5 | 1 |
|
|
| DirectVulkan (stock renderbuffer FBO) | 46.15% | 39.11% | 5024 | 292 | 5 | 2 |
|
|
|
|
The third row is what stock dEQP reports; the gap to the second row is entirely
|
|
the renderbuffer-FBO readback defect.
|
|
|
|
## MobileGL constraints the port works around
|
|
|
|
- **DirectVulkan cannot use an EGL pbuffer.** That path needs
|
|
`VK_EXT_headless_surface`, which Adreno's Android driver does not expose; it
|
|
fails inside `eglMakeCurrent`. The platform therefore gets a real
|
|
`ANativeWindow` from **`AImageReader`** — an ordinary BufferQueue producer that
|
|
`vkCreateAndroidSurfaceKHR` accepts, with no Activity. An `onImageAvailable`
|
|
listener must drain the queue or the producer blocks once `maxImages` buffers
|
|
are in flight and the next swap deadlocks.
|
|
- **`eglMakeCurrent` requires draw == read** and rejects `EGL_NO_SURFACE` with
|
|
`EGL_BAD_MATCH`, so dEQP's `surfaceless` platform cannot be used at all, and
|
|
`--deqp-surface-type=fbo` (which asks the platform for `SURFACETYPE_DONT_CARE`)
|
|
must still be given a real surface.
|
|
- **Every EGL call must go through the dynamically loaded library.** dEQP's
|
|
`surfaceless` platform mixes wrapper calls with globally linked `egl*` symbols;
|
|
copying that on Android silently reaches the system EGL and invalidates the
|
|
measurement. The `mobilegl` target links no `libEGL`/`libGLESv*` at all.
|
|
- **Desktop-GL configs need `EGL_OPENGL_BIT`.** The surfaceless port always asks
|
|
for an ES bit, which can never satisfy a GL 3.3 core context.
|
|
- MobileGL aborts during static teardown (`FORTIFY: pthread_mutex_lock called on
|
|
a destroyed mutex`) *after* the work is done; flush and `_exit()` in any small
|
|
tool, or its exit code and output are lost.
|
|
|
|
## Contents
|
|
|
|
platform/tcuMobileGLPlatform.{cpp,hpp} dEQP tcu::Platform for MobileGL
|
|
targets/mobilegl.cmake VK-GL-CTS target (-DDEQP_TARGET=mobilegl)
|
|
targets/ndk-modern.cmake NDK toolchain hook (sets DE_OS/DE_CPU)
|
|
probe/mgprobe.c preflight gate
|
|
scripts/sync_to_cts.py inject the port into a CTS checkout
|
|
scripts/run_cts.py crash- and reboot-resuming runner
|
|
scripts/qpa_report.py .qpa -> conformance rate
|