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