Skip to main content

oxiui_compute_wgpu/
lib.rs

1//! # `oxiui-compute-wgpu`
2//!
3//! Pure-Rust wgpu GPU-compute abstraction for the COOLJAPAN ecosystem.
4//!
5//! This crate consolidates the repeated `Instance → Adapter → Device → Queue`
6//! initialisation boilerplate that `oxiaero-cfd`, `oxiaero-mcdc`, and similar
7//! crates each duplicated for pure GPU compute workloads (sparse linear
8//! solvers, Lattice-Boltzmann, Monte-Carlo simulations, …).
9//!
10//! ## Quick start
11//!
12//! ```rust
13//! use oxiui_compute_wgpu::{bytemuck, compute_pipeline, read_back, storage_buffer_init, wgpu, ComputeContext};
14//!
15//! let Some(ctx) = ComputeContext::try_new() else {
16//!     return; // no GPU — skip gracefully
17//! };
18//!
19//! let input: Vec<f32> = vec![1.0, 2.0, 3.0, 4.0];
20//! let buffer = storage_buffer_init(&ctx.device, "values", bytemuck::cast_slice(&input));
21//!
22//! const SHADER: &str = r#"
23//!     @group(0) @binding(0) var<storage, read_write> data: array<f32>;
24//!     @compute @workgroup_size(64)
25//!     fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
26//!         if gid.x < arrayLength(&data) {
27//!             data[gid.x] = data[gid.x] * 2.0;
28//!         }
29//!     }
30//! "#;
31//! let pipeline = compute_pipeline(&ctx.device, SHADER, "main");
32//!
33//! let bind_group = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor {
34//!     label: Some("values-bind"),
35//!     layout: &pipeline.get_bind_group_layout(0),
36//!     entries: &[wgpu::BindGroupEntry {
37//!         binding: 0,
38//!         resource: buffer.as_entire_binding(),
39//!     }],
40//! });
41//!
42//! let mut encoder = ctx.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
43//! {
44//!     let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
45//!         label: None,
46//!         timestamp_writes: None,
47//!     });
48//!     pass.set_pipeline(&pipeline);
49//!     pass.set_bind_group(0, &bind_group, &[]);
50//!     pass.dispatch_workgroups((input.len() as u32 + 63) / 64, 1, 1);
51//! }
52//! ctx.queue.submit(std::iter::once(encoder.finish()));
53//!
54//! let output: Vec<f32> = read_back(&ctx.device, &ctx.queue, &buffer, input.len());
55//! assert_eq!(output, vec![2.0, 4.0, 6.0, 8.0]);
56//! ```
57//!
58//! ## Module structure
59//!
60//! | Module | Contents |
61//! |--------|----------|
62//! | [`context`] | [`ComputeContext`] — headless `Device` + `Queue` init, multi-queue, `from_device` |
63//! | [`buffer`]  | Storage / uniform / staging buffer helpers + [`read_back`] |
64//! | [`pipeline`] | [`compute_pipeline`] builder |
65//! | [`error`]   | [`ComputeError`] type |
66//! | [`wgsl`]    | WGSL preprocessor, validation, and built-in compute kernels |
67//! | [`dispatch`] | [`Dispatcher`] — high-level GPU compute helpers |
68//! | [`integration`] | Bridges to `oxiui-render-soft`, `oxiui-render-wgpu`, `oxiui-text` |
69//! | `hot_reload` | WGSL hot-reload via `notify` (behind the `hot-reload` feature) |
70//!
71//! ## Dependency re-exports
72//!
73//! `oxiui-compute-wgpu` re-exports [`wgpu`], [`bytemuck`], and [`pollster`] so
74//! that consumers need only a single dependency declaration in their
75//! `Cargo.toml`.
76//!
77//! ## Feature flags
78//!
79//! | Feature | Description |
80//! |---------|-------------|
81//! | `tracing` | Adds `#[tracing::instrument]` spans to key functions for span-based profiling. |
82//! | `hot-reload` | Adds `hot_reload::ShaderWatcher` for live WGSL file watching via `notify`. |
83
84pub mod buffer;
85pub mod context;
86pub mod dispatch;
87pub mod error;
88pub mod integration;
89pub mod pipeline;
90pub mod wgsl;
91
92/// Live WGSL hot-reload via the `notify` file watcher.
93///
94/// Enable with `features = ["hot-reload"]` in `Cargo.toml`.
95#[cfg(feature = "hot-reload")]
96pub mod hot_reload;
97
98// ── Flat re-exports ────────────────────────────────────────────────────────────
99
100// From buffer
101pub use buffer::{
102    mapped_storage_init, read_back, read_back_async, read_back_range, staging_buffer,
103    storage_buffer_init, uniform_buffer, BufferPool, SubAllocator, SubRegion, TypedBuffer,
104};
105
106// From context
107pub use context::{ComputeContext, ContextBuilder};
108
109// From dispatch
110pub use dispatch::Dispatcher;
111
112// From error
113pub use error::ComputeError;
114
115// From pipeline
116pub use pipeline::{
117    checked_compute_pipeline, compute_pipeline, dispatch_1d, dispatch_2d, dispatch_3d,
118    encode_indirect_dispatch, supports_immediates, validate_immediates, DispatchBuilder,
119    DispatchResult, PipelineCache, MAX_WORKGROUPS,
120};
121
122// From wgsl
123pub use wgsl::{
124    preprocess, validate, WgslDiagnostic, WgslError, SHADER_BITONIC_SORT, SHADER_HISTOGRAM,
125    SHADER_MAP_F32_TEMPLATE, SHADER_MATMUL, SHADER_PREFIX_SUM, SHADER_REDUCTION_SUM,
126    SHADER_SPH_DENSITY, SHADER_ZIP_MAP_F32_TEMPLATE,
127};
128
129// ── Underlying crate re-exports ───────────────────────────────────────────────
130
131/// Re-export of [`wgpu`] so consumers need only declare `oxiui-compute-wgpu`.
132pub use wgpu;
133
134/// Re-export of [`bytemuck`] for `Pod`/`Zeroable` derives and casting helpers.
135pub use bytemuck;
136
137/// Re-export of [`pollster`] for blocking on async wgpu operations.
138pub use pollster;
139
140// ── Top-level tests ───────────────────────────────────────────────────────────
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn compute_context_try_new_does_not_panic() {
148        // Gracefully skips if no GPU adapter — must never panic.
149        let _ = ComputeContext::try_new();
150    }
151
152    #[test]
153    fn compute_context_new_returns_result() {
154        match ComputeContext::new() {
155            Ok(_ctx) => { /* GPU available — context created successfully */ }
156            Err(ComputeError::NoAdapter) => {
157                // No GPU on this host (CI, headless VM) — acceptable skip.
158            }
159            Err(e) => panic!("unexpected error: {e}"),
160        }
161    }
162}