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