Zero-Cost Slicing (Slice & SliceMut)
While GpuVec<T> owns physical allocations in VRAM, algorithms frequently need to operate on sub-ranges of data (such as image tiles, particle partitions, or sub-matrices) without copying memory.
Enki provides Slice<'a, T> (read-only) and SliceMut<'a, T> (exclusive mutable) to enable zero-allocation, borrowed sub-views over existing GPU buffers.
#![allow(unused)]
fn main() {
use enki::*;
let mut buffer = gpu_vec![0u32; 1024];
// Zero-allocation borrowed sub-views
let first_half: Slice<u32> = buffer.slice(..512);
let mut second_half: SliceMut<u32> = buffer.slice_mut(512..);
}
1. Zero-Cost Physical Address Arithmetic
Unlike traditional graphics APIs where creating a sub-buffer requires creating new Vulkan buffer views (VkBufferView) or allocating fresh descriptor sets, slicing in Enki is a purely mathematical zero-cost abstraction:
Parent GpuVec (Base BDA: 0x7F00_0000, Stride: 4 bytes)
┌──────────────────────────────────────┬──────────────────────────────────────┐
│ Elements [0..512] │ Elements [512..1024] │
└──────────────────────────────────────┴──────────────────────────────────────┘
▲ ▲
│ │
Slice A: 0x7F00_0000 Slice B: 0x7F00_0000 + (512 * 4)
(Device Address: 0x7F00_0000) (Device Address: 0x7F00_0800)
When you slice a container:
device_address = parent_bda + (element_offset * stride)
Creating, cloning, or passing a slice performs zero GPU allocations, zero syscalls, and zero driver submissions. It simply passes a 16-byte fat pointer (bda: u64, count: u64) into the dispatch ingress.
2. Immutable Slices (Slice<'a, T>)
Slice<'a, T> represents a read-only view into a sub-range of GPU memory:
- Implements
Clone: Multiple read-only slices derived from the same parent buffer can freely overlap and exist simultaneously. - Kernel Binding: In a
#[nam]function, passing&Slice<T>binds as an indexed global slice:#![allow(unused)] fn main() { #[nam] fn read_lookup(_space: &Space, table: &[f32], target: &mut f32) { *target = table[42]; // Free random read access } }
Slicing Sub-ranges
Slices can be recursively partitioned into smaller sub-views:
#![allow(unused)]
fn main() {
let view = buffer.slice(100..500);
let sub_view = view.slice(0..50); // Covers elements 100..150 of parent
let (left, right) = view.split_at(200);
}
3. Mutable Slices (SliceMut<'a, T>)
SliceMut<'a, T> enforces Rust’s exclusive write semantics over a VRAM sub-range:
- Does NOT implement
Clone: Enforces single-writer exclusivity. - Reborrowing: A
SliceMutcan be reborrowed as an immutableSlicevia.as_slice()or consumed via.into_slice(). - Kernel Binding: In a
#[nam], passing&mut SliceMut<T>binds as a global read-write slice&mut [T].
Disjoint Partitioning with split_at_mut
To safely divide a mutable buffer into two independent mutable slices, use split_at_mut:
#![allow(unused)]
fn main() {
let mut buffer = gpu_vec![0.0f32; 1000];
let mut slice = buffer.as_mut_slice();
// Guarantees mathematically disjoint ranges [0..500) and [500..1000)
let (mut left, mut right) = slice.split_at_mut(500);
}
4. Host Manipulation Methods
Both slice types support direct data inspection and modification from the host CPU. Like GpuVec, host readbacks automatically wait on the GPU timeline if the underlying buffer is in-flight:
#![allow(unused)]
fn main() {
let mut slice = buffer.slice_mut(0..100);
// Set single element
slice.set(0, 42.0);
// Bulk upload from CPU slice
let host_data = vec![1.0; 100];
slice.copy_from_slice(&host_data);
// Read back to host Vec
let cpu_copy: Vec<f32> = slice.to_vec();
// Clone into independent physical VRAM allocation
let separate_gpu_vec: GpuVec<f32> = slice.to_gpu_vec();
}
Phase Rule: Slices track the execution phase of their underlying physical allocation. Calling
.to_vec()or.copy_from_slice()on a slice inside an activeenki.flowblock halts execution witherror[E2001].
5. Runtime Bounds Verification (error[E2005])
Attempting to slice beyond the element capacity of a container halts execution before command buffers are recorded:
error[E2005]: GPU slice index out of bounds
--> src/main.rs:18:28
|
18 | let sub = buffer.slice(500..2000);
| ^^^^^^^^^^ invalid slice range specified here
|
= note: GPU slice bounds must reside strictly within the allocated VRAM buffer limits.
= help: verify that range indices satisfy `start <= end` and `end <= slice.len()`.