Initial workspace: GPU context, compute pass, adaptive Slint shell

Establishes the v0.1 foundations on both platforms:

- dr-types: SourceRef (never a filesystem path — Android SAF has none),
  Format, Availability, Validator with ETag quote normalisation
- dr-gpu: wgpu device, compute pass writing a storage texture, resize
- dr-ui: Slint shell with FR-UI-1 adaptive layout, computed in Rust to
  avoid a binding loop
- docker/android: pinned toolchain, verified producing API 28 ARM binaries

Measured the cost of the temporary CPU readback path (dr-gpu bench):
compute is 0.06-0.28ms across sizes while readback is 0.63-7.43ms, so
readback is 90-96% of frame time and scales with area. Recorded in
ARCH §6.1 — this is why spike S1 is the priority.

Mitigations pending S1: reuse the staging buffer, apply at most one
resize per frame, and cap render resolution at 2048 on the long edge.

10 tests passing; core crates cross-compile for aarch64-linux-android.
This commit is contained in:
2026-08-09 07:42:05 +02:00
commit 82a5e21ec6
25 changed files with 10707 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
// Placeholder compute shader — stands in for the develop pipeline.
//
// Its only job is to prove the path: a compute shader writes a storage
// texture, and that texture reaches the screen without a CPU round-trip
// (ARCH §6.1). Replaced by real pipeline stages in v0.2.
struct Params {
width: u32,
height: u32,
// Animates so it is visually obvious the compute pass runs every frame
// rather than a stale texture being redisplayed.
phase: f32,
_pad: f32,
}
@group(0) @binding(0) var output: texture_storage_2d<rgba8unorm, write>;
@group(0) @binding(1) var<uniform> params: Params;
@compute @workgroup_size(8, 8, 1)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
if (gid.x >= params.width || gid.y >= params.height) {
return;
}
let uv = vec2<f32>(
f32(gid.x) / f32(params.width),
f32(gid.y) / f32(params.height),
);
// Warm dark ground with a moving highlight — deliberately unlike a test
// pattern, so a stuck frame is obvious at a glance.
let d = distance(uv, vec2<f32>(0.5 + 0.25 * cos(params.phase), 0.5 + 0.25 * sin(params.phase)));
let glow = 1.0 - smoothstep(0.0, 0.55, d);
let base = vec3<f32>(0.08, 0.07, 0.06);
let accent = vec3<f32>(0.69, 0.23, 0.15);
let rgb = base + accent * glow * (0.35 + 0.65 * uv.y);
textureStore(output, vec2<i32>(gid.xy), vec4<f32>(rgb, 1.0));
}