Skip to content

Commit 68c19de

Browse files
authored
Readback: Add support for texture depth/array layers (#17479)
# Objective Fixes #16963 ## Solution I am - no pun intended - somewhat out of my depth here but this worked in my testing. The validation error is gone and the data read from the GPU looks sensible. I'd greatly appreciate if somebody more familiar with the matter could double-check this. ## References Relevant documentation in [WebGPU](https://gpuweb.github.io/gpuweb/#gputexelcopybufferlayout) and [wgpu](https://github.com/gfx-rs/wgpu/blob/v23/wgpu-types/src/lib.rs#L6350). ## Testing <details><summary>Example code for testing</summary> <p> ```rust use bevy::{ image::{self as bevy_image, TextureFormatPixelInfo}, prelude::*, render::{ render_asset::RenderAssetUsages, render_resource::{ Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, }, }, }; fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins) .add_systems(Startup, setup) .add_systems(Update, readback_system); app.run(); } #[derive(Resource)] struct ImageResource(Handle<Image>); const TEXTURE_HEIGHT: u32 = 64; const TEXTURE_WIDTH: u32 = 32; const TEXTURE_LAYERS: u32 = 4; const FORMAT: TextureFormat = TextureFormat::Rgba8Uint; fn setup(mut commands: Commands, mut images: ResMut<Assets<Image>>) { let layer_pixel_count = (TEXTURE_WIDTH * TEXTURE_HEIGHT) as usize; let layer_size = layer_pixel_count * FORMAT.pixel_size(); let data: Vec<u8> = (0..TEXTURE_LAYERS as u8) .flat_map(|layer| (0..layer_size).map(move |_| layer)) .collect(); let image_size = data.len(); println!("{image_size}"); let image = Image { data, texture_descriptor: TextureDescriptor { label: Some("image"), size: Extent3d { width: TEXTURE_WIDTH, height: TEXTURE_HEIGHT, depth_or_array_layers: TEXTURE_LAYERS, }, mip_level_count: 1, sample_count: 1, dimension: TextureDimension::D2, format: FORMAT, usage: TextureUsages::COPY_DST | TextureUsages::COPY_SRC, view_formats: &[], }, sampler: bevy_image::ImageSampler::Default, texture_view_descriptor: None, asset_usage: RenderAssetUsages::RENDER_WORLD, }; commands.insert_resource(ImageResource(images.add(image))); } fn readback_system( mut commands: Commands, keys: Res<ButtonInput<KeyCode>>, image: Res<ImageResource>, ) { if !keys.just_pressed(KeyCode::KeyR) { return; } commands .spawn(bevy::render::gpu_readback::Readback::Texture( image.0.clone(), )) .observe( |trigger: Trigger<bevy::render::gpu_readback::ReadbackComplete>, mut commands: Commands| { info!("readback complete"); println!("{:#?}", &trigger.0); commands.entity(trigger.observer()).despawn(); }, ); } ``` </p> </details>
1 parent 56aa902 commit 68c19de

File tree

2 files changed

+29
-22
lines changed

2 files changed

+29
-22
lines changed

crates/bevy_render/src/gpu_readback.rs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -240,16 +240,11 @@ fn prepare_buffers(
240240
match readback {
241241
Readback::Texture(image) => {
242242
if let Some(gpu_image) = gpu_images.get(image) {
243-
let layout = layout_data(
244-
gpu_image.size.width,
245-
gpu_image.size.height,
246-
gpu_image.texture_format,
247-
);
243+
let layout = layout_data(gpu_image.size, gpu_image.texture_format);
248244
let buffer = buffer_pool.get(
249245
&render_device,
250246
get_aligned_size(
251-
gpu_image.size.width,
252-
gpu_image.size.height,
247+
gpu_image.size,
253248
gpu_image.texture_format.pixel_size() as u32,
254249
) as u64,
255250
);
@@ -355,20 +350,32 @@ pub(crate) const fn align_byte_size(value: u32) -> u32 {
355350
}
356351

357352
/// Get the size of a image when the size of each row has been rounded up to [`wgpu::COPY_BYTES_PER_ROW_ALIGNMENT`].
358-
pub(crate) const fn get_aligned_size(width: u32, height: u32, pixel_size: u32) -> u32 {
359-
height * align_byte_size(width * pixel_size)
353+
pub(crate) const fn get_aligned_size(extent: Extent3d, pixel_size: u32) -> u32 {
354+
extent.height * align_byte_size(extent.width * pixel_size) * extent.depth_or_array_layers
360355
}
361356

362357
/// Get a [`ImageDataLayout`] aligned such that the image can be copied into a buffer.
363-
pub(crate) fn layout_data(width: u32, height: u32, format: TextureFormat) -> ImageDataLayout {
358+
pub(crate) fn layout_data(extent: Extent3d, format: TextureFormat) -> ImageDataLayout {
364359
ImageDataLayout {
365-
bytes_per_row: if height > 1 {
360+
bytes_per_row: if extent.height > 1 || extent.depth_or_array_layers > 1 {
366361
// 1 = 1 row
367-
Some(get_aligned_size(width, 1, format.pixel_size() as u32))
362+
Some(get_aligned_size(
363+
Extent3d {
364+
width: extent.width,
365+
height: 1,
366+
depth_or_array_layers: 1,
367+
},
368+
format.pixel_size() as u32,
369+
))
370+
} else {
371+
None
372+
},
373+
rows_per_image: if extent.depth_or_array_layers > 1 {
374+
let (_, block_dimension_y) = format.block_dimensions();
375+
Some(extent.height / block_dimension_y)
368376
} else {
369377
None
370378
},
371-
rows_per_image: None,
372-
..Default::default()
379+
offset: 0,
373380
}
374381
}

crates/bevy_render/src/view/window/screenshot.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -366,8 +366,7 @@ fn prepare_screenshot_state(
366366
let texture_view = texture.create_view(&Default::default());
367367
let buffer = render_device.create_buffer(&wgpu::BufferDescriptor {
368368
label: Some("screenshot-transfer-buffer"),
369-
size: gpu_readback::get_aligned_size(size.width, size.height, format.pixel_size() as u32)
370-
as u64,
369+
size: gpu_readback::get_aligned_size(size, format.pixel_size() as u32) as u64,
371370
usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST,
372371
mapped_at_creation: false,
373372
});
@@ -585,17 +584,18 @@ fn render_screenshot(
585584
texture_view: &wgpu::TextureView,
586585
) {
587586
if let Some(prepared_state) = &prepared.get(entity) {
587+
let extent = Extent3d {
588+
width,
589+
height,
590+
depth_or_array_layers: 1,
591+
};
588592
encoder.copy_texture_to_buffer(
589593
prepared_state.texture.as_image_copy(),
590594
wgpu::ImageCopyBuffer {
591595
buffer: &prepared_state.buffer,
592-
layout: gpu_readback::layout_data(width, height, texture_format),
593-
},
594-
Extent3d {
595-
width,
596-
height,
597-
..Default::default()
596+
layout: gpu_readback::layout_data(extent, texture_format),
598597
},
598+
extent,
599599
);
600600

601601
if let Some(pipeline) = pipelines.get_render_pipeline(prepared_state.pipeline_id) {

0 commit comments

Comments
 (0)