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