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 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 pub(crate) fn allocate(
58 &self,
59 size: Size<DevicePixels>,
60 texture_kind: AtlasTextureKind,
61 ) -> Option<AtlasTile> {
62 self.0.lock().allocate(size, texture_kind)
63 }
64
65 pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
66 let mut lock = self.0.lock();
67 let textures = match texture_kind {
68 AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
69 AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
70 };
71 for texture in textures.iter_mut() {
72 texture.clear();
73 }
74 }
75}
76
77impl PlatformAtlas for DirectXAtlas {
78 fn get_or_insert_with<'a>(
79 &self,
80 key: &AtlasKey,
81 build: &mut dyn FnMut() -> anyhow::Result<
82 Option<(Size<DevicePixels>, std::borrow::Cow<'a, [u8]>)>,
83 >,
84 ) -> anyhow::Result<Option<AtlasTile>> {
85 let mut lock = self.0.lock();
86 if let Some(tile) = lock.tiles_by_key.get(key) {
87 Ok(Some(tile.clone()))
88 } else {
89 let Some((size, bytes)) = build()? else {
90 return Ok(None);
91 };
92 let tile = lock
93 .allocate(size, key.texture_kind())
94 .ok_or_else(|| anyhow::anyhow!("failed to allocate"))?;
95 let texture = lock.texture(tile.texture_id);
96 texture.upload(&lock.device_context, tile.bounds, &bytes);
97 lock.tiles_by_key.insert(key.clone(), tile.clone());
98 Ok(Some(tile))
99 }
100 }
101
102 fn remove(&self, key: &AtlasKey) {
103 let mut lock = self.0.lock();
104
105 let Some(id) = lock.tiles_by_key.remove(key).map(|tile| tile.texture_id) else {
106 return;
107 };
108
109 let textures = match id.kind {
110 AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
111 AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
112 };
113
114 let Some(texture_slot) = textures.textures.get_mut(id.index as usize) else {
115 return;
116 };
117
118 if let Some(mut texture) = texture_slot.take() {
119 texture.decrement_ref_count();
120 if texture.is_unreferenced() {
121 textures.free_list.push(texture.id.index as usize);
122 lock.tiles_by_key.remove(key);
123 } else {
124 *texture_slot = Some(texture);
125 }
126 }
127 }
128}
129
130impl DirectXAtlasState {
131 fn allocate(
132 &mut self,
133 size: Size<DevicePixels>,
134 texture_kind: AtlasTextureKind,
135 ) -> Option<AtlasTile> {
136 {
137 let textures = match texture_kind {
138 AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
139 AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
140 };
141
142 if let Some(tile) = textures
143 .iter_mut()
144 .rev()
145 .find_map(|texture| texture.allocate(size))
146 {
147 return Some(tile);
148 }
149 }
150
151 let texture = self.push_texture(size, texture_kind);
152 texture.allocate(size)
153 }
154
155 fn push_texture(
156 &mut self,
157 min_size: Size<DevicePixels>,
158 kind: AtlasTextureKind,
159 ) -> &mut DirectXAtlasTexture {
160 const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
161 width: DevicePixels(1024),
162 height: DevicePixels(1024),
163 };
164 // Max texture size for DirectX. See:
165 // https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-limits
166 const MAX_ATLAS_SIZE: Size<DevicePixels> = Size {
167 width: DevicePixels(16384),
168 height: DevicePixels(16384),
169 };
170 let size = min_size.min(&MAX_ATLAS_SIZE).max(&DEFAULT_ATLAS_SIZE);
171 let pixel_format;
172 let bind_flag;
173 let bytes_per_pixel;
174 match kind {
175 AtlasTextureKind::Monochrome => {
176 pixel_format = DXGI_FORMAT_A8_UNORM;
177 bind_flag = D3D11_BIND_SHADER_RESOURCE;
178 bytes_per_pixel = 1;
179 }
180 AtlasTextureKind::Polychrome => {
181 pixel_format = DXGI_FORMAT_B8G8R8A8_UNORM;
182 bind_flag = D3D11_BIND_SHADER_RESOURCE;
183 bytes_per_pixel = 4;
184 }
185 }
186 let texture_desc = D3D11_TEXTURE2D_DESC {
187 Width: size.width.0 as u32,
188 Height: size.height.0 as u32,
189 MipLevels: 1,
190 ArraySize: 1,
191 Format: pixel_format,
192 SampleDesc: DXGI_SAMPLE_DESC {
193 Count: 1,
194 Quality: 0,
195 },
196 Usage: D3D11_USAGE_DEFAULT,
197 BindFlags: bind_flag.0 as u32,
198 CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32,
199 MiscFlags: 0,
200 };
201 let mut texture: Option<ID3D11Texture2D> = None;
202 unsafe {
203 self.device
204 .CreateTexture2D(&texture_desc, None, Some(&mut texture))
205 .unwrap();
206 }
207 let texture = texture.unwrap();
208
209 let texture_list = match kind {
210 AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
211 AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
212 };
213 let index = texture_list.free_list.pop();
214 let view = unsafe {
215 let mut view = None;
216 self.device
217 .CreateShaderResourceView(&texture, None, Some(&mut view))
218 .unwrap();
219 [view]
220 };
221 let atlas_texture = DirectXAtlasTexture {
222 id: AtlasTextureId {
223 index: index.unwrap_or(texture_list.textures.len()) as u32,
224 kind,
225 },
226 bytes_per_pixel,
227 allocator: etagere::BucketedAtlasAllocator::new(size.into()),
228 texture,
229 view,
230 live_atlas_keys: 0,
231 };
232 if let Some(ix) = index {
233 texture_list.textures[ix] = Some(atlas_texture);
234 texture_list.textures.get_mut(ix).unwrap().as_mut().unwrap()
235 } else {
236 texture_list.textures.push(Some(atlas_texture));
237 texture_list.textures.last_mut().unwrap().as_mut().unwrap()
238 }
239 }
240
241 fn texture(&self, id: AtlasTextureId) -> &DirectXAtlasTexture {
242 let textures = match id.kind {
243 crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
244 crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
245 // crate::AtlasTextureKind::Path => &self.path_textures,
246 };
247 textures[id.index as usize].as_ref().unwrap()
248 }
249}
250
251impl DirectXAtlasTexture {
252 fn clear(&mut self) {
253 self.allocator.clear();
254 }
255
256 fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
257 let allocation = self.allocator.allocate(size.into())?;
258 let tile = AtlasTile {
259 texture_id: self.id,
260 tile_id: allocation.id.into(),
261 bounds: Bounds {
262 origin: allocation.rectangle.min.into(),
263 size,
264 },
265 padding: 0,
266 };
267 self.live_atlas_keys += 1;
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
296 fn decrement_ref_count(&mut self) {
297 self.live_atlas_keys -= 1;
298 }
299
300 fn is_unreferenced(&mut self) -> bool {
301 self.live_atlas_keys == 0
302 }
303}