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