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