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}