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