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