feat(wgpu): add Android native-window surface source#24
Conversation
|
@kolkov — the only missing readiness signal here is approval of the fork workflow run: https://github.com/go-webgpu/webgpu/actions/runs/29677865347. GitHub currently records it as As a preflight at exact head |
kolkov
left a comment
There was a problem hiding this comment.
Clean, well-structured PR. The wire struct ABI is verified correct against webgpu.h, the build tag strategy (wire struct without tag for host testing) is actually an improvement over our existing surface files, and the zero-handle validation + error handling follow the established pattern.
A few things to align before merge:
1. runtime.KeepAlive — pattern divergence (discussion needed)
surface_android.go adds runtime.KeepAlive(&source) and runtime.KeepAlive(&desc) after the .Call(). None of the existing surface files (surface_windows.go, surface_linux.go, surface_darwin.go) use KeepAlive.
Both source and desc are stack-allocated variables. Per Go GC rules, stack variables are GC roots and cannot be collected while the frame is alive. goffi v0.4.1+ also added internal KeepAlive for call arguments (see goffi CHANGELOG).
The KeepAlive calls are technically harmless (compiler fence, no-op for stack data), but they create an inconsistency. Two options:
- Remove them from this PR to match existing surface files, or
- Keep them and add a follow-up PR applying the same pattern to all
surface_*.gofiles
I'm fine with either — just want a conscious decision rather than silent divergence.
2. Minor style: explicit Next: 0 and inline size comments
All existing wire struct constructors explicitly initialize Next: 0:
// surface_windows.go pattern:
chain: ChainedStruct{
Next: 0,
SType: uint32(STypeSurfaceSourceWindowsHWND),
},And wire struct definitions have inline size comments:
// surface_darwin.go pattern:
type surfaceSourceMetalLayer struct {
chain ChainedStruct // 16 bytes: next (8) + sType (4) + padding (4)
layer uintptr // 8 bytes - CAMetalLayer*
}Please add both for consistency:
type surfaceSourceAndroidNativeWindow struct {
chain ChainedStruct // 16 bytes: next (8) + sType (4) + padding (4)
window uintptr // 8 bytes - ANativeWindow*
}chain: ChainedStruct{
Next: 0,
SType: uint32(STypeSurfaceSourceAndroidNativeWindow),
},3. What the PR does well
- Separate wire file without build tag — lets CI verify ABI layout without Android toolchain. This is better than our current approach where wire structs are locked behind platform build tags. We should consider adopting this pattern for future surface additions.
var _ func(*Instance, uintptr) (*Surface, error)signature assertion — clean compile-time API contract check.- Ownership documentation in godoc — "The caller must keep its ANativeWindow reference alive until the returned Surface is released" is exactly the right level of documentation for an unsafe ownership boundary.
- ABI test thoroughness — sizeof, offsetof, chain.Next, chain.SType, window value — all verified.
Summary
| Check | Status |
|---|---|
Wire struct matches WGPUSurfaceSourceAndroidNativeWindow in webgpu.h |
✅ sizeof=24, offset(window)=16 |
SType matches WGPUSType_SurfaceSourceAndroidNativeWindow (0x8) |
✅ |
| Error handling (nil instance, zero window, zero handle) | ✅ |
| Build tag strategy (wire=host, method=android) | ✅ Improvement |
Cross-compile GOOS=android GOARCH=arm64 CGO_ENABLED=0 |
✅ |
| Pattern consistency with existing surface files |
Approve with the style alignment above. CI workflow run approved — let's see the Codecov report.
kolkov
left a comment
There was a problem hiding this comment.
Style alignment confirmed — KeepAlive removed, Next: 0 explicit, inline size comments added. Pattern now matches existing surface files. LGTM.
Summary
Add the missing wgpu-native v29 Android surface-source wrapper:
Instance.CreateSurfaceFromAndroidNativeWindowon Android;WGPUSurfaceSourceAndroidNativeWindowwith the existing v29SType;ANativeWindow*before FFI;Why
The bindings already expose
STypeSurfaceSourceAndroidNativeWindow, but there is no constructor that can pass the corresponding chained descriptor towgpuInstanceCreateSurface. That leaves Android consumers unable to use wgpu-native through the canonical Go wrapper.This is the Rust-wrapper prerequisite for gogpu/wgpu#273. The #273 description records its current exact integration head; its CI pins this PR at exact helper head
08592c9f5916b64dfc70aba9e67a74a764bb3ef5through the canonical PR ref. After #24 merges and is released, #273 can consume the canonical version. The pure-Go Vulkan implementation in gogpu/wgpu#268 does not depend on this helper; only the optional-tags rustAndroid path does.Ownership boundary
The caller owns its
ANativeWindowreference and must keep it valid until the returned surface is released. This method neither acquires nor releases the window, and it adds no Activity, JNI, packaging, cgo, or nativeexport policy.Android API 29 is a downstream install/native floor and API 36 is a downstream application compile/target choice; the wgpu-native wire descriptor itself is API-level independent.
Validation
GOWORK=off go test ./wgpu -run 'Test(ABISurfaceSourceAndroidNativeWindow|SurfaceSourceAndroidNativeWindowRejectsZero)$'GOOS=android GOARCH=arm64 CGO_ENABLED=0 GOWORK=off go test -exec=true ./wgpugit diff --checkThe full downstream WGPU Android matrix on the current #273 branch compiles every package and test through this exact helper head in both cgo modes and audits the resulting Rust-wrapper ELF dependency on
libwgpu_native.so.