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