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 }
177 AtlasTextureKind::Path => {
178 pixel_format = DXGI_FORMAT_R16_FLOAT;
179 bind_flag = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
180 bytes_per_pixel = 2;
181 }
182 }
183 let texture_desc = D3D11_TEXTURE2D_DESC {
184 Width: size.width.0 as u32,
185 Height: size.height.0 as u32,
186 MipLevels: 1,
187 ArraySize: 1,
188 Format: pixel_format,
189 SampleDesc: DXGI_SAMPLE_DESC {
190 Count: 1,
191 Quality: 0,
192 },
193 Usage: D3D11_USAGE_DEFAULT,
194 BindFlags: bind_flag.0 as u32,
195 CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32,
196 MiscFlags: 0,
197 };
198 let mut texture: Option<ID3D11Texture2D> = None;
199 unsafe {
200 self.device
201 .CreateTexture2D(&texture_desc, None, Some(&mut texture))
202 .unwrap();
203 }
204 let texture = texture.unwrap();
205
206 let textures = match kind {
207 AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
208 AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
209 AtlasTextureKind::Path => &mut self.path_textures,
210 };
211 let rtv = match kind {
212 AtlasTextureKind::Path => unsafe {
213 let mut view: Option<ID3D11RenderTargetView> = None;
214 self.device
215 .CreateRenderTargetView(&texture, None, Some(&mut view))
216 .unwrap();
217 [view]
218 },
219 _ => [None],
220 };
221 let view = unsafe {
222 let mut view = None;
223 self.device
224 .CreateShaderResourceView(&texture, None, Some(&mut view))
225 .unwrap();
226 [view]
227 };
228 let atlas_texture = DirectXAtlasTexture {
229 id: AtlasTextureId {
230 index: textures.len() as u32,
231 kind,
232 },
233 bytes_per_pixel,
234 allocator: etagere::BucketedAtlasAllocator::new(size.into()),
235 texture,
236 rtv,
237 view,
238 };
239 textures.push(atlas_texture);
240 textures.last_mut().unwrap()
241 }
242
243 fn texture(&self, id: AtlasTextureId) -> &DirectXAtlasTexture {
244 let textures = match id.kind {
245 crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
246 crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
247 crate::AtlasTextureKind::Path => &self.path_textures,
248 };
249 &textures[id.index as usize]
250 }
251}
252
253impl DirectXAtlasTexture {
254 fn clear(&mut self) {
255 self.allocator.clear();
256 }
257
258 fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
259 let allocation = self.allocator.allocate(size.into())?;
260 let tile = AtlasTile {
261 texture_id: self.id,
262 tile_id: allocation.id.into(),
263 bounds: Bounds {
264 origin: allocation.rectangle.min.into(),
265 size,
266 },
267 padding: 0,
268 };
269 Some(tile)
270 }
271
272 fn upload(
273 &self,
274 device_context: &ID3D11DeviceContext,
275 bounds: Bounds<DevicePixels>,
276 bytes: &[u8],
277 ) {
278 unsafe {
279 device_context.UpdateSubresource(
280 &self.texture,
281 0,
282 Some(&D3D11_BOX {
283 left: bounds.left().0 as u32,
284 top: bounds.top().0 as u32,
285 front: 0,
286 right: bounds.right().0 as u32,
287 bottom: bounds.bottom().0 as u32,
288 back: 1,
289 }),
290 bytes.as_ptr() as _,
291 bounds.size.width.to_bytes(self.bytes_per_pixel as u8),
292 0,
293 );
294 }
295 }
296}