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