1use std::{
2 mem::ManuallyDrop,
3 sync::{Arc, OnceLock},
4};
5
6use ::util::ResultExt;
7use anyhow::{Context, Result};
8use windows::{
9 Win32::{
10 Foundation::HWND,
11 Graphics::{
12 Direct3D::*,
13 Direct3D11::*,
14 DirectComposition::*,
15 DirectWrite::*,
16 Dxgi::{Common::*, *},
17 },
18 },
19 core::Interface,
20};
21
22use crate::{
23 platform::windows::directx_renderer::shader_resources::{
24 RawShaderBytes, ShaderModule, ShaderTarget,
25 },
26 *,
27};
28
29pub(crate) const DISABLE_DIRECT_COMPOSITION: &str = "GPUI_DISABLE_DIRECT_COMPOSITION";
30const RENDER_TARGET_FORMAT: DXGI_FORMAT = DXGI_FORMAT_B8G8R8A8_UNORM;
31// This configuration is used for MSAA rendering on paths only, and it's guaranteed to be supported by DirectX 11.
32const PATH_MULTISAMPLE_COUNT: u32 = 4;
33
34pub(crate) struct FontInfo {
35 pub gamma_ratios: [f32; 4],
36 pub grayscale_enhanced_contrast: f32,
37}
38
39pub(crate) struct DirectXRenderer {
40 hwnd: HWND,
41 atlas: Arc<DirectXAtlas>,
42 devices: ManuallyDrop<DirectXRendererDevices>,
43 resources: ManuallyDrop<DirectXResources>,
44 globals: DirectXGlobalElements,
45 pipelines: DirectXRenderPipelines,
46 direct_composition: Option<DirectComposition>,
47 font_info: &'static FontInfo,
48}
49
50/// Direct3D objects
51#[derive(Clone)]
52pub(crate) struct DirectXRendererDevices {
53 pub(crate) adapter: IDXGIAdapter1,
54 pub(crate) dxgi_factory: IDXGIFactory6,
55 pub(crate) device: ID3D11Device,
56 pub(crate) device_context: ID3D11DeviceContext,
57 dxgi_device: Option<IDXGIDevice>,
58}
59
60struct DirectXResources {
61 // Direct3D rendering objects
62 swap_chain: IDXGISwapChain1,
63 render_target: ManuallyDrop<ID3D11Texture2D>,
64 render_target_view: [Option<ID3D11RenderTargetView>; 1],
65
66 // Path intermediate textures (with MSAA)
67 path_intermediate_texture: ID3D11Texture2D,
68 path_intermediate_srv: [Option<ID3D11ShaderResourceView>; 1],
69 path_intermediate_msaa_texture: ID3D11Texture2D,
70 path_intermediate_msaa_view: [Option<ID3D11RenderTargetView>; 1],
71
72 // Cached window size and viewport
73 width: u32,
74 height: u32,
75 viewport: [D3D11_VIEWPORT; 1],
76}
77
78struct DirectXRenderPipelines {
79 shadow_pipeline: PipelineState<Shadow>,
80 quad_pipeline: PipelineState<Quad>,
81 path_rasterization_pipeline: PipelineState<PathRasterizationSprite>,
82 path_sprite_pipeline: PipelineState<PathSprite>,
83 underline_pipeline: PipelineState<Underline>,
84 mono_sprites: PipelineState<MonochromeSprite>,
85 poly_sprites: PipelineState<PolychromeSprite>,
86}
87
88struct DirectXGlobalElements {
89 global_params_buffer: [Option<ID3D11Buffer>; 1],
90 sampler: [Option<ID3D11SamplerState>; 1],
91}
92
93struct DirectComposition {
94 comp_device: IDCompositionDevice,
95 comp_target: IDCompositionTarget,
96 comp_visual: IDCompositionVisual,
97}
98
99impl DirectXRendererDevices {
100 pub(crate) fn new(
101 directx_devices: &DirectXDevices,
102 disable_direct_composition: bool,
103 ) -> Result<ManuallyDrop<Self>> {
104 let DirectXDevices {
105 adapter,
106 dxgi_factory,
107 device,
108 device_context,
109 } = directx_devices;
110 let dxgi_device = if disable_direct_composition {
111 None
112 } else {
113 Some(device.cast().context("Creating DXGI device")?)
114 };
115
116 Ok(ManuallyDrop::new(Self {
117 adapter: adapter.clone(),
118 dxgi_factory: dxgi_factory.clone(),
119 device: device.clone(),
120 device_context: device_context.clone(),
121 dxgi_device,
122 }))
123 }
124}
125
126impl DirectXRenderer {
127 pub(crate) fn new(
128 hwnd: HWND,
129 directx_devices: &DirectXDevices,
130 disable_direct_composition: bool,
131 ) -> Result<Self> {
132 if disable_direct_composition {
133 log::info!("Direct Composition is disabled.");
134 }
135
136 let devices = DirectXRendererDevices::new(directx_devices, disable_direct_composition)
137 .context("Creating DirectX devices")?;
138 let atlas = Arc::new(DirectXAtlas::new(&devices.device, &devices.device_context));
139
140 let resources = DirectXResources::new(&devices, 1, 1, hwnd, disable_direct_composition)
141 .context("Creating DirectX resources")?;
142 let globals = DirectXGlobalElements::new(&devices.device)
143 .context("Creating DirectX global elements")?;
144 let pipelines = DirectXRenderPipelines::new(&devices.device)
145 .context("Creating DirectX render pipelines")?;
146
147 let direct_composition = if disable_direct_composition {
148 None
149 } else {
150 let composition = DirectComposition::new(devices.dxgi_device.as_ref().unwrap(), hwnd)
151 .context("Creating DirectComposition")?;
152 composition
153 .set_swap_chain(&resources.swap_chain)
154 .context("Setting swap chain for DirectComposition")?;
155 Some(composition)
156 };
157
158 Ok(DirectXRenderer {
159 hwnd,
160 atlas,
161 devices,
162 resources,
163 globals,
164 pipelines,
165 direct_composition,
166 font_info: Self::get_font_info(),
167 })
168 }
169
170 pub(crate) fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
171 self.atlas.clone()
172 }
173
174 fn pre_draw(&self) -> Result<()> {
175 update_buffer(
176 &self.devices.device_context,
177 self.globals.global_params_buffer[0].as_ref().unwrap(),
178 &[GlobalParams {
179 gamma_ratios: self.font_info.gamma_ratios,
180 viewport_size: [
181 self.resources.viewport[0].Width,
182 self.resources.viewport[0].Height,
183 ],
184 grayscale_enhanced_contrast: self.font_info.grayscale_enhanced_contrast,
185 _pad: 0,
186 }],
187 )?;
188 unsafe {
189 self.devices.device_context.ClearRenderTargetView(
190 self.resources.render_target_view[0].as_ref().unwrap(),
191 &[0.0; 4],
192 );
193 self.devices
194 .device_context
195 .OMSetRenderTargets(Some(&self.resources.render_target_view), None);
196 self.devices
197 .device_context
198 .RSSetViewports(Some(&self.resources.viewport));
199 }
200 Ok(())
201 }
202
203 #[inline]
204 fn present(&mut self) -> Result<()> {
205 let result = unsafe { self.resources.swap_chain.Present(0, DXGI_PRESENT(0)) };
206 result.ok().context("Presenting swap chain failed")
207 }
208
209 pub(crate) fn handle_device_lost(&mut self, directx_devices: &DirectXDevices) {
210 try_to_recover_from_device_lost(
211 || {
212 self.handle_device_lost_impl(directx_devices)
213 .context("DirectXRenderer handling device lost")
214 },
215 |_| {},
216 || {
217 log::error!(
218 "DirectXRenderer failed to recover from device lost after multiple attempts"
219 );
220 // Do something here?
221 // At this point, the device loss is considered unrecoverable.
222 },
223 );
224 }
225
226 fn handle_device_lost_impl(&mut self, directx_devices: &DirectXDevices) -> Result<()> {
227 let disable_direct_composition = self.direct_composition.is_none();
228
229 unsafe {
230 #[cfg(debug_assertions)]
231 report_live_objects(&self.devices.device)
232 .context("Failed to report live objects after device lost")
233 .log_err();
234
235 ManuallyDrop::drop(&mut self.resources);
236 self.devices.device_context.OMSetRenderTargets(None, None);
237 self.devices.device_context.ClearState();
238 self.devices.device_context.Flush();
239
240 #[cfg(debug_assertions)]
241 report_live_objects(&self.devices.device)
242 .context("Failed to report live objects after device lost")
243 .log_err();
244
245 drop(self.direct_composition.take());
246 ManuallyDrop::drop(&mut self.devices);
247 }
248
249 let devices = DirectXRendererDevices::new(directx_devices, disable_direct_composition)
250 .context("Recreating DirectX devices")?;
251 let resources = DirectXResources::new(
252 &devices,
253 self.resources.width,
254 self.resources.height,
255 self.hwnd,
256 disable_direct_composition,
257 )?;
258 let globals = DirectXGlobalElements::new(&devices.device)?;
259 let pipelines = DirectXRenderPipelines::new(&devices.device)?;
260
261 let direct_composition = if disable_direct_composition {
262 None
263 } else {
264 let composition =
265 DirectComposition::new(devices.dxgi_device.as_ref().unwrap(), self.hwnd)?;
266 composition.set_swap_chain(&resources.swap_chain)?;
267 Some(composition)
268 };
269
270 self.atlas
271 .handle_device_lost(&devices.device, &devices.device_context);
272 self.devices = devices;
273 self.resources = resources;
274 self.globals = globals;
275 self.pipelines = pipelines;
276 self.direct_composition = direct_composition;
277
278 unsafe {
279 self.devices
280 .device_context
281 .OMSetRenderTargets(Some(&self.resources.render_target_view), None);
282 }
283 Ok(())
284 }
285
286 pub(crate) fn draw(&mut self, scene: &Scene) -> Result<()> {
287 self.pre_draw()?;
288 for batch in scene.batches() {
289 match batch {
290 PrimitiveBatch::Shadows(shadows) => self.draw_shadows(shadows),
291 PrimitiveBatch::Quads(quads) => self.draw_quads(quads),
292 PrimitiveBatch::Paths(paths) => {
293 self.draw_paths_to_intermediate(paths)?;
294 self.draw_paths_from_intermediate(paths)
295 }
296 PrimitiveBatch::Underlines(underlines) => self.draw_underlines(underlines),
297 PrimitiveBatch::MonochromeSprites {
298 texture_id,
299 sprites,
300 } => self.draw_monochrome_sprites(texture_id, sprites),
301 PrimitiveBatch::PolychromeSprites {
302 texture_id,
303 sprites,
304 } => self.draw_polychrome_sprites(texture_id, sprites),
305 PrimitiveBatch::Surfaces(surfaces) => self.draw_surfaces(surfaces),
306 }.context(format!("scene too large: {} paths, {} shadows, {} quads, {} underlines, {} mono, {} poly, {} surfaces",
307 scene.paths.len(),
308 scene.shadows.len(),
309 scene.quads.len(),
310 scene.underlines.len(),
311 scene.monochrome_sprites.len(),
312 scene.polychrome_sprites.len(),
313 scene.surfaces.len(),))?;
314 }
315 self.present()
316 }
317
318 pub(crate) fn resize(&mut self, new_size: Size<DevicePixels>) -> Result<()> {
319 let width = new_size.width.0.max(1) as u32;
320 let height = new_size.height.0.max(1) as u32;
321 if self.resources.width == width && self.resources.height == height {
322 return Ok(());
323 }
324 self.resources.width = width;
325 self.resources.height = height;
326
327 // Clear the render target before resizing
328 unsafe { self.devices.device_context.OMSetRenderTargets(None, None) };
329 unsafe { ManuallyDrop::drop(&mut self.resources.render_target) };
330 drop(self.resources.render_target_view[0].take().unwrap());
331
332 // Resizing the swap chain requires a call to the underlying DXGI adapter, which can return the device removed error.
333 // The app might have moved to a monitor that's attached to a different graphics device.
334 // When a graphics device is removed or reset, the desktop resolution often changes, resulting in a window size change.
335 // But here we just return the error, because we are handling device lost scenarios elsewhere.
336 unsafe {
337 self.resources
338 .swap_chain
339 .ResizeBuffers(
340 BUFFER_COUNT as u32,
341 width,
342 height,
343 RENDER_TARGET_FORMAT,
344 DXGI_SWAP_CHAIN_FLAG(0),
345 )
346 .context("Failed to resize swap chain")?;
347 }
348
349 self.resources
350 .recreate_resources(&self.devices, width, height)?;
351 unsafe {
352 self.devices
353 .device_context
354 .OMSetRenderTargets(Some(&self.resources.render_target_view), None);
355 }
356
357 Ok(())
358 }
359
360 fn draw_shadows(&mut self, shadows: &[Shadow]) -> Result<()> {
361 if shadows.is_empty() {
362 return Ok(());
363 }
364 self.pipelines.shadow_pipeline.update_buffer(
365 &self.devices.device,
366 &self.devices.device_context,
367 shadows,
368 )?;
369 self.pipelines.shadow_pipeline.draw(
370 &self.devices.device_context,
371 &self.resources.viewport,
372 &self.globals.global_params_buffer,
373 D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
374 4,
375 shadows.len() as u32,
376 )
377 }
378
379 fn draw_quads(&mut self, quads: &[Quad]) -> Result<()> {
380 if quads.is_empty() {
381 return Ok(());
382 }
383 self.pipelines.quad_pipeline.update_buffer(
384 &self.devices.device,
385 &self.devices.device_context,
386 quads,
387 )?;
388 self.pipelines.quad_pipeline.draw(
389 &self.devices.device_context,
390 &self.resources.viewport,
391 &self.globals.global_params_buffer,
392 D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
393 4,
394 quads.len() as u32,
395 )
396 }
397
398 fn draw_paths_to_intermediate(&mut self, paths: &[Path<ScaledPixels>]) -> Result<()> {
399 if paths.is_empty() {
400 return Ok(());
401 }
402
403 // Clear intermediate MSAA texture
404 unsafe {
405 self.devices.device_context.ClearRenderTargetView(
406 self.resources.path_intermediate_msaa_view[0]
407 .as_ref()
408 .unwrap(),
409 &[0.0; 4],
410 );
411 // Set intermediate MSAA texture as render target
412 self.devices
413 .device_context
414 .OMSetRenderTargets(Some(&self.resources.path_intermediate_msaa_view), None);
415 }
416
417 // Collect all vertices and sprites for a single draw call
418 let mut vertices = Vec::new();
419
420 for path in paths {
421 vertices.extend(path.vertices.iter().map(|v| PathRasterizationSprite {
422 xy_position: v.xy_position,
423 st_position: v.st_position,
424 color: path.color,
425 bounds: path.clipped_bounds(),
426 }));
427 }
428
429 self.pipelines.path_rasterization_pipeline.update_buffer(
430 &self.devices.device,
431 &self.devices.device_context,
432 &vertices,
433 )?;
434 self.pipelines.path_rasterization_pipeline.draw(
435 &self.devices.device_context,
436 &self.resources.viewport,
437 &self.globals.global_params_buffer,
438 D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
439 vertices.len() as u32,
440 1,
441 )?;
442
443 // Resolve MSAA to non-MSAA intermediate texture
444 unsafe {
445 self.devices.device_context.ResolveSubresource(
446 &self.resources.path_intermediate_texture,
447 0,
448 &self.resources.path_intermediate_msaa_texture,
449 0,
450 RENDER_TARGET_FORMAT,
451 );
452 // Restore main render target
453 self.devices
454 .device_context
455 .OMSetRenderTargets(Some(&self.resources.render_target_view), None);
456 }
457
458 Ok(())
459 }
460
461 fn draw_paths_from_intermediate(&mut self, paths: &[Path<ScaledPixels>]) -> Result<()> {
462 let Some(first_path) = paths.first() else {
463 return Ok(());
464 };
465
466 // When copying paths from the intermediate texture to the drawable,
467 // each pixel must only be copied once, in case of transparent paths.
468 //
469 // If all paths have the same draw order, then their bounds are all
470 // disjoint, so we can copy each path's bounds individually. If this
471 // batch combines different draw orders, we perform a single copy
472 // for a minimal spanning rect.
473 let sprites = if paths.last().unwrap().order == first_path.order {
474 paths
475 .iter()
476 .map(|path| PathSprite {
477 bounds: path.clipped_bounds(),
478 })
479 .collect::<Vec<_>>()
480 } else {
481 let mut bounds = first_path.clipped_bounds();
482 for path in paths.iter().skip(1) {
483 bounds = bounds.union(&path.clipped_bounds());
484 }
485 vec![PathSprite { bounds }]
486 };
487
488 self.pipelines.path_sprite_pipeline.update_buffer(
489 &self.devices.device,
490 &self.devices.device_context,
491 &sprites,
492 )?;
493
494 // Draw the sprites with the path texture
495 self.pipelines.path_sprite_pipeline.draw_with_texture(
496 &self.devices.device_context,
497 &self.resources.path_intermediate_srv,
498 &self.resources.viewport,
499 &self.globals.global_params_buffer,
500 &self.globals.sampler,
501 sprites.len() as u32,
502 )
503 }
504
505 fn draw_underlines(&mut self, underlines: &[Underline]) -> Result<()> {
506 if underlines.is_empty() {
507 return Ok(());
508 }
509 self.pipelines.underline_pipeline.update_buffer(
510 &self.devices.device,
511 &self.devices.device_context,
512 underlines,
513 )?;
514 self.pipelines.underline_pipeline.draw(
515 &self.devices.device_context,
516 &self.resources.viewport,
517 &self.globals.global_params_buffer,
518 D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
519 4,
520 underlines.len() as u32,
521 )
522 }
523
524 fn draw_monochrome_sprites(
525 &mut self,
526 texture_id: AtlasTextureId,
527 sprites: &[MonochromeSprite],
528 ) -> Result<()> {
529 if sprites.is_empty() {
530 return Ok(());
531 }
532 self.pipelines.mono_sprites.update_buffer(
533 &self.devices.device,
534 &self.devices.device_context,
535 sprites,
536 )?;
537 let texture_view = self.atlas.get_texture_view(texture_id);
538 self.pipelines.mono_sprites.draw_with_texture(
539 &self.devices.device_context,
540 &texture_view,
541 &self.resources.viewport,
542 &self.globals.global_params_buffer,
543 &self.globals.sampler,
544 sprites.len() as u32,
545 )
546 }
547
548 fn draw_polychrome_sprites(
549 &mut self,
550 texture_id: AtlasTextureId,
551 sprites: &[PolychromeSprite],
552 ) -> Result<()> {
553 if sprites.is_empty() {
554 return Ok(());
555 }
556 self.pipelines.poly_sprites.update_buffer(
557 &self.devices.device,
558 &self.devices.device_context,
559 sprites,
560 )?;
561 let texture_view = self.atlas.get_texture_view(texture_id);
562 self.pipelines.poly_sprites.draw_with_texture(
563 &self.devices.device_context,
564 &texture_view,
565 &self.resources.viewport,
566 &self.globals.global_params_buffer,
567 &self.globals.sampler,
568 sprites.len() as u32,
569 )
570 }
571
572 fn draw_surfaces(&mut self, surfaces: &[PaintSurface]) -> Result<()> {
573 if surfaces.is_empty() {
574 return Ok(());
575 }
576 Ok(())
577 }
578
579 pub(crate) fn gpu_specs(&self) -> Result<GpuSpecs> {
580 let desc = unsafe { self.devices.adapter.GetDesc1() }?;
581 let is_software_emulated = (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE.0 as u32) != 0;
582 let device_name = String::from_utf16_lossy(&desc.Description)
583 .trim_matches(char::from(0))
584 .to_string();
585 let driver_name = match desc.VendorId {
586 0x10DE => "NVIDIA Corporation".to_string(),
587 0x1002 => "AMD Corporation".to_string(),
588 0x8086 => "Intel Corporation".to_string(),
589 id => format!("Unknown Vendor (ID: {:#X})", id),
590 };
591 let driver_version = match desc.VendorId {
592 0x10DE => nvidia::get_driver_version(),
593 0x1002 => amd::get_driver_version(),
594 // For Intel and other vendors, we use the DXGI API to get the driver version.
595 _ => dxgi::get_driver_version(&self.devices.adapter),
596 }
597 .context("Failed to get gpu driver info")
598 .log_err()
599 .unwrap_or("Unknown Driver".to_string());
600 Ok(GpuSpecs {
601 is_software_emulated,
602 device_name,
603 driver_name,
604 driver_info: driver_version,
605 })
606 }
607
608 pub(crate) fn get_font_info() -> &'static FontInfo {
609 static CACHED_FONT_INFO: OnceLock<FontInfo> = OnceLock::new();
610 CACHED_FONT_INFO.get_or_init(|| unsafe {
611 let factory: IDWriteFactory5 = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED).unwrap();
612 let render_params: IDWriteRenderingParams1 =
613 factory.CreateRenderingParams().unwrap().cast().unwrap();
614 FontInfo {
615 gamma_ratios: Self::get_gamma_ratios(render_params.GetGamma()),
616 grayscale_enhanced_contrast: render_params.GetGrayscaleEnhancedContrast(),
617 }
618 })
619 }
620
621 // Gamma ratios for brightening/darkening edges for better contrast
622 // https://github.com/microsoft/terminal/blob/1283c0f5b99a2961673249fa77c6b986efb5086c/src/renderer/atlas/dwrite.cpp#L50
623 fn get_gamma_ratios(gamma: f32) -> [f32; 4] {
624 const GAMMA_INCORRECT_TARGET_RATIOS: [[f32; 4]; 13] = [
625 [0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0], // gamma = 1.0
626 [0.0166 / 4.0, -0.0807 / 4.0, 0.2227 / 4.0, -0.0751 / 4.0], // gamma = 1.1
627 [0.0350 / 4.0, -0.1760 / 4.0, 0.4325 / 4.0, -0.1370 / 4.0], // gamma = 1.2
628 [0.0543 / 4.0, -0.2821 / 4.0, 0.6302 / 4.0, -0.1876 / 4.0], // gamma = 1.3
629 [0.0739 / 4.0, -0.3963 / 4.0, 0.8167 / 4.0, -0.2287 / 4.0], // gamma = 1.4
630 [0.0933 / 4.0, -0.5161 / 4.0, 0.9926 / 4.0, -0.2616 / 4.0], // gamma = 1.5
631 [0.1121 / 4.0, -0.6395 / 4.0, 1.1588 / 4.0, -0.2877 / 4.0], // gamma = 1.6
632 [0.1300 / 4.0, -0.7649 / 4.0, 1.3159 / 4.0, -0.3080 / 4.0], // gamma = 1.7
633 [0.1469 / 4.0, -0.8911 / 4.0, 1.4644 / 4.0, -0.3234 / 4.0], // gamma = 1.8
634 [0.1627 / 4.0, -1.0170 / 4.0, 1.6051 / 4.0, -0.3347 / 4.0], // gamma = 1.9
635 [0.1773 / 4.0, -1.1420 / 4.0, 1.7385 / 4.0, -0.3426 / 4.0], // gamma = 2.0
636 [0.1908 / 4.0, -1.2652 / 4.0, 1.8650 / 4.0, -0.3476 / 4.0], // gamma = 2.1
637 [0.2031 / 4.0, -1.3864 / 4.0, 1.9851 / 4.0, -0.3501 / 4.0], // gamma = 2.2
638 ];
639
640 const NORM13: f32 = ((0x10000 as f64) / (255.0 * 255.0) * 4.0) as f32;
641 const NORM24: f32 = ((0x100 as f64) / (255.0) * 4.0) as f32;
642
643 let index = ((gamma * 10.0).round() as usize).clamp(10, 22) - 10;
644 let ratios = GAMMA_INCORRECT_TARGET_RATIOS[index];
645
646 [
647 ratios[0] * NORM13,
648 ratios[1] * NORM24,
649 ratios[2] * NORM13,
650 ratios[3] * NORM24,
651 ]
652 }
653}
654
655impl DirectXResources {
656 pub fn new(
657 devices: &DirectXRendererDevices,
658 width: u32,
659 height: u32,
660 hwnd: HWND,
661 disable_direct_composition: bool,
662 ) -> Result<ManuallyDrop<Self>> {
663 let swap_chain = if disable_direct_composition {
664 create_swap_chain(&devices.dxgi_factory, &devices.device, hwnd, width, height)?
665 } else {
666 create_swap_chain_for_composition(
667 &devices.dxgi_factory,
668 &devices.device,
669 width,
670 height,
671 )?
672 };
673
674 let (
675 render_target,
676 render_target_view,
677 path_intermediate_texture,
678 path_intermediate_srv,
679 path_intermediate_msaa_texture,
680 path_intermediate_msaa_view,
681 viewport,
682 ) = create_resources(devices, &swap_chain, width, height)?;
683 set_rasterizer_state(&devices.device, &devices.device_context)?;
684
685 Ok(ManuallyDrop::new(Self {
686 swap_chain,
687 render_target,
688 render_target_view,
689 path_intermediate_texture,
690 path_intermediate_msaa_texture,
691 path_intermediate_msaa_view,
692 path_intermediate_srv,
693 viewport,
694 width,
695 height,
696 }))
697 }
698
699 #[inline]
700 fn recreate_resources(
701 &mut self,
702 devices: &DirectXRendererDevices,
703 width: u32,
704 height: u32,
705 ) -> Result<()> {
706 let (
707 render_target,
708 render_target_view,
709 path_intermediate_texture,
710 path_intermediate_srv,
711 path_intermediate_msaa_texture,
712 path_intermediate_msaa_view,
713 viewport,
714 ) = create_resources(devices, &self.swap_chain, width, height)?;
715 self.render_target = render_target;
716 self.render_target_view = render_target_view;
717 self.path_intermediate_texture = path_intermediate_texture;
718 self.path_intermediate_msaa_texture = path_intermediate_msaa_texture;
719 self.path_intermediate_msaa_view = path_intermediate_msaa_view;
720 self.path_intermediate_srv = path_intermediate_srv;
721 self.viewport = viewport;
722 Ok(())
723 }
724}
725
726impl DirectXRenderPipelines {
727 pub fn new(device: &ID3D11Device) -> Result<Self> {
728 let shadow_pipeline = PipelineState::new(
729 device,
730 "shadow_pipeline",
731 ShaderModule::Shadow,
732 4,
733 create_blend_state(device)?,
734 )?;
735 let quad_pipeline = PipelineState::new(
736 device,
737 "quad_pipeline",
738 ShaderModule::Quad,
739 64,
740 create_blend_state(device)?,
741 )?;
742 let path_rasterization_pipeline = PipelineState::new(
743 device,
744 "path_rasterization_pipeline",
745 ShaderModule::PathRasterization,
746 32,
747 create_blend_state_for_path_rasterization(device)?,
748 )?;
749 let path_sprite_pipeline = PipelineState::new(
750 device,
751 "path_sprite_pipeline",
752 ShaderModule::PathSprite,
753 4,
754 create_blend_state_for_path_sprite(device)?,
755 )?;
756 let underline_pipeline = PipelineState::new(
757 device,
758 "underline_pipeline",
759 ShaderModule::Underline,
760 4,
761 create_blend_state(device)?,
762 )?;
763 let mono_sprites = PipelineState::new(
764 device,
765 "monochrome_sprite_pipeline",
766 ShaderModule::MonochromeSprite,
767 512,
768 create_blend_state(device)?,
769 )?;
770 let poly_sprites = PipelineState::new(
771 device,
772 "polychrome_sprite_pipeline",
773 ShaderModule::PolychromeSprite,
774 16,
775 create_blend_state(device)?,
776 )?;
777
778 Ok(Self {
779 shadow_pipeline,
780 quad_pipeline,
781 path_rasterization_pipeline,
782 path_sprite_pipeline,
783 underline_pipeline,
784 mono_sprites,
785 poly_sprites,
786 })
787 }
788}
789
790impl DirectComposition {
791 pub fn new(dxgi_device: &IDXGIDevice, hwnd: HWND) -> Result<Self> {
792 let comp_device = get_comp_device(dxgi_device)?;
793 let comp_target = unsafe { comp_device.CreateTargetForHwnd(hwnd, true) }?;
794 let comp_visual = unsafe { comp_device.CreateVisual() }?;
795
796 Ok(Self {
797 comp_device,
798 comp_target,
799 comp_visual,
800 })
801 }
802
803 pub fn set_swap_chain(&self, swap_chain: &IDXGISwapChain1) -> Result<()> {
804 unsafe {
805 self.comp_visual.SetContent(swap_chain)?;
806 self.comp_target.SetRoot(&self.comp_visual)?;
807 self.comp_device.Commit()?;
808 }
809 Ok(())
810 }
811}
812
813impl DirectXGlobalElements {
814 pub fn new(device: &ID3D11Device) -> Result<Self> {
815 let global_params_buffer = unsafe {
816 let desc = D3D11_BUFFER_DESC {
817 ByteWidth: std::mem::size_of::<GlobalParams>() as u32,
818 Usage: D3D11_USAGE_DYNAMIC,
819 BindFlags: D3D11_BIND_CONSTANT_BUFFER.0 as u32,
820 CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32,
821 ..Default::default()
822 };
823 let mut buffer = None;
824 device.CreateBuffer(&desc, None, Some(&mut buffer))?;
825 [buffer]
826 };
827
828 let sampler = unsafe {
829 let desc = D3D11_SAMPLER_DESC {
830 Filter: D3D11_FILTER_MIN_MAG_MIP_LINEAR,
831 AddressU: D3D11_TEXTURE_ADDRESS_WRAP,
832 AddressV: D3D11_TEXTURE_ADDRESS_WRAP,
833 AddressW: D3D11_TEXTURE_ADDRESS_WRAP,
834 MipLODBias: 0.0,
835 MaxAnisotropy: 1,
836 ComparisonFunc: D3D11_COMPARISON_ALWAYS,
837 BorderColor: [0.0; 4],
838 MinLOD: 0.0,
839 MaxLOD: D3D11_FLOAT32_MAX,
840 };
841 let mut output = None;
842 device.CreateSamplerState(&desc, Some(&mut output))?;
843 [output]
844 };
845
846 Ok(Self {
847 global_params_buffer,
848 sampler,
849 })
850 }
851}
852
853#[derive(Debug, Default)]
854#[repr(C)]
855struct GlobalParams {
856 gamma_ratios: [f32; 4],
857 viewport_size: [f32; 2],
858 grayscale_enhanced_contrast: f32,
859 _pad: u32,
860}
861
862struct PipelineState<T> {
863 label: &'static str,
864 vertex: ID3D11VertexShader,
865 fragment: ID3D11PixelShader,
866 buffer: ID3D11Buffer,
867 buffer_size: usize,
868 view: [Option<ID3D11ShaderResourceView>; 1],
869 blend_state: ID3D11BlendState,
870 _marker: std::marker::PhantomData<T>,
871}
872
873impl<T> PipelineState<T> {
874 fn new(
875 device: &ID3D11Device,
876 label: &'static str,
877 shader_module: ShaderModule,
878 buffer_size: usize,
879 blend_state: ID3D11BlendState,
880 ) -> Result<Self> {
881 let vertex = {
882 let raw_shader = RawShaderBytes::new(shader_module, ShaderTarget::Vertex)?;
883 create_vertex_shader(device, raw_shader.as_bytes())?
884 };
885 let fragment = {
886 let raw_shader = RawShaderBytes::new(shader_module, ShaderTarget::Fragment)?;
887 create_fragment_shader(device, raw_shader.as_bytes())?
888 };
889 let buffer = create_buffer(device, std::mem::size_of::<T>(), buffer_size)?;
890 let view = create_buffer_view(device, &buffer)?;
891
892 Ok(PipelineState {
893 label,
894 vertex,
895 fragment,
896 buffer,
897 buffer_size,
898 view,
899 blend_state,
900 _marker: std::marker::PhantomData,
901 })
902 }
903
904 fn update_buffer(
905 &mut self,
906 device: &ID3D11Device,
907 device_context: &ID3D11DeviceContext,
908 data: &[T],
909 ) -> Result<()> {
910 if self.buffer_size < data.len() {
911 let new_buffer_size = data.len().next_power_of_two();
912 log::info!(
913 "Updating {} buffer size from {} to {}",
914 self.label,
915 self.buffer_size,
916 new_buffer_size
917 );
918 let buffer = create_buffer(device, std::mem::size_of::<T>(), new_buffer_size)?;
919 let view = create_buffer_view(device, &buffer)?;
920 self.buffer = buffer;
921 self.view = view;
922 self.buffer_size = new_buffer_size;
923 }
924 update_buffer(device_context, &self.buffer, data)
925 }
926
927 fn draw(
928 &self,
929 device_context: &ID3D11DeviceContext,
930 viewport: &[D3D11_VIEWPORT],
931 global_params: &[Option<ID3D11Buffer>],
932 topology: D3D_PRIMITIVE_TOPOLOGY,
933 vertex_count: u32,
934 instance_count: u32,
935 ) -> Result<()> {
936 set_pipeline_state(
937 device_context,
938 &self.view,
939 topology,
940 viewport,
941 &self.vertex,
942 &self.fragment,
943 global_params,
944 &self.blend_state,
945 );
946 unsafe {
947 device_context.DrawInstanced(vertex_count, instance_count, 0, 0);
948 }
949 Ok(())
950 }
951
952 fn draw_with_texture(
953 &self,
954 device_context: &ID3D11DeviceContext,
955 texture: &[Option<ID3D11ShaderResourceView>],
956 viewport: &[D3D11_VIEWPORT],
957 global_params: &[Option<ID3D11Buffer>],
958 sampler: &[Option<ID3D11SamplerState>],
959 instance_count: u32,
960 ) -> Result<()> {
961 set_pipeline_state(
962 device_context,
963 &self.view,
964 D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
965 viewport,
966 &self.vertex,
967 &self.fragment,
968 global_params,
969 &self.blend_state,
970 );
971 unsafe {
972 device_context.PSSetSamplers(0, Some(sampler));
973 device_context.VSSetShaderResources(0, Some(texture));
974 device_context.PSSetShaderResources(0, Some(texture));
975
976 device_context.DrawInstanced(4, instance_count, 0, 0);
977 }
978 Ok(())
979 }
980}
981
982#[derive(Clone, Copy)]
983#[repr(C)]
984struct PathRasterizationSprite {
985 xy_position: Point<ScaledPixels>,
986 st_position: Point<f32>,
987 color: Background,
988 bounds: Bounds<ScaledPixels>,
989}
990
991#[derive(Clone, Copy)]
992#[repr(C)]
993struct PathSprite {
994 bounds: Bounds<ScaledPixels>,
995}
996
997impl Drop for DirectXRenderer {
998 fn drop(&mut self) {
999 #[cfg(debug_assertions)]
1000 report_live_objects(&self.devices.device).ok();
1001 unsafe {
1002 ManuallyDrop::drop(&mut self.devices);
1003 ManuallyDrop::drop(&mut self.resources);
1004 }
1005 }
1006}
1007
1008impl Drop for DirectXResources {
1009 fn drop(&mut self) {
1010 unsafe {
1011 ManuallyDrop::drop(&mut self.render_target);
1012 }
1013 }
1014}
1015
1016#[inline]
1017fn get_comp_device(dxgi_device: &IDXGIDevice) -> Result<IDCompositionDevice> {
1018 Ok(unsafe { DCompositionCreateDevice(dxgi_device)? })
1019}
1020
1021fn create_swap_chain_for_composition(
1022 dxgi_factory: &IDXGIFactory6,
1023 device: &ID3D11Device,
1024 width: u32,
1025 height: u32,
1026) -> Result<IDXGISwapChain1> {
1027 let desc = DXGI_SWAP_CHAIN_DESC1 {
1028 Width: width,
1029 Height: height,
1030 Format: RENDER_TARGET_FORMAT,
1031 Stereo: false.into(),
1032 SampleDesc: DXGI_SAMPLE_DESC {
1033 Count: 1,
1034 Quality: 0,
1035 },
1036 BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT,
1037 BufferCount: BUFFER_COUNT as u32,
1038 // Composition SwapChains only support the DXGI_SCALING_STRETCH Scaling.
1039 Scaling: DXGI_SCALING_STRETCH,
1040 SwapEffect: DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL,
1041 AlphaMode: DXGI_ALPHA_MODE_PREMULTIPLIED,
1042 Flags: 0,
1043 };
1044 Ok(unsafe { dxgi_factory.CreateSwapChainForComposition(device, &desc, None)? })
1045}
1046
1047fn create_swap_chain(
1048 dxgi_factory: &IDXGIFactory6,
1049 device: &ID3D11Device,
1050 hwnd: HWND,
1051 width: u32,
1052 height: u32,
1053) -> Result<IDXGISwapChain1> {
1054 use windows::Win32::Graphics::Dxgi::DXGI_MWA_NO_ALT_ENTER;
1055
1056 let desc = DXGI_SWAP_CHAIN_DESC1 {
1057 Width: width,
1058 Height: height,
1059 Format: RENDER_TARGET_FORMAT,
1060 Stereo: false.into(),
1061 SampleDesc: DXGI_SAMPLE_DESC {
1062 Count: 1,
1063 Quality: 0,
1064 },
1065 BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT,
1066 BufferCount: BUFFER_COUNT as u32,
1067 Scaling: DXGI_SCALING_NONE,
1068 SwapEffect: DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL,
1069 AlphaMode: DXGI_ALPHA_MODE_IGNORE,
1070 Flags: 0,
1071 };
1072 let swap_chain =
1073 unsafe { dxgi_factory.CreateSwapChainForHwnd(device, hwnd, &desc, None, None) }?;
1074 unsafe { dxgi_factory.MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER) }?;
1075 Ok(swap_chain)
1076}
1077
1078#[inline]
1079fn create_resources(
1080 devices: &DirectXRendererDevices,
1081 swap_chain: &IDXGISwapChain1,
1082 width: u32,
1083 height: u32,
1084) -> Result<(
1085 ManuallyDrop<ID3D11Texture2D>,
1086 [Option<ID3D11RenderTargetView>; 1],
1087 ID3D11Texture2D,
1088 [Option<ID3D11ShaderResourceView>; 1],
1089 ID3D11Texture2D,
1090 [Option<ID3D11RenderTargetView>; 1],
1091 [D3D11_VIEWPORT; 1],
1092)> {
1093 let (render_target, render_target_view) =
1094 create_render_target_and_its_view(swap_chain, &devices.device)?;
1095 let (path_intermediate_texture, path_intermediate_srv) =
1096 create_path_intermediate_texture(&devices.device, width, height)?;
1097 let (path_intermediate_msaa_texture, path_intermediate_msaa_view) =
1098 create_path_intermediate_msaa_texture_and_view(&devices.device, width, height)?;
1099 let viewport = set_viewport(&devices.device_context, width as f32, height as f32);
1100 Ok((
1101 render_target,
1102 render_target_view,
1103 path_intermediate_texture,
1104 path_intermediate_srv,
1105 path_intermediate_msaa_texture,
1106 path_intermediate_msaa_view,
1107 viewport,
1108 ))
1109}
1110
1111#[inline]
1112fn create_render_target_and_its_view(
1113 swap_chain: &IDXGISwapChain1,
1114 device: &ID3D11Device,
1115) -> Result<(
1116 ManuallyDrop<ID3D11Texture2D>,
1117 [Option<ID3D11RenderTargetView>; 1],
1118)> {
1119 let render_target: ID3D11Texture2D = unsafe { swap_chain.GetBuffer(0) }?;
1120 let mut render_target_view = None;
1121 unsafe { device.CreateRenderTargetView(&render_target, None, Some(&mut render_target_view))? };
1122 Ok((
1123 ManuallyDrop::new(render_target),
1124 [Some(render_target_view.unwrap())],
1125 ))
1126}
1127
1128#[inline]
1129fn create_path_intermediate_texture(
1130 device: &ID3D11Device,
1131 width: u32,
1132 height: u32,
1133) -> Result<(ID3D11Texture2D, [Option<ID3D11ShaderResourceView>; 1])> {
1134 let texture = unsafe {
1135 let mut output = None;
1136 let desc = D3D11_TEXTURE2D_DESC {
1137 Width: width,
1138 Height: height,
1139 MipLevels: 1,
1140 ArraySize: 1,
1141 Format: RENDER_TARGET_FORMAT,
1142 SampleDesc: DXGI_SAMPLE_DESC {
1143 Count: 1,
1144 Quality: 0,
1145 },
1146 Usage: D3D11_USAGE_DEFAULT,
1147 BindFlags: (D3D11_BIND_RENDER_TARGET.0 | D3D11_BIND_SHADER_RESOURCE.0) as u32,
1148 CPUAccessFlags: 0,
1149 MiscFlags: 0,
1150 };
1151 device.CreateTexture2D(&desc, None, Some(&mut output))?;
1152 output.unwrap()
1153 };
1154
1155 let mut shader_resource_view = None;
1156 unsafe { device.CreateShaderResourceView(&texture, None, Some(&mut shader_resource_view))? };
1157
1158 Ok((texture, [Some(shader_resource_view.unwrap())]))
1159}
1160
1161#[inline]
1162fn create_path_intermediate_msaa_texture_and_view(
1163 device: &ID3D11Device,
1164 width: u32,
1165 height: u32,
1166) -> Result<(ID3D11Texture2D, [Option<ID3D11RenderTargetView>; 1])> {
1167 let msaa_texture = unsafe {
1168 let mut output = None;
1169 let desc = D3D11_TEXTURE2D_DESC {
1170 Width: width,
1171 Height: height,
1172 MipLevels: 1,
1173 ArraySize: 1,
1174 Format: RENDER_TARGET_FORMAT,
1175 SampleDesc: DXGI_SAMPLE_DESC {
1176 Count: PATH_MULTISAMPLE_COUNT,
1177 Quality: D3D11_STANDARD_MULTISAMPLE_PATTERN.0 as u32,
1178 },
1179 Usage: D3D11_USAGE_DEFAULT,
1180 BindFlags: D3D11_BIND_RENDER_TARGET.0 as u32,
1181 CPUAccessFlags: 0,
1182 MiscFlags: 0,
1183 };
1184 device.CreateTexture2D(&desc, None, Some(&mut output))?;
1185 output.unwrap()
1186 };
1187 let mut msaa_view = None;
1188 unsafe { device.CreateRenderTargetView(&msaa_texture, None, Some(&mut msaa_view))? };
1189 Ok((msaa_texture, [Some(msaa_view.unwrap())]))
1190}
1191
1192#[inline]
1193fn set_viewport(
1194 device_context: &ID3D11DeviceContext,
1195 width: f32,
1196 height: f32,
1197) -> [D3D11_VIEWPORT; 1] {
1198 let viewport = [D3D11_VIEWPORT {
1199 TopLeftX: 0.0,
1200 TopLeftY: 0.0,
1201 Width: width,
1202 Height: height,
1203 MinDepth: 0.0,
1204 MaxDepth: 1.0,
1205 }];
1206 unsafe { device_context.RSSetViewports(Some(&viewport)) };
1207 viewport
1208}
1209
1210#[inline]
1211fn set_rasterizer_state(device: &ID3D11Device, device_context: &ID3D11DeviceContext) -> Result<()> {
1212 let desc = D3D11_RASTERIZER_DESC {
1213 FillMode: D3D11_FILL_SOLID,
1214 CullMode: D3D11_CULL_NONE,
1215 FrontCounterClockwise: false.into(),
1216 DepthBias: 0,
1217 DepthBiasClamp: 0.0,
1218 SlopeScaledDepthBias: 0.0,
1219 DepthClipEnable: true.into(),
1220 ScissorEnable: false.into(),
1221 MultisampleEnable: true.into(),
1222 AntialiasedLineEnable: false.into(),
1223 };
1224 let rasterizer_state = unsafe {
1225 let mut state = None;
1226 device.CreateRasterizerState(&desc, Some(&mut state))?;
1227 state.unwrap()
1228 };
1229 unsafe { device_context.RSSetState(&rasterizer_state) };
1230 Ok(())
1231}
1232
1233// https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ns-d3d11-d3d11_blend_desc
1234#[inline]
1235fn create_blend_state(device: &ID3D11Device) -> Result<ID3D11BlendState> {
1236 // If the feature level is set to greater than D3D_FEATURE_LEVEL_9_3, the display
1237 // device performs the blend in linear space, which is ideal.
1238 let mut desc = D3D11_BLEND_DESC::default();
1239 desc.RenderTarget[0].BlendEnable = true.into();
1240 desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
1241 desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
1242 desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
1243 desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
1244 desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
1245 desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ONE;
1246 desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8;
1247 unsafe {
1248 let mut state = None;
1249 device.CreateBlendState(&desc, Some(&mut state))?;
1250 Ok(state.unwrap())
1251 }
1252}
1253
1254#[inline]
1255fn create_blend_state_for_path_rasterization(device: &ID3D11Device) -> Result<ID3D11BlendState> {
1256 // If the feature level is set to greater than D3D_FEATURE_LEVEL_9_3, the display
1257 // device performs the blend in linear space, which is ideal.
1258 let mut desc = D3D11_BLEND_DESC::default();
1259 desc.RenderTarget[0].BlendEnable = true.into();
1260 desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
1261 desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
1262 desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE;
1263 desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
1264 desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
1265 desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA;
1266 desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8;
1267 unsafe {
1268 let mut state = None;
1269 device.CreateBlendState(&desc, Some(&mut state))?;
1270 Ok(state.unwrap())
1271 }
1272}
1273
1274#[inline]
1275fn create_blend_state_for_path_sprite(device: &ID3D11Device) -> Result<ID3D11BlendState> {
1276 // If the feature level is set to greater than D3D_FEATURE_LEVEL_9_3, the display
1277 // device performs the blend in linear space, which is ideal.
1278 let mut desc = D3D11_BLEND_DESC::default();
1279 desc.RenderTarget[0].BlendEnable = true.into();
1280 desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
1281 desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
1282 desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE;
1283 desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
1284 desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
1285 desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ONE;
1286 desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8;
1287 unsafe {
1288 let mut state = None;
1289 device.CreateBlendState(&desc, Some(&mut state))?;
1290 Ok(state.unwrap())
1291 }
1292}
1293
1294#[inline]
1295fn create_vertex_shader(device: &ID3D11Device, bytes: &[u8]) -> Result<ID3D11VertexShader> {
1296 unsafe {
1297 let mut shader = None;
1298 device.CreateVertexShader(bytes, None, Some(&mut shader))?;
1299 Ok(shader.unwrap())
1300 }
1301}
1302
1303#[inline]
1304fn create_fragment_shader(device: &ID3D11Device, bytes: &[u8]) -> Result<ID3D11PixelShader> {
1305 unsafe {
1306 let mut shader = None;
1307 device.CreatePixelShader(bytes, None, Some(&mut shader))?;
1308 Ok(shader.unwrap())
1309 }
1310}
1311
1312#[inline]
1313fn create_buffer(
1314 device: &ID3D11Device,
1315 element_size: usize,
1316 buffer_size: usize,
1317) -> Result<ID3D11Buffer> {
1318 let desc = D3D11_BUFFER_DESC {
1319 ByteWidth: (element_size * buffer_size) as u32,
1320 Usage: D3D11_USAGE_DYNAMIC,
1321 BindFlags: D3D11_BIND_SHADER_RESOURCE.0 as u32,
1322 CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32,
1323 MiscFlags: D3D11_RESOURCE_MISC_BUFFER_STRUCTURED.0 as u32,
1324 StructureByteStride: element_size as u32,
1325 };
1326 let mut buffer = None;
1327 unsafe { device.CreateBuffer(&desc, None, Some(&mut buffer)) }?;
1328 Ok(buffer.unwrap())
1329}
1330
1331#[inline]
1332fn create_buffer_view(
1333 device: &ID3D11Device,
1334 buffer: &ID3D11Buffer,
1335) -> Result<[Option<ID3D11ShaderResourceView>; 1]> {
1336 let mut view = None;
1337 unsafe { device.CreateShaderResourceView(buffer, None, Some(&mut view)) }?;
1338 Ok([view])
1339}
1340
1341#[inline]
1342fn update_buffer<T>(
1343 device_context: &ID3D11DeviceContext,
1344 buffer: &ID3D11Buffer,
1345 data: &[T],
1346) -> Result<()> {
1347 unsafe {
1348 let mut dest = std::mem::zeroed();
1349 device_context.Map(buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, Some(&mut dest))?;
1350 std::ptr::copy_nonoverlapping(data.as_ptr(), dest.pData as _, data.len());
1351 device_context.Unmap(buffer, 0);
1352 }
1353 Ok(())
1354}
1355
1356#[inline]
1357fn set_pipeline_state(
1358 device_context: &ID3D11DeviceContext,
1359 buffer_view: &[Option<ID3D11ShaderResourceView>],
1360 topology: D3D_PRIMITIVE_TOPOLOGY,
1361 viewport: &[D3D11_VIEWPORT],
1362 vertex_shader: &ID3D11VertexShader,
1363 fragment_shader: &ID3D11PixelShader,
1364 global_params: &[Option<ID3D11Buffer>],
1365 blend_state: &ID3D11BlendState,
1366) {
1367 unsafe {
1368 device_context.VSSetShaderResources(1, Some(buffer_view));
1369 device_context.PSSetShaderResources(1, Some(buffer_view));
1370 device_context.IASetPrimitiveTopology(topology);
1371 device_context.RSSetViewports(Some(viewport));
1372 device_context.VSSetShader(vertex_shader, None);
1373 device_context.PSSetShader(fragment_shader, None);
1374 device_context.VSSetConstantBuffers(0, Some(global_params));
1375 device_context.PSSetConstantBuffers(0, Some(global_params));
1376 device_context.OMSetBlendState(blend_state, None, 0xFFFFFFFF);
1377 }
1378}
1379
1380#[cfg(debug_assertions)]
1381fn report_live_objects(device: &ID3D11Device) -> Result<()> {
1382 let debug_device: ID3D11Debug = device.cast()?;
1383 unsafe {
1384 debug_device.ReportLiveDeviceObjects(D3D11_RLDO_DETAIL)?;
1385 }
1386 Ok(())
1387}
1388
1389const BUFFER_COUNT: usize = 3;
1390
1391pub(crate) mod shader_resources {
1392 use anyhow::Result;
1393
1394 #[cfg(debug_assertions)]
1395 use windows::{
1396 Win32::Graphics::Direct3D::{
1397 Fxc::{D3DCOMPILE_DEBUG, D3DCOMPILE_SKIP_OPTIMIZATION, D3DCompileFromFile},
1398 ID3DBlob,
1399 },
1400 core::{HSTRING, PCSTR},
1401 };
1402
1403 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
1404 pub(crate) enum ShaderModule {
1405 Quad,
1406 Shadow,
1407 Underline,
1408 PathRasterization,
1409 PathSprite,
1410 MonochromeSprite,
1411 PolychromeSprite,
1412 EmojiRasterization,
1413 }
1414
1415 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
1416 pub(crate) enum ShaderTarget {
1417 Vertex,
1418 Fragment,
1419 }
1420
1421 pub(crate) struct RawShaderBytes<'t> {
1422 inner: &'t [u8],
1423
1424 #[cfg(debug_assertions)]
1425 _blob: ID3DBlob,
1426 }
1427
1428 impl<'t> RawShaderBytes<'t> {
1429 pub(crate) fn new(module: ShaderModule, target: ShaderTarget) -> Result<Self> {
1430 #[cfg(not(debug_assertions))]
1431 {
1432 Ok(Self::from_bytes(module, target))
1433 }
1434 #[cfg(debug_assertions)]
1435 {
1436 let blob = build_shader_blob(module, target)?;
1437 let inner = unsafe {
1438 std::slice::from_raw_parts(
1439 blob.GetBufferPointer() as *const u8,
1440 blob.GetBufferSize(),
1441 )
1442 };
1443 Ok(Self { inner, _blob: blob })
1444 }
1445 }
1446
1447 pub(crate) fn as_bytes(&'t self) -> &'t [u8] {
1448 self.inner
1449 }
1450
1451 #[cfg(not(debug_assertions))]
1452 fn from_bytes(module: ShaderModule, target: ShaderTarget) -> Self {
1453 let bytes = match module {
1454 ShaderModule::Quad => match target {
1455 ShaderTarget::Vertex => QUAD_VERTEX_BYTES,
1456 ShaderTarget::Fragment => QUAD_FRAGMENT_BYTES,
1457 },
1458 ShaderModule::Shadow => match target {
1459 ShaderTarget::Vertex => SHADOW_VERTEX_BYTES,
1460 ShaderTarget::Fragment => SHADOW_FRAGMENT_BYTES,
1461 },
1462 ShaderModule::Underline => match target {
1463 ShaderTarget::Vertex => UNDERLINE_VERTEX_BYTES,
1464 ShaderTarget::Fragment => UNDERLINE_FRAGMENT_BYTES,
1465 },
1466 ShaderModule::PathRasterization => match target {
1467 ShaderTarget::Vertex => PATH_RASTERIZATION_VERTEX_BYTES,
1468 ShaderTarget::Fragment => PATH_RASTERIZATION_FRAGMENT_BYTES,
1469 },
1470 ShaderModule::PathSprite => match target {
1471 ShaderTarget::Vertex => PATH_SPRITE_VERTEX_BYTES,
1472 ShaderTarget::Fragment => PATH_SPRITE_FRAGMENT_BYTES,
1473 },
1474 ShaderModule::MonochromeSprite => match target {
1475 ShaderTarget::Vertex => MONOCHROME_SPRITE_VERTEX_BYTES,
1476 ShaderTarget::Fragment => MONOCHROME_SPRITE_FRAGMENT_BYTES,
1477 },
1478 ShaderModule::PolychromeSprite => match target {
1479 ShaderTarget::Vertex => POLYCHROME_SPRITE_VERTEX_BYTES,
1480 ShaderTarget::Fragment => POLYCHROME_SPRITE_FRAGMENT_BYTES,
1481 },
1482 ShaderModule::EmojiRasterization => match target {
1483 ShaderTarget::Vertex => EMOJI_RASTERIZATION_VERTEX_BYTES,
1484 ShaderTarget::Fragment => EMOJI_RASTERIZATION_FRAGMENT_BYTES,
1485 },
1486 };
1487 Self { inner: bytes }
1488 }
1489 }
1490
1491 #[cfg(debug_assertions)]
1492 pub(super) fn build_shader_blob(entry: ShaderModule, target: ShaderTarget) -> Result<ID3DBlob> {
1493 unsafe {
1494 use windows::Win32::Graphics::{
1495 Direct3D::ID3DInclude, Hlsl::D3D_COMPILE_STANDARD_FILE_INCLUDE,
1496 };
1497
1498 let shader_name = if matches!(entry, ShaderModule::EmojiRasterization) {
1499 "color_text_raster.hlsl"
1500 } else {
1501 "shaders.hlsl"
1502 };
1503
1504 let entry = format!(
1505 "{}_{}\0",
1506 entry.as_str(),
1507 match target {
1508 ShaderTarget::Vertex => "vertex",
1509 ShaderTarget::Fragment => "fragment",
1510 }
1511 );
1512 let target = match target {
1513 ShaderTarget::Vertex => "vs_4_1\0",
1514 ShaderTarget::Fragment => "ps_4_1\0",
1515 };
1516
1517 let mut compile_blob = None;
1518 let mut error_blob = None;
1519 let shader_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
1520 .join(&format!("src/platform/windows/{}", shader_name))
1521 .canonicalize()?;
1522
1523 let entry_point = PCSTR::from_raw(entry.as_ptr());
1524 let target_cstr = PCSTR::from_raw(target.as_ptr());
1525
1526 // really dirty trick because winapi bindings are unhappy otherwise
1527 let include_handler = &std::mem::transmute::<usize, ID3DInclude>(
1528 D3D_COMPILE_STANDARD_FILE_INCLUDE as usize,
1529 );
1530
1531 let ret = D3DCompileFromFile(
1532 &HSTRING::from(shader_path.to_str().unwrap()),
1533 None,
1534 include_handler,
1535 entry_point,
1536 target_cstr,
1537 D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION,
1538 0,
1539 &mut compile_blob,
1540 Some(&mut error_blob),
1541 );
1542 if ret.is_err() {
1543 let Some(error_blob) = error_blob else {
1544 return Err(anyhow::anyhow!("{ret:?}"));
1545 };
1546
1547 let error_string =
1548 std::ffi::CStr::from_ptr(error_blob.GetBufferPointer() as *const i8)
1549 .to_string_lossy();
1550 log::error!("Shader compile error: {}", error_string);
1551 return Err(anyhow::anyhow!("Compile error: {}", error_string));
1552 }
1553 Ok(compile_blob.unwrap())
1554 }
1555 }
1556
1557 #[cfg(not(debug_assertions))]
1558 include!(concat!(env!("OUT_DIR"), "/shaders_bytes.rs"));
1559
1560 #[cfg(debug_assertions)]
1561 impl ShaderModule {
1562 pub fn as_str(&self) -> &str {
1563 match self {
1564 ShaderModule::Quad => "quad",
1565 ShaderModule::Shadow => "shadow",
1566 ShaderModule::Underline => "underline",
1567 ShaderModule::PathRasterization => "path_rasterization",
1568 ShaderModule::PathSprite => "path_sprite",
1569 ShaderModule::MonochromeSprite => "monochrome_sprite",
1570 ShaderModule::PolychromeSprite => "polychrome_sprite",
1571 ShaderModule::EmojiRasterization => "emoji_rasterization",
1572 }
1573 }
1574 }
1575}
1576
1577mod nvidia {
1578 use std::{
1579 ffi::CStr,
1580 os::raw::{c_char, c_int, c_uint},
1581 };
1582
1583 use anyhow::Result;
1584 use windows::{Win32::System::LibraryLoader::GetProcAddress, core::s};
1585
1586 use crate::with_dll_library;
1587
1588 // https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_lite_common.h#L180
1589 const NVAPI_SHORT_STRING_MAX: usize = 64;
1590
1591 // https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_lite_common.h#L235
1592 #[allow(non_camel_case_types)]
1593 type NvAPI_ShortString = [c_char; NVAPI_SHORT_STRING_MAX];
1594
1595 // https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_lite_common.h#L447
1596 #[allow(non_camel_case_types)]
1597 type NvAPI_SYS_GetDriverAndBranchVersion_t = unsafe extern "C" fn(
1598 driver_version: *mut c_uint,
1599 build_branch_string: *mut NvAPI_ShortString,
1600 ) -> c_int;
1601
1602 pub(super) fn get_driver_version() -> Result<String> {
1603 #[cfg(target_pointer_width = "64")]
1604 let nvidia_dll_name = s!("nvapi64.dll");
1605 #[cfg(target_pointer_width = "32")]
1606 let nvidia_dll_name = s!("nvapi.dll");
1607
1608 with_dll_library(nvidia_dll_name, |nvidia_dll| unsafe {
1609 let nvapi_query_addr = GetProcAddress(nvidia_dll, s!("nvapi_QueryInterface"))
1610 .ok_or_else(|| anyhow::anyhow!("Failed to get nvapi_QueryInterface address"))?;
1611 let nvapi_query: extern "C" fn(u32) -> *mut () = std::mem::transmute(nvapi_query_addr);
1612
1613 // https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_interface.h#L41
1614 let nvapi_get_driver_version_ptr = nvapi_query(0x2926aaad);
1615 if nvapi_get_driver_version_ptr.is_null() {
1616 anyhow::bail!("Failed to get NVIDIA driver version function pointer");
1617 }
1618 let nvapi_get_driver_version: NvAPI_SYS_GetDriverAndBranchVersion_t =
1619 std::mem::transmute(nvapi_get_driver_version_ptr);
1620
1621 let mut driver_version: c_uint = 0;
1622 let mut build_branch_string: NvAPI_ShortString = [0; NVAPI_SHORT_STRING_MAX];
1623 let result = nvapi_get_driver_version(
1624 &mut driver_version as *mut c_uint,
1625 &mut build_branch_string as *mut NvAPI_ShortString,
1626 );
1627
1628 if result != 0 {
1629 anyhow::bail!(
1630 "Failed to get NVIDIA driver version, error code: {}",
1631 result
1632 );
1633 }
1634 let major = driver_version / 100;
1635 let minor = driver_version % 100;
1636 let branch_string = CStr::from_ptr(build_branch_string.as_ptr());
1637 Ok(format!(
1638 "{}.{} {}",
1639 major,
1640 minor,
1641 branch_string.to_string_lossy()
1642 ))
1643 })
1644 }
1645}
1646
1647mod amd {
1648 use std::os::raw::{c_char, c_int, c_void};
1649
1650 use anyhow::Result;
1651 use windows::{Win32::System::LibraryLoader::GetProcAddress, core::s};
1652
1653 use crate::with_dll_library;
1654
1655 // https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L145
1656 const AGS_CURRENT_VERSION: i32 = (6 << 22) | (3 << 12);
1657
1658 // https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L204
1659 // This is an opaque type, using struct to represent it properly for FFI
1660 #[repr(C)]
1661 struct AGSContext {
1662 _private: [u8; 0],
1663 }
1664
1665 #[repr(C)]
1666 pub struct AGSGPUInfo {
1667 pub driver_version: *const c_char,
1668 pub radeon_software_version: *const c_char,
1669 pub num_devices: c_int,
1670 pub devices: *mut c_void,
1671 }
1672
1673 // https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L429
1674 #[allow(non_camel_case_types)]
1675 type agsInitialize_t = unsafe extern "C" fn(
1676 version: c_int,
1677 config: *const c_void,
1678 context: *mut *mut AGSContext,
1679 gpu_info: *mut AGSGPUInfo,
1680 ) -> c_int;
1681
1682 // https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L436
1683 #[allow(non_camel_case_types)]
1684 type agsDeInitialize_t = unsafe extern "C" fn(context: *mut AGSContext) -> c_int;
1685
1686 pub(super) fn get_driver_version() -> Result<String> {
1687 #[cfg(target_pointer_width = "64")]
1688 let amd_dll_name = s!("amd_ags_x64.dll");
1689 #[cfg(target_pointer_width = "32")]
1690 let amd_dll_name = s!("amd_ags_x86.dll");
1691
1692 with_dll_library(amd_dll_name, |amd_dll| unsafe {
1693 let ags_initialize_addr = GetProcAddress(amd_dll, s!("agsInitialize"))
1694 .ok_or_else(|| anyhow::anyhow!("Failed to get agsInitialize address"))?;
1695 let ags_deinitialize_addr = GetProcAddress(amd_dll, s!("agsDeInitialize"))
1696 .ok_or_else(|| anyhow::anyhow!("Failed to get agsDeInitialize address"))?;
1697
1698 let ags_initialize: agsInitialize_t = std::mem::transmute(ags_initialize_addr);
1699 let ags_deinitialize: agsDeInitialize_t = std::mem::transmute(ags_deinitialize_addr);
1700
1701 let mut context: *mut AGSContext = std::ptr::null_mut();
1702 let mut gpu_info: AGSGPUInfo = AGSGPUInfo {
1703 driver_version: std::ptr::null(),
1704 radeon_software_version: std::ptr::null(),
1705 num_devices: 0,
1706 devices: std::ptr::null_mut(),
1707 };
1708
1709 let result = ags_initialize(
1710 AGS_CURRENT_VERSION,
1711 std::ptr::null(),
1712 &mut context,
1713 &mut gpu_info,
1714 );
1715 if result != 0 {
1716 anyhow::bail!("Failed to initialize AMD AGS, error code: {}", result);
1717 }
1718
1719 // Vulkan actually returns this as the driver version
1720 let software_version = if !gpu_info.radeon_software_version.is_null() {
1721 std::ffi::CStr::from_ptr(gpu_info.radeon_software_version)
1722 .to_string_lossy()
1723 .into_owned()
1724 } else {
1725 "Unknown Radeon Software Version".to_string()
1726 };
1727
1728 let driver_version = if !gpu_info.driver_version.is_null() {
1729 std::ffi::CStr::from_ptr(gpu_info.driver_version)
1730 .to_string_lossy()
1731 .into_owned()
1732 } else {
1733 "Unknown Radeon Driver Version".to_string()
1734 };
1735
1736 ags_deinitialize(context);
1737 Ok(format!("{} ({})", software_version, driver_version))
1738 })
1739 }
1740}
1741
1742mod dxgi {
1743 use windows::{
1744 Win32::Graphics::Dxgi::{IDXGIAdapter1, IDXGIDevice},
1745 core::Interface,
1746 };
1747
1748 pub(super) fn get_driver_version(adapter: &IDXGIAdapter1) -> anyhow::Result<String> {
1749 let number = unsafe { adapter.CheckInterfaceSupport(&IDXGIDevice::IID as _) }?;
1750 Ok(format!(
1751 "{}.{}.{}.{}",
1752 number >> 48,
1753 (number >> 32) & 0xFFFF,
1754 (number >> 16) & 0xFFFF,
1755 number & 0xFFFF
1756 ))
1757 }
1758}