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