Skip to main content

gpu/
lib.rs

1// gpu -- GPU access for the UI, 2D CAD and 3D games: an explicit API in the manner of Vulkan
2// and WebGPU over one backend per target (docs/GPU.md).  Backends now: Vulkan on Windows, on Linux,
3// there in programs linked against glibc, and on Android (vulkan-1.dll, libvulkan.so.1, libvulkan.so
4// loaded at run time; vk_*.rs; on Linux the windows go through dma-bufs, vk_dmabuf.rs), WebGPU in the
5// browser (web_*.rs, web/gpu.js; WGSL instead of SPIR-V, what it cannot do is said in web_dev.rs).
6// Planned: Metal; it provides the same types and methods.
7//
8//   Instance::new() / with(&InstanceDesc) -> Result<Instance>   the system's GPU API;
9//                    Err(Disabled) with jfgui_GPU=0, Err(NoLoader) without a loader
10//   instance.adapters() -> Result<Vec<Adapter>>   every GPU, discrete ones first
11//   instance.adapter() -> Result<Adapter>         the first usable one (jfgui_GPU_ADAPTER: an
12//                    index into adapters() or a part of the name)
13//   instance.adapters_low_power(display)          the GPUs for showing windows, frugal ones first
14//   instance.prepare() -> Result<bool>             adapter and device are there (false: the browser's
15//                    answer is on its way; never waits; true at once elsewhere)
16//   adapter.info() / features() / limits() / level() / create_device(&DeviceDesc)
17//   instance.validating(), validation_messages() -> (errors, warnings)   jfgui_GPU_VALIDATE
18//   device.queue() -> &Queue; features(), limits(), info(), level(), is_lost(), poll(),
19//          wait(id, timeout_ns), wait_idle(), memory_reserved()
20//   device.create_buffer(&BufferDesc)               Buffer: size(), write(off, &[T]),
21//                                                   read(off, &mut [T]) (Upload / Readback)
22//   device.create_texture(&TextureDesc)             Texture: view(), create_view(&ViewDesc),
23//                                                   width(), height(), format(), mips(), layers()
24//   device.create_sampler(&SamplerDesc)             Sampler
25//   ShaderModule::from_spirv(&device, &[u32])       SPIR-V words (1.3 on Vulkan 1.1, up to 1.6);
26//                                                   entry_points(), constants() (its overrides)
27//   device.create_bind_group_layout(&[Binding])     BindGroupLayout
28//   device.create_pipeline_layout(&PipelineLayoutDesc)   PipelineLayout
29//   device.create_bind_group(&BindGroupDesc)        BindGroup
30//   device.create_bindless(&BindlessDesc)           Bindless (level Voll): set_texture /
31//                                                   set_sampler / set_buffer / set_storage_texture
32//   device.create_compute_pipeline(&ComputePipelineDesc)  ComputePipeline
33//   device.create_render_pipeline(&RenderPipelineDesc)    RenderPipeline
34//                    each ShaderStage with its pipeline constants (WGSL override, SPIR-V
35//                    SpecId): &[Constant { id, value: f64 }], converted to the override's type
36//                    as WebGPU does; the workgroup size checked against the limits
37//   device.create_timestamps(n)                     Timestamps: read_ms(first, n) (since the
38//                                                   device's first read), read_ticks
39//   device.encoder() -> CommandEncoder              copies, fill, barriers, compute, render
40//                    passes, draws, indirect draws, timestamps; finish() -> CommandBuffer
41//   queue.submit([cmd, ..]) -> SubmitId             timeline semaphore value
42//   queue.write_buffer / write_texture / write_texture_rects / write_texture_rects_at (staged in
43//                    a ring, before the next submit), read_buffer (waits)
44//   Surface::new(&instance, plat_window)            a plat window (Windows: HWND; Linux: what its
45//                                                   display accepts, plat::win_gpu_child)
46//   Surface::native(&instance, handles, plat_window)  a window given by its handles (a child);
47//                    track_size(): the size for a rebuild on a worker (Linux)
48//   device.create_swapchain(&surface, &SwapchainDesc) -> Swapchain: acquire() -> Option<Frame>,
49//                    view(&frame), texture(&frame), present(frame, [cmd, ..]), resize(),
50//                    try_acquire() (never waits or rebuilds), rebuild(), and for the same code on
51//                    every target flush(), busy(), next_look(retry) (a frame reaches a Linux window
52//                    after the GPU has finished it: vk_dmabuf.rs), damage(rects)
53//
54// Conventions shared by every backend (and the WGSL compiler): normalized device coordinates
55// with y up and depth 0 ..= 1, framebuffer and texture origin top left (Vulkan: the viewport
56// is flipped); front faces counter-clockwise in framebuffer coordinates by default; group g /
57// binding b of a pipeline layout = WGSL @group(g) @binding(b) = SPIR-V DescriptorSet g /
58// Binding b; push constants: one block from offset 0 visible to every stage, at most
59// PUSH_CONSTANTS_MAX bytes; vertex input is optional (vertex pulling from storage buffers).
60//
61// Feature levels: Basis = what WebGPU and Vulkan 1.1 do (compute, storage buffers, single
62// indirect draws and dispatches, texture arrays, timestamps where present); Voll = Vulkan 1.3
63// with descriptor indexing (Bindless), draw_indexed_indirect_count, multi-draw indirect, draw
64// parameters and subgroup operations.  Mesh shaders and ray tracing are reported in Features,
65// not enabled.  Renderers ask device.level() / features().
66//
67// Errors: every call returns Result; misuse the backend can detect (sizes, alignments, states,
68// a draw without pipeline, a Voll command on Basis) is GpuError::Invalid -- recorded commands
69// report it from finish().  A lost device (driver reset, GPU removed) is GpuError::DeviceLost
70// on every later call; drop the Device and create a new one from the adapter.
71//
72// Lifetimes: resources are reference-counted handles; a command encoder borrows what it
73// records, and a dropped resource is destroyed once the GPU has finished every submission made
74// before the drop (the device's timeline).  Nothing waits on the CPU unless asked (wait,
75// read_buffer, Timestamps::read_*).
76//
77// Environment: jfgui_GPU=0 (off), jfgui_GPU_DLL (the loader's file name or path),
78// jfgui_GPU_ADAPTER (index or name part), jfgui_GPU_PRESENT=fifo|mailbox|immediate,
79// jfgui_GPU_VALIDATE=1 (the Khronos validation layer with synchronization validation when it is
80// installed, messages on stderr), jfgui_GPU_DEBUG=1 (the driver's debug messages).
81#![no_std]
82
83extern crate alloc;
84
85use alloc::string::String;
86
87#[cfg(not(target_arch = "wasm32"))]
88mod vk_cmd;
89#[cfg(not(target_arch = "wasm32"))]
90mod vk_dev;
91#[cfg(not(target_arch = "wasm32"))]
92mod vk_ffi;
93#[cfg(not(target_arch = "wasm32"))]
94mod vk_inst;
95#[cfg(not(target_arch = "wasm32"))]
96mod vk_load;
97#[cfg(not(target_arch = "wasm32"))]
98mod vk_mem;
99#[cfg(not(target_arch = "wasm32"))]
100mod vk_res;
101#[cfg(not(any(jfgui_glibc, target_arch = "wasm32")))]
102mod vk_swap;
103// Linux: images without a VkSurface, handed to the window by plat as dma-bufs; the same types
104#[cfg(jfgui_glibc)]
105#[path = "vk_dmabuf.rs"]
106mod vk_swap;
107// the browser: WebGPU through web/gpu.js, the same types; no Vulkan code in a wasm bundle
108#[cfg(target_arch = "wasm32")]
109mod web_cmd;
110#[cfg(target_arch = "wasm32")]
111mod web_dev;
112#[cfg(target_arch = "wasm32")]
113mod web_res;
114#[cfg(target_arch = "wasm32")]
115mod web_swap;
116
117#[cfg(not(target_arch = "wasm32"))]
118pub use vk_cmd::{CommandBuffer, CommandEncoder};
119#[cfg(not(target_arch = "wasm32"))]
120pub use vk_dev::{Device, Queue};
121#[cfg(not(target_arch = "wasm32"))]
122pub use vk_inst::{Adapter, Instance, low_power_rank, validation_messages};
123#[cfg(not(target_arch = "wasm32"))]
124pub use vk_res::{BindGroup, BindGroupLayout, Bindless, Buffer, ComputePipeline, PipelineLayout, RenderPipeline, Sampler,
125                 ShaderModule, Texture, TextureView, Timestamps};
126#[cfg(not(target_arch = "wasm32"))]
127pub use vk_swap::{Frame, Surface, Swapchain, TryAcquire};
128#[cfg(target_arch = "wasm32")]
129pub use web_cmd::{CommandBuffer, CommandEncoder};
130#[cfg(target_arch = "wasm32")]
131pub use web_dev::{Adapter, Device, Instance, Queue, low_power_rank, validation_messages};
132#[cfg(target_arch = "wasm32")]
133pub use web_res::{BindGroup, BindGroupLayout, Bindless, Buffer, ComputePipeline, PipelineLayout, RenderPipeline, Sampler,
134                  ShaderModule, Texture, TextureView, Timestamps};
135#[cfg(target_arch = "wasm32")]
136pub use web_swap::{Frame, Surface, Swapchain, TryAcquire};
137
138pub type Result<T> = core::result::Result<T, GpuError>;
139
140/// the most push-constant bytes a pipeline layout may declare (every backend guarantees it)
141pub const PUSH_CONSTANTS_MAX: u32 = 128;
142/// the most bind groups of a pipeline layout
143pub const BIND_GROUPS_MAX: u32 = 4;
144/// the most colour targets of a render pass
145pub const COLOR_TARGETS_MAX: usize = 8;
146
147/// what went wrong
148#[derive(Clone, Copy, Debug, PartialEq, Eq)]
149#[non_exhaustive]
150pub enum GpuError {
151    /// the GPU is switched off (jfgui_GPU=0)
152    Disabled,
153    /// the GPU API is not installed (no vulkan-1.dll or libvulkan.so.1, jfgui_GPU_DLL names a
154    /// missing file, a Linux program not linked against glibc, a browser without navigator.gpu)
155    NoLoader,
156    /// no usable GPU (or jfgui_GPU_ADAPTER matched none)
157    NoAdapter,
158    /// the adapter or this backend lacks something (a feature, a format, the level)
159    Unsupported(&'static str),
160    /// a call the API does not allow (sizes, alignments, states, order)
161    Invalid(&'static str),
162    /// host or device memory exhausted
163    OutOfMemory,
164    /// the device was lost (driver reset, GPU removed): recreate it
165    DeviceLost,
166    /// the window's surface is gone
167    SurfaceLost,
168    /// a wait ran out of time
169    Timeout,
170    /// results are not available yet (timestamps not written or not finished)
171    NotReady,
172    /// another failure of the GPU API (its code: a VkResult)
173    Failed(i32),
174}
175
176impl core::fmt::Display for GpuError {
177    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
178        match self {
179            GpuError::Disabled => f.write_str("GPU disabled (jfgui_GPU=0)"),
180            #[cfg(not(target_arch = "wasm32"))]
181            GpuError::NoLoader => f.write_str("no GPU API installed (Vulkan loader missing)"),
182            #[cfg(target_arch = "wasm32")]
183            GpuError::NoLoader => f.write_str("no WebGPU in this browser"),
184            GpuError::NoAdapter => f.write_str("no usable GPU"),
185            GpuError::Unsupported(w) => write!(f, "not supported: {w}"),
186            GpuError::Invalid(w) => write!(f, "invalid use: {w}"),
187            GpuError::OutOfMemory => f.write_str("out of GPU or host memory"),
188            GpuError::DeviceLost => f.write_str("GPU device lost"),
189            GpuError::SurfaceLost => f.write_str("window surface lost"),
190            GpuError::Timeout => f.write_str("GPU wait timed out"),
191            GpuError::NotReady => f.write_str("GPU results not ready"),
192            GpuError::Failed(c) => write!(f, "GPU call failed ({c})"),
193        }
194    }
195}
196
197/// plain data that may be copied to and from GPU memory byte for byte: no padding, no
198/// pointers, every bit pattern valid
199///
200/// # Safety
201/// Implemented here for the integer and float types and arrays of them.
202pub unsafe trait Pod: Copy + 'static {}
203// SAFETY: integers and floats have no padding and accept every bit pattern.
204unsafe impl Pod for u8 {}
205// SAFETY: as above.
206unsafe impl Pod for i8 {}
207// SAFETY: as above.
208unsafe impl Pod for u16 {}
209// SAFETY: as above.
210unsafe impl Pod for i16 {}
211// SAFETY: as above.
212unsafe impl Pod for u32 {}
213// SAFETY: as above.
214unsafe impl Pod for i32 {}
215// SAFETY: as above.
216unsafe impl Pod for u64 {}
217// SAFETY: as above.
218unsafe impl Pod for i64 {}
219// SAFETY: as above.
220unsafe impl Pod for f32 {}
221// SAFETY: as above.
222unsafe impl Pod for f64 {}
223// SAFETY: an array of Pod elements has no padding between them.
224unsafe impl<T: Pod, const N: usize> Pod for [T; N] {}
225
226/// the bytes of a slice of plain data
227pub fn bytes_of<T: Pod>(s: &[T]) -> &[u8] {
228    // SAFETY: T is Pod (no padding), the length covers exactly the slice's memory.
229    unsafe { core::slice::from_raw_parts(s.as_ptr() as *const u8, core::mem::size_of_val(s)) }
230}
231
232/// the feature level of a device (Basis < Voll)
233#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
234#[non_exhaustive]
235pub enum Level {
236    /// WebGPU / Vulkan 1.1: compute, storage buffers, single indirect draws, texture arrays
237    Basis,
238    /// Vulkan 1.3 with bindless descriptors, indirect count, multi-draw, subgroups
239    Voll,
240}
241
242/// what kind of GPU
243#[derive(Clone, Copy, Debug, PartialEq, Eq)]
244#[non_exhaustive]
245pub enum AdapterKind {
246    Discrete,
247    Integrated,
248    Virtual,
249    Cpu,
250    Other,
251}
252
253/// who an adapter is
254#[derive(Clone, Debug)]
255pub struct AdapterInfo {
256    /// the GPU's name ("NVIDIA GeForce RTX 2070 SUPER")
257    pub name: String,
258    pub vendor_id: u32,
259    pub device_id: u32,
260    /// the vendor's name for well-known vendor ids ("NVIDIA", "AMD", "Intel", ..)
261    pub vendor: &'static str,
262    pub kind: AdapterKind,
263    /// the driver's name ("NVIDIA") and its information string (the version as the vendor
264    /// writes it)
265    pub driver: String,
266    pub driver_info: String,
267    /// the driver version decoded by the vendor's scheme
268    pub driver_version: String,
269    /// the API version the device supports (major, minor, patch)
270    pub api_version: (u32, u32, u32),
271    /// "Vulkan", later "WebGPU" / "Metal"
272    pub backend: &'static str,
273    /// why the backend cannot use this adapter (None: usable)
274    pub missing: Option<&'static str>,
275}
276
277/// what a device can do beyond the Basis level
278#[derive(Clone, Copy, Debug, Default)]
279pub struct Features {
280    /// GPU timestamps on the queue (Timestamps)
281    pub timestamps: bool,
282    /// draw_indexed_indirect_multi (several draws from one buffer)
283    pub multi_draw_indirect: bool,
284    /// a first instance other than 0 in indirect draws
285    pub draw_indirect_first_instance: bool,
286    /// draw_indexed_indirect_count
287    pub draw_indirect_count: bool,
288    /// Bindless tables (descriptor indexing, update after bind)
289    pub bindless: bool,
290    /// subgroup operations (basic, vote, ballot, arithmetic, shuffle) in compute shaders
291    pub subgroups: bool,
292    /// what WebGPU's `subgroups` feature needs (lib/wgsl's `enable subgroups`): the operations
293    /// of `subgroups` plus relative shuffles and quad operations, in compute and fragment shaders
294    pub subgroups_webgpu: bool,
295    /// the stages with subgroup operations (subgroup_stage::*) and the operations
296    /// (subgroup_op::*), as the device reports them
297    pub subgroup_stages: u32,
298    pub subgroup_ops: u32,
299    /// Device::create_compute_pipeline_sized: a required subgroup size (WGSL @subgroup_size;
300    /// Limits::subgroup_min_size ..= subgroup_max_size)
301    pub subgroup_size_control: bool,
302    /// subgroup operations on f16 values too
303    pub subgroup_extended_types: bool,
304    /// the base vertex / base instance / draw index builtins
305    pub draw_parameters: bool,
306    pub shader_f16: bool,
307    pub shader_f64: bool,
308    pub shader_i16: bool,
309    pub shader_i64: bool,
310    pub shader_i8: bool,
311    /// 16 / 8 bit values in storage buffers
312    pub storage_16bit: bool,
313    /// 16-bit values in uniform buffers and push constants too (WGSL f16 there)
314    pub uniform_16bit: bool,
315    pub push_constant_16bit: bool,
316    /// uniform buffers in the storage layout (uniformBufferStandardLayout: a WGSL uniform
317    /// buffer whose layout is not std140, e.g. mat2x2f)
318    pub uniform_standard_layout: bool,
319    /// OpDemoteToHelperInvocation (lib/wgsl's discard at the level Voll)
320    pub demote: bool,
321    /// WGSL @builtin(clip_distances), @builtin(primitive_index) (geometryShader),
322    /// @interpolate(.., sample) / @builtin(sample_index) (sampleRateShading)
323    pub clip_distances: bool,
324    pub primitive_index: bool,
325    pub sample_shading: bool,
326    pub storage_8bit: bool,
327    /// BC1 .. BC7 compressed textures
328    pub texture_bc: bool,
329    pub anisotropy: bool,
330    pub depth_clamp: bool,
331    /// storage writes in vertex / fragment shaders
332    pub vertex_stores: bool,
333    pub fragment_stores: bool,
334    /// storage textures without a format in the shader
335    pub storage_write_without_format: bool,
336    /// shader access to buffers by address
337    pub buffer_address: bool,
338    /// presentation to windows (Swapchain)
339    pub swapchain: bool,
340    /// present on the adapter, not enabled yet: mesh shaders, ray queries, ray-tracing pipelines
341    pub mesh_shader: bool,
342    pub ray_query: bool,
343    pub ray_pipeline: bool,
344}
345
346/// the bits of Features::subgroup_stages
347pub mod subgroup_stage {
348    pub const COMPUTE: u32 = 1;
349    pub const FRAGMENT: u32 = 2;
350    pub const VERTEX: u32 = 4;
351}
352
353/// the bits of Features::subgroup_ops (the Vulkan feature bits)
354pub mod subgroup_op {
355    pub const BASIC: u32 = 1;
356    pub const VOTE: u32 = 2;
357    pub const ARITHMETIC: u32 = 4;
358    pub const BALLOT: u32 = 8;
359    pub const SHUFFLE: u32 = 0x10;
360    pub const SHUFFLE_RELATIVE: u32 = 0x20;
361    pub const CLUSTERED: u32 = 0x40;
362    pub const QUAD: u32 = 0x80;
363}
364
365/// the device's limits
366#[derive(Clone, Copy, Debug, Default)]
367pub struct Limits {
368    pub max_texture_2d: u32,
369    pub max_texture_3d: u32,
370    pub max_texture_layers: u32,
371    /// the largest range of a storage / uniform buffer binding (bytes)
372    pub max_storage_binding: u32,
373    pub max_uniform_binding: u32,
374    /// push-constant bytes of the device (the API allows PUSH_CONSTANTS_MAX)
375    pub max_push_constants: u32,
376    pub max_bind_groups: u32,
377    pub max_compute_workgroup_size: [u32; 3],
378    pub max_compute_invocations: u32,
379    pub max_compute_workgroups: [u32; 3],
380    pub max_compute_shared_memory: u32,
381    pub min_uniform_offset_alignment: u32,
382    pub min_storage_offset_alignment: u32,
383    pub max_color_targets: u32,
384    pub max_vertex_buffers: u32,
385    pub max_vertex_attributes: u32,
386    pub max_draw_indirect_count: u32,
387    pub max_anisotropy: f32,
388    /// nanoseconds per timestamp tick
389    pub timestamp_period_ns: f32,
390    /// threads per subgroup (0: unknown)
391    pub subgroup_size: u32,
392    /// the subgroup sizes shaders can get (without size control: subgroup_size twice)
393    pub subgroup_min_size: u32,
394    pub subgroup_max_size: u32,
395    /// the most slots of a Bindless table (Voll; 0 on Basis)
396    pub max_bindless_textures: u32,
397    pub max_bindless_samplers: u32,
398    pub max_bindless_buffers: u32,
399    pub max_bindless_storage_textures: u32,
400    /// bytes of the device-local memory heaps
401    pub device_memory: u64,
402}
403
404/// Instance::with
405#[derive(Clone, Copy)]
406pub struct InstanceDesc<'a> {
407    /// the application's name for the driver
408    pub app_name: &'a str,
409    /// the validation layer (as jfgui_GPU_VALIDATE=1)
410    pub validate: bool,
411}
412
413impl InstanceDesc<'_> {
414    pub const DEFAULT: InstanceDesc<'static> = InstanceDesc { app_name: "jfgui", validate: false };
415}
416
417/// Adapter::create_device
418#[derive(Clone, Copy)]
419pub struct DeviceDesc {
420    /// the highest level to enable (the device gets the highest it supports up to this one)
421    pub max_level: Level,
422}
423
424impl DeviceDesc {
425    pub const DEFAULT: DeviceDesc = DeviceDesc { max_level: Level::Voll };
426}
427
428/// a submission's place on the device timeline (Device::wait)
429#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
430pub struct SubmitId(pub u64);
431
432macro_rules! flags {
433    ($(#[$m:meta])* $name:ident { $( $(#[$fm:meta])* $f:ident = $v:expr ),* $(,)? }) => {
434        $(#[$m])*
435        #[derive(Clone, Copy, Debug, PartialEq, Eq)]
436        pub struct $name(pub u32);
437        impl $name {
438            pub const NONE: $name = $name(0);
439            $( $(#[$fm])* pub const $f: $name = $name($v); )*
440            pub const fn contains(self, o: $name) -> bool {
441                self.0 & o.0 == o.0
442            }
443            pub const fn intersects(self, o: $name) -> bool {
444                self.0 & o.0 != 0
445            }
446        }
447        impl core::ops::BitOr for $name {
448            type Output = $name;
449            fn bitor(self, o: $name) -> $name {
450                $name(self.0 | o.0)
451            }
452        }
453    };
454}
455
456flags! {
457    /// how a buffer is used (copies to and from it are always allowed)
458    BufferUsage {
459        UNIFORM = 1,
460        STORAGE = 2,
461        INDEX = 4,
462        VERTEX = 8,
463        /// indirect draw / dispatch arguments and draw counts
464        INDIRECT = 16,
465    }
466}
467
468flags! {
469    /// how a texture is used
470    TextureUsage {
471        COPY_SRC = 1,
472        COPY_DST = 2,
473        SAMPLED = 4,
474        STORAGE = 8,
475        COLOR_TARGET = 16,
476        DEPTH_TARGET = 32,
477    }
478}
479
480flags! {
481    /// shader stages that see a binding
482    Stages {
483        VERTEX = 1,
484        FRAGMENT = 2,
485        COMPUTE = 4,
486        ALL = 7,
487    }
488}
489
490flags! {
491    /// memory accesses for CommandEncoder::barrier: what must finish (before) and what waits
492    /// (after); textures change layout with texture_barrier instead
493    Access {
494        COPY_READ = 1,
495        COPY_WRITE = 2,
496        COMPUTE_READ = 4,
497        COMPUTE_WRITE = 8,
498        /// vertex and fragment shader reads (storage, uniform, sampled)
499        GRAPHICS_READ = 16,
500        GRAPHICS_WRITE = 32,
501        /// indirect arguments and draw counts
502        INDIRECT = 64,
503        /// index and vertex buffers
504        INDEX_VERTEX = 128,
505        COLOR_TARGET = 256,
506        DEPTH_TARGET = 512,
507        /// the CPU through Buffer::read / write
508        HOST_READ = 1024,
509        HOST_WRITE = 2048,
510    }
511}
512
513/// where a buffer's memory lives
514#[derive(Clone, Copy, Debug, PartialEq, Eq)]
515#[non_exhaustive]
516pub enum MemoryKind {
517    /// GPU memory (fastest for the GPU; filled by copies or Queue::write_buffer)
518    Device,
519    /// CPU memory the GPU reads (staging, per-frame data; Buffer::write)
520    Upload,
521    /// CPU memory the GPU writes (results; Buffer::read)
522    Readback,
523}
524
525/// Device::create_buffer
526#[derive(Clone, Copy, Debug)]
527pub struct BufferDesc {
528    pub size: u64,
529    pub usage: BufferUsage,
530    pub memory: MemoryKind,
531}
532
533/// texel formats
534#[derive(Clone, Copy, Debug, PartialEq, Eq)]
535#[non_exhaustive]
536pub enum Format {
537    R8Unorm,
538    Rg8Unorm,
539    Rgba8Unorm,
540    Rgba8Srgb,
541    Bgra8Unorm,
542    Bgra8Srgb,
543    Rgb10a2Unorm,
544    Rg11b10Float,
545    R16Float,
546    Rg16Float,
547    Rgba16Float,
548    R32Uint,
549    R32Sint,
550    R32Float,
551    Rg32Uint,
552    Rg32Float,
553    Rgba32Uint,
554    Rgba32Float,
555    Depth16,
556    Depth32Float,
557    Depth24Stencil8,
558    Depth32FloatStencil8,
559    Bc1Unorm,
560    Bc1Srgb,
561    Bc3Unorm,
562    Bc3Srgb,
563    Bc4Unorm,
564    Bc5Unorm,
565    Bc7Unorm,
566    Bc7Srgb,
567}
568
569impl Format {
570    /// bytes per texel block (a texel, or a 4 x 4 block of a compressed format)
571    pub const fn block_bytes(self) -> u32 {
572        use Format::*;
573        match self {
574            R8Unorm => 1,
575            Rg8Unorm | R16Float | Depth16 => 2,
576            Rgba8Unorm | Rgba8Srgb | Bgra8Unorm | Bgra8Srgb | Rgb10a2Unorm | Rg11b10Float | Rg16Float | R32Uint
577            | R32Sint | R32Float | Depth32Float | Depth24Stencil8 => 4,
578            Rgba16Float | Rg32Uint | Rg32Float | Depth32FloatStencil8 => 8,
579            Rgba32Uint | Rgba32Float => 16,
580            Bc1Unorm | Bc1Srgb | Bc4Unorm => 8,
581            Bc3Unorm | Bc3Srgb | Bc5Unorm | Bc7Unorm | Bc7Srgb => 16,
582        }
583    }
584    /// the block's width and height in texels (4 for compressed formats, else 1)
585    pub const fn block_dim(self) -> u32 {
586        if self.is_compressed() { 4 } else { 1 }
587    }
588    pub const fn is_compressed(self) -> bool {
589        use Format::*;
590        matches!(self, Bc1Unorm | Bc1Srgb | Bc3Unorm | Bc3Srgb | Bc4Unorm | Bc5Unorm | Bc7Unorm | Bc7Srgb)
591    }
592    pub const fn is_depth(self) -> bool {
593        matches!(self, Format::Depth16 | Format::Depth32Float | Format::Depth24Stencil8 | Format::Depth32FloatStencil8)
594    }
595    pub const fn has_stencil(self) -> bool {
596        matches!(self, Format::Depth24Stencil8 | Format::Depth32FloatStencil8)
597    }
598    pub const fn is_srgb(self) -> bool {
599        matches!(self, Format::Rgba8Srgb | Format::Bgra8Srgb | Format::Bc1Srgb | Format::Bc3Srgb | Format::Bc7Srgb)
600    }
601}
602
603/// Device::create_texture: a 2D texture (layers > 1: an array; cube: 6 * n layers) or a 3D one
604/// (depth > 1)
605#[derive(Clone, Copy, Debug)]
606pub struct TextureDesc {
607    pub width: u32,
608    pub height: u32,
609    /// 1 for 2D textures
610    pub depth: u32,
611    /// 1 for a single texture
612    pub layers: u32,
613    /// mip levels (1 = none)
614    pub mips: u32,
615    pub format: Format,
616    pub usage: TextureUsage,
617    /// cube faces: layers is a multiple of 6
618    pub cube: bool,
619}
620
621impl TextureDesc {
622    /// a single-level 2D texture
623    pub const fn d2(width: u32, height: u32, format: Format, usage: TextureUsage) -> TextureDesc {
624        TextureDesc { width, height, depth: 1, layers: 1, mips: 1, format, usage, cube: false }
625    }
626}
627
628/// a view's dimension
629#[derive(Clone, Copy, Debug, PartialEq, Eq)]
630#[non_exhaustive]
631pub enum ViewDim {
632    D2,
633    D2Array,
634    Cube,
635    CubeArray,
636    D3,
637}
638
639/// Texture::create_view: a range of mips and layers (counts 0 = to the end)
640#[derive(Clone, Copy, Debug)]
641pub struct ViewDesc {
642    pub dim: ViewDim,
643    pub base_mip: u32,
644    pub mips: u32,
645    pub base_layer: u32,
646    pub layers: u32,
647}
648
649/// the state a texture is in (texture_barrier, render pass attachments, write_texture)
650#[derive(Clone, Copy, Debug, PartialEq, Eq)]
651#[non_exhaustive]
652pub enum Layout {
653    /// contents discarded; waits for earlier colour, copy and compute work (and for the
654    /// swapchain image's acquisition)
655    Undefined,
656    /// storage textures and anything at once (slower on some GPUs)
657    General,
658    CopySrc,
659    CopyDst,
660    /// sampled in shaders (also depth textures)
661    Sampled,
662    ColorTarget,
663    DepthTarget,
664    /// depth test without writes while shaders sample it
665    DepthRead,
666    /// handed to the window (the last layout of a swapchain image)
667    Present,
668}
669
670#[derive(Clone, Copy, Debug, PartialEq, Eq)]
671#[non_exhaustive]
672pub enum Filter {
673    Nearest,
674    Linear,
675}
676
677#[derive(Clone, Copy, Debug, PartialEq, Eq)]
678#[non_exhaustive]
679pub enum AddressMode {
680    Repeat,
681    MirrorRepeat,
682    ClampToEdge,
683}
684
685/// comparison functions (depth test, comparison samplers)
686#[derive(Clone, Copy, Debug, PartialEq, Eq)]
687#[non_exhaustive]
688pub enum Compare {
689    Never,
690    Less,
691    Equal,
692    LessEqual,
693    Greater,
694    NotEqual,
695    GreaterEqual,
696    Always,
697}
698
699/// Device::create_sampler
700#[derive(Clone, Copy, Debug)]
701pub struct SamplerDesc {
702    pub mag: Filter,
703    pub min: Filter,
704    pub mip: Filter,
705    /// u, v, w
706    pub address: [AddressMode; 3],
707    /// 1 = off, up to Limits::max_anisotropy (needs Features::anisotropy)
708    pub anisotropy: u16,
709    /// a comparison sampler (shadow maps)
710    pub compare: Option<Compare>,
711    pub lod_min: f32,
712    pub lod_max: f32,
713}
714
715impl SamplerDesc {
716    pub const LINEAR: SamplerDesc = SamplerDesc {
717        mag: Filter::Linear,
718        min: Filter::Linear,
719        mip: Filter::Linear,
720        address: [AddressMode::ClampToEdge; 3],
721        anisotropy: 1,
722        compare: None,
723        lod_min: 0.0,
724        lod_max: 1000.0,
725    };
726    pub const NEAREST: SamplerDesc =
727        SamplerDesc { mag: Filter::Nearest, min: Filter::Nearest, mip: Filter::Nearest, ..SamplerDesc::LINEAR };
728}
729
730/// what a binding holds
731#[derive(Clone, Copy, Debug, PartialEq, Eq)]
732#[non_exhaustive]
733pub enum BindingKind {
734    UniformBuffer,
735    /// read and written by shaders
736    StorageBuffer,
737    /// read only (also visible to vertex shaders without Features::vertex_stores)
738    StorageBufferRead,
739    /// a sampled texture (Layout::Sampled)
740    Texture,
741    /// a storage texture (Layout::General)
742    StorageTexture,
743    Sampler,
744}
745
746/// one binding of a BindGroupLayout
747#[derive(Clone, Copy, Debug)]
748pub struct Binding {
749    pub binding: u32,
750    pub kind: BindingKind,
751    pub stages: Stages,
752}
753
754/// Device::create_pipeline_layout
755#[derive(Clone, Copy)]
756pub struct PipelineLayoutDesc<'a> {
757    /// bind group g = @group(g); at most BIND_GROUPS_MAX
758    pub groups: &'a [&'a BindGroupLayout],
759    /// push-constant bytes (a multiple of 4, at most PUSH_CONSTANTS_MAX)
760    pub push_constants: u32,
761}
762
763/// a resource bound into a BindGroup
764#[derive(Clone, Copy)]
765#[non_exhaustive]
766pub enum Resource<'a> {
767    /// a buffer range (size 0: to the end); offsets aligned to Limits::min_*_offset_alignment
768    Buffer { buffer: &'a Buffer, offset: u64, size: u64 },
769    Texture(&'a TextureView),
770    StorageTexture(&'a TextureView),
771    Sampler(&'a Sampler),
772}
773
774#[derive(Clone, Copy)]
775pub struct BindEntry<'a> {
776    pub binding: u32,
777    pub resource: Resource<'a>,
778}
779
780/// Device::create_bind_group: one entry per binding of the layout
781#[derive(Clone, Copy)]
782pub struct BindGroupDesc<'a> {
783    pub layout: &'a BindGroupLayout,
784    pub entries: &'a [BindEntry<'a>],
785}
786
787/// Device::create_bindless: the slots of each kind (bindings 0 textures, 1 samplers, 2 storage
788/// buffers, 3 storage textures of one group)
789#[derive(Clone, Copy, Debug)]
790pub struct BindlessDesc {
791    pub textures: u32,
792    pub samplers: u32,
793    pub buffers: u32,
794    pub storage_textures: u32,
795}
796
797/// a pipeline-overridable constant: WGSL `@id(id) override`, SPIR-V `SpecId id`.  The value
798/// is an f64 as in WebGPU's `GPUProgrammableStage.constants` and is converted to the
799/// override's type the way WebGPU converts it: bool true unless 0 or NaN; i32 / u32 (and the
800/// other integers) truncated toward 0, Invalid when not finite or out of range; f32 / f16 /
801/// f64 rounded to nearest, Invalid when not finite or out of range.  An id the module does not
802/// declare, or the same id twice, is Invalid (Vulkan would ignore it; WebGPU does not).
803/// Overrides the pipeline leaves out keep the module's default.
804#[derive(Clone, Copy, Debug, PartialEq)]
805pub struct Constant {
806    pub id: u32,
807    pub value: f64,
808}
809
810/// a shader entry point and its pipeline constants
811#[derive(Clone, Copy)]
812pub struct ShaderStage<'a> {
813    pub module: &'a ShaderModule,
814    pub entry: &'a str,
815    /// pipeline-overridable constants of this stage (empty: the module's defaults)
816    pub constants: &'a [Constant],
817}
818
819impl<'a> ShaderStage<'a> {
820    /// an entry point without pipeline constants
821    pub const fn new(module: &'a ShaderModule, entry: &'a str) -> ShaderStage<'a> {
822        ShaderStage { module, entry, constants: &[] }
823    }
824}
825
826/// Device::create_compute_pipeline
827#[derive(Clone, Copy)]
828pub struct ComputePipelineDesc<'a> {
829    pub layout: &'a PipelineLayout,
830    pub shader: ShaderStage<'a>,
831}
832
833#[derive(Clone, Copy, Debug, PartialEq, Eq)]
834#[non_exhaustive]
835pub enum Topology {
836    Points,
837    Lines,
838    LineStrip,
839    Triangles,
840    TriangleStrip,
841}
842
843#[derive(Clone, Copy, Debug, PartialEq, Eq)]
844#[non_exhaustive]
845pub enum Cull {
846    None,
847    Front,
848    Back,
849}
850
851/// vertex attribute formats
852#[derive(Clone, Copy, Debug, PartialEq, Eq)]
853#[non_exhaustive]
854pub enum VertexFormat {
855    F32,
856    F32x2,
857    F32x3,
858    F32x4,
859    U32,
860    U32x2,
861    U32x4,
862    /// four bytes as 0 ..= 1
863    Unorm8x4,
864}
865
866#[derive(Clone, Copy, Debug)]
867pub struct VertexAttribute {
868    pub location: u32,
869    pub format: VertexFormat,
870    pub offset: u32,
871}
872
873/// a vertex buffer slot (empty list: vertex pulling)
874#[derive(Clone, Copy)]
875pub struct VertexBufferLayout<'a> {
876    pub stride: u32,
877    pub per_instance: bool,
878    pub attributes: &'a [VertexAttribute],
879}
880
881#[derive(Clone, Copy, Debug, PartialEq, Eq)]
882#[non_exhaustive]
883pub enum BlendFactor {
884    Zero,
885    One,
886    Src,
887    OneMinusSrc,
888    Dst,
889    OneMinusDst,
890    SrcAlpha,
891    OneMinusSrcAlpha,
892    DstAlpha,
893    OneMinusDstAlpha,
894}
895
896#[derive(Clone, Copy, Debug, PartialEq, Eq)]
897#[non_exhaustive]
898pub enum BlendOp {
899    Add,
900    Subtract,
901    ReverseSubtract,
902    Min,
903    Max,
904}
905
906#[derive(Clone, Copy, Debug, PartialEq, Eq)]
907pub struct BlendComponent {
908    pub src: BlendFactor,
909    pub dst: BlendFactor,
910    pub op: BlendOp,
911}
912
913/// colour and alpha blending of a colour target
914#[derive(Clone, Copy, Debug, PartialEq, Eq)]
915pub struct Blend {
916    pub color: BlendComponent,
917    pub alpha: BlendComponent,
918}
919
920impl Blend {
921    /// premultiplied alpha: src + dst * (1 - src.a)
922    pub const PREMULTIPLIED: Blend = Blend {
923        color: BlendComponent { src: BlendFactor::One, dst: BlendFactor::OneMinusSrcAlpha, op: BlendOp::Add },
924        alpha: BlendComponent { src: BlendFactor::One, dst: BlendFactor::OneMinusSrcAlpha, op: BlendOp::Add },
925    };
926    /// straight alpha: src * src.a + dst * (1 - src.a)
927    pub const ALPHA: Blend = Blend {
928        color: BlendComponent { src: BlendFactor::SrcAlpha, dst: BlendFactor::OneMinusSrcAlpha, op: BlendOp::Add },
929        alpha: BlendComponent { src: BlendFactor::One, dst: BlendFactor::OneMinusSrcAlpha, op: BlendOp::Add },
930    };
931    /// additive: src + dst
932    pub const ADD: Blend = Blend {
933        color: BlendComponent { src: BlendFactor::One, dst: BlendFactor::One, op: BlendOp::Add },
934        alpha: BlendComponent { src: BlendFactor::One, dst: BlendFactor::One, op: BlendOp::Add },
935    };
936}
937
938/// a colour target of a render pipeline
939#[derive(Clone, Copy, Debug)]
940pub struct ColorTarget {
941    pub format: Format,
942    pub blend: Option<Blend>,
943    /// bits 0..3 = r, g, b, a (0xF: all)
944    pub write_mask: u32,
945}
946
947/// the depth test of a render pipeline
948#[derive(Clone, Copy, Debug)]
949pub struct DepthState {
950    pub format: Format,
951    pub write: bool,
952    pub compare: Compare,
953}
954
955/// Device::create_render_pipeline
956#[derive(Clone, Copy)]
957pub struct RenderPipelineDesc<'a> {
958    pub layout: &'a PipelineLayout,
959    pub vertex: ShaderStage<'a>,
960    pub fragment: Option<ShaderStage<'a>>,
961    /// vertex buffer slots (empty: the vertex shader pulls its data)
962    pub buffers: &'a [VertexBufferLayout<'a>],
963    pub topology: Topology,
964    pub cull: Cull,
965    /// front faces are clockwise (default counter-clockwise)
966    pub front_cw: bool,
967    pub targets: &'a [ColorTarget],
968    pub depth: Option<DepthState>,
969}
970
971/// what a render pass does with an attachment's old contents
972#[derive(Clone, Copy, Debug, PartialEq, Eq)]
973#[non_exhaustive]
974pub enum Load {
975    Keep,
976    Clear,
977    Discard,
978}
979
980/// a colour attachment of a render pass: the view goes from `before` to ColorTarget at the
981/// start and to `after` at the end
982#[derive(Clone, Copy)]
983pub struct ColorAttachment<'a> {
984    pub view: &'a TextureView,
985    pub load: Load,
986    pub clear: [f32; 4],
987    pub store: bool,
988    pub before: Layout,
989    pub after: Layout,
990}
991
992/// the depth attachment of a render pass (DepthTarget during the pass)
993#[derive(Clone, Copy)]
994pub struct DepthAttachment<'a> {
995    pub view: &'a TextureView,
996    pub load: Load,
997    pub clear: f32,
998    pub store: bool,
999    pub before: Layout,
1000    pub after: Layout,
1001}
1002
1003/// CommandEncoder::begin_render (the slice may be a temporary: only the views are borrowed
1004/// for the encoder's lifetime)
1005#[derive(Clone, Copy)]
1006pub struct RenderPassDesc<'d, 'a> {
1007    pub color: &'d [ColorAttachment<'a>],
1008    pub depth: Option<DepthAttachment<'a>>,
1009}
1010
1011#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1012#[non_exhaustive]
1013pub enum IndexFormat {
1014    U16,
1015    U32,
1016}
1017
1018/// a region of one mip of a texture: origin and size in texels (z: 3D textures), layers
1019#[derive(Clone, Copy)]
1020pub struct TextureRegion<'a> {
1021    pub texture: &'a Texture,
1022    pub mip: u32,
1023    pub origin: [u32; 3],
1024    pub size: [u32; 3],
1025    pub base_layer: u32,
1026    pub layers: u32,
1027}
1028
1029impl<'a> TextureRegion<'a> {
1030    /// all of mip `mip` and every layer
1031    pub fn mip(texture: &'a Texture, mip: u32) -> TextureRegion<'a> {
1032        let s = |v: u32| (v >> mip).max(1);
1033        TextureRegion {
1034            texture,
1035            mip,
1036            origin: [0; 3],
1037            size: [s(texture.width()), s(texture.height()), s(texture.depth())],
1038            base_layer: 0,
1039            layers: texture.layers(),
1040        }
1041    }
1042}
1043
1044/// the layout of texel data in a buffer or slice: rows of bytes_per_row bytes (0: tightly
1045/// packed), rows_per_image rows per layer / slice (0: the region's height)
1046#[derive(Clone, Copy, Debug, Default)]
1047pub struct DataLayout {
1048    pub offset: u64,
1049    pub bytes_per_row: u32,
1050    pub rows_per_image: u32,
1051}
1052
1053/// how frames reach the window
1054#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1055pub enum PresentMode {
1056    /// vsync: one image per refresh, never tears (always available)
1057    Fifo,
1058    /// the newest image at the refresh, older ones dropped (no tearing, low latency)
1059    Mailbox,
1060    /// at once (tears; for measurements)
1061    Immediate,
1062}
1063
1064/// Device::create_swapchain
1065#[derive(Clone, Copy, Debug)]
1066pub struct SwapchainDesc {
1067    pub present: PresentMode,
1068    /// an sRGB format (the GPU encodes gamma on writes)
1069    pub srgb: bool,
1070    /// the images may be copied from (read-back of the presented image)
1071    pub copy_src: bool,
1072    /// frames the CPU may record ahead of the GPU (1 ..= 3)
1073    pub frames_in_flight: u32,
1074    /// images of the swapchain at least (0: the surface's minimum + 1)
1075    pub min_images: u32,
1076}
1077
1078impl SwapchainDesc {
1079    pub const DEFAULT: SwapchainDesc =
1080        SwapchainDesc { present: PresentMode::Fifo, srgb: false, copy_src: false, frames_in_flight: 2, min_images: 0 };
1081}