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