Overview
Buffer defaults bias toward CPU-visible memory (adds COPY_DST), even for static vertex buffers that are uploaded once and never modified. This results in suboptimal memory placement on discrete GPUs and unnecessary overhead. Static geometry should default to DEVICE_LOCAL memory, with CPU-visible properties reserved for buffers that require CPU updates (uniform streams, dynamic meshes).
Current State
Properties::default() returns CPU_VISIBLE:
// crates/lambda-rs/src/render/buffer.rsimplDefaultforProperties{fndefault() -> Self{Properties::CPU_VISIBLE}}Additionally, build_from_mesh hardcodes CPU_VISIBLE for vertex buffers created from meshes:
// crates/lambda-rs/src/render/buffer.rspubfnbuild_from_mesh(mesh:&Mesh,gpu:&Gpu,) -> Result<Buffer,&'staticstr>{let builder = Self::new();return builder
.with_length(std::mem::size_of_val(mesh.vertices())).with_usage(Usage::VERTEX).with_properties(Properties::CPU_VISIBLE).with_buffer_type(BufferType::Vertex).build(gpu, mesh.vertices().to_vec());}This means static meshes that are loaded once and never modified are placed in CPU-visible memory, which on discrete GPUs means system RAM accessed over PCIe rather than fast VRAM.
Scope
Goals:
- Default
Properties to DEVICE_LOCAL for optimal GPU performance - Update
build_from_mesh to use DEVICE_LOCAL - Provide clear guidance on when to use
CPU_VISIBLE vs DEVICE_LOCAL
Non-Goals:
- Automatic memory tier selection based on update frequency
- Staging buffer abstraction for uploads (future work)
Proposed API
// crates/lambda-rs/src/render/buffer.rsimplDefaultforProperties{fndefault() -> Self{Properties::DEVICE_LOCAL// Changed from CPU_VISIBLE}}// build_from_mesh updated to use DEVICE_LOCALpubfnbuild_from_mesh(mesh:&Mesh,gpu:&Gpu,) -> Result<Buffer,&'staticstr>{let builder = Self::new();return builder
.with_length(std::mem::size_of_val(mesh.vertices())).with_usage(Usage::VERTEX).with_properties(Properties::DEVICE_LOCAL)// Changed from CPU_VISIBLE.with_buffer_type(BufferType::Vertex).build(gpu, mesh.vertices().to_vec());}Usage guidance:
// Static geometry (loaded once, never updated) - use default DEVICE_LOCALlet static_mesh_buffer = BufferBuilder::new().with_usage(Usage::VERTEX).build(&gpu, vertices)?;// Dynamic geometry (updated per-frame) - explicit CPU_VISIBLElet dynamic_buffer = BufferBuilder::new().with_usage(Usage::VERTEX).with_properties(Properties::CPU_VISIBLE).build(&gpu, initial_vertices)?;// Uniform buffers (updated frequently) - explicit CPU_VISIBLElet uniform_buffer = BufferBuilder::new().with_usage(Usage::UNIFORM).with_properties(Properties::CPU_VISIBLE).build(&gpu, uniform_data)?;
Acceptance Criteria
Affected Crates
lambda-rs
Notes
- This is a breaking change for users who rely on implicit
CPU_VISIBLE for buffers they update from the CPU - Migration: add explicit
.with_properties(Properties::CPU_VISIBLE) for dynamic buffers - Future work: staging buffer helper for efficient DEVICE_LOCAL uploads
Overview
Buffer defaults bias toward CPU-visible memory (adds
COPY_DST), even for static vertex buffers that are uploaded once and never modified. This results in suboptimal memory placement on discrete GPUs and unnecessary overhead. Static geometry should default toDEVICE_LOCALmemory, with CPU-visible properties reserved for buffers that require CPU updates (uniform streams, dynamic meshes).Current State
Properties::default()returnsCPU_VISIBLE:Additionally,
build_from_meshhardcodesCPU_VISIBLEfor vertex buffers created from meshes:This means static meshes that are loaded once and never modified are placed in CPU-visible memory, which on discrete GPUs means system RAM accessed over PCIe rather than fast VRAM.
Scope
Goals:
PropertiestoDEVICE_LOCALfor optimal GPU performancebuild_from_meshto useDEVICE_LOCALCPU_VISIBLEvsDEVICE_LOCALNon-Goals:
Proposed API
Usage guidance:
Acceptance Criteria
Properties::default()returnsDEVICE_LOCALbuild_from_meshusesDEVICE_LOCALDEVICE_LOCALvsCPU_VISIBLEtradeoffsCPU_VISIBLECPU_VISIBLEAffected Crates
lambda-rs
Notes
CPU_VISIBLEfor buffers they update from the CPU.with_properties(Properties::CPU_VISIBLE)for dynamic buffers