use crate::error::RenderError;
use std::time::Instant;
use wgpu::*;
const MAX_TEXTURE_CACHE_SIZE: usize = 100;
pub(super) struct SixelTextureInfo {
pub(super) texture: Texture,
#[allow(dead_code)] view: TextureView,
pub(super) bind_group: BindGroup,
pub(super) width: u32,
pub(super) height: u32,
}
pub(super) struct CachedTexture {
pub(super) texture: SixelTextureInfo,
pub(super) last_used: Instant,
}
impl super::GraphicsRenderer {
pub fn get_or_create_texture(
&mut self,
device: &Device,
queue: &Queue,
id: u64,
rgba_data: &[u8],
width: u32,
height: u32,
) -> Result<(), RenderError> {
if let Some(cached) = self.texture_cache.get_mut(&id) {
cached.last_used = Instant::now();
const VIRTUAL_PLACEMENT_ID_FLAG: u64 = 1u64 << 63;
if id & VIRTUAL_PLACEMENT_ID_FLAG != 0 {
return Ok(());
}
let expected_size = (width * height * 4) as usize;
if rgba_data.len() != expected_size {
return Err(RenderError::InvalidTextureData {
expected: expected_size,
actual: rgba_data.len(),
});
}
queue.write_texture(
TexelCopyTextureInfo {
texture: &cached.texture.texture,
mip_level: 0,
origin: Origin3d::ZERO,
aspect: TextureAspect::All,
},
rgba_data,
TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(4 * width),
rows_per_image: Some(height),
},
Extent3d {
width,
height,
depth_or_array_layers: 1,
},
);
return Ok(());
}
let expected_size = (width * height * 4) as usize;
if rgba_data.len() != expected_size {
return Err(RenderError::InvalidTextureData {
expected: expected_size,
actual: rgba_data.len(),
});
}
if self.texture_cache.len() >= MAX_TEXTURE_CACHE_SIZE
&& let Some((&lru_id, _)) = self
.texture_cache
.iter()
.min_by_key(|(_, cached)| cached.last_used)
{
log::debug!(
"[GRAPHICS] Evicting LRU texture: id={}, cache_size={}",
lru_id,
self.texture_cache.len()
);
self.texture_cache.remove(&lru_id);
}
let texture = device.create_texture(&TextureDescriptor {
label: Some(&format!("Sixel Texture {}", id)),
size: Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: TextureFormat::Rgba8Unorm,
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
view_formats: &[],
});
queue.write_texture(
TexelCopyTextureInfo {
texture: &texture,
mip_level: 0,
origin: Origin3d::ZERO,
aspect: TextureAspect::All,
},
rgba_data,
TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(4 * width),
rows_per_image: Some(height),
},
Extent3d {
width,
height,
depth_or_array_layers: 1,
},
);
let view = texture.create_view(&TextureViewDescriptor::default());
let bind_group = device.create_bind_group(&BindGroupDescriptor {
label: Some(&format!("Sixel Bind Group {}", id)),
layout: &self.bind_group_layout,
entries: &[
BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&view),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::Sampler(&self.sampler),
},
],
});
self.texture_cache.insert(
id,
CachedTexture {
texture: SixelTextureInfo {
texture,
view,
bind_group,
width,
height,
},
last_used: Instant::now(),
},
);
log::debug!(
"[GRAPHICS] Created sixel texture: id={}, size={}x{}, cache_size={}/{}",
id,
width,
height,
self.texture_cache.len(),
MAX_TEXTURE_CACHE_SIZE
);
Ok(())
}
}