1use std::{collections::HashMap, hash::BuildHasherDefault, sync::Arc};
2
3use ::util::ResultExt;
4use anyhow::{Context, Result};
5use collections::FxHasher;
6#[cfg(not(feature = "enable-renderdoc"))]
7use windows::Win32::Graphics::DirectComposition::*;
8use windows::{
9 Win32::{
10 Foundation::HWND,
11 Graphics::{
12 Direct3D::*,
13 Direct3D11::*,
14 Dxgi::{Common::*, *},
15 },
16 },
17 core::*,
18};
19
20use crate::*;
21
22pub(crate) struct DirectXRenderer {
23 atlas: Arc<DirectXAtlas>,
24 devices: DirectXDevices,
25 context: DirectXContext,
26 globals: DirectXGlobalElements,
27 pipelines: DirectXRenderPipelines,
28 hwnd: HWND,
29 transparent: bool,
30}
31
32#[derive(Clone)]
33pub(crate) struct DirectXDevices {
34 dxgi_factory: IDXGIFactory6,
35 dxgi_device: IDXGIDevice,
36 device: ID3D11Device,
37 device_context: ID3D11DeviceContext,
38}
39
40struct DirectXContext {
41 swap_chain: IDXGISwapChain1,
42 back_buffer: [Option<ID3D11RenderTargetView>; 1],
43 viewport: [D3D11_VIEWPORT; 1],
44 #[cfg(not(feature = "enable-renderdoc"))]
45 direct_composition: DirectComposition,
46}
47
48struct DirectXRenderPipelines {
49 shadow_pipeline: PipelineState,
50 quad_pipeline: PipelineState,
51 path_raster_pipeline: PipelineState,
52 paths_pipeline: PipelineState,
53 underline_pipeline: PipelineState,
54 mono_sprites: PipelineState,
55 poly_sprites: PipelineState,
56}
57
58struct DirectXGlobalElements {
59 global_params_buffer: [Option<ID3D11Buffer>; 1],
60 sampler: [Option<ID3D11SamplerState>; 1],
61 blend_state: ID3D11BlendState,
62 blend_state_for_pr: ID3D11BlendState,
63}
64
65#[cfg(not(feature = "enable-renderdoc"))]
66struct DirectComposition {
67 comp_device: IDCompositionDevice,
68 comp_target: IDCompositionTarget,
69 comp_visual: IDCompositionVisual,
70}
71
72impl DirectXDevices {
73 pub(crate) fn new() -> Result<Self> {
74 let dxgi_factory = get_dxgi_factory()?;
75 let adapter = get_adapter(&dxgi_factory)?;
76 let (device, device_context) = {
77 let mut device: Option<ID3D11Device> = None;
78 let mut context: Option<ID3D11DeviceContext> = None;
79 get_device(&adapter, Some(&mut device), Some(&mut context))?;
80 (device.unwrap(), context.unwrap())
81 };
82 let dxgi_device: IDXGIDevice = device.cast()?;
83
84 Ok(Self {
85 dxgi_factory,
86 dxgi_device,
87 device,
88 device_context,
89 })
90 }
91}
92
93impl DirectXRenderer {
94 pub(crate) fn new(devices: DirectXDevices, hwnd: HWND, transparent: bool) -> Result<Self> {
95 let atlas = Arc::new(DirectXAtlas::new(
96 devices.device.clone(),
97 devices.device_context.clone(),
98 ));
99 let context = DirectXContext::new(&devices, hwnd, transparent)?;
100 let globals = DirectXGlobalElements::new(&devices.device)?;
101 let pipelines = DirectXRenderPipelines::new(&devices.device)?;
102 Ok(DirectXRenderer {
103 atlas,
104 devices,
105 context,
106 globals,
107 pipelines,
108 hwnd,
109 transparent,
110 })
111 }
112
113 pub(crate) fn spirite_atlas(&self) -> Arc<dyn PlatformAtlas> {
114 self.atlas.clone()
115 }
116
117 pub(crate) fn draw(&mut self, scene: &Scene) -> Result<()> {
118 let Some(path_tiles) = self.rasterize_paths(scene.paths()) else {
119 return Err(anyhow::anyhow!(
120 "failed to rasterize {} paths",
121 scene.paths().len()
122 ));
123 };
124 pre_draw(
125 &self.devices.device_context,
126 &self.globals.global_params_buffer,
127 &self.context.viewport,
128 &self.context.back_buffer,
129 [0.0, 0.0, 0.0, 0.0],
130 &self.globals.blend_state,
131 )?;
132 for batch in scene.batches() {
133 match batch {
134 PrimitiveBatch::Shadows(shadows) => self.draw_shadows(shadows),
135 PrimitiveBatch::Quads(quads) => self.draw_quads(quads),
136 PrimitiveBatch::Paths(paths) => self.draw_paths(paths, &path_tiles),
137 PrimitiveBatch::Underlines(underlines) => self.draw_underlines(underlines),
138 PrimitiveBatch::MonochromeSprites {
139 texture_id,
140 sprites,
141 } => self.draw_monochrome_sprites(texture_id, sprites),
142 PrimitiveBatch::PolychromeSprites {
143 texture_id,
144 sprites,
145 } => self.draw_polychrome_sprites(texture_id, sprites),
146 PrimitiveBatch::Surfaces(surfaces) => self.draw_surfaces(surfaces),
147 }.context(format!("scene too large: {} paths, {} shadows, {} quads, {} underlines, {} mono, {} poly, {} surfaces",
148 scene.paths.len(),
149 scene.shadows.len(),
150 scene.quads.len(),
151 scene.underlines.len(),
152 scene.monochrome_sprites.len(),
153 scene.polychrome_sprites.len(),
154 scene.surfaces.len(),))?;
155 }
156 unsafe { self.context.swap_chain.Present(0, 0) }.ok()?;
157 Ok(())
158 }
159
160 pub(crate) fn resize(&mut self, new_size: Size<DevicePixels>) -> Result<()> {
161 unsafe { self.devices.device_context.OMSetRenderTargets(None, None) };
162 drop(self.context.back_buffer[0].take().unwrap());
163 unsafe {
164 self.context.swap_chain.ResizeBuffers(
165 BUFFER_COUNT as u32,
166 new_size.width.0 as u32,
167 new_size.height.0 as u32,
168 DXGI_FORMAT_B8G8R8A8_UNORM,
169 0,
170 )?;
171 }
172 let backbuffer = set_render_target_view(
173 &self.context.swap_chain,
174 &self.devices.device,
175 &self.devices.device_context,
176 )?;
177 self.context.back_buffer[0] = Some(backbuffer);
178 self.context.viewport = set_viewport(
179 &self.devices.device_context,
180 new_size.width.0 as f32,
181 new_size.height.0 as f32,
182 );
183 Ok(())
184 }
185
186 #[cfg(not(feature = "enable-renderdoc"))]
187 pub(crate) fn update_transparency(
188 &mut self,
189 background_appearance: WindowBackgroundAppearance,
190 ) -> Result<()> {
191 // We only support setting `Transparent` and `Opaque` for now.
192 match background_appearance {
193 WindowBackgroundAppearance::Opaque => {
194 if self.transparent {
195 return Err(anyhow::anyhow!(
196 "Set opaque backgroud from transparent background, a restart is required. Or, you can open a new window."
197 ));
198 }
199 }
200 WindowBackgroundAppearance::Transparent | WindowBackgroundAppearance::Blurred => {
201 if !self.transparent {
202 return Err(anyhow::anyhow!(
203 "Set transparent backgroud from opaque background, a restart is required. Or, you can open a new window."
204 ));
205 }
206 }
207 }
208 Ok(())
209 }
210
211 #[cfg(feature = "enable-renderdoc")]
212 pub(crate) fn update_transparency(
213 &mut self,
214 background_appearance: WindowBackgroundAppearance,
215 ) -> Result<()> {
216 if background_appearance != WindowBackgroundAppearance::Opaque {
217 Err(anyhow::anyhow!(
218 "Set transparent background not supported when feature \"enable-renderdoc\" is enabled."
219 ))
220 } else {
221 Ok(())
222 }
223 }
224
225 fn draw_shadows(&mut self, shadows: &[Shadow]) -> Result<()> {
226 if shadows.is_empty() {
227 return Ok(());
228 }
229 update_buffer_capacity(
230 &self.pipelines.shadow_pipeline,
231 std::mem::size_of::<Shadow>(),
232 shadows.len(),
233 &self.devices.device,
234 )
235 .map(|input| update_pipeline(&mut self.pipelines.shadow_pipeline, input));
236 update_buffer(
237 &self.devices.device_context,
238 &self.pipelines.shadow_pipeline.buffer,
239 shadows,
240 )?;
241 draw_normal(
242 &self.devices.device_context,
243 &self.pipelines.shadow_pipeline,
244 &self.context.viewport,
245 &self.globals.global_params_buffer,
246 D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
247 4,
248 shadows.len() as u32,
249 )
250 }
251
252 fn draw_quads(&mut self, quads: &[Quad]) -> Result<()> {
253 if quads.is_empty() {
254 return Ok(());
255 }
256 update_buffer_capacity(
257 &self.pipelines.quad_pipeline,
258 std::mem::size_of::<Quad>(),
259 quads.len(),
260 &self.devices.device,
261 )
262 .map(|input| update_pipeline(&mut self.pipelines.quad_pipeline, input));
263 update_buffer(
264 &self.devices.device_context,
265 &self.pipelines.quad_pipeline.buffer,
266 quads,
267 )?;
268 draw_normal(
269 &self.devices.device_context,
270 &self.pipelines.quad_pipeline,
271 &self.context.viewport,
272 &self.globals.global_params_buffer,
273 D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
274 4,
275 quads.len() as u32,
276 )
277 }
278
279 fn rasterize_paths(
280 &mut self,
281 paths: &[Path<ScaledPixels>],
282 ) -> Option<HashMap<PathId, AtlasTile>> {
283 self.atlas.clear_textures(AtlasTextureKind::Path);
284
285 let mut tiles = HashMap::default();
286 let mut vertices_by_texture_id: HashMap<
287 AtlasTextureId,
288 Vec<PathVertex<ScaledPixels>>,
289 BuildHasherDefault<FxHasher>,
290 > = HashMap::default();
291 for path in paths {
292 let clipped_bounds = path.bounds.intersect(&path.content_mask.bounds);
293
294 let tile = self
295 .atlas
296 .allocate(clipped_bounds.size.map(Into::into), AtlasTextureKind::Path)?;
297 vertices_by_texture_id
298 .entry(tile.texture_id)
299 .or_insert(Vec::new())
300 .extend(path.vertices.iter().map(|vertex| PathVertex {
301 xy_position: vertex.xy_position - clipped_bounds.origin
302 + tile.bounds.origin.map(Into::into),
303 content_mask: ContentMask {
304 bounds: tile.bounds.map(Into::into),
305 },
306 }));
307 tiles.insert(path.id, tile);
308 }
309
310 for (texture_id, vertices) in vertices_by_texture_id {
311 let (texture_size, rtv) = self.atlas.get_texture_drawing_info(texture_id);
312 let viewport = [D3D11_VIEWPORT {
313 TopLeftX: 0.0,
314 TopLeftY: 0.0,
315 Width: texture_size.width,
316 Height: texture_size.height,
317 MinDepth: 0.0,
318 MaxDepth: 1.0,
319 }];
320 pre_draw(
321 &self.devices.device_context,
322 &self.globals.global_params_buffer,
323 &viewport,
324 &rtv,
325 [0.0, 0.0, 0.0, 1.0],
326 &self.globals.blend_state_for_pr,
327 )
328 .log_err()?;
329 update_buffer_capacity(
330 &self.pipelines.path_raster_pipeline,
331 std::mem::size_of::<PathVertex<ScaledPixels>>(),
332 vertices.len(),
333 &self.devices.device,
334 )
335 .map(|input| update_pipeline(&mut self.pipelines.path_raster_pipeline, input));
336 update_buffer(
337 &self.devices.device_context,
338 &self.pipelines.path_raster_pipeline.buffer,
339 &vertices,
340 )
341 .log_err()?;
342 draw_normal(
343 &self.devices.device_context,
344 &self.pipelines.path_raster_pipeline,
345 &viewport,
346 &self.globals.global_params_buffer,
347 D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
348 vertices.len() as u32,
349 1,
350 )
351 .log_err()?;
352 }
353 Some(tiles)
354 }
355
356 fn draw_paths(
357 &mut self,
358 paths: &[Path<ScaledPixels>],
359 path_tiles: &HashMap<PathId, AtlasTile>,
360 ) -> Result<()> {
361 if paths.is_empty() {
362 return Ok(());
363 }
364 for path in paths {
365 let tile = &path_tiles[&path.id];
366 let texture_view = self.atlas.get_texture_view(tile.texture_id);
367 let origin = path.bounds.intersect(&path.content_mask.bounds).origin;
368 let sprites = [PathSprite {
369 bounds: Bounds {
370 origin: origin.map(|p| p.floor()),
371 size: tile.bounds.size.map(Into::into),
372 },
373 color: path.color,
374 tile: (*tile).clone(),
375 }];
376 update_buffer_capacity(
377 &self.pipelines.paths_pipeline,
378 std::mem::size_of::<PathSprite>(),
379 1,
380 &self.devices.device,
381 )
382 .map(|input| update_pipeline(&mut self.pipelines.paths_pipeline, input));
383 update_buffer(
384 &self.devices.device_context,
385 &self.pipelines.paths_pipeline.buffer,
386 &sprites,
387 )?;
388 draw_with_texture(
389 &self.devices.device_context,
390 &self.pipelines.paths_pipeline,
391 &texture_view,
392 &self.context.viewport,
393 &self.globals.global_params_buffer,
394 &self.globals.sampler,
395 1,
396 )?;
397 }
398 Ok(())
399 }
400
401 fn draw_underlines(&mut self, underlines: &[Underline]) -> Result<()> {
402 if underlines.is_empty() {
403 return Ok(());
404 }
405 update_buffer_capacity(
406 &self.pipelines.underline_pipeline,
407 std::mem::size_of::<Underline>(),
408 underlines.len(),
409 &self.devices.device,
410 )
411 .map(|input| update_pipeline(&mut self.pipelines.underline_pipeline, input));
412 update_buffer(
413 &self.devices.device_context,
414 &self.pipelines.underline_pipeline.buffer,
415 underlines,
416 )?;
417 draw_normal(
418 &self.devices.device_context,
419 &self.pipelines.underline_pipeline,
420 &self.context.viewport,
421 &self.globals.global_params_buffer,
422 D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
423 4,
424 underlines.len() as u32,
425 )
426 }
427
428 fn draw_monochrome_sprites(
429 &mut self,
430 texture_id: AtlasTextureId,
431 sprites: &[MonochromeSprite],
432 ) -> Result<()> {
433 if sprites.is_empty() {
434 return Ok(());
435 }
436 let texture_view = self.atlas.get_texture_view(texture_id);
437 update_buffer_capacity(
438 &self.pipelines.mono_sprites,
439 std::mem::size_of::<MonochromeSprite>(),
440 sprites.len(),
441 &self.devices.device,
442 )
443 .map(|input| update_pipeline(&mut self.pipelines.mono_sprites, input));
444 update_buffer(
445 &self.devices.device_context,
446 &self.pipelines.mono_sprites.buffer,
447 sprites,
448 )?;
449 draw_with_texture(
450 &self.devices.device_context,
451 &self.pipelines.mono_sprites,
452 &texture_view,
453 &self.context.viewport,
454 &self.globals.global_params_buffer,
455 &self.globals.sampler,
456 sprites.len() as u32,
457 )
458 }
459
460 fn draw_polychrome_sprites(
461 &mut self,
462 texture_id: AtlasTextureId,
463 sprites: &[PolychromeSprite],
464 ) -> Result<()> {
465 if sprites.is_empty() {
466 return Ok(());
467 }
468 let texture_view = self.atlas.get_texture_view(texture_id);
469 update_buffer_capacity(
470 &self.pipelines.poly_sprites,
471 std::mem::size_of::<PolychromeSprite>(),
472 sprites.len(),
473 &self.devices.device,
474 )
475 .map(|input| update_pipeline(&mut self.pipelines.poly_sprites, input));
476 update_buffer(
477 &self.devices.device_context,
478 &self.pipelines.poly_sprites.buffer,
479 sprites,
480 )?;
481 draw_with_texture(
482 &self.devices.device_context,
483 &self.pipelines.poly_sprites,
484 &texture_view,
485 &self.context.viewport,
486 &self.globals.global_params_buffer,
487 &self.globals.sampler,
488 sprites.len() as u32,
489 )
490 }
491
492 fn draw_surfaces(&mut self, surfaces: &[Surface]) -> Result<()> {
493 if surfaces.is_empty() {
494 return Ok(());
495 }
496 Ok(())
497 }
498}
499
500impl DirectXContext {
501 pub fn new(devices: &DirectXDevices, hwnd: HWND, transparent: bool) -> Result<Self> {
502 #[cfg(not(feature = "enable-renderdoc"))]
503 let swap_chain = create_swap_chain(&devices.dxgi_factory, &devices.device, transparent)?;
504 #[cfg(feature = "enable-renderdoc")]
505 let swap_chain =
506 create_swap_chain_default(&devices.dxgi_factory, &devices.device, hwnd, transparent)?;
507 #[cfg(not(feature = "enable-renderdoc"))]
508 let direct_composition = DirectComposition::new(&devices.dxgi_device, hwnd)?;
509 #[cfg(not(feature = "enable-renderdoc"))]
510 direct_composition.set_swap_chain(&swap_chain)?;
511 let back_buffer = [Some(set_render_target_view(
512 &swap_chain,
513 &devices.device,
514 &devices.device_context,
515 )?)];
516 let viewport = set_viewport(&devices.device_context, 1.0, 1.0);
517 set_rasterizer_state(&devices.device, &devices.device_context)?;
518
519 Ok(Self {
520 swap_chain,
521 back_buffer,
522 viewport,
523 #[cfg(not(feature = "enable-renderdoc"))]
524 direct_composition,
525 })
526 }
527}
528
529impl DirectXRenderPipelines {
530 pub fn new(device: &ID3D11Device) -> Result<Self> {
531 let shadow_pipeline = create_pipieline(
532 device,
533 "shadow_vertex",
534 "shadow_fragment",
535 std::mem::size_of::<Shadow>(),
536 32,
537 )?;
538 let quad_pipeline = create_pipieline(
539 device,
540 "quad_vertex",
541 "quad_fragment",
542 std::mem::size_of::<Quad>(),
543 32,
544 )?;
545 let path_raster_pipeline = create_pipieline(
546 device,
547 "path_rasterization_vertex",
548 "path_rasterization_fragment",
549 std::mem::size_of::<PathVertex<ScaledPixels>>(),
550 32,
551 )?;
552 let paths_pipeline = create_pipieline(
553 device,
554 "paths_vertex",
555 "paths_fragment",
556 std::mem::size_of::<PathSprite>(),
557 1,
558 )?;
559 let underline_pipeline = create_pipieline(
560 device,
561 "underline_vertex",
562 "underline_fragment",
563 std::mem::size_of::<Underline>(),
564 32,
565 )?;
566 let mono_sprites = create_pipieline(
567 device,
568 "monochrome_sprite_vertex",
569 "monochrome_sprite_fragment",
570 std::mem::size_of::<MonochromeSprite>(),
571 32,
572 )?;
573 let poly_sprites = create_pipieline(
574 device,
575 "polychrome_sprite_vertex",
576 "polychrome_sprite_fragment",
577 std::mem::size_of::<PolychromeSprite>(),
578 32,
579 )?;
580
581 Ok(Self {
582 shadow_pipeline,
583 quad_pipeline,
584 path_raster_pipeline,
585 paths_pipeline,
586 underline_pipeline,
587 mono_sprites,
588 poly_sprites,
589 })
590 }
591}
592
593#[cfg(not(feature = "enable-renderdoc"))]
594impl DirectComposition {
595 pub fn new(dxgi_device: &IDXGIDevice, hwnd: HWND) -> Result<Self> {
596 let comp_device = get_comp_device(&dxgi_device)?;
597 let comp_target = unsafe { comp_device.CreateTargetForHwnd(hwnd, true) }?;
598 let comp_visual = unsafe { comp_device.CreateVisual() }?;
599
600 Ok(Self {
601 comp_device,
602 comp_target,
603 comp_visual,
604 })
605 }
606
607 pub fn set_swap_chain(&self, swap_chain: &IDXGISwapChain1) -> Result<()> {
608 unsafe {
609 self.comp_visual.SetContent(swap_chain)?;
610 self.comp_target.SetRoot(&self.comp_visual)?;
611 self.comp_device.Commit()?;
612 }
613 Ok(())
614 }
615}
616
617impl DirectXGlobalElements {
618 pub fn new(device: &ID3D11Device) -> Result<Self> {
619 let global_params_buffer = unsafe {
620 let desc = D3D11_BUFFER_DESC {
621 ByteWidth: std::mem::size_of::<GlobalParams>() as u32,
622 Usage: D3D11_USAGE_DYNAMIC,
623 BindFlags: D3D11_BIND_CONSTANT_BUFFER.0 as u32,
624 CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32,
625 ..Default::default()
626 };
627 let mut buffer = None;
628 device.CreateBuffer(&desc, None, Some(&mut buffer))?;
629 [buffer]
630 };
631
632 let sampler = unsafe {
633 let desc = D3D11_SAMPLER_DESC {
634 Filter: D3D11_FILTER_MIN_MAG_MIP_LINEAR,
635 AddressU: D3D11_TEXTURE_ADDRESS_WRAP,
636 AddressV: D3D11_TEXTURE_ADDRESS_WRAP,
637 AddressW: D3D11_TEXTURE_ADDRESS_WRAP,
638 MipLODBias: 0.0,
639 MaxAnisotropy: 1,
640 ComparisonFunc: D3D11_COMPARISON_ALWAYS,
641 BorderColor: [0.0; 4],
642 MinLOD: 0.0,
643 MaxLOD: D3D11_FLOAT32_MAX,
644 };
645 let mut output = None;
646 device.CreateSamplerState(&desc, Some(&mut output))?;
647 [output]
648 };
649
650 let blend_state = create_blend_state(device)?;
651 let blend_state_for_pr = create_blend_state_for_path_raster(device)?;
652
653 Ok(Self {
654 global_params_buffer,
655 sampler,
656 blend_state,
657 blend_state_for_pr,
658 })
659 }
660}
661
662#[derive(Debug, Default)]
663#[repr(C)]
664struct GlobalParams {
665 viewport_size: [f32; 2],
666 _pad: u64,
667}
668
669struct PipelineState {
670 vertex: ID3D11VertexShader,
671 fragment: ID3D11PixelShader,
672 buffer: ID3D11Buffer,
673 buffer_size: usize,
674 view: [Option<ID3D11ShaderResourceView>; 1],
675}
676
677#[derive(Clone, Debug, Eq, PartialEq)]
678#[repr(C)]
679struct PathSprite {
680 bounds: Bounds<ScaledPixels>,
681 color: Hsla,
682 tile: AtlasTile,
683}
684
685fn get_dxgi_factory() -> Result<IDXGIFactory6> {
686 #[cfg(debug_assertions)]
687 let factory_flag = DXGI_CREATE_FACTORY_DEBUG;
688 #[cfg(not(debug_assertions))]
689 let factory_flag = 0u32;
690 unsafe { Ok(CreateDXGIFactory2(factory_flag)?) }
691}
692
693fn get_adapter(dxgi_factory: &IDXGIFactory6) -> Result<IDXGIAdapter1> {
694 for adapter_index in 0.. {
695 let adapter: IDXGIAdapter1 = unsafe {
696 dxgi_factory
697 .EnumAdapterByGpuPreference(adapter_index, DXGI_GPU_PREFERENCE_MINIMUM_POWER)
698 }?;
699 {
700 let desc = unsafe { adapter.GetDesc1() }?;
701 println!(
702 "Select GPU: {}",
703 String::from_utf16_lossy(&desc.Description)
704 );
705 }
706 // Check to see whether the adapter supports Direct3D 11, but don't
707 // create the actual device yet.
708 if get_device(&adapter, None, None).log_err().is_some() {
709 return Ok(adapter);
710 }
711 }
712
713 unreachable!()
714}
715
716fn get_device(
717 adapter: &IDXGIAdapter1,
718 device: Option<*mut Option<ID3D11Device>>,
719 context: Option<*mut Option<ID3D11DeviceContext>>,
720) -> Result<()> {
721 #[cfg(debug_assertions)]
722 let device_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_DEBUG;
723 #[cfg(not(debug_assertions))]
724 let device_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
725 Ok(unsafe {
726 D3D11CreateDevice(
727 adapter,
728 D3D_DRIVER_TYPE_UNKNOWN,
729 None,
730 device_flags,
731 Some(&[D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_11_1]),
732 D3D11_SDK_VERSION,
733 device,
734 None,
735 context,
736 )?
737 })
738}
739
740#[cfg(not(feature = "enable-renderdoc"))]
741fn get_comp_device(dxgi_device: &IDXGIDevice) -> Result<IDCompositionDevice> {
742 Ok(unsafe { DCompositionCreateDevice(dxgi_device)? })
743}
744
745fn create_swap_chain(
746 dxgi_factory: &IDXGIFactory6,
747 device: &ID3D11Device,
748 transparent: bool,
749) -> Result<IDXGISwapChain1> {
750 let alpha_mode = if transparent {
751 DXGI_ALPHA_MODE_PREMULTIPLIED
752 } else {
753 DXGI_ALPHA_MODE_IGNORE
754 };
755 let desc = DXGI_SWAP_CHAIN_DESC1 {
756 Width: 1,
757 Height: 1,
758 Format: DXGI_FORMAT_B8G8R8A8_UNORM,
759 Stereo: false.into(),
760 SampleDesc: DXGI_SAMPLE_DESC {
761 Count: 1,
762 Quality: 0,
763 },
764 BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT,
765 BufferCount: BUFFER_COUNT as u32,
766 // Composition SwapChains only support the DXGI_SCALING_STRETCH Scaling.
767 Scaling: DXGI_SCALING_STRETCH,
768 SwapEffect: DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL,
769 AlphaMode: alpha_mode,
770 Flags: 0,
771 };
772 Ok(unsafe { dxgi_factory.CreateSwapChainForComposition(device, &desc, None)? })
773}
774
775#[cfg(feature = "enable-renderdoc")]
776fn create_swap_chain_default(
777 dxgi_factory: &IDXGIFactory6,
778 device: &ID3D11Device,
779 hwnd: HWND,
780 _transparent: bool,
781) -> Result<IDXGISwapChain1> {
782 use windows::Win32::Graphics::Dxgi::DXGI_MWA_NO_ALT_ENTER;
783
784 let desc = DXGI_SWAP_CHAIN_DESC1 {
785 Width: 1,
786 Height: 1,
787 Format: DXGI_FORMAT_B8G8R8A8_UNORM,
788 Stereo: false.into(),
789 SampleDesc: DXGI_SAMPLE_DESC {
790 Count: 1,
791 Quality: 0,
792 },
793 BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT,
794 BufferCount: BUFFER_COUNT as u32,
795 Scaling: DXGI_SCALING_STRETCH,
796 SwapEffect: DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL,
797 AlphaMode: DXGI_ALPHA_MODE_IGNORE,
798 Flags: 0,
799 };
800 let swap_chain =
801 unsafe { dxgi_factory.CreateSwapChainForHwnd(device, hwnd, &desc, None, None) }?;
802 unsafe { dxgi_factory.MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER) }?;
803 Ok(swap_chain)
804}
805
806fn set_render_target_view(
807 swap_chain: &IDXGISwapChain1,
808 device: &ID3D11Device,
809 device_context: &ID3D11DeviceContext,
810) -> Result<ID3D11RenderTargetView> {
811 // In dx11, ID3D11RenderTargetView is supposed to always point to the new back buffer.
812 // https://stackoverflow.com/questions/65246961/does-the-backbuffer-that-a-rendertargetview-points-to-automagically-change-after
813 let back_buffer = unsafe {
814 let resource: ID3D11Texture2D = swap_chain.GetBuffer(0)?;
815 let mut buffer: Option<ID3D11RenderTargetView> = None;
816 device.CreateRenderTargetView(&resource, None, Some(&mut buffer))?;
817 buffer.unwrap()
818 };
819 unsafe { device_context.OMSetRenderTargets(Some(&[Some(back_buffer.clone())]), None) };
820 Ok(back_buffer)
821}
822
823fn set_viewport(
824 device_context: &ID3D11DeviceContext,
825 width: f32,
826 height: f32,
827) -> [D3D11_VIEWPORT; 1] {
828 let viewport = [D3D11_VIEWPORT {
829 TopLeftX: 0.0,
830 TopLeftY: 0.0,
831 Width: width,
832 Height: height,
833 MinDepth: 0.0,
834 MaxDepth: 1.0,
835 }];
836 unsafe { device_context.RSSetViewports(Some(&viewport)) };
837 viewport
838}
839
840fn set_rasterizer_state(device: &ID3D11Device, device_context: &ID3D11DeviceContext) -> Result<()> {
841 let desc = D3D11_RASTERIZER_DESC {
842 FillMode: D3D11_FILL_SOLID,
843 CullMode: D3D11_CULL_NONE,
844 FrontCounterClockwise: false.into(),
845 DepthBias: 0,
846 DepthBiasClamp: 0.0,
847 SlopeScaledDepthBias: 0.0,
848 DepthClipEnable: true.into(),
849 ScissorEnable: false.into(),
850 MultisampleEnable: false.into(),
851 AntialiasedLineEnable: false.into(),
852 };
853 let rasterizer_state = unsafe {
854 let mut state = None;
855 device.CreateRasterizerState(&desc, Some(&mut state))?;
856 state.unwrap()
857 };
858 unsafe { device_context.RSSetState(&rasterizer_state) };
859 Ok(())
860}
861
862// https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ns-d3d11-d3d11_blend_desc
863fn create_blend_state(device: &ID3D11Device) -> Result<ID3D11BlendState> {
864 // If the feature level is set to greater than D3D_FEATURE_LEVEL_9_3, the display
865 // device performs the blend in linear space, which is ideal.
866 let mut desc = D3D11_BLEND_DESC::default();
867 desc.RenderTarget[0].BlendEnable = true.into();
868 desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
869 desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
870 desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
871 desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
872 desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
873 desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ONE;
874 desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8;
875 unsafe {
876 let mut state = None;
877 device.CreateBlendState(&desc, Some(&mut state))?;
878 Ok(state.unwrap())
879 }
880}
881
882fn create_blend_state_for_path_raster(device: &ID3D11Device) -> Result<ID3D11BlendState> {
883 // If the feature level is set to greater than D3D_FEATURE_LEVEL_9_3, the display
884 // device performs the blend in linear space, which is ideal.
885 let mut desc = D3D11_BLEND_DESC::default();
886 desc.RenderTarget[0].BlendEnable = true.into();
887 desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
888 desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
889 desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE;
890 desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
891 desc.RenderTarget[0].DestBlend = D3D11_BLEND_ONE;
892 desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ONE;
893 desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8;
894 unsafe {
895 let mut state = None;
896 device.CreateBlendState(&desc, Some(&mut state))?;
897 Ok(state.unwrap())
898 }
899}
900
901fn create_pipieline(
902 device: &ID3D11Device,
903 vertex_entry: &str,
904 fragment_entry: &str,
905 element_size: usize,
906 buffer_size: usize,
907) -> Result<PipelineState> {
908 let vertex = {
909 let shader_blob = shader_resources::build_shader_blob(vertex_entry, "vs_5_0")?;
910 let bytes = unsafe {
911 std::slice::from_raw_parts(
912 shader_blob.GetBufferPointer() as *mut u8,
913 shader_blob.GetBufferSize(),
914 )
915 };
916 create_vertex_shader(device, bytes)?
917 };
918 let fragment = {
919 let shader_blob = shader_resources::build_shader_blob(fragment_entry, "ps_5_0")?;
920 let bytes = unsafe {
921 std::slice::from_raw_parts(
922 shader_blob.GetBufferPointer() as *mut u8,
923 shader_blob.GetBufferSize(),
924 )
925 };
926 create_fragment_shader(device, bytes)?
927 };
928 let buffer = create_buffer(device, element_size, buffer_size)?;
929 let view = create_buffer_view(device, &buffer)?;
930 Ok(PipelineState {
931 vertex,
932 fragment,
933 buffer,
934 buffer_size,
935 view,
936 })
937}
938
939fn create_vertex_shader(device: &ID3D11Device, bytes: &[u8]) -> Result<ID3D11VertexShader> {
940 unsafe {
941 let mut shader = None;
942 device.CreateVertexShader(bytes, None, Some(&mut shader))?;
943 Ok(shader.unwrap())
944 }
945}
946
947fn create_fragment_shader(device: &ID3D11Device, bytes: &[u8]) -> Result<ID3D11PixelShader> {
948 unsafe {
949 let mut shader = None;
950 device.CreatePixelShader(bytes, None, Some(&mut shader))?;
951 Ok(shader.unwrap())
952 }
953}
954
955fn create_buffer(
956 device: &ID3D11Device,
957 element_size: usize,
958 buffer_size: usize,
959) -> Result<ID3D11Buffer> {
960 let desc = D3D11_BUFFER_DESC {
961 ByteWidth: (element_size * buffer_size) as u32,
962 Usage: D3D11_USAGE_DYNAMIC,
963 BindFlags: D3D11_BIND_SHADER_RESOURCE.0 as u32,
964 CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32,
965 MiscFlags: D3D11_RESOURCE_MISC_BUFFER_STRUCTURED.0 as u32,
966 StructureByteStride: element_size as u32,
967 };
968 let mut buffer = None;
969 unsafe { device.CreateBuffer(&desc, None, Some(&mut buffer)) }?;
970 Ok(buffer.unwrap())
971}
972
973fn create_buffer_view(
974 device: &ID3D11Device,
975 buffer: &ID3D11Buffer,
976) -> Result<[Option<ID3D11ShaderResourceView>; 1]> {
977 let mut view = None;
978 unsafe { device.CreateShaderResourceView(buffer, None, Some(&mut view)) }?;
979 Ok([view])
980}
981
982fn update_global_params(
983 device_context: &ID3D11DeviceContext,
984 buffer: &[Option<ID3D11Buffer>; 1],
985 globals: GlobalParams,
986) -> Result<()> {
987 let buffer = buffer[0].as_ref().unwrap();
988 unsafe {
989 let mut data = std::mem::zeroed();
990 device_context.Map(buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, Some(&mut data))?;
991 std::ptr::copy_nonoverlapping(&globals, data.pData as *mut _, 1);
992 device_context.Unmap(buffer, 0);
993 }
994 Ok(())
995}
996
997fn pre_draw(
998 device_context: &ID3D11DeviceContext,
999 global_params_buffer: &[Option<ID3D11Buffer>; 1],
1000 view_port: &[D3D11_VIEWPORT; 1],
1001 render_target_view: &[Option<ID3D11RenderTargetView>; 1],
1002 clear_color: [f32; 4],
1003 blend_state: &ID3D11BlendState,
1004) -> Result<()> {
1005 update_global_params(
1006 device_context,
1007 global_params_buffer,
1008 GlobalParams {
1009 viewport_size: [view_port[0].Width, view_port[0].Height],
1010 ..Default::default()
1011 },
1012 )?;
1013 unsafe {
1014 device_context.RSSetViewports(Some(view_port));
1015 device_context.OMSetRenderTargets(Some(render_target_view), None);
1016 device_context.ClearRenderTargetView(render_target_view[0].as_ref().unwrap(), &clear_color);
1017 device_context.OMSetBlendState(blend_state, None, 0xFFFFFFFF);
1018 }
1019 Ok(())
1020}
1021
1022fn update_buffer_capacity(
1023 pipeline: &PipelineState,
1024 element_size: usize,
1025 data_size: usize,
1026 device: &ID3D11Device,
1027) -> Option<(ID3D11Buffer, usize, [Option<ID3D11ShaderResourceView>; 1])> {
1028 if pipeline.buffer_size >= data_size {
1029 return None;
1030 }
1031 println!("buffer too small: {} < {}", pipeline.buffer_size, data_size);
1032 let buffer_size = data_size.next_power_of_two();
1033 println!("New size: {}", buffer_size);
1034 let buffer = create_buffer(device, element_size, buffer_size).unwrap();
1035 let view = create_buffer_view(device, &buffer).unwrap();
1036 Some((buffer, buffer_size, view))
1037}
1038
1039fn update_pipeline(
1040 pipeline: &mut PipelineState,
1041 input: (ID3D11Buffer, usize, [Option<ID3D11ShaderResourceView>; 1]),
1042) {
1043 pipeline.buffer = input.0;
1044 pipeline.buffer_size = input.1;
1045 pipeline.view = input.2;
1046}
1047
1048fn update_buffer<T>(
1049 device_context: &ID3D11DeviceContext,
1050 buffer: &ID3D11Buffer,
1051 data: &[T],
1052) -> Result<()> {
1053 unsafe {
1054 let mut dest = std::mem::zeroed();
1055 device_context.Map(buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, Some(&mut dest))?;
1056 std::ptr::copy_nonoverlapping(data.as_ptr(), dest.pData as _, data.len());
1057 device_context.Unmap(buffer, 0);
1058 }
1059 Ok(())
1060}
1061
1062fn draw_normal(
1063 device_context: &ID3D11DeviceContext,
1064 pipeline: &PipelineState,
1065 viewport: &[D3D11_VIEWPORT],
1066 global_params: &[Option<ID3D11Buffer>],
1067 topology: D3D_PRIMITIVE_TOPOLOGY,
1068 vertex_count: u32,
1069 instance_count: u32,
1070) -> Result<()> {
1071 unsafe {
1072 device_context.VSSetShaderResources(1, Some(&pipeline.view));
1073 device_context.PSSetShaderResources(1, Some(&pipeline.view));
1074 device_context.IASetPrimitiveTopology(topology);
1075 device_context.RSSetViewports(Some(viewport));
1076 device_context.VSSetShader(&pipeline.vertex, None);
1077 device_context.PSSetShader(&pipeline.fragment, None);
1078 device_context.VSSetConstantBuffers(0, Some(global_params));
1079 device_context.PSSetConstantBuffers(0, Some(global_params));
1080
1081 device_context.DrawInstanced(vertex_count, instance_count, 0, 0);
1082 }
1083 Ok(())
1084}
1085
1086fn draw_with_texture(
1087 device_context: &ID3D11DeviceContext,
1088 pipeline: &PipelineState,
1089 texture: &[Option<ID3D11ShaderResourceView>],
1090 viewport: &[D3D11_VIEWPORT],
1091 global_params: &[Option<ID3D11Buffer>],
1092 sampler: &[Option<ID3D11SamplerState>],
1093 instance_count: u32,
1094) -> Result<()> {
1095 unsafe {
1096 device_context.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
1097 device_context.RSSetViewports(Some(viewport));
1098 device_context.VSSetShader(&pipeline.vertex, None);
1099 device_context.PSSetShader(&pipeline.fragment, None);
1100 device_context.VSSetConstantBuffers(0, Some(global_params));
1101 device_context.PSSetConstantBuffers(0, Some(global_params));
1102 device_context.VSSetShaderResources(1, Some(&pipeline.view));
1103 device_context.PSSetShaderResources(1, Some(&pipeline.view));
1104 device_context.PSSetSamplers(0, Some(sampler));
1105 device_context.VSSetShaderResources(0, Some(texture));
1106 device_context.PSSetShaderResources(0, Some(texture));
1107
1108 device_context.DrawInstanced(4, instance_count, 0, 0);
1109 }
1110 Ok(())
1111}
1112
1113const BUFFER_COUNT: usize = 3;
1114
1115mod shader_resources {
1116 use anyhow::Result;
1117 use windows::Win32::Graphics::Direct3D::{
1118 Fxc::{D3DCOMPILE_DEBUG, D3DCOMPILE_SKIP_OPTIMIZATION, D3DCompileFromFile},
1119 ID3DBlob,
1120 };
1121 use windows_core::{HSTRING, PCSTR};
1122
1123 pub(super) fn build_shader_blob(entry: &str, target: &str) -> Result<ID3DBlob> {
1124 unsafe {
1125 let mut entry = entry.to_owned();
1126 let mut target = target.to_owned();
1127 let mut compile_blob = None;
1128 let mut error_blob = None;
1129 let shader_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
1130 .join("src/platform/windows/shaders.hlsl")
1131 .canonicalize()
1132 .unwrap();
1133 entry.push_str("\0");
1134 target.push_str("\0");
1135 let entry_point = PCSTR::from_raw(entry.as_ptr());
1136 let target_cstr = PCSTR::from_raw(target.as_ptr());
1137 #[cfg(debug_assertions)]
1138 let compile_flag = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
1139 #[cfg(not(debug_assertions))]
1140 let compile_flag = 0;
1141 let ret = D3DCompileFromFile(
1142 &HSTRING::from(shader_path.to_str().unwrap()),
1143 None,
1144 None,
1145 entry_point,
1146 target_cstr,
1147 compile_flag,
1148 0,
1149 &mut compile_blob,
1150 Some(&mut error_blob),
1151 );
1152 if ret.is_err() {
1153 let Some(error_blob) = error_blob else {
1154 return Err(anyhow::anyhow!("{ret:?}"));
1155 };
1156 let string_len = error_blob.GetBufferSize();
1157 let error_string_encode = Vec::from_raw_parts(
1158 error_blob.GetBufferPointer() as *mut u8,
1159 string_len,
1160 string_len,
1161 );
1162 return Err(anyhow::anyhow!(
1163 "Compile error: {}",
1164 String::from_utf8_lossy(&error_string_encode)
1165 ));
1166 }
1167 Ok(compile_blob.unwrap())
1168 }
1169 }
1170}