use bevy_asset::{Assets, RenderAssetUsages};
use bevy_camera::{Camera3d, Camera3dDepthTextureUsage};
use bevy_ecs::prelude::{Commands, Component, Entity, On, Query, ResMut, With};
use bevy_math::UVec2;
use bevy_render::gpu_readback::{Readback, ReadbackComplete};
use bevy_render::render_resource::{BufferUsages, TextureUsages};
use bevy_render::renderer::RenderDevice;
use bevy_render::storage::ShaderBuffer;
use bevy_render::sync_world::SyncToRenderWorld;
use crate::depth_copy::DepthCopyTarget;
use crate::target::{TargetSize, unpadded_rows};
pub(crate) const DEPTH_BYTES_PER_PIXEL: usize = 4;
pub(crate) const fn depth_row_stride(width: u32) -> usize {
RenderDevice::align_copy_bytes_per_row(width as usize * DEPTH_BYTES_PER_PIXEL)
}
#[derive(Component, Debug, Clone, Copy, Default)]
pub struct DepthReadback;
#[derive(Component, Debug, Clone, Copy, Default)]
pub struct DepthOcclusion;
#[derive(Component, Debug, Default)]
#[non_exhaustive]
pub struct DepthFrame {
pub depths: Vec<f32>,
pub size: UVec2,
}
#[derive(Component)]
pub(crate) struct DepthTarget {
size: UVec2,
reader: Entity,
}
#[derive(Component)]
pub(crate) struct DepthReaderOf(Entity);
pub(crate) fn sync_depth_targets(
mut buffers: ResMut<Assets<ShaderBuffer>>,
mut cameras: Query<
(Entity, &TargetSize, &mut Camera3d, Option<&DepthTarget>),
With<DepthReadback>,
>,
mut commands: Commands,
) {
for (camera, target, mut camera3d, current) in &mut cameras {
if let Some(usages) = missing_copy_usages(&camera3d) {
camera3d.depth_texture_usages = usages;
}
if current.is_some_and(|depth| depth.size == target.0) {
continue;
}
let handle = buffers.add(create_depth_buffer(target.0));
let reader = match current {
Some(depth) => depth.reader,
None => commands
.spawn((DepthReaderOf(camera), SyncToRenderWorld))
.id(),
};
commands
.entity(reader)
.insert(Readback::buffer(handle.clone()));
commands
.entity(camera)
.insert((
DepthTarget {
size: target.0,
reader,
},
DepthCopyTarget(handle),
))
.insert_if_new(DepthFrame::default());
}
}
fn missing_copy_usages(camera3d: &Camera3d) -> Option<Camera3dDepthTextureUsage> {
let usages = TextureUsages::from_bits_truncate(camera3d.depth_texture_usages.0);
(!usages.contains(TextureUsages::COPY_SRC)).then(|| (usages | TextureUsages::COPY_SRC).into())
}
fn create_depth_buffer(size: UVec2) -> ShaderBuffer {
let stride = depth_row_stride(size.x);
let mut buffer =
ShaderBuffer::with_size(stride * size.y as usize, RenderAssetUsages::default());
buffer.buffer_description.usage = BufferUsages::COPY_DST | BufferUsages::COPY_SRC;
buffer
}
pub(crate) fn store_depth_readback(
complete: On<ReadbackComplete>,
readers: Query<&DepthReaderOf>,
mut cameras: Query<(&DepthTarget, &mut DepthFrame)>,
) {
let Ok(reader) = readers.get(complete.entity) else {
return;
};
if let Ok((target, mut frame)) = cameras.get_mut(reader.0) {
copy_unpadded_f32(&complete.data, target.size, &mut frame);
}
}
fn copy_unpadded_f32(bytes: &[u8], size: UVec2, frame: &mut DepthFrame) {
let row_bytes = size.x as usize * DEPTH_BYTES_PER_PIXEL;
let Some(rows) = unpadded_rows(bytes, row_bytes, size.y as usize) else {
return;
};
frame.size = size;
frame.depths.clear();
frame.depths.reserve(size.x as usize * size.y as usize);
for row in rows {
let (pixels, _) = row.as_chunks::<DEPTH_BYTES_PER_PIXEL>();
frame
.depths
.extend(pixels.iter().copied().map(f32::from_ne_bytes));
}
}
#[cfg(test)]
mod tests {
use bevy_math::UVec2;
use super::{DEPTH_BYTES_PER_PIXEL, DepthFrame, copy_unpadded_f32};
fn padded_depth_rows(depths: &[&[f32]], stride: usize) -> Vec<u8> {
let mut padded = Vec::new();
for row in depths {
let mut bytes: Vec<u8> = row.iter().flat_map(|d| d.to_ne_bytes()).collect();
bytes.resize(stride, 0xEE);
padded.extend_from_slice(&bytes);
}
padded
}
#[test]
fn strips_padding_and_reinterprets_depths() {
let bytes = padded_depth_rows(&[&[1.0, 0.5], &[0.25, 0.0]], 256);
let mut frame = DepthFrame::default();
copy_unpadded_f32(
&bytes[..256 + 2 * DEPTH_BYTES_PER_PIXEL],
UVec2::new(2, 2),
&mut frame,
);
assert_eq!(frame.depths, [1.0, 0.5, 0.25, 0.0]);
assert_eq!(frame.size, UVec2::new(2, 2));
}
#[test]
fn stale_short_data_keeps_the_previous_frame() {
let mut frame = DepthFrame {
depths: vec![0.5; 4],
size: UVec2::new(2, 2),
};
copy_unpadded_f32(&[0; 16], UVec2::new(80, 2), &mut frame);
assert_eq!(frame.depths, vec![0.5; 4]);
}
}