use ahash::{HashMap, HashSet};
use re_mutex::Mutex;
use super::ImageDataToTextureError;
use super::image_data_to_texture::transfer_image_data_to_texture;
use crate::RenderContext;
use crate::resource_managers::ImageDataDesc;
use crate::wgpu_resources::{GpuTexture, GpuTexturePool, TextureDesc};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AlphaChannelUsage {
DontKnow,
Opaque,
AlphaChannelInUse,
}
#[derive(Clone)]
pub struct GpuTexture2D {
texture: GpuTexture,
alpha_channel_usage: AlphaChannelUsage,
}
impl std::fmt::Debug for GpuTexture2D {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self {
texture,
alpha_channel_usage,
} = self;
f.debug_struct("GpuTexture2D")
.field("handle", &texture.handle)
.field("alpha_channel_usage", alpha_channel_usage)
.finish()
}
}
impl GpuTexture2D {
pub fn new(texture: GpuTexture, alpha_channel_usage: AlphaChannelUsage) -> Option<Self> {
if texture.texture.dimension() != wgpu::TextureDimension::D2 {
return None;
}
let has_alpha_channel = texture_format_has_alpha_channel(texture.texture.format());
let alpha_channel_usage = if has_alpha_channel {
alpha_channel_usage
} else {
re_log::debug_assert!(
alpha_channel_usage != AlphaChannelUsage::AlphaChannelInUse,
"alpha_channel_usage is AlphaChannelInUse but texture format {:?} has no alpha channel",
texture.texture.format()
);
AlphaChannelUsage::Opaque
};
Some(Self {
texture,
alpha_channel_usage,
})
}
#[inline]
pub fn handle(&self) -> crate::wgpu_resources::GpuTextureHandle {
self.texture.handle
}
#[inline]
pub fn alpha_channel_usage(&self) -> AlphaChannelUsage {
self.alpha_channel_usage
}
#[inline]
pub fn width(&self) -> u32 {
self.texture.texture.width()
}
#[inline]
pub fn height(&self) -> u32 {
self.texture.texture.height()
}
#[inline]
pub fn width_height(&self) -> [u32; 2] {
[self.width(), self.height()]
}
#[inline]
pub fn format(&self) -> wgpu::TextureFormat {
self.texture.texture.format()
}
}
impl AsRef<GpuTexture> for GpuTexture2D {
#[inline(always)]
fn as_ref(&self) -> &GpuTexture {
&self.texture
}
}
impl std::ops::Deref for GpuTexture2D {
type Target = GpuTexture;
#[inline(always)]
fn deref(&self) -> &GpuTexture {
&self.texture
}
}
impl std::borrow::Borrow<GpuTexture> for GpuTexture2D {
#[inline(always)]
fn borrow(&self) -> &GpuTexture {
&self.texture
}
}
#[derive(thiserror::Error, Debug)]
pub enum TextureManager2DError<DataCreationError> {
#[error(transparent)]
ImageDataToTextureError(#[from] ImageDataToTextureError),
#[error(transparent)]
DataCreation(DataCreationError),
}
impl From<TextureManager2DError<never::Never>> for ImageDataToTextureError {
fn from(err: TextureManager2DError<never::Never>) -> Self {
match err {
TextureManager2DError::ImageDataToTextureError(texture_creation) => texture_creation,
TextureManager2DError::DataCreation(never) => match never {},
}
}
}
pub struct TextureManager2D {
white_texture_unorm: GpuTexture2D,
zeroed_texture_float: GpuTexture2D,
zeroed_texture_sint: GpuTexture2D,
zeroed_texture_uint: GpuTexture2D,
inner: Mutex<Inner>,
}
#[derive(Default)]
struct Inner {
texture_cache: HashMap<u64, GpuTexture2D>,
accessed_textures: HashSet<u64>,
}
impl Inner {
fn begin_frame(&mut self, _frame_index: u64) {
self.texture_cache
.retain(|k, _| self.accessed_textures.contains(k));
self.accessed_textures.clear();
}
}
impl TextureManager2D {
pub(crate) fn new(
device: &wgpu::Device,
queue: &wgpu::Queue,
texture_pool: &GpuTexturePool,
) -> Self {
re_tracing::profile_function!();
let white_texture_unorm = GpuTexture2D {
alpha_channel_usage: AlphaChannelUsage::Opaque,
texture: texture_pool.alloc(
device,
&TextureDesc {
label: "white pixel - unorm".into(),
format: wgpu::TextureFormat::Rgba8Unorm,
size: wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
},
),
};
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &white_texture_unorm.texture.texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
&[255, 255, 255, 255],
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(4),
rows_per_image: None,
},
wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
);
let zeroed_texture_float =
create_zero_texture(texture_pool, device, wgpu::TextureFormat::Rgba8Unorm);
let zeroed_texture_sint =
create_zero_texture(texture_pool, device, wgpu::TextureFormat::Rgba8Sint);
let zeroed_texture_uint =
create_zero_texture(texture_pool, device, wgpu::TextureFormat::Rgba8Uint);
Self {
white_texture_unorm,
zeroed_texture_float,
zeroed_texture_sint,
zeroed_texture_uint,
inner: Default::default(),
}
}
#[expect(clippy::unused_self)]
pub fn create(
&self,
render_ctx: &RenderContext,
creation_desc: ImageDataDesc<'_>,
) -> Result<GpuTexture2D, ImageDataToTextureError> {
let alpha_channel_usage = creation_desc.alpha_channel_usage;
let texture = creation_desc.create_target_texture(
render_ctx,
wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_SRC,
);
transfer_image_data_to_texture(render_ctx, creation_desc, &texture)?;
Ok(GpuTexture2D::new(texture, alpha_channel_usage).expect("Texture is known to be 2D"))
}
pub fn get_or_create(
&self,
key: u64,
render_ctx: &RenderContext,
texture_desc: ImageDataDesc<'_>,
) -> Result<GpuTexture2D, ImageDataToTextureError> {
self.get_or_create_with(key, render_ctx, || texture_desc)
}
pub fn get_or_create_with<'a>(
&self,
key: u64,
render_ctx: &RenderContext,
create_texture_desc: impl FnOnce() -> ImageDataDesc<'a>,
) -> Result<GpuTexture2D, ImageDataToTextureError> {
self.get_or_try_create_with(key, render_ctx, || -> Result<_, never::Never> {
Ok(create_texture_desc())
})
.map_err(|err| err.into())
}
pub fn get_or_try_create_with<'a, Err: std::fmt::Display>(
&self,
key: u64,
render_ctx: &RenderContext,
try_create_texture_desc: impl FnOnce() -> Result<ImageDataDesc<'a>, Err>,
) -> Result<GpuTexture2D, TextureManager2DError<Err>> {
let mut inner = self.inner.lock();
let texture_handle = match inner.texture_cache.entry(key) {
std::collections::hash_map::Entry::Occupied(texture_handle) => {
texture_handle.get().clone() }
std::collections::hash_map::Entry::Vacant(entry) => {
let tex_creation_desc = try_create_texture_desc()
.map_err(|err| TextureManager2DError::DataCreation(err))?;
let alpha_channel_usage = tex_creation_desc.alpha_channel_usage;
let texture = tex_creation_desc.create_target_texture(
render_ctx,
wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_SRC,
);
transfer_image_data_to_texture(render_ctx, tex_creation_desc, &texture)?;
entry
.insert(GpuTexture2D {
texture,
alpha_channel_usage,
})
.clone()
}
};
inner.accessed_textures.insert(key);
Ok(texture_handle)
}
pub fn white_texture_unorm_handle(&self) -> &GpuTexture2D {
&self.white_texture_unorm
}
pub fn white_texture_unorm(&self) -> &GpuTexture {
&self.white_texture_unorm.texture
}
pub fn zeroed_texture_float(&self) -> &GpuTexture {
&self.zeroed_texture_float.texture
}
pub fn zeroed_texture_sint(&self) -> &GpuTexture {
&self.zeroed_texture_sint.texture
}
pub fn zeroed_texture_uint(&self) -> &GpuTexture {
&self.zeroed_texture_uint.texture
}
pub(crate) fn begin_frame(&self, _frame_index: u64) {
self.inner.lock().begin_frame(_frame_index);
}
}
fn texture_format_has_alpha_channel(format: wgpu::TextureFormat) -> bool {
#[expect(clippy::match_same_arms)]
match format {
wgpu::TextureFormat::Rgba8Unorm
| wgpu::TextureFormat::Rgba8UnormSrgb
| wgpu::TextureFormat::Rgba8Snorm
| wgpu::TextureFormat::Rgba8Uint
| wgpu::TextureFormat::Rgba8Sint
| wgpu::TextureFormat::Bgra8Unorm
| wgpu::TextureFormat::Bgra8UnormSrgb
| wgpu::TextureFormat::Rgb10a2Uint
| wgpu::TextureFormat::Rgb10a2Unorm
| wgpu::TextureFormat::Rgba16Uint
| wgpu::TextureFormat::Rgba16Sint
| wgpu::TextureFormat::Rgba16Unorm
| wgpu::TextureFormat::Rgba16Snorm
| wgpu::TextureFormat::Rgba16Float
| wgpu::TextureFormat::Rgba32Uint
| wgpu::TextureFormat::Rgba32Sint
| wgpu::TextureFormat::Rgba32Float => true,
wgpu::TextureFormat::Bc1RgbaUnorm
| wgpu::TextureFormat::Bc1RgbaUnormSrgb
| wgpu::TextureFormat::Bc2RgbaUnorm
| wgpu::TextureFormat::Bc2RgbaUnormSrgb
| wgpu::TextureFormat::Bc3RgbaUnorm
| wgpu::TextureFormat::Bc3RgbaUnormSrgb
| wgpu::TextureFormat::Bc7RgbaUnorm
| wgpu::TextureFormat::Bc7RgbaUnormSrgb
| wgpu::TextureFormat::Etc2Rgb8A1Unorm
| wgpu::TextureFormat::Etc2Rgb8A1UnormSrgb
| wgpu::TextureFormat::Etc2Rgba8Unorm
| wgpu::TextureFormat::Etc2Rgba8UnormSrgb
| wgpu::TextureFormat::Astc { .. } => true,
wgpu::TextureFormat::R8Unorm
| wgpu::TextureFormat::R8Snorm
| wgpu::TextureFormat::R8Uint
| wgpu::TextureFormat::R8Sint
| wgpu::TextureFormat::R16Uint
| wgpu::TextureFormat::R16Sint
| wgpu::TextureFormat::R16Unorm
| wgpu::TextureFormat::R16Snorm
| wgpu::TextureFormat::R16Float
| wgpu::TextureFormat::Rg8Unorm
| wgpu::TextureFormat::Rg8Snorm
| wgpu::TextureFormat::Rg8Uint
| wgpu::TextureFormat::Rg8Sint
| wgpu::TextureFormat::R32Uint
| wgpu::TextureFormat::R32Sint
| wgpu::TextureFormat::R32Float
| wgpu::TextureFormat::Rg16Uint
| wgpu::TextureFormat::Rg16Sint
| wgpu::TextureFormat::Rg16Unorm
| wgpu::TextureFormat::Rg16Snorm
| wgpu::TextureFormat::Rg16Float
| wgpu::TextureFormat::R64Uint
| wgpu::TextureFormat::Rg32Uint
| wgpu::TextureFormat::Rg32Sint
| wgpu::TextureFormat::Rg32Float => false,
wgpu::TextureFormat::Rgb9e5Ufloat | wgpu::TextureFormat::Rg11b10Ufloat => false,
wgpu::TextureFormat::Stencil8
| wgpu::TextureFormat::Depth16Unorm
| wgpu::TextureFormat::Depth24Plus
| wgpu::TextureFormat::Depth24PlusStencil8
| wgpu::TextureFormat::Depth32Float
| wgpu::TextureFormat::Depth32FloatStencil8 => false,
wgpu::TextureFormat::NV12 | wgpu::TextureFormat::P010 => false,
wgpu::TextureFormat::Bc4RUnorm
| wgpu::TextureFormat::Bc4RSnorm
| wgpu::TextureFormat::Bc5RgUnorm
| wgpu::TextureFormat::Bc5RgSnorm
| wgpu::TextureFormat::Bc6hRgbUfloat
| wgpu::TextureFormat::Bc6hRgbFloat
| wgpu::TextureFormat::Etc2Rgb8Unorm
| wgpu::TextureFormat::Etc2Rgb8UnormSrgb
| wgpu::TextureFormat::EacR11Unorm
| wgpu::TextureFormat::EacR11Snorm
| wgpu::TextureFormat::EacRg11Unorm
| wgpu::TextureFormat::EacRg11Snorm => false,
}
}
fn create_zero_texture(
texture_pool: &GpuTexturePool,
device: &wgpu::Device,
format: wgpu::TextureFormat,
) -> GpuTexture2D {
GpuTexture2D {
alpha_channel_usage: if texture_format_has_alpha_channel(format) {
AlphaChannelUsage::AlphaChannelInUse
} else {
AlphaChannelUsage::Opaque
},
texture: texture_pool.alloc(
device,
&TextureDesc {
label: format!("zeroed pixel {format:?}").into(),
format,
size: wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
usage: wgpu::TextureUsages::TEXTURE_BINDING,
},
),
}
}