Android: runtime build + CI emulator tests#3194
Open
xen2 wants to merge 35 commits into
Open
Conversation
52974e4 to
f108233
Compare
Kryptos-FR
reviewed
May 28, 2026
| <ItemGroup> | ||
| <PackageVersion Include="Avalonia" Version="$(AvaloniaVersion)" /> | ||
| <PackageVersion Include="Avalonia.Controls.DataGrid" Version="$(AvaloniaVersion)" /> | ||
| <PackageVersion Include="Avalonia.Android" Version="$(AvaloniaVersion)" /> |
…to 21 Remove deprecated per-platform sysroot paths (platforms/android-9/arch-*) that no longer exist in modern NDK (r21+). The unified sysroot from StrideNativeAndroidClang is already passed. Also bump 32-bit min API from 16 to 21 (Android 5.0).
…dows) CompileNativeClang_Android assumed Windows-only: hardcoded 'prebuilt/windows-x86_64/bin/clang.exe' for the toolchain folder and '\' path separators in every Exec command. On Linux the toolchain folder resolved empty → '/bin/clang.exe' (exit 127) and backslashes ended up as literal characters in output paths (e.g. obj/.../\Celt_armeabi-v7a.o). Detect the host tag (windows-x86_64 / linux-x86_64 / darwin-x86_64) and the exe extension up front, then thread them through every clang invocation; switch the Android Exec templates to forward slashes (which NDK clang accepts on every host). Fail fast with a clear error if no matching toolchain dir exists.
Downloads from KhronosGroup GitHub releases, caches locally in %LOCALAPPDATA%\Stride\VulkanValidation\. The .so is bundled into the APK so the Vulkan loader picks it up automatically. Only active for Android + Vulkan + Debug configuration.
Add NuGetResolverModuleInitializerGenerate and _GetProjectVersion to StrideAppendWorktreeVersion's BeforeTargets. Without them the entry-point .cs and the nuspec <dependency> entries kept the un-suffixed version, so CompilerApp in a secondary worktree asked the resolver for "4.4.0.2" while the actually-built packages were "4.4.0.2-devN".
The unconditional `AndroidResgenNamespace = $(AssemblyName)` override broke projects whose assembly name isn't a valid C# identifier (Stride.Graphics.Tests .10_0/.11_0, where the segment starts with a digit). Wire the assignment through the standard `Condition="'$(AndroidResgenNamespace)' == ''"` so a csproj can override. Override applied in Stride.Graphics.Tests.10_0/.11_0 to use the bare `Stride.Graphics.Tests` namespace for the generated Resource class.
AssetCompiler runs in-process on the host and loads native libs (libstrideaudio for Celt, etc). Mutate the local _StridePlatforms helper rather than the global StridePlatforms — the latter is immutable when set via -p: and also drives StrideTestPlatforms, which should stay scoped to the mobile target. Opt out with -p:StridePlatformAutoAddHost=false for CI runtime-only mobile builds.
Otherwise MSBuild's Inputs/Outputs check on StrideCompileAsset sees the freshly-touched bundle and skips re-running CompilerApp next build, silently packing the partial bundle into the APK. Now a failed compile leaves no marker and the next build retries.
…phics API - Include Vortice.Vulkan PackageReference for Android TFM - Guard DefaultWindow, OES texture, and MediaCodec video with STRIDE_GRAPHICS_API_OPENGLES instead of just STRIDE_PLATFORM_ANDROID
Bitmap.Config.Argb8888 stores pixels as RGBA in memory on Android (per the NDK: ANDROID_BITMAP_FORMAT_RGBA_8888 — bytes R, G, B, A), not BGRA. LoadFromMemory was swapping R<->B in the wrong case, which cancelled out for round-trip through Android-only code (so existing load/save tests passed) but produced channel-swapped images visible only when comparing against externally-rendered references. Drop the swap in LoadFromMemory so bytes flow straight through to R8G8B8A8 image storage.
Three latent bugs surfaced when compiling textures to ASTC for the Android target: - AstcEncNative.AstcEncSwizzle was declared `: byte`, but the native astcenc_swz enum is int-sized; marshalling the 4-byte struct fed garbage for G/B/A and astcenc returned ASTCENC_ERR_BAD_SWIZZLE. - AstcTexLib.Compress/Decompress freed image.Data twice when the source came through a library that owns the buffer (e.g. ImageSharpTexLib): DisposingLibrary.Dispose(image) frees it, then the explicit FreeHGlobal freed it again → "double free or corruption" abort. - TextureTool.ExecuteRequest used the outer-scope `library` variable in the 2-step fallback branch where it is always null, throwing NRE.
…emulator Multi-target xunit.runner.stride for net10.0-android with an Avalonia activity entry; the Tests SDK packages test projects as APKs. Re-derive StrideGraphicsApi to Vulkan and pin RuntimeIdentifier=android-x64 for mobile, gate per-RID inner builds (StrideHostGraphicsApi decouples build-host from device API), and collapse MainActivity onto AvaloniaMainActivity<App>.
…nt extra) MainActivity reads Intent xunit_command=run and sets App.HeadlessMode; the single-view branch awaits discovery, runs the assembly via RunTestsAsync, and exits with FailedCount. Discovery runs on a background task so Android doesn't ANR. trx/test root live in FilesDir (targetSdk 30+ scoped storage).
…er names for gold paths
…A × UNorm/SRgb variants
SDL can't init its window without an Activity in the xunit harness.
Cross-platform (Windows PowerShell 5.1 / PowerShell Core 7+ on Linux/macOS): boots an emulator if no device is connected (swiftshader_indirect cold boot), installs the APK, pushes gold images to /sdcard/Android/data/<pkg>/files/tests/, launches MainActivity with --es xunit_command run, polls until the process exits, then pulls tests/local/ (TRX + generated images) back. Captures logcat in threadtime format alongside the results, parses the TRX for pass/fail.
…ainst another checkout
20+ min APK build dominates the workflow; cache key uses sources/**,
tests/**, build/**, deps/** and project files but excludes workflow
YAML and tests/android/run-*.{sh,ps1}. Iterating on the test step
or workflow tweaks reuses cached APKs; any engine/test source change
invalidates correctly.
Texture.NewFromAndroidHardwareBuffer imports an AHardwareBuffer as a sampleable Vulkan Texture. RGBA buffers sample directly; YUV external formats attach a VkSamplerYcbcrConversion (usable from custom shaders; ordinary materials need immutable-sampler descriptor wiring not yet exposed by Stride's effect system). Enables VK_ANDROID_external_memory_android_hardware_buffer and the extensions in its dependency chain when available.
ImageReader-fed Yuv420888 decoder; CPU YUV->RGBA into the VideoComponent target. Zero-copy via Texture.NewFromAndroidHardwareBuffer needs YcbcrConversion sampler wiring not yet in Stride's effect system.
- OpenSL: destroy orphan sources before outputMix/Engine teardown; stop+clear queue before per-source destroy. - C#: drain the static DynamicSoundSource worker before tearing down the native device.
Launch the emulator with VK_DRIVER_FILES pointing at the platform's Lavapipe ICD from Stride.Dependencies.Lavapipe, so local runs match CI Vulkan rendering and gold paths. Linux uses -gpu host + -feature ForceGpuHost to forward Vulkan to the host loader, plus a small implicit layer that stamps the host OS into the device name for per-host gold.
OnImageAvailable doesn't fire reliably under the emulator's gfxstream BufferQueue, so MediaCodec now pulls frames via AcquireLatestImage each Update tick. Adds VideoInstance.FramesPresented (incremented by every backend on present) and the video smoke test polls it with a wall-clock timeout instead of a fixed frame budget. Drops the now-unused IMediaCodecBackend interface.
Microsoft.Android defaults to Fast Deployment in Debug, which assumes VS will push managed assemblies via /t:Install. CLI builds rarely do that, so the resulting APK aborts on launch with 'No assemblies found in .__override__/x86_64'. Default EmbedAssembliesIntoApk=true when BuildingInsideVisualStudio != true.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR Details
Brings Android Vulkan to CI parity with the other platforms: runtime builds on Linux/macOS hosts, gameplay tests run on a KVM-accelerated x86_64 emulator, plus the platform fixes the path turned up.
What's in
pwshdriver that pushes APK + gold and pulls TRX viaadb. Audio tests use a headlessGameContext; desktop-only tests skipped.test-android-game.yml) — Ubuntu + KVM x86_64 emulator (API 34, SwiftShader), per-suite serial run, source-keyed APK cache for fast test-step iteration. Path-filtered auto-trigger on Android-touching PRs;/test android-gamechatops fallback; nightly schedule on master. NewAndroid-Runtimecompile job mirrorsiOS-Runtime.Stride.CompareGold --portfor parallel checkouts;tests/android/start-emulator.ps1cross-platform launcher matching CI's emulator flags.Known limitations
-m:1(serial) — workaround for parallel-build.nupkg/.aarlock races, structural fix pending.Test plan
Android-Runtimebuild green on this branchtest-android-gameworkflow green/test android-gamechatops command works from a PR commentTypes of changes
Checklist