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