Skip to main content

truce_gpu/
platform.rs

1//! Platform window bridging for baseview / wgpu.
2//!
3//! `WgpuBackend::from_window` consumes a `baseview::Window`; that
4//! window exposes raw-window-handle 0.5 handles but wgpu wants 0.6,
5//! so the bridge re-encodes per platform here. Lives in `truce-gpu`
6//! so the wgpu pipeline crate is self-contained; the per-OS
7//! HWND/NSView lookups + DPI queries live next door in
8//! `truce_gui::platform`.
9
10#[cfg(not(target_os = "ios"))]
11#[allow(unused_imports)]
12use raw_window_handle::{
13    HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle as RwhRawDisplayHandle,
14    RawWindowHandle as RwhRawWindowHandle,
15};
16
17#[cfg(target_os = "windows")]
18fn current_module_hinstance() -> Option<std::num::NonZeroIsize> {
19    unsafe extern "system" {
20        fn GetModuleHandleW(lpModuleName: *const u16) -> isize;
21    }
22    // SAFETY: `GetModuleHandleW(NULL)` is documented to return the running
23    // EXE's HMODULE without acquiring a refcount; no threading or aliasing
24    // concerns. Returns 0 only in pathological cases (kernel32 missing).
25    let hmodule = unsafe { GetModuleHandleW(std::ptr::null()) };
26    std::num::NonZeroIsize::new(hmodule)
27}
28
29/// wgpu backends for an editor surface embedded in a host-owned
30/// child window. Mirror of `truce_gui::platform::editor_wgpu_backends`
31/// (truce-gpu can't depend on truce-gui without a dep cycle). Windows
32/// is DX12 (the only backend feature compiled in on Windows), macOS
33/// is Metal, Linux keeps `PRIMARY`. Keep the two copies in sync.
34#[must_use]
35pub fn editor_wgpu_backends() -> wgpu::Backends {
36    #[cfg(target_os = "windows")]
37    {
38        wgpu::Backends::DX12
39    }
40    #[cfg(target_os = "macos")]
41    {
42        wgpu::Backends::METAL
43    }
44    #[cfg(not(any(target_os = "windows", target_os = "macos")))]
45    {
46        wgpu::Backends::PRIMARY
47    }
48}
49
50/// `wgpu::InstanceDescriptor` for editor surfaces, pinning the DX12
51/// shader compiler to **FXC**. Mirror of
52/// `truce_gui::platform::editor_instance_descriptor` - see it for why
53/// the wgpu-29 DX12 default (dynamic DXC) breaks inside Pro Tools.
54/// Keep the two copies in sync.
55#[must_use]
56pub fn editor_instance_descriptor() -> wgpu::InstanceDescriptor {
57    let mut desc = wgpu::InstanceDescriptor::new_without_display_handle();
58    desc.backends = editor_wgpu_backends();
59    desc.backend_options.dx12.shader_compiler = wgpu::Dx12Compiler::Fxc;
60    desc
61}
62
63/// Bridge a baseview raw-window-handle 0.5 to a wgpu-compatible
64/// `SurfaceTargetUnsafe` using rwh 0.6 types.
65///
66/// # Safety
67/// The window handle must be valid for the lifetime of the returned surface.
68#[cfg(not(target_os = "ios"))]
69#[must_use]
70pub unsafe fn create_wgpu_surface(
71    instance: &wgpu::Instance,
72    window: &baseview::Window,
73) -> Option<wgpu::Surface<'static>> {
74    unsafe {
75        let rwh = window.raw_window_handle();
76        let surface_target = match rwh {
77            #[cfg(target_os = "macos")]
78            RwhRawWindowHandle::AppKit(handle) => {
79                let ns_view = handle.ns_view;
80                if ns_view.is_null() {
81                    return None;
82                }
83                let rwh6_window = wgpu::rwh::RawWindowHandle::AppKit(
84                    wgpu::rwh::AppKitWindowHandle::new(std::ptr::NonNull::new(ns_view)?),
85                );
86                let rwh6_display =
87                    wgpu::rwh::RawDisplayHandle::AppKit(wgpu::rwh::AppKitDisplayHandle::new());
88                wgpu::SurfaceTargetUnsafe::RawHandle {
89                    raw_display_handle: Some(rwh6_display),
90                    raw_window_handle: rwh6_window,
91                }
92            }
93            #[cfg(target_os = "windows")]
94            RwhRawWindowHandle::Win32(handle) => {
95                let hwnd = handle.hwnd;
96                if hwnd.is_null() {
97                    return None;
98                }
99                let mut win32 =
100                    wgpu::rwh::Win32WindowHandle::new(std::num::NonZeroIsize::new(hwnd as isize)?);
101                // wgpu's Vulkan backend requires `hinstance` to be set
102                // (`vkCreateWin32SurfaceKHR` rejects a null HINSTANCE).
103                // baseview leaves the rwh 0.5 `hinstance` field at null,
104                // so populate it here with the running module's HMODULE.
105                win32.hinstance = current_module_hinstance();
106                let rwh6_window = wgpu::rwh::RawWindowHandle::Win32(win32);
107                let rwh6_display =
108                    wgpu::rwh::RawDisplayHandle::Windows(wgpu::rwh::WindowsDisplayHandle::new());
109                wgpu::SurfaceTargetUnsafe::RawHandle {
110                    raw_display_handle: Some(rwh6_display),
111                    raw_window_handle: rwh6_window,
112                }
113            }
114            #[cfg(target_os = "linux")]
115            RwhRawWindowHandle::Xlib(handle) => {
116                let RwhRawDisplayHandle::Xlib(display_handle) = window.raw_display_handle() else {
117                    return None;
118                };
119                let display_ptr = std::ptr::NonNull::new(display_handle.display);
120                let rwh6_window = wgpu::rwh::RawWindowHandle::Xlib(
121                    wgpu::rwh::XlibWindowHandle::new(handle.window),
122                );
123                let rwh6_display = wgpu::rwh::RawDisplayHandle::Xlib(
124                    wgpu::rwh::XlibDisplayHandle::new(display_ptr, display_handle.screen),
125                );
126                wgpu::SurfaceTargetUnsafe::RawHandle {
127                    raw_display_handle: Some(rwh6_display),
128                    raw_window_handle: rwh6_window,
129                }
130            }
131            _ => return None,
132        };
133
134        instance.create_surface_unsafe(surface_target).ok()
135    }
136}