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