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