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