use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
static NEXT_SCENE_TEXTURE_ID: AtomicU64 = AtomicU64::new(1);
const SHADER_PROGRAM_NAMESPACE: u64 = 1 << 63;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TextureDesc {
pub width: u32,
pub height: u32,
pub format: wgpu::TextureFormat,
pub usage: wgpu::TextureUsages,
pub label: Option<String>,
}
impl TextureDesc {
pub const fn sample_count(&self) -> u32 {
1
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct TextureId(pub u64);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct SceneTextureId(u64);
impl SceneTextureId {
#[must_use]
pub fn mint() -> Self {
Self(NEXT_SCENE_TEXTURE_ID.fetch_add(1, Ordering::Relaxed))
}
#[must_use]
pub const fn for_shader_program(program_id: u64) -> Self {
Self(program_id | SHADER_PROGRAM_NAMESPACE)
}
#[must_use]
pub const fn get(self) -> u64 {
self.0
}
#[must_use]
pub const fn is_shader_program(self) -> bool {
self.0 & SHADER_PROGRAM_NAMESPACE != 0
}
}
#[derive(Clone, Debug)]
pub struct Texture<T = wgpu::Texture, V = wgpu::TextureView> {
texture: T,
view: V,
desc: TextureDesc,
scene_id: SceneTextureId,
}
impl<T, V> Texture<T, V> {
pub fn new(texture: T, view: V, desc: TextureDesc) -> Self {
Self {
texture,
view,
desc,
scene_id: SceneTextureId::mint(),
}
}
pub fn texture(&self) -> &T {
&self.texture
}
pub fn view(&self) -> &V {
&self.view
}
pub fn size(&self) -> (u32, u32) {
(self.desc.width, self.desc.height)
}
pub fn format(&self) -> wgpu::TextureFormat {
self.desc.format
}
pub fn desc(&self) -> &TextureDesc {
&self.desc
}
pub fn as_scene_texture(&self) -> SceneTextureId {
self.scene_id
}
}
#[derive(Clone, Debug)]
pub struct Attachment<'a, V> {
pub view: &'a wgpu::TextureView,
pub load: wgpu::LoadOp<V>,
pub store: wgpu::StoreOp,
}
pub type ColorAttachment<'a> = Attachment<'a, wgpu::Color>;
pub type DepthAttachment<'a> = Attachment<'a, f32>;
#[derive(Clone, Debug, Default)]
pub struct RenderTarget<'a> {
pub color: Vec<ColorAttachment<'a>>,
pub depth: Option<DepthAttachment<'a>>,
}
#[derive(Debug)]
pub struct TextureRegistry<V = wgpu::TextureView> {
views: HashMap<SceneTextureId, V>,
}
impl<V> Default for TextureRegistry<V> {
fn default() -> Self {
Self {
views: HashMap::new(),
}
}
}
impl<V> TextureRegistry<V> {
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, id: SceneTextureId, view: V) -> Option<V> {
self.views.insert(id, view)
}
pub fn get(&self, id: SceneTextureId) -> Option<&V> {
self.views.get(&id)
}
pub fn remove(&mut self, id: SceneTextureId) -> Option<V> {
self.views.remove(&id)
}
pub fn len(&self) -> usize {
self.views.len()
}
pub fn is_empty(&self) -> bool {
self.views.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn fake_desc() -> TextureDesc {
TextureDesc {
width: 64,
height: 64,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING,
label: Some("test".to_string()),
}
}
#[test]
fn sample_count_is_always_one_and_has_no_setter() {
let desc = fake_desc();
assert_eq!(desc.sample_count(), 1);
}
fn fake_texture() -> Texture<u32, u32> {
Texture::new(0, 0, fake_desc())
}
#[test]
fn a_shader_program_id_never_collides_with_a_minted_one() {
for _ in 0..64 {
let minted = fake_texture().as_scene_texture();
assert_eq!(minted.get() & SHADER_PROGRAM_NAMESPACE, 0);
assert_ne!(minted, SceneTextureId::for_shader_program(minted.get()));
}
}
#[test]
fn is_shader_program_distinguishes_the_two_namespaces() {
let minted = fake_texture().as_scene_texture();
let shader = SceneTextureId::for_shader_program(minted.get());
assert!(!minted.is_shader_program());
assert!(shader.is_shader_program());
}
#[test]
fn a_shader_program_id_is_a_pure_function_of_the_program() {
assert_eq!(
SceneTextureId::for_shader_program(9),
SceneTextureId::for_shader_program(9)
);
assert_ne!(
SceneTextureId::for_shader_program(9),
SceneTextureId::for_shader_program(10)
);
}
#[test]
fn as_scene_texture_mints_unique_ids() {
let a = fake_texture();
let b = fake_texture();
assert_ne!(a.as_scene_texture(), b.as_scene_texture());
}
#[test]
fn as_scene_texture_mints_unique_ids_across_threads() {
let handles: Vec<_> = (0..8)
.map(|_| std::thread::spawn(|| fake_texture().as_scene_texture()))
.collect();
let mut ids: Vec<SceneTextureId> = handles.into_iter().map(|h| h.join().unwrap()).collect();
ids.sort_by_key(|id| id.0);
ids.dedup();
assert_eq!(ids.len(), 8, "all ids must be unique across threads");
}
#[test]
fn as_scene_texture_is_stable_across_clone() {
let a = fake_texture();
let b = a.clone();
assert_eq!(a.as_scene_texture(), b.as_scene_texture());
}
#[test]
fn texture_id_wraps_the_caller_supplied_value() {
let id = TextureId(42);
assert_eq!(id.0, 42);
assert_eq!(id, TextureId(42));
assert_ne!(id, TextureId(43));
}
#[test]
fn render_target_defaults_to_no_attachments() {
let target: RenderTarget = RenderTarget::default();
assert!(target.color.is_empty());
assert!(target.depth.is_none());
}
#[test]
fn registry_insert_get_remove_round_trip() {
let mut registry: TextureRegistry<&'static str> = TextureRegistry::new();
let id = SceneTextureId::mint();
assert!(registry.is_empty());
assert_eq!(registry.insert(id, "view-a"), None);
assert_eq!(registry.len(), 1);
assert_eq!(registry.get(id), Some(&"view-a"));
assert_eq!(registry.insert(id, "view-b"), Some("view-a"));
assert_eq!(registry.get(id), Some(&"view-b"));
assert_eq!(registry.remove(id), Some("view-b"));
assert_eq!(registry.get(id), None);
assert!(registry.is_empty());
}
#[test]
fn registry_get_and_remove_miss_on_unknown_id() {
let mut registry: TextureRegistry<u32> = TextureRegistry::new();
let unknown = SceneTextureId::mint();
assert_eq!(registry.get(unknown), None);
assert_eq!(registry.remove(unknown), None);
}
#[test]
fn registry_distinguishes_ids() {
let mut registry: TextureRegistry<u32> = TextureRegistry::new();
let a = SceneTextureId::mint();
let b = SceneTextureId::mint();
registry.insert(a, 1);
registry.insert(b, 2);
assert_eq!(registry.get(a), Some(&1));
assert_eq!(registry.get(b), Some(&2));
assert_eq!(registry.len(), 2);
}
}