Files
MobileGL/tools/cts/skills/linux-gl-cts-on-mobilegl/SKILL.md
T

7.3 KiB

name, description
name description
linux-gl-cts-on-mobilegl Run the Khronos OpenGL CTS (VK-GL-CTS glcts) against MobileGL on desktop Linux with no GPU and no device, and report a per-backend conformance rate for Espryt (DirectGLES) and Magma (DirectVulkan). Use when measuring or iterating on the conformance of one test group, when a fix needs a before/after number, or when neither an Android device nor a Windows GPU box is available.

OpenGL CTS on MobileGL (desktop Linux)

Overview

glcts is built as an ordinary x86-64 host executable that reaches OpenGL only through libMobileGL.so, using the mobilegl-desktop VK-GL-CTS target and the tcu::Platform port in tools/cts/platform/. Nothing links libGL or libEGL, so a result is unambiguously MobileGL's.

Both backends run headless on software rendering, so this needs no GPU at all:

  • Espryt (DirectGLES) drives Mesa's OpenGL ES through the system EGL.
  • Magma (DirectVulkan) runs on lavapipe, whose VK_EXT_headless_surface is what the desktop platform port's pbuffer path requires.

Always report the two backends separately. They are different implementations of the same front end, they fail different cases, and a single combined number hides which one a change moved.

Prerequisites

sudo apt-get install -y ninja-build cmake libvulkan-dev \
    libegl1-mesa-dev libgles2-mesa-dev mesa-vulkan-drivers

mesa-vulkan-drivers is what installs lavapipe; without it DirectVulkan has no ICD and eglInitialize fails inside the backend. A C++23 toolchain is required (GCC 13+, or Clang 20+ — Clang 18 defines __cpp_concepts as 201907L, which switches libstdc++'s <expected> off and the build fails in MG_Util/ShaderTranspiler/Types.h).

export MG=<path-to-MobileGL-worktree>
export CTS=<path-to-VK-GL-CTS-checkout>

Step 1 — build libMobileGL.so

git -C "$MG" submodule update --init --recursive
python3 "$MG/3rdparty/glslang/update_glslang_sources.py"   # SPIRV-Tools; ENABLE_OPT is forced on

cmake -S "$MG" -B "$MG/build-linux" -G Ninja -DCMAKE_BUILD_TYPE=Release \
      -DMOBILEGL_BUILD_TEST=OFF -DMOBILEGL_BUILD_BENCHMARK=OFF \
      -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
cmake --build "$MG/build-linux" --parallel "$(nproc)"

Add -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=FALSE when iterating. The Release configuration turns LTO on, which makes every relink cost minutes for no behavioural difference; a conformance number is identical either way.

Step 2 — get VK-GL-CTS and build glcts

Use a release tag so the mustpass list, and therefore the reported rate, is citable.

git -C "$CTS" checkout opengl-cts-4.6.8.1
python3 "$CTS/external/fetch_sources.py"

python3 "$MG/tools/cts/scripts/sync_to_cts.py" "$CTS"
git -C "$CTS" apply "$MG/tools/cts/patches/0001-fbo-color-texture-attachment.patch"

cmake -S "$CTS" -B "$CTS/build-cts" -G Ninja -DDEQP_TARGET=mobilegl-desktop \
      -DCMAKE_BUILD_TYPE=Release \
      -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
ninja -C "$CTS/build-cts" glcts

Confirm the configure output says *** Using MobileGL desktop target. Budget a couple of hours for the glcts link on a small machine; it is a one-time cost that ccache makes cheap afterwards.

If fetch_sources.py dies with HTTP Error 403 it is an egress policy blocking the GitHub archive downloads (zlib, libpng), not a broken checkout: git clone still works, so clone the package at the tag the script pins into external/<pkg>/src by hand — for libpng also copy scripts/pnglibconf.h.prebuilt to src/pnglibconf.h, which is what the script's post-extract step does.

Step 3 — run, once per backend

cd "$MG"
for BACKEND in DirectGLES DirectVulkan; do
  python3 tools/cts/scripts/run_cts_local.py --backend "$BACKEND" \
      --glcts "$CTS/build-cts/external/openglcts/modules/glcts" \
      --lib   build-linux/libMobileGL.so \
      --caselist cases.txt --outdir "runs/${BACKEND}" \
      --env EGL_PLATFORM=surfaceless
  python3 tools/cts/scripts/qpa_report.py "runs/${BACKEND}" --label "$BACKEND"
done

cases.txt is any subset of a mustpass list. For one group, filter the list rather than running the whole suite:

grep direct_state_access \
  "$CTS"/external/openglcts/data/gl_cts/data/mustpass/gl/khronos_mustpass/main/gl45-main.txt \
  > cases.txt

run_cts_local.py re-invokes glcts with only the cases that have no result yet, so a crash costs one case rather than the run, and it records the case that was open when the process died as Crash. qpa_report.py scores Pass, NotSupported and the warning statuses as non-failures, the way Khronos scores a submission, and reports the strict Pass-only rate alongside.

Required flags, and why

Flag Why it is not optional
--env EGL_PLATFORM=surfaceless For DirectGLES. With no /dev/dri node Mesa's EGL fails eglInitialize on the default display, and MobileGL surfaces that as EGL_BAD_ALLOC out of eglCreatePbufferSurface — which points at the wrong call entirely. Harmless for DirectVulkan, so pass it to both.
--deqp-surface-type=fbo (the runner's default) DirectVulkan reads back zeros from the default framebuffer. dEQP verifies nearly everything through glReadPixels, so rendering to the surface scores Magma near zero for a reason unrelated to conformance. Use it for both backends so the two numbers stay comparable.
--deqp-terminate-on-device-lost=disable (supplied by the runner) Its default calls glGetGraphicsResetStatus() after every case. That is GL 4.5 / KHR_robustness, absent from what MobileGL exports, so the pointer is null and the process segfaults on the first case.

What this environment does and does not tell you

Reproducible here, and MobileGL's own rather than a driver quirk:

  • DirectVulkan's default-framebuffer readback returns zeros; a user FBO, renderbuffer- or texture-attached, is correct on both backends. This is the same defect the Android runs work around, so it can be debugged without a phone.

Different from a real GPU, so do not read conformance into it:

  • lavapipe supports renderbuffer formats Adreno reports as unsupported, so the Android runs see NotSupported where these do not, and vice versa.
  • Backend limits differ. GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT is 16 on llvmpipe and on lavapipe; a device that reports 1 will not exercise the same paths.
  • Everything is a software rasterizer, so a case that fails only under real timing or real tiling will not fail here.

Reference results: KHR-GL45.direct_state_access

opengl-cts-4.6.8.1, the 371 direct_state_access cases of the gl45-main mustpass list, Mesa 25.2.8, --deqp-surface-type=fbo.

backend renderer conformance strict Pass Fail InternalError Crash
DirectGLES Espryt 82.48% 74.93% 57 8 0
DirectVulkan Magma 73.58% 73.32% 87 8 3

Contents

platform/tcuMobileGLPlatform.{cpp,hpp}   dEQP tcu::Platform for MobileGL
targets/mobilegl-desktop.cmake           VK-GL-CTS target (-DDEQP_TARGET=mobilegl-desktop)
scripts/sync_to_cts.py                   inject the port into a CTS checkout
scripts/run_cts_local.py                 crash-resuming local-host runner
scripts/qpa_report.py                    .qpa -> conformance rate