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