Allow Image to retain shared immutable pixel storage
## Motivation
We are developing a Bevy game whose precompiled assets are stored in read-only, mmap-backed rkyv archives. Texture payloads are already in GPU-ready layouts and can be exposed as immutable `bytes::Bytes` slices without allocating or copying their contents. Bevy's current `Image.data: Option<Vec<u8>>` requires an additional allocator-owned copy before upload.
PR #18751 explored replacing `Vec<u8>` with `Bytes`. A complete replacement, however, makes Bevy's mutable-image APIs less natural. The storage distinction can instead be encapsulated behind a small copy-on-write type.
## Proposed API
The exact names are illustrative, but the important properties are separate owned and shared representations, immutable slice access for rendering, and explicit copy-on-write mutable access:
```rust
#[derive(Clone, Debug)]
pub enum ImageData {
Owned(Vec<u8>),
Shared(bytes::Bytes),
}
impl ImageData {
pub fn as_slice(&self) -> &[u8] {
match self {
Self::Owned(data) => data,
Self::Shared(data) => data,
}
}
pub fn make_mut(&mut self) -> &mut [u8] {
match self {
Self::Owned(data) => data,
Self::Shared(data) => {
let owned = data.to_vec();
*self = Self::Owned(owned);
self.make_mut()
}
}
}
}
```
`Image` would retain `Option<ImageData>` while exposing storage through methods rather than requiring callers to depend on either container. Existing decoded, procedural, and frequently modified images would use `Owned`. Immutable assets could use `Shared`, including `Bytes::from_owner(mmap)` and zero-copy subranges.
Existing constructors could continue accepting `Vec<u8>` through `Into<ImageData>`. Read-only access would return `&[u8]`, and pixel-editing APIs would call `make_mut()`. Cloning `Owned` data retains the current deep-copy behavior; cloning `Shared` data is a cheap shared-owner clone. The first mutation of shared data performs one explicit copy, after which further mutations use the owned buffer.
## Rendering
GPU preparation already consumes a byte slice, so both variants follow the same wgpu upload path:
```rust
if let Some(data) = image.data() {
render_device.create_texture_with_data(
render_queue,
&image.texture_descriptor,
image.data_order,
data,
);
}
```
This removes the otherwise mandatory mmap-to-`Vec` copy. It does not attempt to remove the required CPU-to-GPU transfer or replace Bevy's renderer.
This storage abstraction may fit naturally with the field encapsulation proposed in #11888. It preserves ordinary mutable-image behavior while allowing asset loaders to retain immutable external storage safely.
0 条评论