Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

texture! and sampler!

Declare texture and sampler bindings.

texture!

#![allow(unused)]
fn main() {
texture!(group(N), binding(M), NAME: TextureKind<SampleType>);
texture!(group(N), binding(M), NAME: Texture2D<f32>, unfilterable);
}

Generates:

@group(N) @binding(M) var NAME: TextureKind<SampleType>;

Texture Kinds

KindDepth variant
Texture1D
Texture2DTextureDepth2D
Texture2DArrayTextureDepth2DArray
Texture3D
TextureCubeTextureDepthCube
TextureCubeArrayTextureDepthCubeArray
TextureMultisampled2D

The sample type for color textures is typically <f32>. Depth textures need no sample type parameter.

Unfilterable Textures

Some texture formats are not filterable on the GPU — R32Float, Rg32Float, and other 32-bit float formats. Bind group layouts must declare this, or binding such a texture fails wgpu validation. Declare it with the unfilterable modifier:

#![allow(unused)]
fn main() {
texture!(group(0), binding(0), HEIGHTS: Texture2D<f32>, unfilterable);
}

The modifier is only valid on <f32>-sampled textures; integer-sampled, depth, and storage textures reject it with a compile error, since their layouts have no filtering flag.

The WGSL output is unchanged — WGSL has no unfilterable variant of texture_2d<f32>. The modifier only sets filterable: false on the generated wgpu bind group layout entry, so binding a non-filterable format (e.g. R32Float) passes validation.

Read texels with texture_load, or sample through a non-filtering sampler declared with the same modifier:

#![allow(unused)]
fn main() {
sampler!(group(0), binding(1), HEIGHTS_SMP: Sampler, unfilterable);
}

The bound wgpu sampler must use FilterMode::Nearest for all filters — a non-filtering layout slot rejects filtering samplers. WGSL output is unchanged: sampler has no non-filtering variant, so the modifier only sets SamplerBindingType::NonFiltering on the generated bind group layout entry.

Storage Textures

Storage textures (texture_storage_* in WGSL) are declared with TextureStorage types. They take two type parameters: a texel format marker and an access mode marker:

#![allow(unused)]
fn main() {
texture!(group(0), binding(0), OUTPUT: TextureStorage2D<Rgba8unorm, Write>);
}
KindWGSL
TextureStorage1D<F, A>texture_storage_1d<format, access>
TextureStorage2D<F, A>texture_storage_2d<format, access>
TextureStorage2DArray<F, A>texture_storage_2d_array<format, access>
TextureStorage3D<F, A>texture_storage_3d<format, access>

Texel format markers (unit structs implementing WgslTexelFormat):

MarkerWGSL formatValue type
Rgba8unormrgba8unormVec4f
Rgba8uintrgba8uintVec4u
Rgba16floatrgba16floatVec4f
R32uintr32uintVec4u
R32floatr32floatVec4f
Rg32floatrg32floatVec4f
Rgba32floatrgba32floatVec4f
Bgra8unormbgra8unormVec4f

Access mode markers:

MarkerWGSLDescription
ReadreadRead-only (load with texture_load_storage)
WritewriteWrite-only (store with texture_store)
ReadWriteread_writeBoth (requires texture_formats_tier1)

The enable texture_formats_tier1; directive is auto-hoisted to the start of the assembled WGSL translation unit when storage textures are present — you don’t need to add it manually.

sampler!

#![allow(unused)]
fn main() {
sampler!(group(N), binding(M), NAME: Sampler);
sampler!(group(N), binding(M), NAME: SamplerComparison);
sampler!(group(N), binding(M), NAME: Sampler, unfilterable);
}

Generates:

@group(N) @binding(M) var NAME: sampler;
@group(N) @binding(M) var NAME: sampler_comparison;

The unfilterable modifier does not change the WGSL output; it sets SamplerBindingType::NonFiltering on the generated wgpu bind group layout entry (see Unfilterable Textures).

Two-Level Binding

Both macros produce a hidden __NAME static plus a visible pub const NAME: &'static ... so the binding can be passed by value. You reference NAME directly in functions:

#![allow(unused)]
fn main() {
#[wgsl]
pub mod texturing {
    use wgsl_rs::std::*;

    texture!(group(0), binding(0), ALBEDO: Texture2D<f32>);
    sampler!(group(0), binding(1), LIN: Sampler);

    #[fragment]
    pub fn fs_main(
        #[location(0)] uv: Vec2f,
    ) -> Vec4f {
        texture_sample(ALBEDO, LIN, uv)
    }
}
}

Passing to Functions

Texture and sampler bindings are passed by value (no &) — the visible NAME is already a reference:

#![allow(unused)]
fn main() {
pub fn sample_albedo(uv: Vec2f, tex: Texture2D<f32>, smp: Sampler) -> Vec4f {
    texture_sample(tex, smp, uv)
}
}

Notes

  • SamplerComparison is used with textureSampleCompare and textureSampleCompareLevel for shadow maps.
  • Pair each texture with its sampler; binding numbers must not collide within a group.