Skip to main content

wasdk_wgpu/
lib.rs

1//! A [`wgpu`] backend over the engine's `wasi:webgpu`, for a program built as a component.
2//!
3//! wgpu's `custom` feature lets an external crate implement its dispatch traits. This crate
4//! implements them over the [`wasdk::webgpu`] bindings, so anything written against the `wgpu`
5//! API (valo, and the Waft UI framework through it) runs in a component unchanged.
6//! `wasi:webgpu` mirrors the WebGPU specification, so the object model mirrors wgpu's own
7//! browser backend: each wgpu object owns the wasi resource behind it and drops it with itself,
8//! and descriptors borrow those resources from their owners for the duration of a call.
9//!
10//! ```ignore
11//! let instance = wasdk_wgpu::instance();
12//! let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions::default()).await?;
13//! let (device, queue) = adapter.request_device(&wgpu::DeviceDescriptor::default()).await?;
14//!
15//! // A view is the window a surface is made for, shared by the program and the surface:
16//! let view = wasdk_wgpu::SharedView::new(view);
17//! let surface = instance.create_surface(view.clone())?;
18//! let metrics = view.metrics();
19//! let config = surface.get_default_config(&adapter, metrics.width, metrics.height).unwrap();
20//! surface.configure(&device, &config);
21//!
22//! // Each frame:
23//! let wgpu::CurrentSurfaceTexture::Success(frame) = surface.get_current_texture() else { .. };
24//! // ...draw into `frame.texture` and submit...
25//! view.set_layers(&layers); // a program with composition layers
26//! queue.present(frame); // presents the view
27//!
28//! // A video frame's hardware buffer is sampled through a texture imported of it on the device:
29//! let video_texture = wasdk_wgpu::import_texture(&device, &video_frame.hardware_buffer());
30//! ```
31//!
32//! # Drawing on a view
33//!
34//! A view is drawn on through wgpu's surface, the one way this crate draws on one, as a browser's
35//! canvas is (`surface`): a
36//! [`SharedView`] is the window `Instance::create_surface` takes, its handle a
37//! `WebCanvasWindowHandle` pointing at it, so a framework's renderer that makes its surface from
38//! a window's handles runs unchanged. The surface offers the engine's canvas format, which the
39//! canvas is drawn in, `PresentMode::Fifo`, opaque and premultiplied alpha, and drawing into,
40//! sampling and copying its textures; `configure` sets the view's canvas to the size configured,
41//! which its textures are from then on whatever the view's own size, and configures a canvas
42//! context of the view's against the device, its view formats passed through, of which the
43//! engine serves the format's sRGB counterpart. `Queue::present` presents the view, with the layers it holds beneath it
44//! (`view.set_layers`); a frame dropped without a present is not presented.
45//!
46//! # What the boundary cannot express
47//!
48//! `wasi:webgpu@0.3.0-rc.2` is the WebGPU API and nothing beyond it. Everything wgpu offers
49//! past WebGPU panics at the call with a message naming the gap, or returns the error where the
50//! wgpu signature has one. The complete list:
51//!
52//! - **Surfaces on anything but a view.** `Instance::create_surface` with a window handle that is
53//!   not a [`SharedView`]'s, or a target that is not a window handle: wgpu gives a backend of its
54//!   own no way to make the `CreateSurfaceError` it would answer. A surface's present modes but
55//!   `Fifo` and the automatic ones, its alpha modes `PostMultiplied` and `Inherit`, and colour
56//!   spaces a WebGPU canvas has no name for (`ExtendedSrgbLinear`, `Bt2100Pq`, `Bt2100Hlg`).
57//! - **`as_hal` and pipeline caches.** There is no hal underneath; `create_pipeline_cache` and
58//!   a `cache` in a pipeline descriptor.
59//! - **Ray tracing.** BLAS/TLAS creation, compaction and builds,
60//!   `BindingType::AccelerationStructure`, `BufferUsages::BLAS_INPUT | TLAS_INPUT`.
61//! - **External textures.** `create_external_texture`, `BindingType::ExternalTexture`.
62//! - **Shader sources other than WGSL.** SPIR-V, GLSL, naga IR, and every passthrough language
63//!   but WGSL.
64//! - **Binding arrays.** `BindGroupLayoutEntry::count`, `BindingResource::*Array`.
65//! - **Transient attachments.** `TextureUsages::TRANSIENT_ATTACHMENT` is a hint the host
66//!   rejects, so it is stripped, as wgpu's browser backend strips it.
67//! - **Browser-only entry points.** `Queue::copy_external_image_to_texture` and a mapped
68//!   range's `as_uint8array`, which exist on wgpu's API only for the web.
69//! - **Mesh shaders.** `create_mesh_pipeline`, `draw_mesh_tasks*`.
70//! - **`multi_draw_*_count`.** Plain `multi_draw_indirect` is unrolled into draws, as on the web.
71//! - **Timestamps inside encoders and passes** (`write_timestamp`): WebGPU writes them only at
72//!   pass boundaries (`timestamp_writes`). **Pipeline statistics queries.**
73//! - **`clear_texture`.**
74//! - **Values without a WebGPU name:** texture formats `R64Uint`, `NV12`, `P010` and HDR ASTC;
75//!   `Float64*` vertex formats; `TextureUsages::STORAGE_ATOMIC`; `StorageTextureAccess::Atomic`;
76//!   `AddressMode::ClampToBorder` and `SamplerDescriptor::border_color`; `TextureAspect::Plane*`;
77//!   `PolygonMode::Line | Point` and conservative rasterization; multiview; shader stages beyond
78//!   vertex, fragment and compute.
79//! - **Features.** A required feature without a WebGPU name (wgpu-native's) fails
80//!   `request_device` with a [`wgpu::RequestDeviceError`]. wasi features without a wgpu bit
81//!   (`core-features-and-limits`, `texture-formats-tier1/2`, `texture-component-swizzle`) are
82//!   not reported.
83//!
84//! Hints with no wasi counterpart and no effect on results are accepted and dropped:
85//! `DeviceDescriptor::{trace, memory_hints, experimental_features}`,
86//! `RequestAdapterOptions::apply_limit_buckets`, `ShaderRuntimeChecks`,
87//! `PipelineCompilationOptions::zero_initialize_workgroup_memory` (WebGPU always zeroes) and
88//! `start/stop_graphics_debugger_capture`. `transition_resources` is a no-op because WebGPU
89//! tracks resource state itself.
90//!
91//! # Where the semantics differ from wgpu-core
92//!
93//! - **Mapping copies.** The boundary shares no memory: `get_mapped_range` copies the range into
94//!   the component (`get-mapped-range-get-with-copy`), and a mutable view is copied back when it
95//!   is dropped (`get-mapped-range-set-with-copy`); drop every `BufferViewMut` before `unmap`.
96//! - **Callbacks fire when the program yields.** `request_adapter`, `request_device`,
97//!   `pop_error_scope` and `get_compilation_info` are wasi async calls awaited by the futures
98//!   wgpu returns. `map_async`, `on_submitted_work_done`, `set_device_lost_callback` and
99//!   `on_uncaptured_error` are spawned onto the program's executor and run when it next
100//!   `.await`s; `Device::poll` cannot wait for them and returns `PollStatus::QueueEmpty` at once,
101//!   exactly as wgpu's web backend does.
102//! - **Submission indices** count submissions per queue; `PollType::Wait` cannot wait on one.
103//! - **A frame on a lost device** is handed out as good: the canvas hands out an invalid texture
104//!   it cannot tell from a good one, and the loss comes through the device's lost callback.
105//! - **`AdapterInfo`** reports `Backend::BrowserWebGpu` (the WebGPU API, not a browser) and zero
106//!   vendor/device ids: the wasi info is strings, which land in `name` and `driver_info`.
107//! - **`LoadOp::DontCare`** clears, as on the web.
108//! - **`Queue::write_buffer_with`** returns `None` for a write that fails validation
109//!   (alignment, bounds, `COPY_DST`), as wgpu documents, instead of raising a validation error.
110//! - **Validation the boundary reports becomes a panic.** Six calls answer with an error the
111//!   wgpu signature has nowhere to put: `Queue::write_buffer`, `Buffer::unmap`,
112//!   `Buffer::get_mapped_range`, writing a mapped range back, `Device::create_query_set` and
113//!   `set_bind_group`. Each panics with the engine's message rather than reaching an error scope,
114//!   where wgpu-core would raise a validation error.
115
116/// Recovers this backend's object from the wgpu API type or dispatch wrapper carrying it; an
117/// object from another wgpu backend is a programming error.
118macro_rules! downcast {
119    ($ty:ident: api $api:ty) => {
120        impl $ty {
121            pub(crate) fn of(api: &$api) -> &Self {
122                api.as_custom::<Self>().expect(concat!(
123                    "wasdk-wgpu: a ",
124                    stringify!($api),
125                    " from another wgpu backend"
126                ))
127            }
128        }
129    };
130    ($ty:ident: dispatch $dispatch:ty) => {
131        impl $ty {
132            pub(crate) fn of_dispatch(dispatch: &$dispatch) -> &Self {
133                dispatch.as_custom::<Self>().expect(concat!(
134                    "wasdk-wgpu: a ",
135                    stringify!($dispatch),
136                    " from another wgpu backend"
137                ))
138            }
139        }
140    };
141    ($ty:ident: api $api:ty, dispatch $dispatch:ty) => {
142        downcast!($ty: api $api);
143        downcast!($ty: dispatch $dispatch);
144    };
145}
146
147mod adapter;
148mod binding;
149mod buffer;
150mod command;
151pub mod convert;
152mod device;
153mod gap;
154mod instance;
155mod pass;
156mod pipeline;
157mod query;
158mod queue;
159mod runtime;
160mod sampler;
161mod shader;
162mod surface;
163mod texture;
164
165use wasdk::gpu_interop;
166use wasdk::hardware_buffers::HardwareBuffer;
167use wgpu::custom::InstanceInterface;
168
169pub use device::WasdkDevice;
170pub use instance::WasdkInstance;
171pub use surface::{SharedView, WasdkSurface, WasdkSurfaceOutputDetail};
172pub use texture::WasdkTexture;
173
174/// The wgpu instance over the engine's WebGPU. `wgpu::Instance::new` on this target has no
175/// backend; this is the one to use.
176pub fn instance() -> wgpu::Instance {
177    let descriptor = wgpu::InstanceDescriptor::new_without_display_handle();
178    wgpu::Instance::from_custom(<WasdkInstance as InstanceInterface>::new(descriptor))
179}
180
181/// `buffer` imported as a texture on `device`, over its memory, as a `wgpu::Texture`
182/// (`wasdk:platform/gpu-interop`): a buffer the program allocated, an image the hardware codecs
183/// decoded or a video frame's, which the texture holds, with its frame, until it is dropped. Its
184/// size and format are the buffer's, and its usages follow from the buffer's usage.
185pub fn import_texture(device: &wgpu::Device, buffer: &HardwareBuffer) -> wgpu::Texture {
186    let texture = gpu_interop::import_texture(WasdkDevice::of(device).gpu_device(), buffer);
187    let descriptor = wgpu::TextureDescriptor {
188        label: Some("hardware buffer"),
189        size: wgpu::Extent3d {
190            width: texture.width(),
191            height: texture.height(),
192            depth_or_array_layers: texture.depth_or_array_layers(),
193        },
194        mip_level_count: texture.mip_level_count(),
195        sample_count: texture.sample_count(),
196        dimension: convert::texture_dimension_from_wasi(texture.dimension()),
197        format: convert::texture_format_from_wasi(texture.format()),
198        usage: convert::texture_usages_from_wasi(texture.usage()),
199        view_formats: &[],
200    };
201    wgpu::Texture::from_custom(WasdkTexture::new(texture), &descriptor)
202}