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