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