directx_atlas.rs

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