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