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