use std::sync::Arc;
#[cfg(not(target_arch = "wasm32"))]
use once_cell::sync::Lazy;
#[cfg(target_arch = "wasm32")]
use std::sync::OnceLock;
#[cfg(target_arch = "wasm32")]
#[repr(transparent)]
struct WasmSyncCell<T>(T);
#[cfg(target_arch = "wasm32")]
unsafe impl<T> Send for WasmSyncCell<T> {}
#[cfg(target_arch = "wasm32")]
unsafe impl<T> Sync for WasmSyncCell<T> {}
#[cfg(target_arch = "wasm32")]
impl<T> std::ops::Deref for WasmSyncCell<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
#[cfg(not(target_arch = "wasm32"))]
pub static GPU_CONTEXT: Lazy<GpuContext> = Lazy::new(|| {
pollster::block_on(GpuContext::init(None)).expect("Failed to initialize GPU context")
});
#[cfg(not(target_arch = "wasm32"))]
static GPU_CONTEXT_RESULT: Lazy<Result<GpuContext, String>> =
Lazy::new(|| pollster::block_on(GpuContext::init(None)));
#[cfg(target_arch = "wasm32")]
static GPU_CONTEXT_CELL: OnceLock<WasmSyncCell<GpuContext>> = OnceLock::new();
pub struct GpuContext {
device: Arc<wgpu::Device>,
queue: Arc<wgpu::Queue>,
#[cfg(target_arch = "wasm32")]
adapter: Arc<wgpu::Adapter>,
#[cfg(target_arch = "wasm32")]
surface: Option<wgpu::Surface<'static>>,
}
impl GpuContext {
async fn init(
#[allow(unused_variables)] canvas_selector: Option<&str>,
) -> Result<Self, String> {
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
..Default::default()
});
#[cfg(target_arch = "wasm32")]
let surface = if let Some(selector) = canvas_selector {
use wasm_bindgen::JsCast;
let window = web_sys::window().ok_or("no `window` in this context")?;
let document = window.document().ok_or("no `document` in this context")?;
let canvas = document
.query_selector(selector)
.map_err(|e| format!("query_selector failed: {:?}", e))?
.ok_or_else(|| format!("no element matched canvas selector `{}`", selector))?
.dyn_into::<web_sys::HtmlCanvasElement>()
.map_err(|_| format!("element matching `{}` is not an HTMLCanvasElement", selector))?;
let target = wgpu::SurfaceTarget::Canvas(canvas);
Some(
instance
.create_surface(target)
.map_err(|e| format!("create_surface from canvas failed: {}", e))?,
)
} else {
None
};
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
#[cfg(target_arch = "wasm32")]
compatible_surface: surface.as_ref(),
#[cfg(not(target_arch = "wasm32"))]
compatible_surface: None,
force_fallback_adapter: false,
})
.await
.ok_or("No GPU adapter found")?;
let info = adapter.get_info();
#[cfg(not(target_arch = "wasm32"))]
eprintln!(
"[gpu] backend={:?} device='{}' vendor={} device_type={:?}",
info.backend, info.name, info.vendor, info.device_type
);
#[cfg(target_arch = "wasm32")]
web_sys::console::log_1(
&format!(
"[gpu] backend={:?} device='{}' vendor={} device_type={:?}",
info.backend, info.name, info.vendor, info.device_type
)
.into(),
);
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: Some("Reflow Shared GPU"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
memory_hints: wgpu::MemoryHints::default(),
},
None,
)
.await
.map_err(|e| format!("GPU device request failed: {}", e))?;
Ok(Self {
device: Arc::new(device),
queue: Arc::new(queue),
#[cfg(target_arch = "wasm32")]
adapter: Arc::new(adapter),
#[cfg(target_arch = "wasm32")]
surface,
})
}
#[cfg(target_arch = "wasm32")]
pub fn surface(&self) -> Option<&wgpu::Surface<'static>> {
self.surface.as_ref()
}
#[cfg(target_arch = "wasm32")]
pub fn configure_surface(&self, width: u32, height: u32) -> Result<(), String> {
let surface = self
.surface
.as_ref()
.ok_or("GPU context was initialized without a canvas")?;
let mut config = surface
.get_default_config(&self.adapter, width.max(1), height.max(1))
.ok_or("surface incompatible with adapter")?;
let caps = surface.get_capabilities(&self.adapter);
if let Some(srgb) = caps.formats.iter().copied().find(|f| f.is_srgb()) {
config.format = srgb;
}
surface.configure(&self.device, &config);
Ok(())
}
pub fn device(&self) -> &wgpu::Device {
&self.device
}
pub fn queue(&self) -> &wgpu::Queue {
&self.queue
}
pub fn submit_and_poll(&self, command_buffer: wgpu::CommandBuffer) {
self.queue.submit(std::iter::once(command_buffer));
}
}
#[cfg(target_arch = "wasm32")]
pub async fn init_gpu_context(canvas_selector: Option<&str>) -> Result<(), String> {
if GPU_CONTEXT_CELL.get().is_some() {
return Ok(());
}
let ctx = GpuContext::init(canvas_selector).await?;
let _ = GPU_CONTEXT_CELL.set(WasmSyncCell(ctx));
Ok(())
}
#[cfg(not(target_arch = "wasm32"))]
pub async fn init_gpu_context(_canvas_selector: Option<&str>) -> Result<(), String> {
Lazy::force(&GPU_CONTEXT);
Ok(())
}
pub fn try_gpu_context() -> Result<&'static GpuContext, String> {
#[cfg(not(target_arch = "wasm32"))]
{
match &*GPU_CONTEXT_RESULT {
Ok(ctx) => Ok(ctx),
Err(err) => Err(err.clone()),
}
}
#[cfg(target_arch = "wasm32")]
{
GPU_CONTEXT_CELL
.get()
.map(|cell| &**cell)
.ok_or_else(|| {
"GPU context not initialized — call `initGpuContext()` (or \
reflow_components::gpu::init_gpu_context().await) before \
constructing GPU actors"
.to_string()
})
}
}