directx_atlas.rs

  1use collections::FxHashMap;
  2use etagere::BucketedAtlasAllocator;
  3use parking_lot::Mutex;
  4use windows::Win32::Graphics::{
  5    Direct3D11::{
  6        D3D11_BIND_SHADER_RESOURCE, D3D11_BOX, D3D11_CPU_ACCESS_WRITE, D3D11_TEXTURE2D_DESC,
  7        D3D11_USAGE_DEFAULT, ID3D11Device, ID3D11DeviceContext, ID3D11ShaderResourceView,
  8        ID3D11Texture2D,
  9    },
 10    Dxgi::Common::{DXGI_FORMAT_A8_UNORM, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_SAMPLE_DESC},
 11};
 12
 13use crate::{
 14    AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
 15    Point, Size, platform::AtlasTextureList,
 16};
 17
 18pub(crate) struct DirectXAtlas(Mutex<DirectXAtlasState>);
 19
 20struct DirectXAtlasState {
 21    device: ID3D11Device,
 22    device_context: ID3D11DeviceContext,
 23    monochrome_textures: AtlasTextureList<DirectXAtlasTexture>,
 24    polychrome_textures: AtlasTextureList<DirectXAtlasTexture>,
 25    tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
 26}
 27
 28struct DirectXAtlasTexture {
 29    id: AtlasTextureId,
 30    bytes_per_pixel: u32,
 31    allocator: BucketedAtlasAllocator,
 32    texture: ID3D11Texture2D,
 33    view: [Option<ID3D11ShaderResourceView>; 1],
 34    live_atlas_keys: u32,
 35}
 36
 37impl DirectXAtlas {
 38    pub(crate) fn new(device: ID3D11Device, device_context: ID3D11DeviceContext) -> Self {
 39        DirectXAtlas(Mutex::new(DirectXAtlasState {
 40            device,
 41            device_context,
 42            monochrome_textures: Default::default(),
 43            polychrome_textures: Default::default(),
 44            tiles_by_key: Default::default(),
 45        }))
 46    }
 47
 48    pub(crate) fn get_texture_view(
 49        &self,
 50        id: AtlasTextureId,
 51    ) -> [Option<ID3D11ShaderResourceView>; 1] {
 52        let lock = self.0.lock();
 53        let tex = lock.texture(id);
 54        tex.view.clone()
 55    }
 56}
 57
 58impl PlatformAtlas for DirectXAtlas {
 59    fn get_or_insert_with<'a>(
 60        &self,
 61        key: &AtlasKey,
 62        build: &mut dyn FnMut() -> anyhow::Result<
 63            Option<(Size<DevicePixels>, std::borrow::Cow<'a, [u8]>)>,
 64        >,
 65    ) -> anyhow::Result<Option<AtlasTile>> {
 66        let mut lock = self.0.lock();
 67        if let Some(tile) = lock.tiles_by_key.get(key) {
 68            Ok(Some(tile.clone()))
 69        } else {
 70            let Some((size, bytes)) = build()? else {
 71                return Ok(None);
 72            };
 73            let tile = lock
 74                .allocate(size, key.texture_kind())
 75                .ok_or_else(|| anyhow::anyhow!("failed to allocate"))?;
 76            let texture = lock.texture(tile.texture_id);
 77            texture.upload(&lock.device_context, tile.bounds, &bytes);
 78            lock.tiles_by_key.insert(key.clone(), tile.clone());
 79            Ok(Some(tile))
 80        }
 81    }
 82
 83    fn remove(&self, key: &AtlasKey) {
 84        let mut lock = self.0.lock();
 85
 86        let Some(id) = lock.tiles_by_key.remove(key).map(|tile| tile.texture_id) else {
 87            return;
 88        };
 89
 90        let textures = match id.kind {
 91            AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
 92            AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
 93        };
 94
 95        let Some(texture_slot) = textures.textures.get_mut(id.index as usize) else {
 96            return;
 97        };
 98
 99        if let Some(mut texture) = texture_slot.take() {
100            texture.decrement_ref_count();
101            if texture.is_unreferenced() {
102                textures.free_list.push(texture.id.index as usize);
103                lock.tiles_by_key.remove(key);
104            } else {
105                *texture_slot = Some(texture);
106            }
107        }
108    }
109}
110
111impl DirectXAtlasState {
112    fn allocate(
113        &mut self,
114        size: Size<DevicePixels>,
115        texture_kind: AtlasTextureKind,
116    ) -> Option<AtlasTile> {
117        {
118            let textures = match texture_kind {
119                AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
120                AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
121            };
122
123            if let Some(tile) = textures
124                .iter_mut()
125                .rev()
126                .find_map(|texture| texture.allocate(size))
127            {
128                return Some(tile);
129            }
130        }
131
132        let texture = self.push_texture(size, texture_kind);
133        texture.allocate(size)
134    }
135
136    fn push_texture(
137        &mut self,
138        min_size: Size<DevicePixels>,
139        kind: AtlasTextureKind,
140    ) -> &mut DirectXAtlasTexture {
141        const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
142            width: DevicePixels(1024),
143            height: DevicePixels(1024),
144        };
145        // Max texture size for DirectX. See:
146        // https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-limits
147        const MAX_ATLAS_SIZE: Size<DevicePixels> = Size {
148            width: DevicePixels(16384),
149            height: DevicePixels(16384),
150        };
151        let size = min_size.min(&MAX_ATLAS_SIZE).max(&DEFAULT_ATLAS_SIZE);
152        let pixel_format;
153        let bind_flag;
154        let bytes_per_pixel;
155        match kind {
156            AtlasTextureKind::Monochrome => {
157                pixel_format = DXGI_FORMAT_A8_UNORM;
158                bind_flag = D3D11_BIND_SHADER_RESOURCE;
159                bytes_per_pixel = 1;
160            }
161            AtlasTextureKind::Polychrome => {
162                pixel_format = DXGI_FORMAT_B8G8R8A8_UNORM;
163                bind_flag = D3D11_BIND_SHADER_RESOURCE;
164                bytes_per_pixel = 4;
165            }
166        }
167        let texture_desc = D3D11_TEXTURE2D_DESC {
168            Width: size.width.0 as u32,
169            Height: size.height.0 as u32,
170            MipLevels: 1,
171            ArraySize: 1,
172            Format: pixel_format,
173            SampleDesc: DXGI_SAMPLE_DESC {
174                Count: 1,
175                Quality: 0,
176            },
177            Usage: D3D11_USAGE_DEFAULT,
178            BindFlags: bind_flag.0 as u32,
179            CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32,
180            MiscFlags: 0,
181        };
182        let mut texture: Option<ID3D11Texture2D> = None;
183        unsafe {
184            self.device
185                .CreateTexture2D(&texture_desc, None, Some(&mut texture))
186                .unwrap();
187        }
188        let texture = texture.unwrap();
189
190        let texture_list = match kind {
191            AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
192            AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
193        };
194        let index = texture_list.free_list.pop();
195        let view = unsafe {
196            let mut view = None;
197            self.device
198                .CreateShaderResourceView(&texture, None, Some(&mut view))
199                .unwrap();
200            [view]
201        };
202        let atlas_texture = DirectXAtlasTexture {
203            id: AtlasTextureId {
204                index: index.unwrap_or(texture_list.textures.len()) as u32,
205                kind,
206            },
207            bytes_per_pixel,
208            allocator: etagere::BucketedAtlasAllocator::new(size.into()),
209            texture,
210            view,
211            live_atlas_keys: 0,
212        };
213        if let Some(ix) = index {
214            texture_list.textures[ix] = Some(atlas_texture);
215            texture_list.textures.get_mut(ix).unwrap().as_mut().unwrap()
216        } else {
217            texture_list.textures.push(Some(atlas_texture));
218            texture_list.textures.last_mut().unwrap().as_mut().unwrap()
219        }
220    }
221
222    fn texture(&self, id: AtlasTextureId) -> &DirectXAtlasTexture {
223        let textures = match id.kind {
224            crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
225            crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
226            // crate::AtlasTextureKind::Path => &self.path_textures,
227        };
228        textures[id.index as usize].as_ref().unwrap()
229    }
230}
231
232impl DirectXAtlasTexture {
233    fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
234        let allocation = self.allocator.allocate(size.into())?;
235        let tile = AtlasTile {
236            texture_id: self.id,
237            tile_id: allocation.id.into(),
238            bounds: Bounds {
239                origin: allocation.rectangle.min.into(),
240                size,
241            },
242            padding: 0,
243        };
244        self.live_atlas_keys += 1;
245        Some(tile)
246    }
247
248    fn upload(
249        &self,
250        device_context: &ID3D11DeviceContext,
251        bounds: Bounds<DevicePixels>,
252        bytes: &[u8],
253    ) {
254        unsafe {
255            device_context.UpdateSubresource(
256                &self.texture,
257                0,
258                Some(&D3D11_BOX {
259                    left: bounds.left().0 as u32,
260                    top: bounds.top().0 as u32,
261                    front: 0,
262                    right: bounds.right().0 as u32,
263                    bottom: bounds.bottom().0 as u32,
264                    back: 1,
265                }),
266                bytes.as_ptr() as _,
267                bounds.size.width.to_bytes(self.bytes_per_pixel as u8),
268                0,
269            );
270        }
271    }
272
273    fn decrement_ref_count(&mut self) {
274        self.live_atlas_keys -= 1;
275    }
276
277    fn is_unreferenced(&mut self) -> bool {
278        self.live_atlas_keys == 0
279    }
280}
281
282impl From<Size<DevicePixels>> for etagere::Size {
283    fn from(size: Size<DevicePixels>) -> Self {
284        etagere::Size::new(size.width.into(), size.height.into())
285    }
286}
287
288impl From<etagere::Point> for Point<DevicePixels> {
289    fn from(value: etagere::Point) -> Self {
290        Point {
291            x: DevicePixels::from(value.x),
292            y: DevicePixels::from(value.y),
293        }
294    }
295}