#![no_std]
extern crate alloc;
use alloc::string::String;
#[cfg(not(target_arch = "wasm32"))]
mod vk_cmd;
#[cfg(not(target_arch = "wasm32"))]
mod vk_dev;
#[cfg(not(target_arch = "wasm32"))]
mod vk_ffi;
#[cfg(not(target_arch = "wasm32"))]
mod vk_inst;
#[cfg(not(target_arch = "wasm32"))]
mod vk_load;
#[cfg(not(target_arch = "wasm32"))]
mod vk_mem;
#[cfg(not(target_arch = "wasm32"))]
mod vk_res;
#[cfg(not(any(jfgui_glibc, target_arch = "wasm32")))]
mod vk_swap;
#[cfg(jfgui_glibc)]
#[path = "vk_dmabuf.rs"]
mod vk_swap;
#[cfg(target_arch = "wasm32")]
mod web_cmd;
#[cfg(target_arch = "wasm32")]
mod web_dev;
#[cfg(target_arch = "wasm32")]
mod web_res;
#[cfg(target_arch = "wasm32")]
mod web_swap;
#[cfg(not(target_arch = "wasm32"))]
pub use vk_cmd::{CommandBuffer, CommandEncoder};
#[cfg(not(target_arch = "wasm32"))]
pub use vk_dev::{Device, Queue};
#[cfg(not(target_arch = "wasm32"))]
pub use vk_inst::{Adapter, Instance, low_power_rank, validation_messages};
#[cfg(not(target_arch = "wasm32"))]
pub use vk_res::{BindGroup, BindGroupLayout, Bindless, Buffer, ComputePipeline, PipelineLayout, RenderPipeline, Sampler,
ShaderModule, Texture, TextureView, Timestamps};
#[cfg(not(target_arch = "wasm32"))]
pub use vk_swap::{Frame, Surface, Swapchain, TryAcquire};
#[cfg(target_arch = "wasm32")]
pub use web_cmd::{CommandBuffer, CommandEncoder};
#[cfg(target_arch = "wasm32")]
pub use web_dev::{Adapter, Device, Instance, Queue, low_power_rank, validation_messages};
#[cfg(target_arch = "wasm32")]
pub use web_res::{BindGroup, BindGroupLayout, Bindless, Buffer, ComputePipeline, PipelineLayout, RenderPipeline, Sampler,
ShaderModule, Texture, TextureView, Timestamps};
#[cfg(target_arch = "wasm32")]
pub use web_swap::{Frame, Surface, Swapchain, TryAcquire};
pub type Result<T> = core::result::Result<T, GpuError>;
pub const PUSH_CONSTANTS_MAX: u32 = 128;
pub const BIND_GROUPS_MAX: u32 = 4;
pub const COLOR_TARGETS_MAX: usize = 8;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum GpuError {
Disabled,
NoLoader,
NoAdapter,
Unsupported(&'static str),
Invalid(&'static str),
OutOfMemory,
DeviceLost,
SurfaceLost,
Timeout,
NotReady,
Failed(i32),
}
impl core::fmt::Display for GpuError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match self {
GpuError::Disabled => f.write_str("GPU disabled (jfgui_GPU=0)"),
#[cfg(not(target_arch = "wasm32"))]
GpuError::NoLoader => f.write_str("no GPU API installed (Vulkan loader missing)"),
#[cfg(target_arch = "wasm32")]
GpuError::NoLoader => f.write_str("no WebGPU in this browser"),
GpuError::NoAdapter => f.write_str("no usable GPU"),
GpuError::Unsupported(w) => write!(f, "not supported: {w}"),
GpuError::Invalid(w) => write!(f, "invalid use: {w}"),
GpuError::OutOfMemory => f.write_str("out of GPU or host memory"),
GpuError::DeviceLost => f.write_str("GPU device lost"),
GpuError::SurfaceLost => f.write_str("window surface lost"),
GpuError::Timeout => f.write_str("GPU wait timed out"),
GpuError::NotReady => f.write_str("GPU results not ready"),
GpuError::Failed(c) => write!(f, "GPU call failed ({c})"),
}
}
}
pub unsafe trait Pod: Copy + 'static {}
unsafe impl Pod for u8 {}
unsafe impl Pod for i8 {}
unsafe impl Pod for u16 {}
unsafe impl Pod for i16 {}
unsafe impl Pod for u32 {}
unsafe impl Pod for i32 {}
unsafe impl Pod for u64 {}
unsafe impl Pod for i64 {}
unsafe impl Pod for f32 {}
unsafe impl Pod for f64 {}
unsafe impl<T: Pod, const N: usize> Pod for [T; N] {}
pub fn bytes_of<T: Pod>(s: &[T]) -> &[u8] {
unsafe { core::slice::from_raw_parts(s.as_ptr() as *const u8, core::mem::size_of_val(s)) }
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Level {
Basis,
Voll,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum AdapterKind {
Discrete,
Integrated,
Virtual,
Cpu,
Other,
}
#[derive(Clone, Debug)]
pub struct AdapterInfo {
pub name: String,
pub vendor_id: u32,
pub device_id: u32,
pub vendor: &'static str,
pub kind: AdapterKind,
pub driver: String,
pub driver_info: String,
pub driver_version: String,
pub api_version: (u32, u32, u32),
pub backend: &'static str,
pub missing: Option<&'static str>,
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Features {
pub timestamps: bool,
pub multi_draw_indirect: bool,
pub draw_indirect_first_instance: bool,
pub draw_indirect_count: bool,
pub bindless: bool,
pub subgroups: bool,
pub subgroups_webgpu: bool,
pub subgroup_stages: u32,
pub subgroup_ops: u32,
pub subgroup_size_control: bool,
pub subgroup_extended_types: bool,
pub draw_parameters: bool,
pub shader_f16: bool,
pub shader_f64: bool,
pub shader_i16: bool,
pub shader_i64: bool,
pub shader_i8: bool,
pub storage_16bit: bool,
pub uniform_16bit: bool,
pub push_constant_16bit: bool,
pub uniform_standard_layout: bool,
pub demote: bool,
pub clip_distances: bool,
pub primitive_index: bool,
pub sample_shading: bool,
pub storage_8bit: bool,
pub texture_bc: bool,
pub anisotropy: bool,
pub depth_clamp: bool,
pub vertex_stores: bool,
pub fragment_stores: bool,
pub storage_write_without_format: bool,
pub buffer_address: bool,
pub swapchain: bool,
pub mesh_shader: bool,
pub ray_query: bool,
pub ray_pipeline: bool,
}
pub mod subgroup_stage {
pub const COMPUTE: u32 = 1;
pub const FRAGMENT: u32 = 2;
pub const VERTEX: u32 = 4;
}
pub mod subgroup_op {
pub const BASIC: u32 = 1;
pub const VOTE: u32 = 2;
pub const ARITHMETIC: u32 = 4;
pub const BALLOT: u32 = 8;
pub const SHUFFLE: u32 = 0x10;
pub const SHUFFLE_RELATIVE: u32 = 0x20;
pub const CLUSTERED: u32 = 0x40;
pub const QUAD: u32 = 0x80;
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Limits {
pub max_texture_2d: u32,
pub max_texture_3d: u32,
pub max_texture_layers: u32,
pub max_storage_binding: u32,
pub max_uniform_binding: u32,
pub max_push_constants: u32,
pub max_bind_groups: u32,
pub max_compute_workgroup_size: [u32; 3],
pub max_compute_invocations: u32,
pub max_compute_workgroups: [u32; 3],
pub max_compute_shared_memory: u32,
pub min_uniform_offset_alignment: u32,
pub min_storage_offset_alignment: u32,
pub max_color_targets: u32,
pub max_vertex_buffers: u32,
pub max_vertex_attributes: u32,
pub max_draw_indirect_count: u32,
pub max_anisotropy: f32,
pub timestamp_period_ns: f32,
pub subgroup_size: u32,
pub subgroup_min_size: u32,
pub subgroup_max_size: u32,
pub max_bindless_textures: u32,
pub max_bindless_samplers: u32,
pub max_bindless_buffers: u32,
pub max_bindless_storage_textures: u32,
pub device_memory: u64,
}
#[derive(Clone, Copy)]
pub struct InstanceDesc<'a> {
pub app_name: &'a str,
pub validate: bool,
}
impl InstanceDesc<'_> {
pub const DEFAULT: InstanceDesc<'static> = InstanceDesc { app_name: "jfgui", validate: false };
}
#[derive(Clone, Copy)]
pub struct DeviceDesc {
pub max_level: Level,
}
impl DeviceDesc {
pub const DEFAULT: DeviceDesc = DeviceDesc { max_level: Level::Voll };
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct SubmitId(pub u64);
macro_rules! flags {
($(#[$m:meta])* $name:ident { $( $(#[$fm:meta])* $f:ident = $v:expr ),* $(,)? }) => {
$(#[$m])*
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct $name(pub u32);
impl $name {
pub const NONE: $name = $name(0);
$( $(#[$fm])* pub const $f: $name = $name($v); )*
pub const fn contains(self, o: $name) -> bool {
self.0 & o.0 == o.0
}
pub const fn intersects(self, o: $name) -> bool {
self.0 & o.0 != 0
}
}
impl core::ops::BitOr for $name {
type Output = $name;
fn bitor(self, o: $name) -> $name {
$name(self.0 | o.0)
}
}
};
}
flags! {
BufferUsage {
UNIFORM = 1,
STORAGE = 2,
INDEX = 4,
VERTEX = 8,
INDIRECT = 16,
}
}
flags! {
TextureUsage {
COPY_SRC = 1,
COPY_DST = 2,
SAMPLED = 4,
STORAGE = 8,
COLOR_TARGET = 16,
DEPTH_TARGET = 32,
}
}
flags! {
Stages {
VERTEX = 1,
FRAGMENT = 2,
COMPUTE = 4,
ALL = 7,
}
}
flags! {
Access {
COPY_READ = 1,
COPY_WRITE = 2,
COMPUTE_READ = 4,
COMPUTE_WRITE = 8,
GRAPHICS_READ = 16,
GRAPHICS_WRITE = 32,
INDIRECT = 64,
INDEX_VERTEX = 128,
COLOR_TARGET = 256,
DEPTH_TARGET = 512,
HOST_READ = 1024,
HOST_WRITE = 2048,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum MemoryKind {
Device,
Upload,
Readback,
}
#[derive(Clone, Copy, Debug)]
pub struct BufferDesc {
pub size: u64,
pub usage: BufferUsage,
pub memory: MemoryKind,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Format {
R8Unorm,
Rg8Unorm,
Rgba8Unorm,
Rgba8Srgb,
Bgra8Unorm,
Bgra8Srgb,
Rgb10a2Unorm,
Rg11b10Float,
R16Float,
Rg16Float,
Rgba16Float,
R32Uint,
R32Sint,
R32Float,
Rg32Uint,
Rg32Float,
Rgba32Uint,
Rgba32Float,
Depth16,
Depth32Float,
Depth24Stencil8,
Depth32FloatStencil8,
Bc1Unorm,
Bc1Srgb,
Bc3Unorm,
Bc3Srgb,
Bc4Unorm,
Bc5Unorm,
Bc7Unorm,
Bc7Srgb,
}
impl Format {
pub const fn block_bytes(self) -> u32 {
use Format::*;
match self {
R8Unorm => 1,
Rg8Unorm | R16Float | Depth16 => 2,
Rgba8Unorm | Rgba8Srgb | Bgra8Unorm | Bgra8Srgb | Rgb10a2Unorm | Rg11b10Float | Rg16Float | R32Uint
| R32Sint | R32Float | Depth32Float | Depth24Stencil8 => 4,
Rgba16Float | Rg32Uint | Rg32Float | Depth32FloatStencil8 => 8,
Rgba32Uint | Rgba32Float => 16,
Bc1Unorm | Bc1Srgb | Bc4Unorm => 8,
Bc3Unorm | Bc3Srgb | Bc5Unorm | Bc7Unorm | Bc7Srgb => 16,
}
}
pub const fn block_dim(self) -> u32 {
if self.is_compressed() { 4 } else { 1 }
}
pub const fn is_compressed(self) -> bool {
use Format::*;
matches!(self, Bc1Unorm | Bc1Srgb | Bc3Unorm | Bc3Srgb | Bc4Unorm | Bc5Unorm | Bc7Unorm | Bc7Srgb)
}
pub const fn is_depth(self) -> bool {
matches!(self, Format::Depth16 | Format::Depth32Float | Format::Depth24Stencil8 | Format::Depth32FloatStencil8)
}
pub const fn has_stencil(self) -> bool {
matches!(self, Format::Depth24Stencil8 | Format::Depth32FloatStencil8)
}
pub const fn is_srgb(self) -> bool {
matches!(self, Format::Rgba8Srgb | Format::Bgra8Srgb | Format::Bc1Srgb | Format::Bc3Srgb | Format::Bc7Srgb)
}
}
#[derive(Clone, Copy, Debug)]
pub struct TextureDesc {
pub width: u32,
pub height: u32,
pub depth: u32,
pub layers: u32,
pub mips: u32,
pub format: Format,
pub usage: TextureUsage,
pub cube: bool,
}
impl TextureDesc {
pub const fn d2(width: u32, height: u32, format: Format, usage: TextureUsage) -> TextureDesc {
TextureDesc { width, height, depth: 1, layers: 1, mips: 1, format, usage, cube: false }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ViewDim {
D2,
D2Array,
Cube,
CubeArray,
D3,
}
#[derive(Clone, Copy, Debug)]
pub struct ViewDesc {
pub dim: ViewDim,
pub base_mip: u32,
pub mips: u32,
pub base_layer: u32,
pub layers: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Layout {
Undefined,
General,
CopySrc,
CopyDst,
Sampled,
ColorTarget,
DepthTarget,
DepthRead,
Present,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Filter {
Nearest,
Linear,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum AddressMode {
Repeat,
MirrorRepeat,
ClampToEdge,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Compare {
Never,
Less,
Equal,
LessEqual,
Greater,
NotEqual,
GreaterEqual,
Always,
}
#[derive(Clone, Copy, Debug)]
pub struct SamplerDesc {
pub mag: Filter,
pub min: Filter,
pub mip: Filter,
pub address: [AddressMode; 3],
pub anisotropy: u16,
pub compare: Option<Compare>,
pub lod_min: f32,
pub lod_max: f32,
}
impl SamplerDesc {
pub const LINEAR: SamplerDesc = SamplerDesc {
mag: Filter::Linear,
min: Filter::Linear,
mip: Filter::Linear,
address: [AddressMode::ClampToEdge; 3],
anisotropy: 1,
compare: None,
lod_min: 0.0,
lod_max: 1000.0,
};
pub const NEAREST: SamplerDesc =
SamplerDesc { mag: Filter::Nearest, min: Filter::Nearest, mip: Filter::Nearest, ..SamplerDesc::LINEAR };
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum BindingKind {
UniformBuffer,
StorageBuffer,
StorageBufferRead,
Texture,
StorageTexture,
Sampler,
}
#[derive(Clone, Copy, Debug)]
pub struct Binding {
pub binding: u32,
pub kind: BindingKind,
pub stages: Stages,
}
#[derive(Clone, Copy)]
pub struct PipelineLayoutDesc<'a> {
pub groups: &'a [&'a BindGroupLayout],
pub push_constants: u32,
}
#[derive(Clone, Copy)]
#[non_exhaustive]
pub enum Resource<'a> {
Buffer { buffer: &'a Buffer, offset: u64, size: u64 },
Texture(&'a TextureView),
StorageTexture(&'a TextureView),
Sampler(&'a Sampler),
}
#[derive(Clone, Copy)]
pub struct BindEntry<'a> {
pub binding: u32,
pub resource: Resource<'a>,
}
#[derive(Clone, Copy)]
pub struct BindGroupDesc<'a> {
pub layout: &'a BindGroupLayout,
pub entries: &'a [BindEntry<'a>],
}
#[derive(Clone, Copy, Debug)]
pub struct BindlessDesc {
pub textures: u32,
pub samplers: u32,
pub buffers: u32,
pub storage_textures: u32,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Constant {
pub id: u32,
pub value: f64,
}
#[derive(Clone, Copy)]
pub struct ShaderStage<'a> {
pub module: &'a ShaderModule,
pub entry: &'a str,
pub constants: &'a [Constant],
}
impl<'a> ShaderStage<'a> {
pub const fn new(module: &'a ShaderModule, entry: &'a str) -> ShaderStage<'a> {
ShaderStage { module, entry, constants: &[] }
}
}
#[derive(Clone, Copy)]
pub struct ComputePipelineDesc<'a> {
pub layout: &'a PipelineLayout,
pub shader: ShaderStage<'a>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Topology {
Points,
Lines,
LineStrip,
Triangles,
TriangleStrip,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Cull {
None,
Front,
Back,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum VertexFormat {
F32,
F32x2,
F32x3,
F32x4,
U32,
U32x2,
U32x4,
Unorm8x4,
}
#[derive(Clone, Copy, Debug)]
pub struct VertexAttribute {
pub location: u32,
pub format: VertexFormat,
pub offset: u32,
}
#[derive(Clone, Copy)]
pub struct VertexBufferLayout<'a> {
pub stride: u32,
pub per_instance: bool,
pub attributes: &'a [VertexAttribute],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum BlendFactor {
Zero,
One,
Src,
OneMinusSrc,
Dst,
OneMinusDst,
SrcAlpha,
OneMinusSrcAlpha,
DstAlpha,
OneMinusDstAlpha,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum BlendOp {
Add,
Subtract,
ReverseSubtract,
Min,
Max,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BlendComponent {
pub src: BlendFactor,
pub dst: BlendFactor,
pub op: BlendOp,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Blend {
pub color: BlendComponent,
pub alpha: BlendComponent,
}
impl Blend {
pub const PREMULTIPLIED: Blend = Blend {
color: BlendComponent { src: BlendFactor::One, dst: BlendFactor::OneMinusSrcAlpha, op: BlendOp::Add },
alpha: BlendComponent { src: BlendFactor::One, dst: BlendFactor::OneMinusSrcAlpha, op: BlendOp::Add },
};
pub const ALPHA: Blend = Blend {
color: BlendComponent { src: BlendFactor::SrcAlpha, dst: BlendFactor::OneMinusSrcAlpha, op: BlendOp::Add },
alpha: BlendComponent { src: BlendFactor::One, dst: BlendFactor::OneMinusSrcAlpha, op: BlendOp::Add },
};
pub const ADD: Blend = Blend {
color: BlendComponent { src: BlendFactor::One, dst: BlendFactor::One, op: BlendOp::Add },
alpha: BlendComponent { src: BlendFactor::One, dst: BlendFactor::One, op: BlendOp::Add },
};
}
#[derive(Clone, Copy, Debug)]
pub struct ColorTarget {
pub format: Format,
pub blend: Option<Blend>,
pub write_mask: u32,
}
#[derive(Clone, Copy, Debug)]
pub struct DepthState {
pub format: Format,
pub write: bool,
pub compare: Compare,
}
#[derive(Clone, Copy)]
pub struct RenderPipelineDesc<'a> {
pub layout: &'a PipelineLayout,
pub vertex: ShaderStage<'a>,
pub fragment: Option<ShaderStage<'a>>,
pub buffers: &'a [VertexBufferLayout<'a>],
pub topology: Topology,
pub cull: Cull,
pub front_cw: bool,
pub targets: &'a [ColorTarget],
pub depth: Option<DepthState>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Load {
Keep,
Clear,
Discard,
}
#[derive(Clone, Copy)]
pub struct ColorAttachment<'a> {
pub view: &'a TextureView,
pub load: Load,
pub clear: [f32; 4],
pub store: bool,
pub before: Layout,
pub after: Layout,
}
#[derive(Clone, Copy)]
pub struct DepthAttachment<'a> {
pub view: &'a TextureView,
pub load: Load,
pub clear: f32,
pub store: bool,
pub before: Layout,
pub after: Layout,
}
#[derive(Clone, Copy)]
pub struct RenderPassDesc<'d, 'a> {
pub color: &'d [ColorAttachment<'a>],
pub depth: Option<DepthAttachment<'a>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum IndexFormat {
U16,
U32,
}
#[derive(Clone, Copy)]
pub struct TextureRegion<'a> {
pub texture: &'a Texture,
pub mip: u32,
pub origin: [u32; 3],
pub size: [u32; 3],
pub base_layer: u32,
pub layers: u32,
}
impl<'a> TextureRegion<'a> {
pub fn mip(texture: &'a Texture, mip: u32) -> TextureRegion<'a> {
let s = |v: u32| (v >> mip).max(1);
TextureRegion {
texture,
mip,
origin: [0; 3],
size: [s(texture.width()), s(texture.height()), s(texture.depth())],
base_layer: 0,
layers: texture.layers(),
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct DataLayout {
pub offset: u64,
pub bytes_per_row: u32,
pub rows_per_image: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PresentMode {
Fifo,
Mailbox,
Immediate,
}
#[derive(Clone, Copy, Debug)]
pub struct SwapchainDesc {
pub present: PresentMode,
pub srgb: bool,
pub copy_src: bool,
pub frames_in_flight: u32,
pub min_images: u32,
}
impl SwapchainDesc {
pub const DEFAULT: SwapchainDesc =
SwapchainDesc { present: PresentMode::Fifo, srgb: false, copy_src: false, frames_in_flight: 2, min_images: 0 };
}