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