| title | Textures and Samplers | |||||
|---|---|---|---|---|---|---|
| document_id | texture-sampler-spec-2025-10-30 | |||||
| status | draft | |||||
| created | 2025-10-30T00:00:00Z | |||||
| last_updated | 2026-02-07T00:00:00Z | |||||
| version | 0.4.0 | |||||
| engine_workspace_version | 2023.1.30 | |||||
| wgpu_version | 26.0.1 | |||||
| shader_backend_default | naga | |||||
| winit_version | 0.29.10 | |||||
| repo_commit | 71256389b9efe247a59aabffe9de58147b30669d | |||||
| owners |
|
|||||
| reviewers |
|
|||||
| tags |
|
Summary
- Introduces first-class 2D and 3D sampled textures and samplers with a builder-based application programming interface and platform abstraction.
- Rationale: Texture sampling is foundational for images, sprites, materials, volume data, and user interface elements; this specification establishes a portable surface to upload image data to the graphics processing unit (GPU) and sample in fragment shaders.
- Provide 2D and 3D color textures with initial data upload from the central processing unit (CPU) to the GPU.
- Provide samplers with common filtering and address modes (including W for 3D).
- Integrate textures and samplers into bind group layouts and bind groups with explicit view dimensions.
- Expose a stable high-level application programming interface in
lambda-rsbacked bywgputhroughlambda-rs-platform. - Support sRGB and linear color formats appropriate for filtering.
- Storage textures, depth textures, cube maps, and 2D/3D array textures are out of scope for this revision and tracked under “Future Extensions”.
- Multisampled textures and render target (color attachment) workflows are out of scope for this revision and tracked under “Future Extensions”.
- Mipmap generation (automatic or offline); only level 0 is supported in this revision. See “Future Extensions”.
- Partial sub-rect updates; uploads are whole-image at creation time in this revision. See “Future Extensions”.
- Sampled texture: A read-only texture resource accessed in shaders with a separate sampler.
- Texture dimension: The physical dimensionality of the texture storage
(
D2,D3). - View dimension: The dimensionality exposed to shader sampling
(
D2,D3). - Texture view: A typed view of a texture used for binding; by default a full view is created for the specified dimension.
- Sampler: A state object defining filtering and address (wrap) behavior.
- Mipmap level (mip): A downscaled level of a texture. Level 0 is the base.
- sRGB: A gamma-encoded color format (
*Srgb) with conversion to linear space on sampling.
-
Platform (
lambda-rs-platform)- Wraps
wgpu::Texture,wgpu::TextureView, andwgpu::Samplerwith builders that perform validation and queue uploads withwgpu::Queue::write_texture. - Maps public
TextureFormat,TextureDimension,ViewDimension, filter and address enums towgputypes. - Owns raw
wgpuhandles; high-level layer interacts via opaque wrappers.
- Wraps
-
High level (
lambda-rs)- Public builders:
TextureBuilderandSamplerBuilderinlambda::render::texture. - Bind group integration: extend existing builders to declare and bind sampled textures and samplers alongside uniform buffers. Layout entries specify view dimension explicitly or use 2D shorthands.
- Keep
wgpudetails internal; expose stable enums and validation errors.
- Public builders:
Data flow (creation and use):
CPU pixels --> TextureBuilder (platform upload + validation)
--> GPU Texture + default TextureView
SamplerBuilder --> GPU Sampler
BindGroupLayoutBuilder: uniform | sampled_texture | sampler
BindGroupBuilder: buffer | texture(view) | sampler
Render pass: SetPipeline -> SetBindGroup -> Draw
-
Platform layer (
lambda-rs-platform, modulelambda_platform::wgpu::texture)- Types:
Texture,Sampler(own rawwgpuhandles). A default full-range view is created and owned byTexturefor binding. - Enums:
TextureFormat,TextureDimension(D2,D3),ViewDimension(D2,D3),FilterMode,AddressMode. - Builders:
TextureBuildernew_2d(format: TextureFormat)— convenience fordimension = D2.new_3d(format: TextureFormat)— convenience fordimension = D3.with_size(width: u32, height: u32)— required for 2D textures.with_size_3d(width: u32, height: u32, depth: u32)— required for 3D.with_data(pixels: &[u8])— upload full level 0; platform pads rows to satisfybytes_per_rowand setsrows_per_imagefor 3D.with_usage(binding: bool, copy_dst: bool)— controlsTEXTURE_BINDINGandCOPY_DST(default true for both).with_label(label: &str)build(&mut RenderContext)->Result<Texture, Error>
SamplerBuildernew()- Filtering:
nearest(),linear(); shorthandsnearest_clamp(),linear_clamp(). - Addressing:
with_address_mode_u(mode),with_address_mode_v(mode),with_address_mode_w(mode); defaultClampToEdge. - Mip filtering and level-of-detail:
with_lod(min, max),with_mip_filter(mode)(defaultNearest). with_label(label: &str)build(&mut RenderContext)->Sampler
- Types:
-
High-level layer (
lambda-rs, modulelambda::render::texture)- Mirrors platform builders and enums; returns high-level
TextureandSamplerwrappers with nowgpuexposure. - Adds convenience methods consistent with the repository style (for example,
SamplerBuilder::linear_clamp()). Usage toggles MAY be exposed at the high level or fixed to stable defaults.
- Mirrors platform builders and enums; returns high-level
-
Bind group integration (
lambda::render::bind)BindGroupLayoutBuilderadditions:with_sampled_texture(binding: u32)— 2D, filterable float; shorthand, default visibility Fragment.with_sampled_texture_dim(binding: u32, dim: ViewDimension, visibility: BindingVisibility)— explicit dimension (D2orD3), float sample type, not multisampled.with_sampler(binding: u32)— filtering sampler type; default visibility Fragment.
BindGroupBuilderadditions:with_texture(binding: u32, texture: &Texture)— uses the default view that matches the texture’s dimension.with_sampler(binding: u32, sampler: &Sampler).
-
Texture creation
- The builder constructs a texture with
mip_level_count = 1,sample_count = 1, anddimensionequal toD2orD3. - Usage includes
TEXTURE_BINDINGandCOPY_DSTby default. - When
with_datais provided, the upload MUST cover the entire level 0. The platform layer pads rows to abytes_per_rowmultiple of 256 bytes and setsrows_per_imagefor 3D before callingQueue::write_texture. - A default full-range
TextureViewis created and retained by the texture withViewDimensionmatching the texture dimension.
- The builder constructs a texture with
-
Sampler creation
- Defaults:
FilterMode::Nearestfor min/mag/mip,ClampToEdgefor all address modes,lod_min_clamp = 0.0,lod_max_clamp = 32.0. - Shorthands (
nearest_clamp,linear_clamp) set min/mag filter and all address modes toClampToEdge.
- Defaults:
-
Binding
with_sampled_texturedeclares a 2D filterable float texture binding at the specified index with Fragment visibility; shaders declaretexture_2d<f32>.with_sampled_texture_dimdeclares a texture binding with explicit view dimension and visibility; shaders declaretexture_2d<f32>ortexture_3d<f32>.with_samplerdeclares a filtering sampler binding at the specified index with Fragment visibility; shaders declaresamplerand combine with the texture in sampling calls.
-
Limits and dimensions
- Width and height MUST be > 0 and ≤ the corresponding device limit for the chosen dimension.
- 2D check:
≤ limits.max_texture_dimension_2d. - 3D check:
≤ limits.max_texture_dimension_3dfor each axis. - Only textures with
mip_level_count = 1are allowed in this revision.
-
Format and sampling
TextureFormatMUST map to a filterable, color texture format inwgpu.- If a sampled texture is declared, the sample type is float with
filterable = true; incompatible formats MUST be rejected at build time.
-
Upload data
- For 2D,
with_datalength MUST equalwidth * height * bytes_per_pixelof the chosen format for tightly packed rows. - For 3D,
with_datalength MUST equalwidth * height * depth * bytes_per_pixelfor tightly packed input. - The platform layer performs padding to satisfy the 256-byte
bytes_per_rowrequirement and setsrows_per_imageappropriately; mismatched lengths or overflows MUST return an error before encoding. - If
with_datais used, usage MUST includeCOPY_DST. An implementation MAY automatically addCOPY_DSTfor build-time uploads to avoid errors.
- For 2D,
-
Bindings
with_textureandwith_samplerMUST reference resources compatible with the corresponding layout entries, including view dimension; violations surface as validation errors fromwgpuduring bind group creation or render pass encoding.
-
Alignment and layout
bytes_per_rowMUST be a multiple of 256 bytes forwrite_texture.- The platform layer pads each source row to meet this requirement and sets
rows_per_imagefor 3D writes.
-
Supported formats (initial)
Rgba8Unorm,Rgba8UnormSrgb. Additional filterable color formats MAY be added in future revisions.
-
Usage flags
- Textures created for sampling MUST include
TEXTURE_BINDING. When initial data is uploaded at creation,COPY_DSTSHOULD be included.
- Textures created for sampling MUST include
- Upload efficiency
- Use a single
Queue::write_textureper texture to minimize driver overhead. Rationale: Batching uploads reduces command submission costs.
- Use a single
- Large volume data
- Prefer
copy_buffer_to_texturefor very large 3D uploads to reduce CPU staging and allow asynchronous transfers. Rationale: Improves throughput for multi-megabyte volumes.
- Prefer
- Filtering choice
- Prefer
Linearfiltering for downscaled content; useNearestfor pixel art. Rationale: Matches visual expectations and avoids unintended blurring.
- Prefer
- Address modes
- Use
ClampToEdgefor user interface and sprites;Repeatfor tiled backgrounds. Rationale: Prevents sampling beyond image borders where not intended.
- Use
-
Functionality
- Feature flags defined (if applicable) (N/A)
- 2D texture creation and upload
- 3D texture creation and upload
- Sampler creation (U, V, W addressing)
- Bind group layout and binding for texture + sampler (2D/3D)
-
API Surface
- Public builders and enums in
lambda-rs - Platform wrappers in
lambda-rs-platform - Backwards compatibility assessed
- Public builders and enums in
-
Validation and Errors
- Dimension and limit checks (2D/3D)
- Format compatibility checks
- Data length and row padding/rows-per-image validation
-
Performance
- Upload path reasoned and documented
- Memory footprint characterized for common formats
-
Documentation and Examples
- User-facing docs updated
- Minimal example rendering a textured quad (equivalent)
- Migration notes (if applicable) (N/A)
-
Extensions (Planned)
- Mipmapping: generation,
mip_level_count, mip view selection - Texture arrays and cube maps: array/cube view dimensions and layout entries
- Storage textures: read-write bindings and storage-capable formats
- Render-target textures (color):
RENDER_ATTACHMENTusage and MSAA resolve - Additional color formats:
R8Unorm,Rg8Unorm,Rgba16Float, others - Compressed textures: BCn/ASTC/ETC via KTX2/BasisU
- Anisotropic filtering and border color: anisotropy and
ClampToBorder - Sub-rect updates and streaming: partial
write_texture, buffer-to-texture - Alternate view formats:
view_formatsand view creation over subsets - Compare samplers (shadow sampling): comparison binding type and sampling
- LOD bias and per-sample control: expose LOD bias and overrides
- Mipmapping: generation,
-
Unit Tests
- Compute
bytes_per_rowpadding and data size validation (2D/3D). - Compute
rows_per_imagefor 3D uploads. - Format mapping from
TextureFormattowgpu::TextureFormat. - Commands:
cargo test -p lambda-rs-platform -- --nocapture
- Compute
-
Integration Tests
- Render a quad sampling a test checkerboard (2D); verify no validation errors and expected color histogram bounds.
- Render a slice of a 3D texture by fixing W (for example,
uvw.z = 0.5) in the fragment shader; verify sampling and addressing. - Commands:
cargo test --workspace
-
Manual Checks (if necessary)
- Run example that draws a textured quad. Confirm correct sampling with
NearestandLinearand correct addressing withClampToEdgeandRepeat.
- Run example that draws a textured quad. Confirm correct sampling with
- None. This is a new feature area. Future revisions MAY extend formats, dimensions, and render-target usage.
Rust (2D high level)
use lambda::render::texture::{TextureBuilder, SamplerBuilder, TextureFormat};
use lambda::render::bind::{BindGroupLayoutBuilder, BindGroupBuilder, BindingVisibility};
use lambda::render::command::RenderCommand as RC;
let texture2d = TextureBuilder::new_2d(TextureFormat::Rgba8UnormSrgb)
.with_size(512, 512)
.with_data(&pixels)
.with_label("albedo")
.build(render_context.gpu())?;
let sampler = SamplerBuilder::new()
.linear_clamp()
.with_label("albedo-sampler")
.build(render_context.gpu());
let layout2d = BindGroupLayoutBuilder::new()
.with_uniform(0, BindingVisibility::VertexAndFragment)
.with_sampled_texture(1) // 2D shorthand
.with_sampler(2)
.build(render_context.gpu());
let group2d = BindGroupBuilder::new()
.with_layout(&layout2d)
.with_uniform(0, &uniform_buffer)
.with_texture(1, &texture2d)
.with_sampler(2, &sampler)
.build(render_context.gpu());
RC::SetBindGroup { set: 0, group: group_id, dynamic_offsets: vec![] };WGSL snippet (2D)
@group(0) @binding(1) var texture_color: texture_2d<f32>;
@group(0) @binding(2) var sampler_color: sampler;
@fragment
fn fs_main(in_uv: vec2<f32>) -> @location(0) vec4<f32> {
let color = textureSample(texture_color, sampler_color, in_uv);
return color;
}Rust (3D high level)
use lambda::render::texture::{TextureBuilder, TextureFormat};
use lambda::render::bind::{BindGroupLayoutBuilder, BindGroupBuilder};
use lambda::render::texture::ViewDimension;
use lambda::render::bind::BindingVisibility;
let texture3d = TextureBuilder::new_3d(TextureFormat::Rgba8Unorm)
.with_size_3d(128, 128, 64)
.with_data(&voxels)
.with_label("volume")
.build(render_context.gpu())?;
let layout3d = BindGroupLayoutBuilder::new()
.with_sampled_texture_dim(1, ViewDimension::D3, BindingVisibility::Fragment)
.with_sampler(2)
.build(render_context.gpu());
let group3d = BindGroupBuilder::new()
.with_layout(&layout3d)
.with_texture(1, &texture3d)
.with_sampler(2, &sampler)
.build(render_context.gpu());WGSL snippet (3D)
@group(0) @binding(1) var volume_tex: texture_3d<f32>;
@group(0) @binding(2) var volume_samp: sampler;
@fragment
fn fs_main(in_uv: vec2<f32>) -> @location(0) vec4<f32> {
// Sample a middle slice at z = 0.5
let uvw = vec3<f32>(in_uv, 0.5);
return textureSample(volume_tex, volume_samp, uvw);
}- 2025-12-15 (v0.4.0) — Update example code to use
render_context.gpu()for all builder calls. - 2025-11-10 (v0.3.1) — Merge "Future Extensions" into the Requirements Checklist and mark implemented status; metadata updated.
- 2025-11-09 (v0.3.0) — Clarify layout visibility parameters; make sampler
build infallible; correct
BindingVisibilityusage in examples; add “Future Extensions” with planned texture features; metadata updated. - 2025-10-30 (v0.2.0) — Add 3D textures, explicit dimensions in layout and builders, W address mode, validation and examples updated.
- 2025-10-30 (v0.1.0) — Initial draft.
Moved into the Requirements Checklist under “Extensions (Planned)”.