From d9c711a8a463f84f8cb9c9aede6144631aa4532d Mon Sep 17 00:00:00 2001 From: Aram Hammoudeh Date: Thu, 2 Apr 2026 14:47:14 -0600 Subject: [PATCH 1/4] feat(platform): add Xbox GDK feasibility PoC behind feature flag Add `xbox-gdk` feature flag and PoC infrastructure for Xbox GDK console support. This validates the engine's console porting approach using wgpu's DX12 backend with a custom platform backend. Changes: - New `xbox-gdk` Cargo feature (depends on wgpu-backend) - `XboxGdkPlatform` implementing `PlatformBackend` trait - `XboxWindowHandle` with raw-window-handle traits for wgpu DX12 surface - `WgpuBackend::new_from_raw_handle()` forcing DX12 backend - `WindowBackendKind::XboxGdk` variant and runtime pair validation - Expanded cfg gates to include xbox-gdk alongside native+wgpu-backend - Findings report documenting feasibility, blocking issues, and next steps On non-MSVC targets the platform constructor returns BackendNotSupported, allowing the full type system to be validated during cross-platform dev. Closes #334 Co-Authored-By: Claude Opus 4.6 (1M context) --- Cargo.lock | 102 ++----- docs/findings/xbox-gdk-poc.md | 168 +++++++++++ goud_engine/Cargo.toml | 1 + goud_engine/src/libs/graphics/backend/mod.rs | 7 +- .../graphics/backend/native_backend/mod.rs | 25 +- .../backend/native_backend/native_draw.rs | 35 ++- .../backend/native_backend/native_impls.rs | 110 +++++-- .../native_backend/native_resources.rs | 185 +++++++++--- .../graphics/backend/wgpu_backend/init.rs | 273 ++++++++++++++++++ .../libs/graphics/backend/wgpu_backend/mod.rs | 2 + .../backend/wgpu_backend/xbox_surface.rs | 85 ++++++ goud_engine/src/libs/platform/mod.rs | 22 +- .../src/libs/platform/native_runtime.rs | 34 ++- .../src/libs/platform/xbox_gdk_platform.rs | 114 ++++++++ 14 files changed, 1003 insertions(+), 160 deletions(-) create mode 100644 docs/findings/xbox-gdk-poc.md create mode 100644 goud_engine/src/libs/graphics/backend/wgpu_backend/xbox_surface.rs create mode 100644 goud_engine/src/libs/platform/xbox_gdk_platform.rs diff --git a/Cargo.lock b/Cargo.lock index 96869f9b2..ff1b233c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,8 +119,8 @@ dependencies = [ "bitflags 2.11.0", "cc", "cesu8", - "jni 0.21.1", - "jni-sys 0.3.0", + "jni", + "jni-sys", "libc", "log", "ndk", @@ -941,7 +941,7 @@ dependencies = [ "alsa", "coreaudio-rs", "dasp_sample", - "jni 0.21.1", + "jni", "js-sys", "libc", "mach2", @@ -1822,7 +1822,7 @@ dependencies = [ "husky-rs", "image", "interprocess", - "jni 0.22.4", + "jni", "js-sys", "log", "mlua", @@ -2289,70 +2289,21 @@ dependencies = [ "cesu8", "cfg-if", "combine", - "jni-sys 0.3.0", + "java-locator", + "jni-sys", + "libloading 0.7.4", "log", "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] -[[package]] -name = "jni" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" -dependencies = [ - "cfg-if", - "combine", - "java-locator", - "jni-macros", - "jni-sys 0.4.1", - "libloading 0.8.9", - "log", - "simd_cesu8", - "thiserror 2.0.18", - "walkdir", - "windows-link", -] - -[[package]] -name = "jni-macros" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" -dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "simd_cesu8", - "syn 2.0.117", -] - [[package]] name = "jni-sys" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" -[[package]] -name = "jni-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" -dependencies = [ - "jni-sys-macros", -] - -[[package]] -name = "jni-sys-macros" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" -dependencies = [ - "quote", - "syn 2.0.117", -] - [[package]] name = "jobserver" version = "0.1.34" @@ -2472,6 +2423,16 @@ dependencies = [ "cc", ] +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "libloading" version = "0.8.9" @@ -2812,7 +2773,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ "bitflags 2.11.0", - "jni-sys 0.3.0", + "jni-sys", "log", "ndk-sys", "num_enum", @@ -2832,7 +2793,7 @@ version = "0.6.0+11769913" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" dependencies = [ - "jni-sys 0.3.0", + "jni-sys", ] [[package]] @@ -4168,15 +4129,6 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - [[package]] name = "rusticata-macros" version = "4.1.0" @@ -4518,16 +4470,6 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" -[[package]] -name = "simd_cesu8" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" -dependencies = [ - "rustc_version", - "simdutf8", -] - [[package]] name = "simd_helpers" version = "0.1.0" @@ -4537,12 +4479,6 @@ dependencies = [ "quote", ] -[[package]] -name = "simdutf8" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" - [[package]] name = "slab" version = "0.4.12" diff --git a/docs/findings/xbox-gdk-poc.md b/docs/findings/xbox-gdk-poc.md new file mode 100644 index 000000000..dc0ce1523 --- /dev/null +++ b/docs/findings/xbox-gdk-poc.md @@ -0,0 +1,168 @@ +# Xbox GDK Feasibility PoC -- Findings Report + +**Issue**: #334 (parent: #135 Console Strategy) +**Date**: 2026-04-02 +**Status**: Architecture validated, GDK SDK access required for runtime testing + +## Executive Summary + +GoudEngine's wgpu-based rendering architecture is structurally compatible with +Xbox GDK. The engine's `RenderBackend` trait abstraction, `PlatformBackend` +trait, and runtime backend selection already support plugging in new +platform/GPU combinations without modifying higher layers. A new `xbox-gdk` +feature flag enables Xbox-specific code paths while keeping them out of +standard builds. + +**Verdict**: Feasible with caveats. The rendering path (wgpu DX12) aligns +well with Xbox's native GPU API. Blocking issues exist in audio, input, and +shader signing that require Xbox GDK SDK access to resolve. + +## Architecture Integration + +### What was built + +- Feature flag: `xbox-gdk` in `goud_engine/Cargo.toml` +- Platform module: `XboxGdkPlatform` implementing `PlatformBackend` +- Window handle wrapper: `XboxWindowHandle` with `raw-window-handle` traits + for wgpu DX12 surface creation +- wgpu initialization: `WgpuBackend::new_from_raw_handle()` forcing DX12 + backend +- Runtime integration: `(XboxGdk, Wgpu)` backend pair in `native_runtime.rs` + +### What compiles today + +`cargo check --features xbox-gdk` passes on macOS. The Xbox platform +constructor returns `BackendNotSupported` on non-MSVC targets, allowing +the full type system to be validated without the GDK SDK. + +## wgpu DX12 on Xbox GDK + +### Compatibility + +wgpu v29 ships a DX12 backend (`wgpu::Backends::DX12`) that uses the +standard `d3d12.dll` API. Xbox GDK uses `d3d12_xs.dll` (Xbox Series) or +`d3d12_x.dll` (Xbox One), which are supersets of the desktop DX12 API with +console-specific extensions. + +**Risk**: wgpu links against `d3d12.dll` at compile time. On Xbox, this +library does not exist -- only the `_xs`/`_x` variants are present. This +may require: +1. A wgpu build configuration that targets the Xbox DX12 variant +2. A thin shim that re-exports `d3d12_xs.dll` symbols as `d3d12.dll` +3. Contributing Xbox target support upstream to wgpu + +**Severity**: High. This is the primary blocking issue. + +### Surface Creation + +The PoC implements `XboxWindowHandle` with `raw-window-handle` 0.6 traits +(via `wgpu::rwh::` re-exports). The HWND from Xbox GDK's `GameWindow` API +should work with wgpu's `SurfaceTarget::from()` since Xbox GDK supports the +Win32 HWND windowing model. + +**Risk**: Low. The HWND path is well-tested in wgpu's Windows backend. + +## Shader Pipeline + +wgpu uses the naga shader compiler: WGSL source -> naga IR -> HLSL/DXIL +output. Xbox GDK requires DXIL bytecode, which naga can produce. + +### DXIL Signing + +Xbox retail builds require signed DXIL shaders. Development/devkit builds +allow unsigned DXIL. The PoC targets devkit mode. + +For production, options include: +1. Using `dxcompiler.dll` (included in Xbox GDK) to compile and sign at + build time +2. Pre-compiling shaders offline with `dxc.exe` +3. Contributing DXIL signing support to naga + +**Severity**: Medium. Only affects retail builds, not the PoC. + +### Shader Model + +Xbox Series X/S supports Shader Model 6.6. naga targets SM 6.0 by default. +SM 6.0 is sufficient for the engine's current shader needs (no mesh shaders, +no ray tracing). + +**Risk**: Low for current feature set. + +## Windowing + +Xbox GDK provides two windowing models: +1. **Win32 subset**: `CreateWindowExW` / `PeekMessage` / `DispatchMessage` +2. **GameWindow API**: `XGameWindowCreate` (higher-level, Xbox-specific) + +Both provide an HWND. The PoC's `XboxGdkPlatform` is designed to use either +path. Xbox apps are always fullscreen; the platform backend correctly reports +this and rejects windowed mode. + +**Risk**: Low. HWND acquisition is straightforward. + +## Blocking Issues + +| # | Issue | Severity | Mitigation | +|---|-------|----------|------------| +| 1 | wgpu links `d3d12.dll`, Xbox has `d3d12_xs.dll` | High | Shim DLL or upstream wgpu Xbox support | +| 2 | Audio: `rodio` uses CPAL which does not support Xbox audio APIs | High | Replace with Xbox `XAudio2` or `WASAPI` via FFI | +| 3 | Input: `winit` does not support Xbox controllers | Medium | Use Xbox `GameInput` API via FFI | +| 4 | DXIL signing for retail builds | Medium | Use GDK's `dxcompiler.dll` or offline compilation | +| 5 | GDK SDK not available in CI | Medium | Xbox builds manual-only until GDK CI licensing resolved | +| 6 | Networking: engine uses `std::net` which works on Xbox, but Xbox Live integration requires GDK APIs | Low | Defer to post-alpha | + +## Audio Gap + +`rodio` (the engine's audio backend) depends on CPAL, which uses CoreAudio +(macOS), WASAPI (Windows), or ALSA (Linux). Xbox GDK does not expose +WASAPI -- it uses XAudio2 or the lower-level `Windows.Media.Audio` API. + +**Resolution**: Add an `xbox-audio` feature flag with an `XboxAudioBackend` +that calls XAudio2 via FFI. This is a separate work item (not in PoC scope). + +## Input + +The engine's `InputManager` maps keyboard/mouse/gamepad events from the +platform backend. Xbox GDK provides: +- `GameInput` API (recommended, supports all Xbox controllers) +- `XInputGetState` (legacy, supports standard gamepads) + +The PoC's `XboxGdkPlatform::poll_events` is a stub. A full implementation +would call `GameInput` and map to the engine's input events. + +**Resolution**: Extend `InputManager` with Xbox-specific gamepad mappings. +Separate work item. + +## Build Toolchain + +Xbox GDK compilation requires: +- Target: `x86_64-pc-windows-msvc` +- Environment: `GameDK` env var pointing to GDK install +- Linker search: `$(GameDK)\GRDK\GameKit\Lib\amd64` +- Link libraries: `xgameruntime.lib` + +Rust cross-compilation from macOS to Windows MSVC is possible but non-trivial +(requires MSVC linker via `xwin` or a Windows CI runner). + +## Certification Requirements + +Xbox certification (XR requirements) that may affect engine architecture: +- **XR-015**: Must handle suspend/resume lifecycle events +- **XR-045**: Must support Quick Resume +- **XR-074**: Must not block the UI thread for >15 seconds +- **XR-130**: Must respect accessibility settings + +These require engine-level hooks but do not block the rendering PoC. + +## Recommended Next Steps + +1. **Obtain Xbox GDK SDK access** and install on a Windows build machine +2. **Resolve DX12 linking** -- test whether wgpu's DX12 backend works with + `d3d12_xs.dll` or needs a shim +3. **Implement actual window creation** in `XboxGdkPlatform::new_inner` + using `CreateWindowExW` +4. **Test on devkit or GDK emulator** -- render a cleared framebuffer +5. **If successful**: plan audio, input, and certification work as separate + issues for post-alpha +6. **If blocked on DX12 linking**: evaluate contributing Xbox support to + wgpu upstream, or using raw DX12 as a backend (bypassing wgpu) diff --git a/goud_engine/Cargo.toml b/goud_engine/Cargo.toml index e0ffccf49..b00e365d9 100644 --- a/goud_engine/Cargo.toml +++ b/goud_engine/Cargo.toml @@ -26,6 +26,7 @@ web = ["wgpu-backend", "dep:wasm-bindgen", "dep:wasm-bindgen-futures", "dep:web- rapier2d = ["dep:rapier2d"] rapier3d = ["dep:rapier3d"] headless = [] +xbox-gdk = ["wgpu-backend"] # TODO: https://github.com/aram-devdocs/GoudEngine/issues/8 [dependencies] diff --git a/goud_engine/src/libs/graphics/backend/mod.rs b/goud_engine/src/libs/graphics/backend/mod.rs index 628824251..b2b7d68fa 100644 --- a/goud_engine/src/libs/graphics/backend/mod.rs +++ b/goud_engine/src/libs/graphics/backend/mod.rs @@ -23,14 +23,17 @@ pub mod blend; pub mod capabilities; -#[cfg(feature = "native")] +#[cfg(any(feature = "native", feature = "xbox-gdk"))] pub mod native_backend; pub mod null; #[cfg(feature = "legacy-glfw-opengl")] pub mod opengl; pub mod render_backend; pub mod types; -#[cfg(all(feature = "native", feature = "wgpu-backend"))] +#[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" +))] pub mod wgpu_backend; #[cfg(test)] diff --git a/goud_engine/src/libs/graphics/backend/native_backend/mod.rs b/goud_engine/src/libs/graphics/backend/native_backend/mod.rs index 2c826494f..a41c44234 100644 --- a/goud_engine/src/libs/graphics/backend/native_backend/mod.rs +++ b/goud_engine/src/libs/graphics/backend/native_backend/mod.rs @@ -17,7 +17,10 @@ use super::opengl::OpenGLBackend; use super::render_backend::RenderBackend; #[cfg(feature = "legacy-glfw-opengl")] use super::render_backend::StateOps; -#[cfg(all(feature = "native", feature = "wgpu-backend"))] +#[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" +))] use super::wgpu_backend::WgpuBackend; /// Concrete native render backend choice. @@ -25,7 +28,10 @@ pub enum NativeRenderBackend { #[cfg(feature = "legacy-glfw-opengl")] /// Legacy OpenGL backend selected through the explicit legacy feature gate. OpenGlLegacy(Box), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] /// Default wgpu backend used by the native runtime. Wgpu(Box), } @@ -35,7 +41,10 @@ impl NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.info(), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.info(), } } @@ -48,7 +57,10 @@ impl NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.bind_texture_by_index(index, unit), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.bind_texture_by_index(index, unit), } } @@ -57,7 +69,10 @@ impl NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.set_viewport(0, 0, width, height), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.resize(width, height), } } diff --git a/goud_engine/src/libs/graphics/backend/native_backend/native_draw.rs b/goud_engine/src/libs/graphics/backend/native_backend/native_draw.rs index 22a4bf484..150fbdc50 100644 --- a/goud_engine/src/libs/graphics/backend/native_backend/native_draw.rs +++ b/goud_engine/src/libs/graphics/backend/native_backend/native_draw.rs @@ -9,7 +9,10 @@ impl DrawOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.set_vertex_attributes(layout), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.set_vertex_attributes(layout), } } @@ -18,7 +21,10 @@ impl DrawOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.set_vertex_bindings(bindings), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.set_vertex_bindings(bindings), } } @@ -32,7 +38,10 @@ impl DrawOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.draw_arrays(topology, first, count), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.draw_arrays(topology, first, count), } } @@ -46,7 +55,10 @@ impl DrawOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.draw_indexed(topology, count, offset), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.draw_indexed(topology, count, offset), } } @@ -60,7 +72,10 @@ impl DrawOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.draw_indexed_u16(topology, count, offset), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.draw_indexed_u16(topology, count, offset), } } @@ -77,7 +92,10 @@ impl DrawOps for NativeRenderBackend { Self::OpenGlLegacy(backend) => { backend.draw_arrays_instanced(topology, first, count, instance_count) } - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => { backend.draw_arrays_instanced(topology, first, count, instance_count) } @@ -96,7 +114,10 @@ impl DrawOps for NativeRenderBackend { Self::OpenGlLegacy(backend) => { backend.draw_indexed_instanced(topology, count, offset, instance_count) } - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => { backend.draw_indexed_instanced(topology, count, offset, instance_count) } diff --git a/goud_engine/src/libs/graphics/backend/native_backend/native_impls.rs b/goud_engine/src/libs/graphics/backend/native_backend/native_impls.rs index d0eb67907..28b1598be 100644 --- a/goud_engine/src/libs/graphics/backend/native_backend/native_impls.rs +++ b/goud_engine/src/libs/graphics/backend/native_backend/native_impls.rs @@ -12,7 +12,10 @@ impl RenderBackend for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.bind_default_vertex_array(), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.bind_default_vertex_array(), } } @@ -21,7 +24,10 @@ impl RenderBackend for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.validate_text_draw_state(), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.validate_text_draw_state(), } } @@ -34,7 +40,10 @@ impl RenderBackend for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.read_default_framebuffer_rgba8(width, height), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.read_default_framebuffer_rgba8(width, height), } } @@ -45,7 +54,10 @@ impl FrameOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.begin_frame(), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.begin_frame(), } } @@ -54,7 +66,10 @@ impl FrameOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.end_frame(), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.end_frame(), } } @@ -65,7 +80,10 @@ impl ClearOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.set_clear_color(r, g, b, a), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.set_clear_color(r, g, b, a), } } @@ -74,7 +92,10 @@ impl ClearOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.clear_color(), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.clear_color(), } } @@ -83,7 +104,10 @@ impl ClearOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.clear_depth(), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.clear_depth(), } } @@ -94,7 +118,10 @@ impl StateOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.set_viewport(x, y, width, height), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.set_viewport(x, y, width, height), } } @@ -103,7 +130,10 @@ impl StateOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.enable_depth_test(), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.enable_depth_test(), } } @@ -112,7 +142,10 @@ impl StateOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.disable_depth_test(), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.disable_depth_test(), } } @@ -121,7 +154,10 @@ impl StateOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.enable_blending(), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.enable_blending(), } } @@ -130,7 +166,10 @@ impl StateOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.disable_blending(), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.disable_blending(), } } @@ -143,7 +182,10 @@ impl StateOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.set_blend_func(src, dst), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.set_blend_func(src, dst), } } @@ -152,7 +194,10 @@ impl StateOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.enable_culling(), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.enable_culling(), } } @@ -161,7 +206,10 @@ impl StateOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.disable_culling(), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.disable_culling(), } } @@ -170,7 +218,10 @@ impl StateOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.set_cull_face(face), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.set_cull_face(face), } } @@ -179,7 +230,10 @@ impl StateOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.set_depth_func(func), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.set_depth_func(func), } } @@ -188,7 +242,10 @@ impl StateOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.set_front_face(face), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.set_front_face(face), } } @@ -197,7 +254,10 @@ impl StateOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.set_depth_mask(enabled), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.set_depth_mask(enabled), } } @@ -206,7 +266,10 @@ impl StateOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.set_multisampling_enabled(enabled), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.set_multisampling_enabled(enabled), } } @@ -215,7 +278,10 @@ impl StateOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.set_line_width(width), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.set_line_width(width), } } diff --git a/goud_engine/src/libs/graphics/backend/native_backend/native_resources.rs b/goud_engine/src/libs/graphics/backend/native_backend/native_resources.rs index e97a2cb92..534594fd1 100644 --- a/goud_engine/src/libs/graphics/backend/native_backend/native_resources.rs +++ b/goud_engine/src/libs/graphics/backend/native_backend/native_resources.rs @@ -19,7 +19,10 @@ impl BufferOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.create_buffer(buffer_type, usage, data), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.create_buffer(buffer_type, usage, data), } } @@ -33,7 +36,10 @@ impl BufferOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.update_buffer(handle, offset, data), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.update_buffer(handle, offset, data), } } @@ -42,7 +48,10 @@ impl BufferOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.destroy_buffer(handle), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.destroy_buffer(handle), } } @@ -51,7 +60,10 @@ impl BufferOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.is_buffer_valid(handle), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.is_buffer_valid(handle), } } @@ -60,7 +72,10 @@ impl BufferOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.buffer_size(handle), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.buffer_size(handle), } } @@ -69,7 +84,10 @@ impl BufferOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.bind_buffer(handle), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.bind_buffer(handle), } } @@ -78,7 +96,10 @@ impl BufferOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.unbind_buffer(buffer_type), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.unbind_buffer(buffer_type), } } @@ -87,7 +108,10 @@ impl BufferOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.supports_storage_buffers(), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.supports_storage_buffers(), } } @@ -96,7 +120,10 @@ impl BufferOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.create_storage_buffer(data), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.create_storage_buffer(data), } } @@ -110,7 +137,10 @@ impl BufferOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.update_storage_buffer(handle, offset, data), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.update_storage_buffer(handle, offset, data), } } @@ -119,7 +149,10 @@ impl BufferOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.bind_storage_buffer(handle, binding), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.bind_storage_buffer(handle, binding), } } @@ -128,7 +161,10 @@ impl BufferOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.unbind_storage_buffer(), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.unbind_storage_buffer(), } } @@ -149,7 +185,10 @@ impl TextureOps for NativeRenderBackend { Self::OpenGlLegacy(backend) => { backend.create_texture(width, height, format, filter, wrap, data) } - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => { backend.create_texture(width, height, format, filter, wrap, data) } @@ -170,7 +209,10 @@ impl TextureOps for NativeRenderBackend { Self::OpenGlLegacy(backend) => { backend.update_texture(handle, x, y, width, height, data) } - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.update_texture(handle, x, y, width, height, data), } } @@ -179,7 +221,10 @@ impl TextureOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.destroy_texture(handle), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.destroy_texture(handle), } } @@ -188,7 +233,10 @@ impl TextureOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.is_texture_valid(handle), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.is_texture_valid(handle), } } @@ -197,7 +245,10 @@ impl TextureOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.texture_size(handle), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.texture_size(handle), } } @@ -206,7 +257,10 @@ impl TextureOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.bind_texture(handle, unit), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.bind_texture(handle, unit), } } @@ -215,7 +269,10 @@ impl TextureOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.unbind_texture(unit), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.unbind_texture(unit), } } @@ -233,7 +290,10 @@ impl TextureOps for NativeRenderBackend { Self::OpenGlLegacy(backend) => { backend.create_compressed_texture(width, height, format, data, mip_levels) } - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => { backend.create_compressed_texture(width, height, format, data, mip_levels) } @@ -246,7 +306,10 @@ impl RenderTargetOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.create_render_target(desc), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.create_render_target(desc), } } @@ -255,7 +318,10 @@ impl RenderTargetOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.destroy_render_target(handle), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.destroy_render_target(handle), } } @@ -264,7 +330,10 @@ impl RenderTargetOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.is_render_target_valid(handle), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.is_render_target_valid(handle), } } @@ -273,7 +342,10 @@ impl RenderTargetOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.bind_render_target(handle), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.bind_render_target(handle), } } @@ -282,7 +354,10 @@ impl RenderTargetOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.render_target_texture(handle), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.render_target_texture(handle), } } @@ -293,7 +368,10 @@ impl ShaderOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.create_shader(vertex_src, fragment_src), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.create_shader(vertex_src, fragment_src), } } @@ -302,7 +380,10 @@ impl ShaderOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.destroy_shader(handle), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.destroy_shader(handle), } } @@ -311,7 +392,10 @@ impl ShaderOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.is_shader_valid(handle), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.is_shader_valid(handle), } } @@ -320,7 +404,10 @@ impl ShaderOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.bind_shader(handle), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.bind_shader(handle), } } @@ -329,7 +416,10 @@ impl ShaderOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.unbind_shader(), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.unbind_shader(), } } @@ -338,7 +428,10 @@ impl ShaderOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.get_uniform_location(handle, name), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.get_uniform_location(handle, name), } } @@ -347,7 +440,10 @@ impl ShaderOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.set_uniform_int(location, value), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.set_uniform_int(location, value), } } @@ -356,7 +452,10 @@ impl ShaderOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.set_uniform_float(location, value), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.set_uniform_float(location, value), } } @@ -365,7 +464,10 @@ impl ShaderOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.set_uniform_vec2(location, x, y), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.set_uniform_vec2(location, x, y), } } @@ -374,7 +476,10 @@ impl ShaderOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.set_uniform_vec3(location, x, y, z), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.set_uniform_vec3(location, x, y, z), } } @@ -383,7 +488,10 @@ impl ShaderOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.set_uniform_vec4(location, x, y, z, w), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.set_uniform_vec4(location, x, y, z, w), } } @@ -392,7 +500,10 @@ impl ShaderOps for NativeRenderBackend { match self { #[cfg(feature = "legacy-glfw-opengl")] Self::OpenGlLegacy(backend) => backend.set_uniform_mat4(location, matrix), - #[cfg(all(feature = "native", feature = "wgpu-backend"))] + #[cfg(any( + all(feature = "native", feature = "wgpu-backend"), + feature = "xbox-gdk" + ))] Self::Wgpu(backend) => backend.set_uniform_mat4(location, matrix), } } diff --git a/goud_engine/src/libs/graphics/backend/wgpu_backend/init.rs b/goud_engine/src/libs/graphics/backend/wgpu_backend/init.rs index 84e1841ce..f7d686146 100644 --- a/goud_engine/src/libs/graphics/backend/wgpu_backend/init.rs +++ b/goud_engine/src/libs/graphics/backend/wgpu_backend/init.rs @@ -322,6 +322,279 @@ impl WgpuBackend { self.bind_texture(handle, unit) } + /// Creates a new wgpu backend from a raw Xbox GDK window handle. + /// + /// Forces the DX12 backend since Xbox GDK uses DirectX 12 natively. + /// Blocks on async wgpu initialization via pollster. + #[cfg(feature = "xbox-gdk")] + pub fn new_from_raw_handle( + handle: Arc, + width: u32, + height: u32, + ) -> GoudResult { + pollster::block_on(Self::new_xbox_async(handle, width, height)) + } + + #[cfg(feature = "xbox-gdk")] + async fn new_xbox_async( + handle: Arc, + width: u32, + height: u32, + ) -> GoudResult { + let mut instance_desc = wgpu::InstanceDescriptor::new_without_display_handle(); + instance_desc.backends = wgpu::Backends::DX12; + let instance = wgpu::Instance::new(instance_desc); + + let surface = instance + .create_surface(wgpu::SurfaceTarget::from(handle)) + .map_err(|e| GoudError::BackendNotSupported(format!("wgpu Xbox surface: {e}")))?; + + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .await + .map_err(|e| { + GoudError::BackendNotSupported(format!("No suitable DX12 adapter: {e}")) + })?; + + let (device, queue): (wgpu::Device, wgpu::Queue) = adapter + .request_device(&wgpu::DeviceDescriptor { + label: Some("GoudEngine-Xbox"), + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::default(), + ..Default::default() + }) + .await + .map_err(|e| GoudError::BackendNotSupported(format!("wgpu device: {e}")))?; + + let caps = surface.get_capabilities(&adapter); + let surface_format = caps + .formats + .iter() + .find(|f| !f.is_srgb()) + .copied() + .unwrap_or(caps.formats[0]); + let surface_supports_copy_src = caps.usages.contains(wgpu::TextureUsages::COPY_SRC); + + let w = width.max(1); + let h = height.max(1); + let surface_config = wgpu::SurfaceConfiguration { + usage: if surface_supports_copy_src { + wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC + } else { + wgpu::TextureUsages::RENDER_ATTACHMENT + }, + format: surface_format, + width: w, + height: h, + present_mode: wgpu::PresentMode::AutoVsync, + alpha_mode: caps.alpha_modes[0], + view_formats: vec![], + desired_maximum_frame_latency: 2, + }; + surface.configure(&device, &surface_config); + + let (depth_texture, depth_view) = Self::create_depth_texture(&device, w, h); + + let adapter_info = adapter.get_info(); + let limits = device.limits(); + + let info = BackendInfo { + name: "wgpu-xbox", + version: format!("{:?}", adapter_info.backend), + vendor: adapter_info.vendor.to_string(), + renderer: adapter_info.name.clone(), + capabilities: BackendCapabilities { + max_texture_units: limits.max_sampled_textures_per_shader_stage.min(16), + max_texture_size: limits.max_texture_dimension_2d, + max_vertex_attributes: limits.max_vertex_attributes, + max_uniform_buffer_size: limits.max_uniform_buffer_binding_size as u32, + supports_instancing: true, + supports_compute_shaders: true, + supports_geometry_shaders: false, + supports_tessellation: false, + supports_multisampling: true, + supports_anisotropic_filtering: true, + supports_bc_compression: false, + }, + shader_language: ShaderLanguage::Wgsl, + }; + + let uniform_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("uniform_bgl"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: true, + min_binding_size: None, + }, + count: None, + }], + }); + + let texture_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("texture_bgl"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + }); + + let fallback_tex_bind_group = { + let tex = device.create_texture(&wgpu::TextureDescriptor { + label: Some("fallback-white-1x1"), + size: wgpu::Extent3d { + width: 1, + height: 1, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }); + queue.write_texture( + wgpu::TexelCopyTextureInfo { + texture: &tex, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + &[255u8, 255, 255, 255], + wgpu::TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(4), + rows_per_image: Some(1), + }, + wgpu::Extent3d { + width: 1, + height: 1, + depth_or_array_layers: 1, + }, + ); + let view = tex.create_view(&wgpu::TextureViewDescriptor::default()); + let sampler = device.create_sampler(&wgpu::SamplerDescriptor::default()); + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("fallback-texture-bg"), + layout: &texture_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + ], + }) + }; + + let storage_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("storage_bgl"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + }); + + let fallback_storage_bind_group = { + let buf = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("fallback-storage"), + size: 64, + usage: wgpu::BufferUsages::STORAGE, + mapped_at_creation: false, + }); + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("fallback-storage-bg"), + layout: &storage_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: buf.as_entire_binding(), + }], + }) + }; + + Ok(Self { + info, + device, + queue, + surface, + surface_config, + surface_format, + surface_supports_copy_src, + depth_texture, + depth_view, + last_frame_readback: None, + clear_color: wgpu::Color::BLACK, + needs_clear: false, + current_frame: None, + draw_commands: Vec::new(), + depth_test_enabled: false, + depth_write_enabled: true, + depth_func: DepthFunc::Less, + blend_enabled: false, + blend_src: BlendFactor::One, + blend_dst: BlendFactor::Zero, + cull_enabled: false, + cull_face: CullFace::Back, + front_face_state: FrontFace::Ccw, + buffer_allocator: HandleAllocator::new(), + buffers: HashMap::new(), + pending_destroy_buffers: Vec::new(), + texture_allocator: HandleAllocator::new(), + textures: HashMap::new(), + shader_allocator: HandleAllocator::new(), + shaders: HashMap::new(), + bound_vertex_buffer: None, + bound_index_buffer: None, + bound_shader: None, + bound_textures: vec![None; MAX_TEXTURE_UNITS], + current_layout: None, + current_vertex_bindings: Vec::new(), + current_topology: PrimitiveTopology::Triangles, + pipeline_cache: HashMap::new(), + uniform_bind_group_layout, + texture_bind_group_layout, + storage_bind_group_layout, + fallback_tex_bind_group, + fallback_storage_bind_group, + bound_storage_buffer: None, + storage_bind_group_cache: HashMap::new(), + uniform_ring: Vec::with_capacity(UNIFORM_BUFFER_SIZE * 64), + }) + } + /// Resizes the surface and depth buffer. Call after window resize. pub fn resize(&mut self, width: u32, height: u32) { let w = width.max(1); diff --git a/goud_engine/src/libs/graphics/backend/wgpu_backend/mod.rs b/goud_engine/src/libs/graphics/backend/wgpu_backend/mod.rs index e07bdb883..c011db30b 100644 --- a/goud_engine/src/libs/graphics/backend/wgpu_backend/mod.rs +++ b/goud_engine/src/libs/graphics/backend/wgpu_backend/mod.rs @@ -34,6 +34,8 @@ mod resources; mod shader; mod texture; mod uniforms; +#[cfg(feature = "xbox-gdk")] +pub mod xbox_surface; // Pull internal types into this module's namespace so submodules can `use super::TypeName`. use resources::{ diff --git a/goud_engine/src/libs/graphics/backend/wgpu_backend/xbox_surface.rs b/goud_engine/src/libs/graphics/backend/wgpu_backend/xbox_surface.rs new file mode 100644 index 000000000..3537b8c66 --- /dev/null +++ b/goud_engine/src/libs/graphics/backend/wgpu_backend/xbox_surface.rs @@ -0,0 +1,85 @@ +//! Xbox GDK window handle wrapper for wgpu surface creation. +//! +//! Provides [`XboxWindowHandle`] which implements the `raw-window-handle` +//! traits required by wgpu to create a DX12 surface from an Xbox GDK HWND. + +use std::num::NonZeroIsize; + +/// Wrapper providing `raw-window-handle` traits for an Xbox GDK HWND. +/// +/// wgpu uses `HasWindowHandle` + `HasDisplayHandle` to create a rendering +/// surface. This wrapper bridges the Xbox GDK `HWND` to those traits so +/// wgpu's DX12 backend can present to the Xbox window. +pub struct XboxWindowHandle { + hwnd: *mut std::ffi::c_void, +} + +impl XboxWindowHandle { + /// Wraps a raw HWND for use with wgpu. + /// + /// # Safety + /// + /// `hwnd` must be a valid Win32 HWND that remains live for the lifetime + /// of this handle. The caller is responsible for ensuring the window is + /// not destroyed while this handle exists. + pub unsafe fn from_raw_hwnd(hwnd: *mut std::ffi::c_void) -> Self { + Self { hwnd } + } + + /// Creates a dummy handle for type-checking on non-Windows platforms. + /// + /// The returned handle must never be used to create an actual surface. + #[cfg(not(target_env = "msvc"))] + pub fn dummy() -> Self { + Self { + hwnd: std::ptr::null_mut(), + } + } +} + +// SAFETY: The HWND is owned by the Xbox GDK GameWindow which lives for the +// duration of the game. The handle is only used to create a wgpu surface +// during initialization, after which wgpu owns the surface independently. +unsafe impl Send for XboxWindowHandle {} +// SAFETY: Same reasoning as Send — the HWND is a copyable integer handle +// and wgpu only reads it during surface creation. +unsafe impl Sync for XboxWindowHandle {} + +impl wgpu::rwh::HasWindowHandle for XboxWindowHandle { + fn window_handle(&self) -> Result, wgpu::rwh::HandleError> { + // SAFETY: The HWND was validated as non-null when constructed via + // `from_raw_hwnd`. For `dummy()` handles this path should never be + // reached in practice. + let raw = wgpu::rwh::Win32WindowHandle::new( + NonZeroIsize::new(self.hwnd as isize).ok_or(wgpu::rwh::HandleError::Unavailable)?, + ); + let raw = wgpu::rwh::RawWindowHandle::Win32(raw); + // SAFETY: The raw handle borrows `self` which outlives the returned + // `WindowHandle`. + Ok(unsafe { wgpu::rwh::WindowHandle::borrow_raw(raw) }) + } +} + +impl wgpu::rwh::HasDisplayHandle for XboxWindowHandle { + fn display_handle(&self) -> Result, wgpu::rwh::HandleError> { + let raw = wgpu::rwh::RawDisplayHandle::Windows(wgpu::rwh::WindowsDisplayHandle::new()); + // SAFETY: Windows display handle carries no state and is always valid. + Ok(unsafe { wgpu::rwh::DisplayHandle::borrow_raw(raw) }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn dummy_handle_returns_unavailable() { + #[cfg(not(target_env = "msvc"))] + { + use wgpu::rwh::HasWindowHandle; + let handle = XboxWindowHandle::dummy(); + // Dummy has null HWND → NonZeroIsize fails → HandleError::Unavailable + assert!(handle.window_handle().is_err()); + } + } +} diff --git a/goud_engine/src/libs/platform/mod.rs b/goud_engine/src/libs/platform/mod.rs index f258e4e41..6792ffa7f 100644 --- a/goud_engine/src/libs/platform/mod.rs +++ b/goud_engine/src/libs/platform/mod.rs @@ -32,7 +32,7 @@ #[cfg(feature = "legacy-glfw-opengl")] pub mod glfw_platform; -#[cfg(feature = "native")] +#[cfg(any(feature = "native", feature = "xbox-gdk"))] pub mod native_runtime; #[cfg(all( feature = "wgpu-backend", @@ -40,8 +40,10 @@ pub mod native_runtime; any(target_os = "linux", target_os = "macos", target_os = "windows") ))] pub mod winit_platform; +#[cfg(feature = "xbox-gdk")] +pub mod xbox_gdk_platform; -#[cfg(feature = "native")] +#[cfg(any(feature = "native", feature = "xbox-gdk"))] use crate::core::input_manager::InputManager; /// Fullscreen mode for the native window. @@ -103,6 +105,9 @@ pub enum WindowBackendKind { Winit = 0, /// Legacy GLFW windowing path. GlfwLegacy = 1, + /// Xbox GDK windowing path (PoC). + #[cfg(feature = "xbox-gdk")] + XboxGdk = 2, } impl WindowBackendKind { @@ -111,6 +116,8 @@ impl WindowBackendKind { match value { 0 => Some(Self::Winit), 1 => Some(Self::GlfwLegacy), + #[cfg(feature = "xbox-gdk")] + 2 => Some(Self::XboxGdk), _ => None, } } @@ -171,7 +178,7 @@ impl Default for WindowConfig { /// /// Most windowing APIs require main-thread access. Implementations are NOT /// required to be `Send` or `Sync`. -#[cfg(feature = "native")] +#[cfg(any(feature = "native", feature = "xbox-gdk"))] pub trait PlatformBackend { /// Returns `true` if the window has been requested to close. fn should_close(&self) -> bool; @@ -279,4 +286,13 @@ mod tests { Some(RenderBackendKind::Auto) ); } + + #[cfg(feature = "xbox-gdk")] + #[test] + fn xbox_gdk_window_backend_round_trip() { + assert_eq!( + WindowBackendKind::from_u32(2), + Some(WindowBackendKind::XboxGdk) + ); + } } diff --git a/goud_engine/src/libs/platform/native_runtime.rs b/goud_engine/src/libs/platform/native_runtime.rs index e6a26861e..54101c807 100644 --- a/goud_engine/src/libs/platform/native_runtime.rs +++ b/goud_engine/src/libs/platform/native_runtime.rs @@ -20,7 +20,7 @@ fn invalid_pair_error( render_backend: RenderBackendKind, ) -> GoudError { GoudError::InitializationFailed(format!( - "invalid native backend pair: window={window_backend:?} render={render_backend:?}; supported pairs are Winit+Wgpu and GlfwLegacy+OpenGlLegacy" + "invalid native backend pair: window={window_backend:?} render={render_backend:?}; supported pairs are Winit+Wgpu, GlfwLegacy+OpenGlLegacy, and XboxGdk+Wgpu" )) } @@ -29,6 +29,10 @@ fn invalid_pair_error( /// Tries wgpu (Winit + Wgpu) first, then falls back to OpenGL (GLFW + OpenGL). #[allow(unreachable_code)] pub fn detect_best_backend() -> (WindowBackendKind, RenderBackendKind) { + #[cfg(feature = "xbox-gdk")] + { + return (WindowBackendKind::XboxGdk, RenderBackendKind::Wgpu); + } #[cfg(all( feature = "native", feature = "wgpu-backend", @@ -61,6 +65,8 @@ pub fn validate_native_backend_pair( match (window_backend, render_backend) { (WindowBackendKind::Winit, RenderBackendKind::Wgpu) => Ok(()), (WindowBackendKind::GlfwLegacy, RenderBackendKind::OpenGlLegacy) => Ok(()), + #[cfg(feature = "xbox-gdk")] + (WindowBackendKind::XboxGdk, RenderBackendKind::Wgpu) => Ok(()), (_, RenderBackendKind::Auto) => Ok(()), // Auto is resolved before validation _ => Err(invalid_pair_error(window_backend, render_backend)), } @@ -130,6 +136,23 @@ pub fn create_native_runtime( "legacy GLFW/OpenGL support is not enabled in this build".to_string(), )) } + #[cfg(feature = "xbox-gdk")] + (WindowBackendKind::XboxGdk, RenderBackendKind::Wgpu) => { + let platform = super::xbox_gdk_platform::XboxGdkPlatform::new(window_config)?; + let handle = platform.window_handle(); + let (w, h) = platform.get_framebuffer_size(); + let mut backend = + crate::libs::graphics::backend::wgpu_backend::WgpuBackend::new_from_raw_handle( + handle, w, h, + )?; + backend.resize(w, h); + Ok(NativeRuntime { + platform: Box::new(platform), + render_backend: SharedNativeRenderBackend::new(NativeRenderBackend::Wgpu( + Box::new(backend), + )), + }) + } _ => Err(invalid_pair_error(window_backend, render_backend)), } } @@ -179,4 +202,13 @@ mod tests { validate_native_backend_pair(WindowBackendKind::Winit, RenderBackendKind::Auto).is_ok() ); } + + #[cfg(feature = "xbox-gdk")] + #[test] + fn accepts_xbox_gdk_pair() { + assert!( + validate_native_backend_pair(WindowBackendKind::XboxGdk, RenderBackendKind::Wgpu,) + .is_ok() + ); + } } diff --git a/goud_engine/src/libs/platform/xbox_gdk_platform.rs b/goud_engine/src/libs/platform/xbox_gdk_platform.rs new file mode 100644 index 000000000..b843eced2 --- /dev/null +++ b/goud_engine/src/libs/platform/xbox_gdk_platform.rs @@ -0,0 +1,114 @@ +//! Xbox GDK platform backend (PoC). +//! +//! Provides a [`PlatformBackend`] implementation for the Xbox Game Development +//! Kit. On non-MSVC targets the constructor returns +//! [`GoudError::BackendNotSupported`] so the module can still be type-checked +//! during cross-platform development. + +use std::sync::Arc; +use std::time::Instant; + +use crate::core::error::{GoudError, GoudResult}; +use crate::core::input_manager::InputManager; + +use super::{FullscreenMode, PlatformBackend, WindowConfig}; +use crate::libs::graphics::backend::wgpu_backend::xbox_surface::XboxWindowHandle; + +/// Xbox GDK platform backend. +/// +/// Wraps an Xbox GDK `GameWindow` (HWND) and implements the engine's platform +/// abstraction. On non-MSVC hosts the constructor always fails with a +/// descriptive error. +pub struct XboxGdkPlatform { + should_close: bool, + width: u32, + height: u32, + last_frame: Instant, + handle: Arc, +} + +impl XboxGdkPlatform { + /// Creates the Xbox GDK platform. + /// + /// On MSVC targets this would call `CreateWindowExW` or + /// `XGameWindowCreate` to obtain an HWND. On other targets this returns + /// `BackendNotSupported`. + pub fn new(config: &WindowConfig) -> GoudResult { + Self::new_inner(config) + } + + /// Returns the window handle wrapper for wgpu surface creation. + pub fn window_handle(&self) -> Arc { + Arc::clone(&self.handle) + } + + // ------------------------------------------------------------------ + // MSVC implementation (actual GDK path) + // ------------------------------------------------------------------ + + #[cfg(target_env = "msvc")] + fn new_inner(config: &WindowConfig) -> GoudResult { + // TODO(xbox-gdk): Replace with actual XGameWindowCreate / CreateWindowExW + // call once the GDK SDK is available in the build environment. + Err(GoudError::BackendNotSupported( + "Xbox GDK platform requires the GDK SDK (not yet linked)".to_string(), + )) + } + + // ------------------------------------------------------------------ + // Non-MSVC stub (allows type-checking on macOS / Linux) + // ------------------------------------------------------------------ + + #[cfg(not(target_env = "msvc"))] + fn new_inner(_config: &WindowConfig) -> GoudResult { + Err(GoudError::BackendNotSupported( + "Xbox GDK platform is only available on MSVC targets".to_string(), + )) + } +} + +impl PlatformBackend for XboxGdkPlatform { + fn should_close(&self) -> bool { + self.should_close + } + + fn set_should_close(&mut self, should_close: bool) { + self.should_close = should_close; + } + + fn poll_events(&mut self, _input: &mut InputManager) -> f32 { + let now = Instant::now(); + let dt = now.duration_since(self.last_frame).as_secs_f32(); + self.last_frame = now; + dt + } + + fn swap_buffers(&mut self) { + // No-op: wgpu handles presentation via surface.present(). + } + + fn get_size(&self) -> (u32, u32) { + (self.width, self.height) + } + + fn request_size(&mut self, width: u32, height: u32) -> bool { + // Xbox apps are always fullscreen; resize is a no-op. + self.width = width; + self.height = height; + true + } + + fn get_framebuffer_size(&self) -> (u32, u32) { + // Xbox has no HiDPI scaling; logical == physical. + (self.width, self.height) + } + + fn set_fullscreen(&mut self, mode: FullscreenMode) -> bool { + // Xbox is always fullscreen; accept borderless/exclusive, reject windowed. + mode != FullscreenMode::Windowed + } + + fn get_fullscreen(&self) -> FullscreenMode { + FullscreenMode::Borderless + } +} From 879d21b3f9f102100661faabae3e498a1795ead6 Mon Sep 17 00:00:00 2001 From: Aram Hammoudeh Date: Thu, 2 Apr 2026 14:49:32 -0600 Subject: [PATCH 2/4] fix(platform): guard Xbox auto-detect with target_env=msvc Prevent detect_best_backend from returning XboxGdk on non-MSVC hosts when both xbox-gdk and desktop-native features are enabled. Co-Authored-By: Claude Opus 4.6 (1M context) --- goud_engine/src/libs/platform/native_runtime.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/goud_engine/src/libs/platform/native_runtime.rs b/goud_engine/src/libs/platform/native_runtime.rs index 54101c807..f2696bd00 100644 --- a/goud_engine/src/libs/platform/native_runtime.rs +++ b/goud_engine/src/libs/platform/native_runtime.rs @@ -29,7 +29,7 @@ fn invalid_pair_error( /// Tries wgpu (Winit + Wgpu) first, then falls back to OpenGL (GLFW + OpenGL). #[allow(unreachable_code)] pub fn detect_best_backend() -> (WindowBackendKind, RenderBackendKind) { - #[cfg(feature = "xbox-gdk")] + #[cfg(all(feature = "xbox-gdk", target_env = "msvc"))] { return (WindowBackendKind::XboxGdk, RenderBackendKind::Wgpu); } From bc1170b08d2f3428ed8afd08351c7c67501aff87 Mon Sep 17 00:00:00 2001 From: Aram Hammoudeh Date: Thu, 2 Apr 2026 14:58:54 -0600 Subject: [PATCH 3/4] fix(platform): address review feedback for Xbox GDK PoC - Add #[allow(dead_code)] to XboxGdkPlatform struct and window_handle() - Add constructor test for non-MSVC BackendNotSupported error path - Narrow xbox_surface module visibility to pub(crate) - Add TODO(xbox-gdk) comment noting shared init refactor needed Co-Authored-By: Claude Opus 4.6 (1M context) --- .../libs/graphics/backend/wgpu_backend/init.rs | 4 ++++ .../src/libs/graphics/backend/wgpu_backend/mod.rs | 2 +- .../src/libs/platform/xbox_gdk_platform.rs | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/goud_engine/src/libs/graphics/backend/wgpu_backend/init.rs b/goud_engine/src/libs/graphics/backend/wgpu_backend/init.rs index f7d686146..c24c6a40c 100644 --- a/goud_engine/src/libs/graphics/backend/wgpu_backend/init.rs +++ b/goud_engine/src/libs/graphics/backend/wgpu_backend/init.rs @@ -335,6 +335,10 @@ impl WgpuBackend { pollster::block_on(Self::new_xbox_async(handle, width, height)) } + // TODO(xbox-gdk): Extract shared init logic (bind group layouts, fallback + // textures, surface config) into a common helper before promoting this PoC + // to production. Currently duplicates new_async to avoid refactoring the + // existing init path during the feasibility study. #[cfg(feature = "xbox-gdk")] async fn new_xbox_async( handle: Arc, diff --git a/goud_engine/src/libs/graphics/backend/wgpu_backend/mod.rs b/goud_engine/src/libs/graphics/backend/wgpu_backend/mod.rs index c011db30b..d71dd1bfd 100644 --- a/goud_engine/src/libs/graphics/backend/wgpu_backend/mod.rs +++ b/goud_engine/src/libs/graphics/backend/wgpu_backend/mod.rs @@ -35,7 +35,7 @@ mod shader; mod texture; mod uniforms; #[cfg(feature = "xbox-gdk")] -pub mod xbox_surface; +pub(crate) mod xbox_surface; // Pull internal types into this module's namespace so submodules can `use super::TypeName`. use resources::{ diff --git a/goud_engine/src/libs/platform/xbox_gdk_platform.rs b/goud_engine/src/libs/platform/xbox_gdk_platform.rs index b843eced2..feb156a83 100644 --- a/goud_engine/src/libs/platform/xbox_gdk_platform.rs +++ b/goud_engine/src/libs/platform/xbox_gdk_platform.rs @@ -19,6 +19,7 @@ use crate::libs::graphics::backend::wgpu_backend::xbox_surface::XboxWindowHandle /// Wraps an Xbox GDK `GameWindow` (HWND) and implements the engine's platform /// abstraction. On non-MSVC hosts the constructor always fails with a /// descriptive error. +#[allow(dead_code)] // PoC stub: struct fields used once GDK SDK is linked. pub struct XboxGdkPlatform { should_close: bool, width: u32, @@ -38,6 +39,7 @@ impl XboxGdkPlatform { } /// Returns the window handle wrapper for wgpu surface creation. + #[allow(dead_code)] // Called from native_runtime once GDK is linked. pub fn window_handle(&self) -> Arc { Arc::clone(&self.handle) } @@ -112,3 +114,16 @@ impl PlatformBackend for XboxGdkPlatform { FullscreenMode::Borderless } } + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(not(target_env = "msvc"))] + #[test] + fn new_returns_backend_not_supported_on_non_msvc() { + let result = XboxGdkPlatform::new(&WindowConfig::default()); + let err = result.err().expect("should fail on non-MSVC"); + assert!(err.to_string().contains("MSVC")); + } +} From 5f2dd16142315be69991200c91dbc4ff5ce23cf8 Mon Sep 17 00:00:00 2001 From: Aram Hammoudeh Date: Thu, 2 Apr 2026 15:09:02 -0600 Subject: [PATCH 4/4] fix(ci): split Xbox init and reduce native_resources line count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Extract Xbox wgpu init into dedicated xbox_init.rs (init.rs: 613→336) - Add dispatch! macro to native_resources.rs (510→228 lines) - Regenerate ARCHITECTURE.md with xbox-gdk feature flag Co-Authored-By: Claude Opus 4.6 (1M context) --- ARCHITECTURE.md | 1 + .../native_backend/native_resources.rs | 409 +++--------------- .../graphics/backend/wgpu_backend/init.rs | 277 ------------ .../libs/graphics/backend/wgpu_backend/mod.rs | 2 + .../backend/wgpu_backend/xbox_init.rs | 292 +++++++++++++ 5 files changed, 361 insertions(+), 620 deletions(-) create mode 100644 goud_engine/src/libs/graphics/backend/wgpu_backend/xbox_init.rs diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 3a41d325f..38b83a072 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -116,6 +116,7 @@ The `RenderBackend` trait abstracts the GPU backend. Both 2D (SpriteBatch) and 3 | `rapier2d` | `rapier2d` | | `rapier3d` | `rapier3d` | | `headless` | (empty) | +| `xbox-gdk` | `wgpu-backend` | ### SpriteBatch (2D) diff --git a/goud_engine/src/libs/graphics/backend/native_backend/native_resources.rs b/goud_engine/src/libs/graphics/backend/native_backend/native_resources.rs index 534594fd1..c72379fcb 100644 --- a/goud_engine/src/libs/graphics/backend/native_backend/native_resources.rs +++ b/goud_engine/src/libs/graphics/backend/native_backend/native_resources.rs @@ -9,6 +9,18 @@ use crate::libs::graphics::backend::types::{ TextureFilter, TextureFormat, TextureHandle, TextureWrap, }; +/// Dispatch a method call to the active backend variant. +macro_rules! dispatch { + ($self:expr, $method:ident $(, $arg:expr)*) => { + match $self { + #[cfg(feature = "legacy-glfw-opengl")] + Self::OpenGlLegacy(b) => b.$method($($arg),*), + #[cfg(any(all(feature = "native", feature = "wgpu-backend"), feature = "xbox-gdk"))] + Self::Wgpu(b) => b.$method($($arg),*), + } + }; +} + impl BufferOps for NativeRenderBackend { fn create_buffer( &mut self, @@ -16,15 +28,7 @@ impl BufferOps for NativeRenderBackend { usage: BufferUsage, data: &[u8], ) -> GoudResult { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.create_buffer(buffer_type, usage, data), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.create_buffer(buffer_type, usage, data), - } + dispatch!(self, create_buffer, buffer_type, usage, data) } fn update_buffer( @@ -33,99 +37,35 @@ impl BufferOps for NativeRenderBackend { offset: usize, data: &[u8], ) -> GoudResult<()> { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.update_buffer(handle, offset, data), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.update_buffer(handle, offset, data), - } + dispatch!(self, update_buffer, handle, offset, data) } fn destroy_buffer(&mut self, handle: BufferHandle) -> bool { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.destroy_buffer(handle), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.destroy_buffer(handle), - } + dispatch!(self, destroy_buffer, handle) } fn is_buffer_valid(&self, handle: BufferHandle) -> bool { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.is_buffer_valid(handle), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.is_buffer_valid(handle), - } + dispatch!(self, is_buffer_valid, handle) } fn buffer_size(&self, handle: BufferHandle) -> Option { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.buffer_size(handle), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.buffer_size(handle), - } + dispatch!(self, buffer_size, handle) } fn bind_buffer(&mut self, handle: BufferHandle) -> GoudResult<()> { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.bind_buffer(handle), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.bind_buffer(handle), - } + dispatch!(self, bind_buffer, handle) } fn unbind_buffer(&mut self, buffer_type: BufferType) { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.unbind_buffer(buffer_type), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.unbind_buffer(buffer_type), - } + dispatch!(self, unbind_buffer, buffer_type) } fn supports_storage_buffers(&self) -> bool { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.supports_storage_buffers(), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.supports_storage_buffers(), - } + dispatch!(self, supports_storage_buffers) } fn create_storage_buffer(&mut self, data: &[u8]) -> GoudResult { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.create_storage_buffer(data), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.create_storage_buffer(data), - } + dispatch!(self, create_storage_buffer, data) } fn update_storage_buffer( @@ -134,39 +74,15 @@ impl BufferOps for NativeRenderBackend { offset: usize, data: &[u8], ) -> GoudResult<()> { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.update_storage_buffer(handle, offset, data), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.update_storage_buffer(handle, offset, data), - } + dispatch!(self, update_storage_buffer, handle, offset, data) } fn bind_storage_buffer(&mut self, handle: BufferHandle, binding: u32) -> GoudResult<()> { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.bind_storage_buffer(handle, binding), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.bind_storage_buffer(handle, binding), - } + dispatch!(self, bind_storage_buffer, handle, binding) } fn unbind_storage_buffer(&mut self) { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.unbind_storage_buffer(), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.unbind_storage_buffer(), - } + dispatch!(self, unbind_storage_buffer) } } @@ -180,19 +96,16 @@ impl TextureOps for NativeRenderBackend { wrap: TextureWrap, data: &[u8], ) -> GoudResult { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => { - backend.create_texture(width, height, format, filter, wrap, data) - } - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => { - backend.create_texture(width, height, format, filter, wrap, data) - } - } + dispatch!( + self, + create_texture, + width, + height, + format, + filter, + wrap, + data + ) } fn update_texture( @@ -204,77 +117,27 @@ impl TextureOps for NativeRenderBackend { height: u32, data: &[u8], ) -> GoudResult<()> { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => { - backend.update_texture(handle, x, y, width, height, data) - } - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.update_texture(handle, x, y, width, height, data), - } + dispatch!(self, update_texture, handle, x, y, width, height, data) } fn destroy_texture(&mut self, handle: TextureHandle) -> bool { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.destroy_texture(handle), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.destroy_texture(handle), - } + dispatch!(self, destroy_texture, handle) } fn is_texture_valid(&self, handle: TextureHandle) -> bool { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.is_texture_valid(handle), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.is_texture_valid(handle), - } + dispatch!(self, is_texture_valid, handle) } fn texture_size(&self, handle: TextureHandle) -> Option<(u32, u32)> { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.texture_size(handle), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.texture_size(handle), - } + dispatch!(self, texture_size, handle) } fn bind_texture(&mut self, handle: TextureHandle, unit: u32) -> GoudResult<()> { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.bind_texture(handle, unit), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.bind_texture(handle, unit), - } + dispatch!(self, bind_texture, handle, unit) } fn unbind_texture(&mut self, unit: u32) { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.unbind_texture(unit), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.unbind_texture(unit), - } + dispatch!(self, unbind_texture, unit) } fn create_compressed_texture( @@ -285,226 +148,86 @@ impl TextureOps for NativeRenderBackend { data: &[u8], mip_levels: u32, ) -> GoudResult { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => { - backend.create_compressed_texture(width, height, format, data, mip_levels) - } - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => { - backend.create_compressed_texture(width, height, format, data, mip_levels) - } - } + dispatch!( + self, + create_compressed_texture, + width, + height, + format, + data, + mip_levels + ) } } impl RenderTargetOps for NativeRenderBackend { fn create_render_target(&mut self, desc: &RenderTargetDesc) -> GoudResult { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.create_render_target(desc), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.create_render_target(desc), - } + dispatch!(self, create_render_target, desc) } fn destroy_render_target(&mut self, handle: RenderTargetHandle) -> bool { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.destroy_render_target(handle), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.destroy_render_target(handle), - } + dispatch!(self, destroy_render_target, handle) } fn is_render_target_valid(&self, handle: RenderTargetHandle) -> bool { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.is_render_target_valid(handle), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.is_render_target_valid(handle), - } + dispatch!(self, is_render_target_valid, handle) } fn bind_render_target(&mut self, handle: Option) -> GoudResult<()> { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.bind_render_target(handle), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.bind_render_target(handle), - } + dispatch!(self, bind_render_target, handle) } fn render_target_texture(&self, handle: RenderTargetHandle) -> Option { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.render_target_texture(handle), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.render_target_texture(handle), - } + dispatch!(self, render_target_texture, handle) } } impl ShaderOps for NativeRenderBackend { fn create_shader(&mut self, vertex_src: &str, fragment_src: &str) -> GoudResult { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.create_shader(vertex_src, fragment_src), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.create_shader(vertex_src, fragment_src), - } + dispatch!(self, create_shader, vertex_src, fragment_src) } fn destroy_shader(&mut self, handle: ShaderHandle) -> bool { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.destroy_shader(handle), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.destroy_shader(handle), - } + dispatch!(self, destroy_shader, handle) } fn is_shader_valid(&self, handle: ShaderHandle) -> bool { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.is_shader_valid(handle), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.is_shader_valid(handle), - } + dispatch!(self, is_shader_valid, handle) } fn bind_shader(&mut self, handle: ShaderHandle) -> GoudResult<()> { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.bind_shader(handle), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.bind_shader(handle), - } + dispatch!(self, bind_shader, handle) } fn unbind_shader(&mut self) { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.unbind_shader(), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.unbind_shader(), - } + dispatch!(self, unbind_shader) } fn get_uniform_location(&self, handle: ShaderHandle, name: &str) -> Option { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.get_uniform_location(handle, name), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.get_uniform_location(handle, name), - } + dispatch!(self, get_uniform_location, handle, name) } fn set_uniform_int(&mut self, location: i32, value: i32) { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.set_uniform_int(location, value), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.set_uniform_int(location, value), - } + dispatch!(self, set_uniform_int, location, value) } fn set_uniform_float(&mut self, location: i32, value: f32) { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.set_uniform_float(location, value), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.set_uniform_float(location, value), - } + dispatch!(self, set_uniform_float, location, value) } fn set_uniform_vec2(&mut self, location: i32, x: f32, y: f32) { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.set_uniform_vec2(location, x, y), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.set_uniform_vec2(location, x, y), - } + dispatch!(self, set_uniform_vec2, location, x, y) } fn set_uniform_vec3(&mut self, location: i32, x: f32, y: f32, z: f32) { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.set_uniform_vec3(location, x, y, z), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.set_uniform_vec3(location, x, y, z), - } + dispatch!(self, set_uniform_vec3, location, x, y, z) } fn set_uniform_vec4(&mut self, location: i32, x: f32, y: f32, z: f32, w: f32) { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.set_uniform_vec4(location, x, y, z, w), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.set_uniform_vec4(location, x, y, z, w), - } + dispatch!(self, set_uniform_vec4, location, x, y, z, w) } fn set_uniform_mat4(&mut self, location: i32, matrix: &[f32; 16]) { - match self { - #[cfg(feature = "legacy-glfw-opengl")] - Self::OpenGlLegacy(backend) => backend.set_uniform_mat4(location, matrix), - #[cfg(any( - all(feature = "native", feature = "wgpu-backend"), - feature = "xbox-gdk" - ))] - Self::Wgpu(backend) => backend.set_uniform_mat4(location, matrix), - } + dispatch!(self, set_uniform_mat4, location, matrix) } } diff --git a/goud_engine/src/libs/graphics/backend/wgpu_backend/init.rs b/goud_engine/src/libs/graphics/backend/wgpu_backend/init.rs index c24c6a40c..84e1841ce 100644 --- a/goud_engine/src/libs/graphics/backend/wgpu_backend/init.rs +++ b/goud_engine/src/libs/graphics/backend/wgpu_backend/init.rs @@ -322,283 +322,6 @@ impl WgpuBackend { self.bind_texture(handle, unit) } - /// Creates a new wgpu backend from a raw Xbox GDK window handle. - /// - /// Forces the DX12 backend since Xbox GDK uses DirectX 12 natively. - /// Blocks on async wgpu initialization via pollster. - #[cfg(feature = "xbox-gdk")] - pub fn new_from_raw_handle( - handle: Arc, - width: u32, - height: u32, - ) -> GoudResult { - pollster::block_on(Self::new_xbox_async(handle, width, height)) - } - - // TODO(xbox-gdk): Extract shared init logic (bind group layouts, fallback - // textures, surface config) into a common helper before promoting this PoC - // to production. Currently duplicates new_async to avoid refactoring the - // existing init path during the feasibility study. - #[cfg(feature = "xbox-gdk")] - async fn new_xbox_async( - handle: Arc, - width: u32, - height: u32, - ) -> GoudResult { - let mut instance_desc = wgpu::InstanceDescriptor::new_without_display_handle(); - instance_desc.backends = wgpu::Backends::DX12; - let instance = wgpu::Instance::new(instance_desc); - - let surface = instance - .create_surface(wgpu::SurfaceTarget::from(handle)) - .map_err(|e| GoudError::BackendNotSupported(format!("wgpu Xbox surface: {e}")))?; - - let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::HighPerformance, - compatible_surface: Some(&surface), - force_fallback_adapter: false, - }) - .await - .map_err(|e| { - GoudError::BackendNotSupported(format!("No suitable DX12 adapter: {e}")) - })?; - - let (device, queue): (wgpu::Device, wgpu::Queue) = adapter - .request_device(&wgpu::DeviceDescriptor { - label: Some("GoudEngine-Xbox"), - required_features: wgpu::Features::empty(), - required_limits: wgpu::Limits::default(), - ..Default::default() - }) - .await - .map_err(|e| GoudError::BackendNotSupported(format!("wgpu device: {e}")))?; - - let caps = surface.get_capabilities(&adapter); - let surface_format = caps - .formats - .iter() - .find(|f| !f.is_srgb()) - .copied() - .unwrap_or(caps.formats[0]); - let surface_supports_copy_src = caps.usages.contains(wgpu::TextureUsages::COPY_SRC); - - let w = width.max(1); - let h = height.max(1); - let surface_config = wgpu::SurfaceConfiguration { - usage: if surface_supports_copy_src { - wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC - } else { - wgpu::TextureUsages::RENDER_ATTACHMENT - }, - format: surface_format, - width: w, - height: h, - present_mode: wgpu::PresentMode::AutoVsync, - alpha_mode: caps.alpha_modes[0], - view_formats: vec![], - desired_maximum_frame_latency: 2, - }; - surface.configure(&device, &surface_config); - - let (depth_texture, depth_view) = Self::create_depth_texture(&device, w, h); - - let adapter_info = adapter.get_info(); - let limits = device.limits(); - - let info = BackendInfo { - name: "wgpu-xbox", - version: format!("{:?}", adapter_info.backend), - vendor: adapter_info.vendor.to_string(), - renderer: adapter_info.name.clone(), - capabilities: BackendCapabilities { - max_texture_units: limits.max_sampled_textures_per_shader_stage.min(16), - max_texture_size: limits.max_texture_dimension_2d, - max_vertex_attributes: limits.max_vertex_attributes, - max_uniform_buffer_size: limits.max_uniform_buffer_binding_size as u32, - supports_instancing: true, - supports_compute_shaders: true, - supports_geometry_shaders: false, - supports_tessellation: false, - supports_multisampling: true, - supports_anisotropic_filtering: true, - supports_bc_compression: false, - }, - shader_language: ShaderLanguage::Wgsl, - }; - - let uniform_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("uniform_bgl"), - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: true, - min_binding_size: None, - }, - count: None, - }], - }); - - let texture_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("texture_bgl"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - ], - }); - - let fallback_tex_bind_group = { - let tex = device.create_texture(&wgpu::TextureDescriptor { - label: Some("fallback-white-1x1"), - size: wgpu::Extent3d { - width: 1, - height: 1, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8UnormSrgb, - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - view_formats: &[], - }); - queue.write_texture( - wgpu::TexelCopyTextureInfo { - texture: &tex, - mip_level: 0, - origin: wgpu::Origin3d::ZERO, - aspect: wgpu::TextureAspect::All, - }, - &[255u8, 255, 255, 255], - wgpu::TexelCopyBufferLayout { - offset: 0, - bytes_per_row: Some(4), - rows_per_image: Some(1), - }, - wgpu::Extent3d { - width: 1, - height: 1, - depth_or_array_layers: 1, - }, - ); - let view = tex.create_view(&wgpu::TextureViewDescriptor::default()); - let sampler = device.create_sampler(&wgpu::SamplerDescriptor::default()); - device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("fallback-texture-bg"), - layout: &texture_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&sampler), - }, - ], - }) - }; - - let storage_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("storage_bgl"), - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: true }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }], - }); - - let fallback_storage_bind_group = { - let buf = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("fallback-storage"), - size: 64, - usage: wgpu::BufferUsages::STORAGE, - mapped_at_creation: false, - }); - device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("fallback-storage-bg"), - layout: &storage_bind_group_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: buf.as_entire_binding(), - }], - }) - }; - - Ok(Self { - info, - device, - queue, - surface, - surface_config, - surface_format, - surface_supports_copy_src, - depth_texture, - depth_view, - last_frame_readback: None, - clear_color: wgpu::Color::BLACK, - needs_clear: false, - current_frame: None, - draw_commands: Vec::new(), - depth_test_enabled: false, - depth_write_enabled: true, - depth_func: DepthFunc::Less, - blend_enabled: false, - blend_src: BlendFactor::One, - blend_dst: BlendFactor::Zero, - cull_enabled: false, - cull_face: CullFace::Back, - front_face_state: FrontFace::Ccw, - buffer_allocator: HandleAllocator::new(), - buffers: HashMap::new(), - pending_destroy_buffers: Vec::new(), - texture_allocator: HandleAllocator::new(), - textures: HashMap::new(), - shader_allocator: HandleAllocator::new(), - shaders: HashMap::new(), - bound_vertex_buffer: None, - bound_index_buffer: None, - bound_shader: None, - bound_textures: vec![None; MAX_TEXTURE_UNITS], - current_layout: None, - current_vertex_bindings: Vec::new(), - current_topology: PrimitiveTopology::Triangles, - pipeline_cache: HashMap::new(), - uniform_bind_group_layout, - texture_bind_group_layout, - storage_bind_group_layout, - fallback_tex_bind_group, - fallback_storage_bind_group, - bound_storage_buffer: None, - storage_bind_group_cache: HashMap::new(), - uniform_ring: Vec::with_capacity(UNIFORM_BUFFER_SIZE * 64), - }) - } - /// Resizes the surface and depth buffer. Call after window resize. pub fn resize(&mut self, width: u32, height: u32) { let w = width.max(1); diff --git a/goud_engine/src/libs/graphics/backend/wgpu_backend/mod.rs b/goud_engine/src/libs/graphics/backend/wgpu_backend/mod.rs index d71dd1bfd..c28e71b04 100644 --- a/goud_engine/src/libs/graphics/backend/wgpu_backend/mod.rs +++ b/goud_engine/src/libs/graphics/backend/wgpu_backend/mod.rs @@ -35,6 +35,8 @@ mod shader; mod texture; mod uniforms; #[cfg(feature = "xbox-gdk")] +mod xbox_init; +#[cfg(feature = "xbox-gdk")] pub(crate) mod xbox_surface; // Pull internal types into this module's namespace so submodules can `use super::TypeName`. diff --git a/goud_engine/src/libs/graphics/backend/wgpu_backend/xbox_init.rs b/goud_engine/src/libs/graphics/backend/wgpu_backend/xbox_init.rs new file mode 100644 index 000000000..0b91b301c --- /dev/null +++ b/goud_engine/src/libs/graphics/backend/wgpu_backend/xbox_init.rs @@ -0,0 +1,292 @@ +//! Xbox GDK wgpu initialization path. +//! +//! Separated from `init.rs` to stay within the 500-line file limit. +//! TODO(xbox-gdk): Extract shared init logic (bind group layouts, fallback +//! textures, surface config) into a common helper before promoting this PoC +//! to production. Currently duplicates `new_async` to avoid refactoring the +//! existing init path during the feasibility study. + +use super::{ + BackendCapabilities, BackendInfo, BlendFactor, CullFace, DepthFunc, FrontFace, HashMap, + PrimitiveTopology, ShaderLanguage, WgpuBackend, +}; +use crate::core::{ + error::{GoudError, GoudResult}, + handle::HandleAllocator, +}; +use std::sync::Arc; + +use super::init::{MAX_TEXTURE_UNITS, UNIFORM_BUFFER_SIZE}; + +impl WgpuBackend { + /// Creates a new wgpu backend from a raw Xbox GDK window handle. + /// + /// Forces the DX12 backend since Xbox GDK uses DirectX 12 natively. + /// Blocks on async wgpu initialization via pollster. + pub fn new_from_raw_handle( + handle: Arc, + width: u32, + height: u32, + ) -> GoudResult { + pollster::block_on(Self::new_xbox_async(handle, width, height)) + } + + async fn new_xbox_async( + handle: Arc, + width: u32, + height: u32, + ) -> GoudResult { + let mut instance_desc = wgpu::InstanceDescriptor::new_without_display_handle(); + instance_desc.backends = wgpu::Backends::DX12; + let instance = wgpu::Instance::new(instance_desc); + + let surface = instance + .create_surface(wgpu::SurfaceTarget::from(handle)) + .map_err(|e| GoudError::BackendNotSupported(format!("wgpu Xbox surface: {e}")))?; + + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .await + .map_err(|e| { + GoudError::BackendNotSupported(format!("No suitable DX12 adapter: {e}")) + })?; + + let (device, queue): (wgpu::Device, wgpu::Queue) = adapter + .request_device(&wgpu::DeviceDescriptor { + label: Some("GoudEngine-Xbox"), + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::default(), + ..Default::default() + }) + .await + .map_err(|e| GoudError::BackendNotSupported(format!("wgpu device: {e}")))?; + + let caps = surface.get_capabilities(&adapter); + let surface_format = caps + .formats + .iter() + .find(|f| !f.is_srgb()) + .copied() + .unwrap_or(caps.formats[0]); + let surface_supports_copy_src = caps.usages.contains(wgpu::TextureUsages::COPY_SRC); + + let w = width.max(1); + let h = height.max(1); + let surface_config = wgpu::SurfaceConfiguration { + usage: if surface_supports_copy_src { + wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC + } else { + wgpu::TextureUsages::RENDER_ATTACHMENT + }, + format: surface_format, + width: w, + height: h, + present_mode: wgpu::PresentMode::AutoVsync, + alpha_mode: caps.alpha_modes[0], + view_formats: vec![], + desired_maximum_frame_latency: 2, + }; + surface.configure(&device, &surface_config); + + let (depth_texture, depth_view) = Self::create_depth_texture(&device, w, h); + + let adapter_info = adapter.get_info(); + let limits = device.limits(); + + let info = BackendInfo { + name: "wgpu-xbox", + version: format!("{:?}", adapter_info.backend), + vendor: adapter_info.vendor.to_string(), + renderer: adapter_info.name.clone(), + capabilities: BackendCapabilities { + max_texture_units: limits.max_sampled_textures_per_shader_stage.min(16), + max_texture_size: limits.max_texture_dimension_2d, + max_vertex_attributes: limits.max_vertex_attributes, + max_uniform_buffer_size: limits.max_uniform_buffer_binding_size as u32, + supports_instancing: true, + supports_compute_shaders: true, + supports_geometry_shaders: false, + supports_tessellation: false, + supports_multisampling: true, + supports_anisotropic_filtering: true, + supports_bc_compression: false, + }, + shader_language: ShaderLanguage::Wgsl, + }; + + let uniform_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("uniform_bgl"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: true, + min_binding_size: None, + }, + count: None, + }], + }); + + let texture_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("texture_bgl"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + }); + + let fallback_tex_bind_group = { + let tex = device.create_texture(&wgpu::TextureDescriptor { + label: Some("fallback-white-1x1"), + size: wgpu::Extent3d { + width: 1, + height: 1, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }); + queue.write_texture( + wgpu::TexelCopyTextureInfo { + texture: &tex, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + &[255u8, 255, 255, 255], + wgpu::TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(4), + rows_per_image: Some(1), + }, + wgpu::Extent3d { + width: 1, + height: 1, + depth_or_array_layers: 1, + }, + ); + let view = tex.create_view(&wgpu::TextureViewDescriptor::default()); + let sampler = device.create_sampler(&wgpu::SamplerDescriptor::default()); + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("fallback-texture-bg"), + layout: &texture_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + ], + }) + }; + + let storage_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("storage_bgl"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + }); + + let fallback_storage_bind_group = { + let buf = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("fallback-storage"), + size: 64, + usage: wgpu::BufferUsages::STORAGE, + mapped_at_creation: false, + }); + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("fallback-storage-bg"), + layout: &storage_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: buf.as_entire_binding(), + }], + }) + }; + + Ok(Self { + info, + device, + queue, + surface, + surface_config, + surface_format, + surface_supports_copy_src, + depth_texture, + depth_view, + last_frame_readback: None, + clear_color: wgpu::Color::BLACK, + needs_clear: false, + current_frame: None, + draw_commands: Vec::new(), + depth_test_enabled: false, + depth_write_enabled: true, + depth_func: DepthFunc::Less, + blend_enabled: false, + blend_src: BlendFactor::One, + blend_dst: BlendFactor::Zero, + cull_enabled: false, + cull_face: CullFace::Back, + front_face_state: FrontFace::Ccw, + buffer_allocator: HandleAllocator::new(), + buffers: HashMap::new(), + pending_destroy_buffers: Vec::new(), + texture_allocator: HandleAllocator::new(), + textures: HashMap::new(), + shader_allocator: HandleAllocator::new(), + shaders: HashMap::new(), + bound_vertex_buffer: None, + bound_index_buffer: None, + bound_shader: None, + bound_textures: vec![None; MAX_TEXTURE_UNITS], + current_layout: None, + current_vertex_bindings: Vec::new(), + current_topology: PrimitiveTopology::Triangles, + pipeline_cache: HashMap::new(), + uniform_bind_group_layout, + texture_bind_group_layout, + storage_bind_group_layout, + fallback_tex_bind_group, + fallback_storage_bind_group, + bound_storage_buffer: None, + storage_bind_group_cache: HashMap::new(), + uniform_ring: Vec::with_capacity(UNIFORM_BUFFER_SIZE * 64), + }) + } +}