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 = if unsafe { DXGIGetDebugInterface1::<IDXGIInfoQueue>(0) }
978 .log_err()
979 .is_some()
980 {
981 DXGI_CREATE_FACTORY_DEBUG
982 } else {
983 log::error!(
984 "Failed to get DXGI debug interface. DirectX debugging features will be disabled."
985 );
986 DXGI_CREATE_FACTORY_FLAGS::default()
987 };
988 #[cfg(not(debug_assertions))]
989 let factory_flag = DXGI_CREATE_FACTORY_FLAGS::default();
990 unsafe { Ok(CreateDXGIFactory2(factory_flag)?) }
991}
992
993fn get_adapter(dxgi_factory: &IDXGIFactory6) -> Result<IDXGIAdapter1> {
994 for adapter_index in 0.. {
995 let adapter: IDXGIAdapter1 = unsafe {
996 dxgi_factory
997 .EnumAdapterByGpuPreference(adapter_index, DXGI_GPU_PREFERENCE_MINIMUM_POWER)
998 }?;
999 if let Ok(desc) = unsafe { adapter.GetDesc1() } {
1000 let gpu_name = String::from_utf16_lossy(&desc.Description)
1001 .trim_matches(char::from(0))
1002 .to_string();
1003 log::info!("Using GPU: {}", gpu_name);
1004 }
1005 // Check to see whether the adapter supports Direct3D 11, but don't
1006 // create the actual device yet.
1007 if get_device(&adapter, None, None, None).log_err().is_some() {
1008 return Ok(adapter);
1009 }
1010 }
1011
1012 unreachable!()
1013}
1014
1015fn get_device(
1016 adapter: &IDXGIAdapter1,
1017 device: Option<*mut Option<ID3D11Device>>,
1018 context: Option<*mut Option<ID3D11DeviceContext>>,
1019 feature_level: Option<*mut D3D_FEATURE_LEVEL>,
1020) -> Result<()> {
1021 #[cfg(debug_assertions)]
1022 let device_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_DEBUG;
1023 #[cfg(not(debug_assertions))]
1024 let device_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
1025 unsafe {
1026 D3D11CreateDevice(
1027 adapter,
1028 D3D_DRIVER_TYPE_UNKNOWN,
1029 HMODULE::default(),
1030 device_flags,
1031 // 4x MSAA is required for Direct3D Feature Level 10.1 or better
1032 Some(&[
1033 D3D_FEATURE_LEVEL_11_1,
1034 D3D_FEATURE_LEVEL_11_0,
1035 D3D_FEATURE_LEVEL_10_1,
1036 ]),
1037 D3D11_SDK_VERSION,
1038 device,
1039 feature_level,
1040 context,
1041 )?;
1042 }
1043 Ok(())
1044}
1045
1046#[inline]
1047fn get_comp_device(dxgi_device: &IDXGIDevice) -> Result<IDCompositionDevice> {
1048 Ok(unsafe { DCompositionCreateDevice(dxgi_device)? })
1049}
1050
1051fn create_swap_chain_for_composition(
1052 dxgi_factory: &IDXGIFactory6,
1053 device: &ID3D11Device,
1054 width: u32,
1055 height: u32,
1056) -> Result<IDXGISwapChain1> {
1057 let desc = DXGI_SWAP_CHAIN_DESC1 {
1058 Width: width,
1059 Height: height,
1060 Format: RENDER_TARGET_FORMAT,
1061 Stereo: false.into(),
1062 SampleDesc: DXGI_SAMPLE_DESC {
1063 Count: 1,
1064 Quality: 0,
1065 },
1066 BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT,
1067 BufferCount: BUFFER_COUNT as u32,
1068 // Composition SwapChains only support the DXGI_SCALING_STRETCH Scaling.
1069 Scaling: DXGI_SCALING_STRETCH,
1070 SwapEffect: DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL,
1071 AlphaMode: DXGI_ALPHA_MODE_PREMULTIPLIED,
1072 Flags: 0,
1073 };
1074 Ok(unsafe { dxgi_factory.CreateSwapChainForComposition(device, &desc, None)? })
1075}
1076
1077fn create_swap_chain(
1078 dxgi_factory: &IDXGIFactory6,
1079 device: &ID3D11Device,
1080 hwnd: HWND,
1081 width: u32,
1082 height: u32,
1083) -> Result<IDXGISwapChain1> {
1084 use windows::Win32::Graphics::Dxgi::DXGI_MWA_NO_ALT_ENTER;
1085
1086 let desc = DXGI_SWAP_CHAIN_DESC1 {
1087 Width: width,
1088 Height: height,
1089 Format: RENDER_TARGET_FORMAT,
1090 Stereo: false.into(),
1091 SampleDesc: DXGI_SAMPLE_DESC {
1092 Count: 1,
1093 Quality: 0,
1094 },
1095 BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT,
1096 BufferCount: BUFFER_COUNT as u32,
1097 Scaling: DXGI_SCALING_NONE,
1098 SwapEffect: DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL,
1099 AlphaMode: DXGI_ALPHA_MODE_IGNORE,
1100 Flags: 0,
1101 };
1102 let swap_chain =
1103 unsafe { dxgi_factory.CreateSwapChainForHwnd(device, hwnd, &desc, None, None) }?;
1104 unsafe { dxgi_factory.MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER) }?;
1105 Ok(swap_chain)
1106}
1107
1108#[inline]
1109fn create_resources(
1110 devices: &DirectXDevices,
1111 swap_chain: &IDXGISwapChain1,
1112 width: u32,
1113 height: u32,
1114) -> Result<(
1115 ManuallyDrop<ID3D11Texture2D>,
1116 [Option<ID3D11RenderTargetView>; 1],
1117 ID3D11Texture2D,
1118 [Option<ID3D11ShaderResourceView>; 1],
1119 ID3D11Texture2D,
1120 [Option<ID3D11RenderTargetView>; 1],
1121 [D3D11_VIEWPORT; 1],
1122)> {
1123 let (render_target, render_target_view) =
1124 create_render_target_and_its_view(&swap_chain, &devices.device)?;
1125 let (path_intermediate_texture, path_intermediate_srv) =
1126 create_path_intermediate_texture(&devices.device, width, height)?;
1127 let (path_intermediate_msaa_texture, path_intermediate_msaa_view) =
1128 create_path_intermediate_msaa_texture_and_view(&devices.device, width, height)?;
1129 let viewport = set_viewport(&devices.device_context, width as f32, height as f32);
1130 Ok((
1131 render_target,
1132 render_target_view,
1133 path_intermediate_texture,
1134 path_intermediate_srv,
1135 path_intermediate_msaa_texture,
1136 path_intermediate_msaa_view,
1137 viewport,
1138 ))
1139}
1140
1141#[inline]
1142fn create_render_target_and_its_view(
1143 swap_chain: &IDXGISwapChain1,
1144 device: &ID3D11Device,
1145) -> Result<(
1146 ManuallyDrop<ID3D11Texture2D>,
1147 [Option<ID3D11RenderTargetView>; 1],
1148)> {
1149 let render_target: ID3D11Texture2D = unsafe { swap_chain.GetBuffer(0) }?;
1150 let mut render_target_view = None;
1151 unsafe { device.CreateRenderTargetView(&render_target, None, Some(&mut render_target_view))? };
1152 Ok((
1153 ManuallyDrop::new(render_target),
1154 [Some(render_target_view.unwrap())],
1155 ))
1156}
1157
1158#[inline]
1159fn create_path_intermediate_texture(
1160 device: &ID3D11Device,
1161 width: u32,
1162 height: u32,
1163) -> Result<(ID3D11Texture2D, [Option<ID3D11ShaderResourceView>; 1])> {
1164 let texture = unsafe {
1165 let mut output = None;
1166 let desc = D3D11_TEXTURE2D_DESC {
1167 Width: width,
1168 Height: height,
1169 MipLevels: 1,
1170 ArraySize: 1,
1171 Format: RENDER_TARGET_FORMAT,
1172 SampleDesc: DXGI_SAMPLE_DESC {
1173 Count: 1,
1174 Quality: 0,
1175 },
1176 Usage: D3D11_USAGE_DEFAULT,
1177 BindFlags: (D3D11_BIND_RENDER_TARGET.0 | D3D11_BIND_SHADER_RESOURCE.0) as u32,
1178 CPUAccessFlags: 0,
1179 MiscFlags: 0,
1180 };
1181 device.CreateTexture2D(&desc, None, Some(&mut output))?;
1182 output.unwrap()
1183 };
1184
1185 let mut shader_resource_view = None;
1186 unsafe { device.CreateShaderResourceView(&texture, None, Some(&mut shader_resource_view))? };
1187
1188 Ok((texture, [Some(shader_resource_view.unwrap())]))
1189}
1190
1191#[inline]
1192fn create_path_intermediate_msaa_texture_and_view(
1193 device: &ID3D11Device,
1194 width: u32,
1195 height: u32,
1196) -> Result<(ID3D11Texture2D, [Option<ID3D11RenderTargetView>; 1])> {
1197 let msaa_texture = unsafe {
1198 let mut output = None;
1199 let desc = D3D11_TEXTURE2D_DESC {
1200 Width: width,
1201 Height: height,
1202 MipLevels: 1,
1203 ArraySize: 1,
1204 Format: RENDER_TARGET_FORMAT,
1205 SampleDesc: DXGI_SAMPLE_DESC {
1206 Count: PATH_MULTISAMPLE_COUNT,
1207 Quality: D3D11_STANDARD_MULTISAMPLE_PATTERN.0 as u32,
1208 },
1209 Usage: D3D11_USAGE_DEFAULT,
1210 BindFlags: D3D11_BIND_RENDER_TARGET.0 as u32,
1211 CPUAccessFlags: 0,
1212 MiscFlags: 0,
1213 };
1214 device.CreateTexture2D(&desc, None, Some(&mut output))?;
1215 output.unwrap()
1216 };
1217 let mut msaa_view = None;
1218 unsafe { device.CreateRenderTargetView(&msaa_texture, None, Some(&mut msaa_view))? };
1219 Ok((msaa_texture, [Some(msaa_view.unwrap())]))
1220}
1221
1222#[inline]
1223fn set_viewport(
1224 device_context: &ID3D11DeviceContext,
1225 width: f32,
1226 height: f32,
1227) -> [D3D11_VIEWPORT; 1] {
1228 let viewport = [D3D11_VIEWPORT {
1229 TopLeftX: 0.0,
1230 TopLeftY: 0.0,
1231 Width: width,
1232 Height: height,
1233 MinDepth: 0.0,
1234 MaxDepth: 1.0,
1235 }];
1236 unsafe { device_context.RSSetViewports(Some(&viewport)) };
1237 viewport
1238}
1239
1240#[inline]
1241fn set_rasterizer_state(device: &ID3D11Device, device_context: &ID3D11DeviceContext) -> Result<()> {
1242 let desc = D3D11_RASTERIZER_DESC {
1243 FillMode: D3D11_FILL_SOLID,
1244 CullMode: D3D11_CULL_NONE,
1245 FrontCounterClockwise: false.into(),
1246 DepthBias: 0,
1247 DepthBiasClamp: 0.0,
1248 SlopeScaledDepthBias: 0.0,
1249 DepthClipEnable: true.into(),
1250 ScissorEnable: false.into(),
1251 MultisampleEnable: true.into(),
1252 AntialiasedLineEnable: false.into(),
1253 };
1254 let rasterizer_state = unsafe {
1255 let mut state = None;
1256 device.CreateRasterizerState(&desc, Some(&mut state))?;
1257 state.unwrap()
1258 };
1259 unsafe { device_context.RSSetState(&rasterizer_state) };
1260 Ok(())
1261}
1262
1263// https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ns-d3d11-d3d11_blend_desc
1264#[inline]
1265fn create_blend_state(device: &ID3D11Device) -> Result<ID3D11BlendState> {
1266 // If the feature level is set to greater than D3D_FEATURE_LEVEL_9_3, the display
1267 // device performs the blend in linear space, which is ideal.
1268 let mut desc = D3D11_BLEND_DESC::default();
1269 desc.RenderTarget[0].BlendEnable = true.into();
1270 desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
1271 desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
1272 desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
1273 desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
1274 desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
1275 desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ONE;
1276 desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8;
1277 unsafe {
1278 let mut state = None;
1279 device.CreateBlendState(&desc, Some(&mut state))?;
1280 Ok(state.unwrap())
1281 }
1282}
1283
1284#[inline]
1285fn create_blend_state_for_path_rasterization(device: &ID3D11Device) -> Result<ID3D11BlendState> {
1286 // If the feature level is set to greater than D3D_FEATURE_LEVEL_9_3, the display
1287 // device performs the blend in linear space, which is ideal.
1288 let mut desc = D3D11_BLEND_DESC::default();
1289 desc.RenderTarget[0].BlendEnable = true.into();
1290 desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
1291 desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
1292 desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE;
1293 desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
1294 desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
1295 desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA;
1296 desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8;
1297 unsafe {
1298 let mut state = None;
1299 device.CreateBlendState(&desc, Some(&mut state))?;
1300 Ok(state.unwrap())
1301 }
1302}
1303
1304#[inline]
1305fn create_blend_state_for_path_sprite(device: &ID3D11Device) -> Result<ID3D11BlendState> {
1306 // If the feature level is set to greater than D3D_FEATURE_LEVEL_9_3, the display
1307 // device performs the blend in linear space, which is ideal.
1308 let mut desc = D3D11_BLEND_DESC::default();
1309 desc.RenderTarget[0].BlendEnable = true.into();
1310 desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
1311 desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
1312 desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE;
1313 desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
1314 desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
1315 desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ONE;
1316 desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8;
1317 unsafe {
1318 let mut state = None;
1319 device.CreateBlendState(&desc, Some(&mut state))?;
1320 Ok(state.unwrap())
1321 }
1322}
1323
1324#[inline]
1325fn create_vertex_shader(device: &ID3D11Device, bytes: &[u8]) -> Result<ID3D11VertexShader> {
1326 unsafe {
1327 let mut shader = None;
1328 device.CreateVertexShader(bytes, None, Some(&mut shader))?;
1329 Ok(shader.unwrap())
1330 }
1331}
1332
1333#[inline]
1334fn create_fragment_shader(device: &ID3D11Device, bytes: &[u8]) -> Result<ID3D11PixelShader> {
1335 unsafe {
1336 let mut shader = None;
1337 device.CreatePixelShader(bytes, None, Some(&mut shader))?;
1338 Ok(shader.unwrap())
1339 }
1340}
1341
1342#[inline]
1343fn create_buffer(
1344 device: &ID3D11Device,
1345 element_size: usize,
1346 buffer_size: usize,
1347) -> Result<ID3D11Buffer> {
1348 let desc = D3D11_BUFFER_DESC {
1349 ByteWidth: (element_size * buffer_size) as u32,
1350 Usage: D3D11_USAGE_DYNAMIC,
1351 BindFlags: D3D11_BIND_SHADER_RESOURCE.0 as u32,
1352 CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32,
1353 MiscFlags: D3D11_RESOURCE_MISC_BUFFER_STRUCTURED.0 as u32,
1354 StructureByteStride: element_size as u32,
1355 };
1356 let mut buffer = None;
1357 unsafe { device.CreateBuffer(&desc, None, Some(&mut buffer)) }?;
1358 Ok(buffer.unwrap())
1359}
1360
1361#[inline]
1362fn create_buffer_view(
1363 device: &ID3D11Device,
1364 buffer: &ID3D11Buffer,
1365) -> Result<[Option<ID3D11ShaderResourceView>; 1]> {
1366 let mut view = None;
1367 unsafe { device.CreateShaderResourceView(buffer, None, Some(&mut view)) }?;
1368 Ok([view])
1369}
1370
1371#[inline]
1372fn update_buffer<T>(
1373 device_context: &ID3D11DeviceContext,
1374 buffer: &ID3D11Buffer,
1375 data: &[T],
1376) -> Result<()> {
1377 unsafe {
1378 let mut dest = std::mem::zeroed();
1379 device_context.Map(buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, Some(&mut dest))?;
1380 std::ptr::copy_nonoverlapping(data.as_ptr(), dest.pData as _, data.len());
1381 device_context.Unmap(buffer, 0);
1382 }
1383 Ok(())
1384}
1385
1386#[inline]
1387fn set_pipeline_state(
1388 device_context: &ID3D11DeviceContext,
1389 buffer_view: &[Option<ID3D11ShaderResourceView>],
1390 topology: D3D_PRIMITIVE_TOPOLOGY,
1391 viewport: &[D3D11_VIEWPORT],
1392 vertex_shader: &ID3D11VertexShader,
1393 fragment_shader: &ID3D11PixelShader,
1394 global_params: &[Option<ID3D11Buffer>],
1395 blend_state: &ID3D11BlendState,
1396) {
1397 unsafe {
1398 device_context.VSSetShaderResources(1, Some(buffer_view));
1399 device_context.PSSetShaderResources(1, Some(buffer_view));
1400 device_context.IASetPrimitiveTopology(topology);
1401 device_context.RSSetViewports(Some(viewport));
1402 device_context.VSSetShader(vertex_shader, None);
1403 device_context.PSSetShader(fragment_shader, None);
1404 device_context.VSSetConstantBuffers(0, Some(global_params));
1405 device_context.PSSetConstantBuffers(0, Some(global_params));
1406 device_context.OMSetBlendState(blend_state, None, 0xFFFFFFFF);
1407 }
1408}
1409
1410#[cfg(debug_assertions)]
1411fn report_live_objects(device: &ID3D11Device) -> Result<()> {
1412 let debug_device: ID3D11Debug = device.cast()?;
1413 unsafe {
1414 debug_device.ReportLiveDeviceObjects(D3D11_RLDO_DETAIL)?;
1415 }
1416 Ok(())
1417}
1418
1419const BUFFER_COUNT: usize = 3;
1420
1421mod shader_resources {
1422 use anyhow::Result;
1423
1424 #[cfg(debug_assertions)]
1425 use windows::{
1426 Win32::Graphics::Direct3D::{
1427 Fxc::{D3DCOMPILE_DEBUG, D3DCOMPILE_SKIP_OPTIMIZATION, D3DCompileFromFile},
1428 ID3DBlob,
1429 },
1430 core::{HSTRING, PCSTR},
1431 };
1432
1433 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
1434 pub(super) enum ShaderModule {
1435 Quad,
1436 Shadow,
1437 Underline,
1438 PathRasterization,
1439 PathSprite,
1440 MonochromeSprite,
1441 PolychromeSprite,
1442 }
1443
1444 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
1445 pub(super) enum ShaderTarget {
1446 Vertex,
1447 Fragment,
1448 }
1449
1450 pub(super) struct RawShaderBytes<'t> {
1451 inner: &'t [u8],
1452
1453 #[cfg(debug_assertions)]
1454 _blob: ID3DBlob,
1455 }
1456
1457 impl<'t> RawShaderBytes<'t> {
1458 pub(super) fn new(module: ShaderModule, target: ShaderTarget) -> Result<Self> {
1459 #[cfg(not(debug_assertions))]
1460 {
1461 Ok(Self::from_bytes(module, target))
1462 }
1463 #[cfg(debug_assertions)]
1464 {
1465 let blob = build_shader_blob(module, target)?;
1466 let inner = unsafe {
1467 std::slice::from_raw_parts(
1468 blob.GetBufferPointer() as *const u8,
1469 blob.GetBufferSize(),
1470 )
1471 };
1472 Ok(Self { inner, _blob: blob })
1473 }
1474 }
1475
1476 pub(super) fn as_bytes(&'t self) -> &'t [u8] {
1477 self.inner
1478 }
1479
1480 #[cfg(not(debug_assertions))]
1481 fn from_bytes(module: ShaderModule, target: ShaderTarget) -> Self {
1482 let bytes = match module {
1483 ShaderModule::Quad => match target {
1484 ShaderTarget::Vertex => QUAD_VERTEX_BYTES,
1485 ShaderTarget::Fragment => QUAD_FRAGMENT_BYTES,
1486 },
1487 ShaderModule::Shadow => match target {
1488 ShaderTarget::Vertex => SHADOW_VERTEX_BYTES,
1489 ShaderTarget::Fragment => SHADOW_FRAGMENT_BYTES,
1490 },
1491 ShaderModule::Underline => match target {
1492 ShaderTarget::Vertex => UNDERLINE_VERTEX_BYTES,
1493 ShaderTarget::Fragment => UNDERLINE_FRAGMENT_BYTES,
1494 },
1495 ShaderModule::PathRasterization => match target {
1496 ShaderTarget::Vertex => PATH_RASTERIZATION_VERTEX_BYTES,
1497 ShaderTarget::Fragment => PATH_RASTERIZATION_FRAGMENT_BYTES,
1498 },
1499 ShaderModule::PathSprite => match target {
1500 ShaderTarget::Vertex => PATH_SPRITE_VERTEX_BYTES,
1501 ShaderTarget::Fragment => PATH_SPRITE_FRAGMENT_BYTES,
1502 },
1503 ShaderModule::MonochromeSprite => match target {
1504 ShaderTarget::Vertex => MONOCHROME_SPRITE_VERTEX_BYTES,
1505 ShaderTarget::Fragment => MONOCHROME_SPRITE_FRAGMENT_BYTES,
1506 },
1507 ShaderModule::PolychromeSprite => match target {
1508 ShaderTarget::Vertex => POLYCHROME_SPRITE_VERTEX_BYTES,
1509 ShaderTarget::Fragment => POLYCHROME_SPRITE_FRAGMENT_BYTES,
1510 },
1511 };
1512 Self { inner: bytes }
1513 }
1514 }
1515
1516 #[cfg(debug_assertions)]
1517 pub(super) fn build_shader_blob(entry: ShaderModule, target: ShaderTarget) -> Result<ID3DBlob> {
1518 unsafe {
1519 let entry = format!(
1520 "{}_{}\0",
1521 entry.as_str(),
1522 match target {
1523 ShaderTarget::Vertex => "vertex",
1524 ShaderTarget::Fragment => "fragment",
1525 }
1526 );
1527 let target = match target {
1528 ShaderTarget::Vertex => "vs_4_1\0",
1529 ShaderTarget::Fragment => "ps_4_1\0",
1530 };
1531
1532 let mut compile_blob = None;
1533 let mut error_blob = None;
1534 let shader_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
1535 .join("src/platform/windows/shaders.hlsl")
1536 .canonicalize()?;
1537
1538 let entry_point = PCSTR::from_raw(entry.as_ptr());
1539 let target_cstr = PCSTR::from_raw(target.as_ptr());
1540
1541 let ret = D3DCompileFromFile(
1542 &HSTRING::from(shader_path.to_str().unwrap()),
1543 None,
1544 None,
1545 entry_point,
1546 target_cstr,
1547 D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION,
1548 0,
1549 &mut compile_blob,
1550 Some(&mut error_blob),
1551 );
1552 if ret.is_err() {
1553 let Some(error_blob) = error_blob else {
1554 return Err(anyhow::anyhow!("{ret:?}"));
1555 };
1556
1557 let error_string =
1558 std::ffi::CStr::from_ptr(error_blob.GetBufferPointer() as *const i8)
1559 .to_string_lossy();
1560 log::error!("Shader compile error: {}", error_string);
1561 return Err(anyhow::anyhow!("Compile error: {}", error_string));
1562 }
1563 Ok(compile_blob.unwrap())
1564 }
1565 }
1566
1567 #[cfg(not(debug_assertions))]
1568 include!(concat!(env!("OUT_DIR"), "/shaders_bytes.rs"));
1569
1570 #[cfg(debug_assertions)]
1571 impl ShaderModule {
1572 pub fn as_str(&self) -> &str {
1573 match self {
1574 ShaderModule::Quad => "quad",
1575 ShaderModule::Shadow => "shadow",
1576 ShaderModule::Underline => "underline",
1577 ShaderModule::PathRasterization => "path_rasterization",
1578 ShaderModule::PathSprite => "path_sprite",
1579 ShaderModule::MonochromeSprite => "monochrome_sprite",
1580 ShaderModule::PolychromeSprite => "polychrome_sprite",
1581 }
1582 }
1583 }
1584}
1585
1586mod nvidia {
1587 use std::{
1588 ffi::CStr,
1589 os::raw::{c_char, c_int, c_uint},
1590 };
1591
1592 use anyhow::{Context, Result};
1593 use windows::{
1594 Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA},
1595 core::s,
1596 };
1597
1598 // https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_lite_common.h#L180
1599 const NVAPI_SHORT_STRING_MAX: usize = 64;
1600
1601 // https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_lite_common.h#L235
1602 #[allow(non_camel_case_types)]
1603 type NvAPI_ShortString = [c_char; NVAPI_SHORT_STRING_MAX];
1604
1605 // https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_lite_common.h#L447
1606 #[allow(non_camel_case_types)]
1607 type NvAPI_SYS_GetDriverAndBranchVersion_t = unsafe extern "C" fn(
1608 driver_version: *mut c_uint,
1609 build_branch_string: *mut NvAPI_ShortString,
1610 ) -> c_int;
1611
1612 pub(super) fn get_driver_version() -> Result<String> {
1613 unsafe {
1614 // Try to load the NVIDIA driver DLL
1615 #[cfg(target_pointer_width = "64")]
1616 let nvidia_dll = LoadLibraryA(s!("nvapi64.dll")).context("Can't load nvapi64.dll")?;
1617 #[cfg(target_pointer_width = "32")]
1618 let nvidia_dll = LoadLibraryA(s!("nvapi.dll")).context("Can't load nvapi.dll")?;
1619
1620 let nvapi_query_addr = GetProcAddress(nvidia_dll, s!("nvapi_QueryInterface"))
1621 .ok_or_else(|| anyhow::anyhow!("Failed to get nvapi_QueryInterface address"))?;
1622 let nvapi_query: extern "C" fn(u32) -> *mut () = std::mem::transmute(nvapi_query_addr);
1623
1624 // https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_interface.h#L41
1625 let nvapi_get_driver_version_ptr = nvapi_query(0x2926aaad);
1626 if nvapi_get_driver_version_ptr.is_null() {
1627 anyhow::bail!("Failed to get NVIDIA driver version function pointer");
1628 }
1629 let nvapi_get_driver_version: NvAPI_SYS_GetDriverAndBranchVersion_t =
1630 std::mem::transmute(nvapi_get_driver_version_ptr);
1631
1632 let mut driver_version: c_uint = 0;
1633 let mut build_branch_string: NvAPI_ShortString = [0; NVAPI_SHORT_STRING_MAX];
1634 let result = nvapi_get_driver_version(
1635 &mut driver_version as *mut c_uint,
1636 &mut build_branch_string as *mut NvAPI_ShortString,
1637 );
1638
1639 if result != 0 {
1640 anyhow::bail!(
1641 "Failed to get NVIDIA driver version, error code: {}",
1642 result
1643 );
1644 }
1645 let major = driver_version / 100;
1646 let minor = driver_version % 100;
1647 let branch_string = CStr::from_ptr(build_branch_string.as_ptr());
1648 Ok(format!(
1649 "{}.{} {}",
1650 major,
1651 minor,
1652 branch_string.to_string_lossy()
1653 ))
1654 }
1655 }
1656}
1657
1658mod amd {
1659 use std::os::raw::{c_char, c_int, c_void};
1660
1661 use anyhow::{Context, Result};
1662 use windows::{
1663 Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA},
1664 core::s,
1665 };
1666
1667 // https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L145
1668 const AGS_CURRENT_VERSION: i32 = (6 << 22) | (3 << 12);
1669
1670 // https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L204
1671 // This is an opaque type, using struct to represent it properly for FFI
1672 #[repr(C)]
1673 struct AGSContext {
1674 _private: [u8; 0],
1675 }
1676
1677 #[repr(C)]
1678 pub struct AGSGPUInfo {
1679 pub driver_version: *const c_char,
1680 pub radeon_software_version: *const c_char,
1681 pub num_devices: c_int,
1682 pub devices: *mut c_void,
1683 }
1684
1685 // https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L429
1686 #[allow(non_camel_case_types)]
1687 type agsInitialize_t = unsafe extern "C" fn(
1688 version: c_int,
1689 config: *const c_void,
1690 context: *mut *mut AGSContext,
1691 gpu_info: *mut AGSGPUInfo,
1692 ) -> c_int;
1693
1694 // https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L436
1695 #[allow(non_camel_case_types)]
1696 type agsDeInitialize_t = unsafe extern "C" fn(context: *mut AGSContext) -> c_int;
1697
1698 pub(super) fn get_driver_version() -> Result<String> {
1699 unsafe {
1700 #[cfg(target_pointer_width = "64")]
1701 let amd_dll =
1702 LoadLibraryA(s!("amd_ags_x64.dll")).context("Failed to load AMD AGS library")?;
1703 #[cfg(target_pointer_width = "32")]
1704 let amd_dll =
1705 LoadLibraryA(s!("amd_ags_x86.dll")).context("Failed to load AMD AGS library")?;
1706
1707 let ags_initialize_addr = GetProcAddress(amd_dll, s!("agsInitialize"))
1708 .ok_or_else(|| anyhow::anyhow!("Failed to get agsInitialize address"))?;
1709 let ags_deinitialize_addr = GetProcAddress(amd_dll, s!("agsDeInitialize"))
1710 .ok_or_else(|| anyhow::anyhow!("Failed to get agsDeInitialize address"))?;
1711
1712 let ags_initialize: agsInitialize_t = std::mem::transmute(ags_initialize_addr);
1713 let ags_deinitialize: agsDeInitialize_t = std::mem::transmute(ags_deinitialize_addr);
1714
1715 let mut context: *mut AGSContext = std::ptr::null_mut();
1716 let mut gpu_info: AGSGPUInfo = AGSGPUInfo {
1717 driver_version: std::ptr::null(),
1718 radeon_software_version: std::ptr::null(),
1719 num_devices: 0,
1720 devices: std::ptr::null_mut(),
1721 };
1722
1723 let result = ags_initialize(
1724 AGS_CURRENT_VERSION,
1725 std::ptr::null(),
1726 &mut context,
1727 &mut gpu_info,
1728 );
1729 if result != 0 {
1730 anyhow::bail!("Failed to initialize AMD AGS, error code: {}", result);
1731 }
1732
1733 // Vulkan acctually returns this as the driver version
1734 let software_version = if !gpu_info.radeon_software_version.is_null() {
1735 std::ffi::CStr::from_ptr(gpu_info.radeon_software_version)
1736 .to_string_lossy()
1737 .into_owned()
1738 } else {
1739 "Unknown Radeon Software Version".to_string()
1740 };
1741
1742 let driver_version = if !gpu_info.driver_version.is_null() {
1743 std::ffi::CStr::from_ptr(gpu_info.driver_version)
1744 .to_string_lossy()
1745 .into_owned()
1746 } else {
1747 "Unknown Radeon Driver Version".to_string()
1748 };
1749
1750 ags_deinitialize(context);
1751 Ok(format!("{} ({})", software_version, driver_version))
1752 }
1753 }
1754}
1755
1756mod dxgi {
1757 use windows::{
1758 Win32::Graphics::Dxgi::{IDXGIAdapter1, IDXGIDevice},
1759 core::Interface,
1760 };
1761
1762 pub(super) fn get_driver_version(adapter: &IDXGIAdapter1) -> anyhow::Result<String> {
1763 let number = unsafe { adapter.CheckInterfaceSupport(&IDXGIDevice::IID as _) }?;
1764 Ok(format!(
1765 "{}.{}.{}.{}",
1766 number >> 48,
1767 (number >> 32) & 0xFFFF,
1768 (number >> 16) & 0xFFFF,
1769 number & 0xFFFF
1770 ))
1771 }
1772}