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