use super::yuv_converter::{
YuvFormatConversionTask, YuvMatrixCoefficients, YuvPixelLayout, YuvRange,
};
use crate::{
renderer::DrawError,
wgpu_resources::{GpuTexture, TextureDesc},
DebugLabel, RenderContext, Texture2DBufferInfo,
};
#[allow(non_camel_case_types)]
#[derive(Clone, Copy, Debug)]
pub enum SourceImageDataFormat {
WgpuCompatible(wgpu::TextureFormat),
Yuv {
layout: YuvPixelLayout,
coefficients: YuvMatrixCoefficients,
range: YuvRange,
},
}
impl From<wgpu::TextureFormat> for SourceImageDataFormat {
fn from(format: wgpu::TextureFormat) -> Self {
Self::WgpuCompatible(format)
}
}
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
pub enum ImageDataToTextureError {
#[error("Texture {0:?} has zero width or height!")]
ZeroSize(DebugLabel),
#[error(
"Texture {label:?} was {width}x{height}, larger than the max of {max_texture_dimension_2d}"
)]
TooLarge {
label: DebugLabel,
width: u32,
height: u32,
max_texture_dimension_2d: u32,
},
#[error(
"Invalid data length for texture {label:?}. Expected {expected} bytes, got {actual} bytes"
)]
InvalidDataLength {
label: DebugLabel,
expected: usize,
actual: usize,
},
#[error(transparent)]
CpuWriteGpuReadError(#[from] crate::allocator::CpuWriteGpuReadError),
#[error("Texture {label:?} has a format {format:?} that data can't be transferred to!")]
UnsupportedFormatForTransfer {
label: DebugLabel,
format: wgpu::TextureFormat,
},
#[error("Gpu-based conversion for texture {label:?} did not succeed: {err}")]
GpuBasedConversionError { label: DebugLabel, err: DrawError },
#[error("Texture {label:?} has invalid texture usage flags: {actual_usage:?}, expected at least {required_usage:?}")]
InvalidTargetTextureUsageFlags {
label: DebugLabel,
actual_usage: wgpu::TextureUsages,
required_usage: wgpu::TextureUsages,
},
#[error("Texture {label:?} has invalid texture format: {actual_format:?}, expected at least {required_format:?}")]
InvalidTargetTextureFormat {
label: DebugLabel,
actual_format: wgpu::TextureFormat,
required_format: wgpu::TextureFormat,
},
#[error("Unsupported texture format {0:?}")]
UnsupportedTextureFormat(wgpu::TextureFormat),
}
pub struct ImageDataDesc<'a> {
pub label: DebugLabel,
pub data: std::borrow::Cow<'a, [u8]>,
pub format: SourceImageDataFormat,
pub width_height: [u32; 2],
}
impl<'a> ImageDataDesc<'a> {
fn validate(
&self,
limits: &wgpu::Limits,
target_texture_desc: &TextureDesc,
) -> Result<(), ImageDataToTextureError> {
let Self {
label,
data,
format,
width_height,
} = self;
if !target_texture_desc
.usage
.contains(self.target_texture_usage_requirements())
{
return Err(ImageDataToTextureError::InvalidTargetTextureUsageFlags {
label: target_texture_desc.label.clone(),
actual_usage: target_texture_desc.usage,
required_usage: self.target_texture_usage_requirements(),
});
}
if target_texture_desc.format != self.target_texture_format() {
return Err(ImageDataToTextureError::InvalidTargetTextureFormat {
label: target_texture_desc.label.clone(),
actual_format: target_texture_desc.format,
required_format: self.target_texture_format(),
});
}
if width_height[0] == 0 || width_height[1] == 0 {
return Err(ImageDataToTextureError::ZeroSize(label.clone()));
}
let max_texture_dimension_2d = limits.max_texture_dimension_2d;
if width_height[0] > max_texture_dimension_2d || width_height[1] > max_texture_dimension_2d
{
return Err(ImageDataToTextureError::TooLarge {
label: label.clone(),
width: width_height[0],
height: width_height[1],
max_texture_dimension_2d,
});
}
let num_pixels = width_height[0] as usize * width_height[1] as usize;
let expected_num_bytes = match format {
SourceImageDataFormat::WgpuCompatible(format) => {
num_pixels
* format
.block_copy_size(None)
.ok_or(ImageDataToTextureError::UnsupportedTextureFormat(*format))?
as usize
}
SourceImageDataFormat::Yuv { layout: format, .. } => {
format.num_data_buffer_bytes(*width_height)
}
};
if data.len() != expected_num_bytes {
return Err(ImageDataToTextureError::InvalidDataLength {
label: label.clone(),
expected: expected_num_bytes,
actual: data.len(),
});
}
Ok(())
}
pub fn target_texture_usage_requirements(&self) -> wgpu::TextureUsages {
match self.format {
SourceImageDataFormat::WgpuCompatible(_) => wgpu::TextureUsages::COPY_DST, SourceImageDataFormat::Yuv { .. } => {
YuvFormatConversionTask::REQUIRED_TARGET_TEXTURE_USAGE_FLAGS
}
}
}
pub fn target_texture_format(&self) -> wgpu::TextureFormat {
match self.format {
SourceImageDataFormat::WgpuCompatible(format) => format,
SourceImageDataFormat::Yuv { .. } => YuvFormatConversionTask::OUTPUT_FORMAT,
}
}
pub fn create_target_texture(
&self,
ctx: &RenderContext,
texture_usages: wgpu::TextureUsages,
) -> GpuTexture {
ctx.gpu_resources.textures.alloc(
&ctx.device,
&TextureDesc {
label: self.label.clone(),
size: wgpu::Extent3d {
width: self.width_height[0],
height: self.width_height[1],
depth_or_array_layers: 1,
},
mip_level_count: 1, sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: self.target_texture_format(),
usage: self.target_texture_usage_requirements() | texture_usages,
},
)
}
}
pub fn transfer_image_data_to_texture(
ctx: &RenderContext,
image_data: ImageDataDesc<'_>,
target_texture: &GpuTexture,
) -> Result<(), ImageDataToTextureError> {
re_tracing::profile_function!();
image_data.validate(&ctx.device.limits(), &target_texture.creation_desc)?;
let ImageDataDesc {
label,
data,
format: source_format,
width_height: output_width_height,
} = image_data;
let [data_texture_width, data_texture_height] = match source_format {
SourceImageDataFormat::WgpuCompatible(_) => output_width_height,
SourceImageDataFormat::Yuv { layout, .. } => {
layout.data_texture_width_height(output_width_height)
}
};
let data_texture_format = match source_format {
SourceImageDataFormat::WgpuCompatible(format) => format,
SourceImageDataFormat::Yuv { layout, .. } => layout.data_texture_format(),
};
let data_texture_label = match source_format {
SourceImageDataFormat::WgpuCompatible(_) => label.clone(),
SourceImageDataFormat::Yuv { .. } => format!("{label}_source_data").into(),
};
let data_texture = match source_format {
SourceImageDataFormat::Yuv { .. } => ctx.gpu_resources.textures.alloc(
&ctx.device,
&TextureDesc {
label: data_texture_label,
size: wgpu::Extent3d {
width: data_texture_width,
height: data_texture_height,
depth_or_array_layers: 1,
},
mip_level_count: 1, sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: data_texture_format,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
},
),
SourceImageDataFormat::WgpuCompatible(_) => target_texture.clone(),
};
copy_data_to_texture(ctx, &data_texture, data.as_ref())?;
let converter_task = match source_format {
SourceImageDataFormat::WgpuCompatible(_) => {
return Ok(());
}
SourceImageDataFormat::Yuv {
layout,
coefficients,
range,
} => YuvFormatConversionTask::new(
ctx,
layout,
range,
coefficients,
&data_texture,
target_texture,
),
};
converter_task
.convert_input_data_to_texture(ctx)
.map_err(|err| ImageDataToTextureError::GpuBasedConversionError { label, err })
}
fn copy_data_to_texture(
render_ctx: &RenderContext,
data_texture: &GpuTexture,
data: &[u8],
) -> Result<(), ImageDataToTextureError> {
re_tracing::profile_function!();
let buffer_info =
Texture2DBufferInfo::new(data_texture.texture.format(), data_texture.texture.size());
let mut cpu_write_gpu_read_belt = render_ctx.cpu_write_gpu_read_belt.lock();
let mut gpu_read_buffer = cpu_write_gpu_read_belt.allocate::<u8>(
&render_ctx.device,
&render_ctx.gpu_resources.buffers,
buffer_info.buffer_size_padded as usize,
)?;
if buffer_info.buffer_size_padded as usize == data.len() {
re_tracing::profile_scope!("bulk_copy");
gpu_read_buffer.extend_from_slice(data)?;
} else {
re_tracing::profile_scope!("row_by_row_copy");
let bytes_per_row_unpadded = buffer_info.bytes_per_row_unpadded as usize;
let num_padding_bytes_per_row =
buffer_info.bytes_per_row_padded as usize - bytes_per_row_unpadded;
debug_assert!(
num_padding_bytes_per_row > 0,
"No padding bytes, but the unpadded buffer size is not equal to the unpadded buffer."
);
for row in 0..data_texture.texture.size().height as usize {
gpu_read_buffer.extend_from_slice(
&data[(row * bytes_per_row_unpadded)
..(row * bytes_per_row_unpadded + bytes_per_row_unpadded)],
)?;
gpu_read_buffer.add_n(0, num_padding_bytes_per_row)?;
}
}
let mut before_view_builder_encoder =
render_ctx.active_frame.before_view_builder_encoder.lock();
gpu_read_buffer
.copy_to_texture2d_entire_first_layer(before_view_builder_encoder.get(), data_texture)?;
Ok(())
}