Skip to main content

ursus_core/assets/registry/
texture.rs

1use crate::assets::upload::GpuUploadRequest;
2use crate::assets::upload_queue::UploadQueue;
3use crate::render::gfx::types::Format;
4use std::collections::HashMap;
5use std::hash::{Hash, Hasher};
6
7/// The only source of TextureHandle in the system.
8struct TextureHandleAllocator {
9    next: u32,
10}
11
12impl TextureHandleAllocator {
13    fn alloc(&mut self) -> TextureHandle {
14        let h = TextureHandle(self.next);
15        self.next += 1;
16        h
17    }
18}
19impl Default for TextureHandleAllocator {
20    fn default() -> Self {
21        Self { next: 1 } // 0 is reserved for the bindless white reserve.
22    }
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub struct TextureHandle(pub u32);
27
28/// CPU-side texture registration with content-based deduplication.
29#[derive(Default)]
30pub struct TextureRegistry {
31    handles: TextureHandleAllocator,
32    dedup: TextureDedup,
33    upload_queue: UploadQueue,
34}
35
36impl TextureRegistry {
37    pub fn upload_rgba8(
38        &mut self,
39        pixels: Vec<u8>,
40        width: u32,
41        height: u32,
42        format: Format,
43        name: impl Into<String>,
44    ) -> TextureHandle {
45        match self.dedup.register(&pixels, width, height, format, &mut self.handles) {
46            TextureRegistration::Existing(handle) => handle,
47            TextureRegistration::New(handle) => {
48                self.upload_queue.push(GpuUploadRequest::Texture {
49                    handle,
50                    pixels,
51                    width,
52                    height,
53                    format,
54                    name: name.into(),
55                });
56                handle
57            }
58        }
59    }
60
61    pub(crate) fn flush_uploads(&mut self, tx: &std::sync::mpsc::Sender<GpuUploadRequest>) {
62        self.upload_queue.drain_to(tx);
63    }
64}
65
66const TEXTURE_HASH_SAMPLE_COUNT: usize = 64;
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
69struct TextureContentKey(u64, usize, u32, u32, Format);
70
71fn hash_texture(pixels: &[u8], width: u32, height: u32, format: Format) -> TextureContentKey {
72    let mut hasher = std::collections::hash_map::DefaultHasher::new();
73    let len = pixels.len();
74    if len <= TEXTURE_HASH_SAMPLE_COUNT * 2 {
75        pixels.hash(&mut hasher);
76    } else {
77        let step = len / TEXTURE_HASH_SAMPLE_COUNT;
78        let mut i = 0;
79        while i < len {
80            hasher.write_u8(pixels[i]);
81            i += step;
82        }
83        hasher.write(&pixels[..32.min(len)]);
84        hasher.write(&pixels[len - 32.min(len)..]);
85    }
86    TextureContentKey(hasher.finish(), len, width, height, format)
87}
88
89pub(crate) enum TextureRegistration {
90    Existing(TextureHandle),
91    New(TextureHandle),
92}
93
94/// Texture deduplication by content + handle output.
95#[derive(Default)]
96pub(crate) struct TextureDedup {
97    dedup: HashMap<TextureContentKey, TextureHandle>,
98}
99
100impl TextureDedup {
101    fn register(
102        &mut self,
103        pixels: &[u8],
104        width: u32,
105        height: u32,
106        format: Format,
107        handles: &mut TextureHandleAllocator,
108    ) -> TextureRegistration {
109        let key = hash_texture(pixels, width, height, format);
110        if let Some(&handle) = self.dedup.get(&key) {
111            return TextureRegistration::Existing(handle);
112        }
113        let handle = handles.alloc();
114        self.dedup.insert(key, handle);
115        TextureRegistration::New(handle)
116    }
117}