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