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/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..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,12 +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(all(feature = "native", feature = "wgpu-backend"))] - Self::Wgpu(backend) => backend.create_buffer(buffer_type, usage, data), - } + dispatch!(self, create_buffer, buffer_type, usage, data) } fn update_buffer( @@ -30,75 +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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - Self::Wgpu(backend) => backend.create_storage_buffer(data), - } + dispatch!(self, create_storage_buffer, data) } fn update_storage_buffer( @@ -107,30 +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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - Self::Wgpu(backend) => backend.unbind_storage_buffer(), - } + dispatch!(self, unbind_storage_buffer) } } @@ -144,16 +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(all(feature = "native", feature = "wgpu-backend"))] - 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( @@ -165,59 +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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - Self::Wgpu(backend) => backend.unbind_texture(unit), - } + dispatch!(self, unbind_texture, unit) } fn create_compressed_texture( @@ -228,172 +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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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(all(feature = "native", feature = "wgpu-backend"))] - 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/mod.rs b/goud_engine/src/libs/graphics/backend/wgpu_backend/mod.rs index e07bdb883..c28e71b04 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,10 @@ mod resources; 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`. use resources::{ 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), + }) + } +} 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..f2696bd00 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(all(feature = "xbox-gdk", target_env = "msvc"))] + { + 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..feb156a83 --- /dev/null +++ b/goud_engine/src/libs/platform/xbox_gdk_platform.rs @@ -0,0 +1,129 @@ +//! 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. +#[allow(dead_code)] // PoC stub: struct fields used once GDK SDK is linked. +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. + #[allow(dead_code)] // Called from native_runtime once GDK is linked. + 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 + } +} + +#[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")); + } +}