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