use std::{mem::ManuallyDrop, sync::Arc}; use ::util::ResultExt; use anyhow::{Context, Result}; // #[cfg(not(feature = "enable-renderdoc"))] // use windows::Win32::Graphics::DirectComposition::*; use windows::{ Win32::{ Foundation::{HMODULE, HWND}, Graphics::{ Direct3D::*, Direct3D11::*, Dxgi::{Common::*, *}, }, }, core::*, }; use crate::*; const RENDER_TARGET_FORMAT: DXGI_FORMAT = DXGI_FORMAT_B8G8R8A8_UNORM; pub(crate) struct DirectXRenderer { atlas: Arc, devices: DirectXDevices, context: DirectXContext, globals: DirectXGlobalElements, pipelines: DirectXRenderPipelines, hwnd: HWND, transparent: bool, } #[derive(Clone)] pub(crate) struct DirectXDevices { dxgi_factory: IDXGIFactory6, dxgi_device: IDXGIDevice, device: ID3D11Device, device_context: ID3D11DeviceContext, } struct DirectXContext { swap_chain: ManuallyDrop, render_target: ManuallyDrop, render_target_view: [Option; 1], msaa_target: ID3D11Texture2D, msaa_view: ID3D11RenderTargetView, viewport: [D3D11_VIEWPORT; 1], // #[cfg(not(feature = "enable-renderdoc"))] // direct_composition: DirectComposition, } struct DirectXRenderPipelines { shadow_pipeline: PipelineState, quad_pipeline: PipelineState, paths_pipeline: PathsPipelineState, underline_pipeline: PipelineState, mono_sprites: PipelineState, poly_sprites: PipelineState, } struct DirectXGlobalElements { global_params_buffer: [Option; 1], sampler: [Option; 1], blend_state: ID3D11BlendState, } #[repr(C)] struct DrawInstancedIndirectArgs { vertex_count_per_instance: u32, instance_count: u32, start_vertex_location: u32, start_instance_location: u32, } // #[cfg(not(feature = "enable-renderdoc"))] // struct DirectComposition { // comp_device: IDCompositionDevice, // comp_target: IDCompositionTarget, // comp_visual: IDCompositionVisual, // } impl DirectXDevices { pub(crate) fn new() -> Result { let dxgi_factory = get_dxgi_factory()?; let adapter = get_adapter(&dxgi_factory)?; let (device, device_context) = { let mut device: Option = None; let mut context: Option = None; get_device(&adapter, Some(&mut device), Some(&mut context))?; (device.unwrap(), context.unwrap()) }; let dxgi_device: IDXGIDevice = device.cast()?; Ok(Self { dxgi_factory, dxgi_device, device, device_context, }) } } impl DirectXRenderer { pub(crate) fn new(devices: &DirectXDevices, hwnd: HWND, transparent: bool) -> Result { let atlas = Arc::new(DirectXAtlas::new( devices.device.clone(), devices.device_context.clone(), )); let context = DirectXContext::new(devices, hwnd, transparent)?; let globals = DirectXGlobalElements::new(&devices.device)?; let pipelines = DirectXRenderPipelines::new(&devices.device)?; Ok(DirectXRenderer { atlas, devices: devices.clone(), context, globals, pipelines, hwnd, transparent, }) } pub(crate) fn sprite_atlas(&self) -> Arc { self.atlas.clone() } fn pre_draw(&self) -> Result<()> { update_buffer( &self.devices.device_context, self.globals.global_params_buffer[0].as_ref().unwrap(), &[GlobalParams { viewport_size: [ self.context.viewport[0].Width, self.context.viewport[0].Height, ], ..Default::default() }], )?; unsafe { self.devices .device_context .ClearRenderTargetView(&self.context.msaa_view, &[0.0; 4]); self.devices .device_context .OMSetRenderTargets(Some(&[Some(self.context.msaa_view.clone())]), None); self.devices .device_context .RSSetViewports(Some(&self.context.viewport)); self.devices.device_context.OMSetBlendState( &self.globals.blend_state, None, 0xFFFFFFFF, ); } Ok(()) } pub(crate) fn draw(&mut self, scene: &Scene) -> Result<()> { self.pre_draw()?; for batch in scene.batches() { match batch { PrimitiveBatch::Shadows(shadows) => self.draw_shadows(shadows), PrimitiveBatch::Quads(quads) => self.draw_quads(quads), PrimitiveBatch::Paths(paths) => self.draw_paths(paths), PrimitiveBatch::Underlines(underlines) => self.draw_underlines(underlines), PrimitiveBatch::MonochromeSprites { texture_id, sprites, } => self.draw_monochrome_sprites(texture_id, sprites), PrimitiveBatch::PolychromeSprites { texture_id, sprites, } => self.draw_polychrome_sprites(texture_id, sprites), PrimitiveBatch::Surfaces(surfaces) => self.draw_surfaces(surfaces), }.context(format!("scene too large: {} paths, {} shadows, {} quads, {} underlines, {} mono, {} poly, {} surfaces", scene.paths.len(), scene.shadows.len(), scene.quads.len(), scene.underlines.len(), scene.monochrome_sprites.len(), scene.polychrome_sprites.len(), scene.surfaces.len(),))?; } unsafe { self.devices.device_context.ResolveSubresource( &*self.context.render_target, 0, &self.context.msaa_target, 0, RENDER_TARGET_FORMAT, ); self.devices .device_context .OMSetRenderTargets(Some(&self.context.render_target_view), None); self.context.swap_chain.Present(0, DXGI_PRESENT(0)).ok()?; } Ok(()) } pub(crate) fn resize(&mut self, new_size: Size) -> Result<()> { unsafe { self.devices.device_context.OMSetRenderTargets(None, None); ManuallyDrop::drop(&mut self.context.render_target); } drop(self.context.render_target_view[0].take().unwrap()); unsafe { self.context .swap_chain .ResizeBuffers( BUFFER_COUNT as u32, new_size.width.0 as u32, new_size.height.0 as u32, RENDER_TARGET_FORMAT, DXGI_SWAP_CHAIN_FLAG(0), ) .unwrap(); } // let backbuffer = set_render_target_view( // &self.context.swap_chain, // &self.devices.device, // &self.devices.device_context, // )?; let (render_target, render_target_view) = create_render_target_and_its_view(&self.context.swap_chain, &self.devices.device) .unwrap(); self.context.render_target = render_target; self.context.render_target_view = render_target_view; unsafe { self.devices .device_context .OMSetRenderTargets(Some(&self.context.render_target_view), None); } let (msaa_target, msaa_view) = create_msaa_target_and_its_view( &self.devices.device, new_size.width.0 as u32, new_size.height.0 as u32, )?; self.context.msaa_target = msaa_target; self.context.msaa_view = msaa_view; self.context.viewport = set_viewport( &self.devices.device_context, new_size.width.0 as f32, new_size.height.0 as f32, ); Ok(()) } // #[cfg(not(feature = "enable-renderdoc"))] // pub(crate) fn update_transparency( // &mut self, // background_appearance: WindowBackgroundAppearance, // ) -> Result<()> { // // We only support setting `Transparent` and `Opaque` for now. // match background_appearance { // WindowBackgroundAppearance::Opaque => { // if self.transparent { // return Err(anyhow::anyhow!( // "Set opaque backgroud from transparent background, a restart is required. Or, you can open a new window." // )); // } // } // WindowBackgroundAppearance::Transparent | WindowBackgroundAppearance::Blurred => { // if !self.transparent { // return Err(anyhow::anyhow!( // "Set transparent backgroud from opaque background, a restart is required. Or, you can open a new window." // )); // } // } // } // Ok(()) // } // #[cfg(feature = "enable-renderdoc")] pub(crate) fn update_transparency( &mut self, background_appearance: WindowBackgroundAppearance, ) -> Result<()> { let transparent = background_appearance != WindowBackgroundAppearance::Opaque; if self.transparent == transparent { return Ok(()); } self.transparent = transparent; // unsafe { // // recreate the swapchain // self.devices.device_context.OMSetRenderTargets(None, None); // drop(self.context.back_buffer[0].take().unwrap()); // ManuallyDrop::drop(&mut self.context.swap_chain); // self.context.swap_chain = create_swap_chain_default( // &self.devices.dxgi_factory, // &self.devices.device, // self.hwnd, // transparent, // )?; // self.context.back_buffer = [Some(set_render_target_view( // &self.context.swap_chain, // &self.devices.device, // &self.devices.device_context, // )?)]; // self.context.viewport = set_viewport( // &self.devices.device_context, // self.context.viewport[0].Width, // self.context.viewport[0].Height, // ); // set_rasterizer_state(&self.devices.device, &self.devices.device_context)?; // } Ok(()) } fn draw_shadows(&mut self, shadows: &[Shadow]) -> Result<()> { if shadows.is_empty() { return Ok(()); } self.pipelines.shadow_pipeline.update_buffer( &self.devices.device, &self.devices.device_context, shadows, )?; self.pipelines.shadow_pipeline.draw( &self.devices.device_context, &self.context.viewport, &self.globals.global_params_buffer, shadows.len() as u32, ) } fn draw_quads(&mut self, quads: &[Quad]) -> Result<()> { if quads.is_empty() { return Ok(()); } self.pipelines.quad_pipeline.update_buffer( &self.devices.device, &self.devices.device_context, quads, )?; self.pipelines.quad_pipeline.draw( &self.devices.device_context, &self.context.viewport, &self.globals.global_params_buffer, quads.len() as u32, ) } fn draw_paths(&mut self, paths: &[Path]) -> Result<()> { if paths.is_empty() { return Ok(()); } let mut vertices = Vec::new(); let mut sprites = Vec::with_capacity(paths.len()); let mut draw_indirect_commands = Vec::with_capacity(paths.len()); let mut start_vertex_location = 0; for (i, path) in paths.iter().enumerate() { draw_indirect_commands.push(DrawInstancedIndirectArgs { vertex_count_per_instance: path.vertices.len() as u32, instance_count: 1, start_vertex_location, start_instance_location: i as u32, }); start_vertex_location += path.vertices.len() as u32; vertices.extend(path.vertices.iter().map(|v| DirectXPathVertex { xy_position: v.xy_position, content_mask: path.content_mask.bounds, sprite_index: i as u32, })); sprites.push(PathSprite { bounds: path.bounds, color: path.color, }); } self.pipelines.paths_pipeline.update_buffer( &self.devices.device, &self.devices.device_context, &sprites, &vertices, &draw_indirect_commands, )?; self.pipelines.paths_pipeline.draw( &self.devices.device_context, paths.len(), &self.context.viewport, &self.globals.global_params_buffer, ) } fn draw_underlines(&mut self, underlines: &[Underline]) -> Result<()> { if underlines.is_empty() { return Ok(()); } self.pipelines.underline_pipeline.update_buffer( &self.devices.device, &self.devices.device_context, underlines, )?; self.pipelines.underline_pipeline.draw( &self.devices.device_context, &self.context.viewport, &self.globals.global_params_buffer, underlines.len() as u32, ) } fn draw_monochrome_sprites( &mut self, texture_id: AtlasTextureId, sprites: &[MonochromeSprite], ) -> Result<()> { if sprites.is_empty() { return Ok(()); } self.pipelines.mono_sprites.update_buffer( &self.devices.device, &self.devices.device_context, sprites, )?; let texture_view = self.atlas.get_texture_view(texture_id); self.pipelines.mono_sprites.draw_with_texture( &self.devices.device_context, &texture_view, &self.context.viewport, &self.globals.global_params_buffer, &self.globals.sampler, sprites.len() as u32, ) } fn draw_polychrome_sprites( &mut self, texture_id: AtlasTextureId, sprites: &[PolychromeSprite], ) -> Result<()> { if sprites.is_empty() { return Ok(()); } self.pipelines.poly_sprites.update_buffer( &self.devices.device, &self.devices.device_context, sprites, )?; let texture_view = self.atlas.get_texture_view(texture_id); self.pipelines.poly_sprites.draw_with_texture( &self.devices.device_context, &texture_view, &self.context.viewport, &self.globals.global_params_buffer, &self.globals.sampler, sprites.len() as u32, ) } fn draw_surfaces(&mut self, surfaces: &[PaintSurface]) -> Result<()> { if surfaces.is_empty() { return Ok(()); } Ok(()) } } impl DirectXContext { pub fn new(devices: &DirectXDevices, hwnd: HWND, transparent: bool) -> Result { // #[cfg(not(feature = "enable-renderdoc"))] // let swap_chain = create_swap_chain(&devices.dxgi_factory, &devices.device, transparent)?; // #[cfg(feature = "enable-renderdoc")] let swap_chain = create_swap_chain_default(&devices.dxgi_factory, &devices.device, hwnd, transparent)?; // #[cfg(not(feature = "enable-renderdoc"))] // let direct_composition = DirectComposition::new(&devices.dxgi_device, hwnd)?; // #[cfg(not(feature = "enable-renderdoc"))] // direct_composition.set_swap_chain(&swap_chain)?; let (render_target, render_target_view) = create_render_target_and_its_view(&swap_chain, &devices.device)?; // let back_buffer = [Some(set_render_target_view( // &swap_chain, // &devices.device, // &devices.device_context, // )?)]; let (msaa_target, msaa_view) = create_msaa_target_and_its_view(&devices.device, 1, 1)?; let viewport = set_viewport(&devices.device_context, 1.0, 1.0); unsafe { devices .device_context .OMSetRenderTargets(Some(&render_target_view), None); } set_rasterizer_state(&devices.device, &devices.device_context)?; Ok(Self { swap_chain, render_target, render_target_view, msaa_target, msaa_view, viewport, // #[cfg(not(feature = "enable-renderdoc"))] // direct_composition, }) } } impl DirectXRenderPipelines { pub fn new(device: &ID3D11Device) -> Result { let shadow_pipeline = PipelineState::new( device, "shadow_pipeline", "shadow_vertex", "shadow_fragment", 4, )?; let quad_pipeline = PipelineState::new(device, "quad_pipeline", "quad_vertex", "quad_fragment", 64)?; let paths_pipeline = PathsPipelineState::new(device)?; let underline_pipeline = PipelineState::new( device, "underline_pipeline", "underline_vertex", "underline_fragment", 4, )?; let mono_sprites = PipelineState::new( device, "monochrome_sprite_pipeline", "monochrome_sprite_vertex", "monochrome_sprite_fragment", 512, )?; let poly_sprites = PipelineState::new( device, "polychrome_sprite_pipeline", "polychrome_sprite_vertex", "polychrome_sprite_fragment", 16, )?; Ok(Self { shadow_pipeline, quad_pipeline, paths_pipeline, underline_pipeline, mono_sprites, poly_sprites, }) } } // #[cfg(not(feature = "enable-renderdoc"))] // impl DirectComposition { // pub fn new(dxgi_device: &IDXGIDevice, hwnd: HWND) -> Result { // let comp_device = get_comp_device(&dxgi_device)?; // let comp_target = unsafe { comp_device.CreateTargetForHwnd(hwnd, true) }?; // let comp_visual = unsafe { comp_device.CreateVisual() }?; // Ok(Self { // comp_device, // comp_target, // comp_visual, // }) // } // pub fn set_swap_chain(&self, swap_chain: &IDXGISwapChain1) -> Result<()> { // unsafe { // self.comp_visual.SetContent(swap_chain)?; // self.comp_target.SetRoot(&self.comp_visual)?; // self.comp_device.Commit()?; // } // Ok(()) // } // } impl DirectXGlobalElements { pub fn new(device: &ID3D11Device) -> Result { let global_params_buffer = unsafe { let desc = D3D11_BUFFER_DESC { ByteWidth: std::mem::size_of::() as u32, Usage: D3D11_USAGE_DYNAMIC, BindFlags: D3D11_BIND_CONSTANT_BUFFER.0 as u32, CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32, ..Default::default() }; let mut buffer = None; device.CreateBuffer(&desc, None, Some(&mut buffer))?; [buffer] }; let sampler = unsafe { let desc = D3D11_SAMPLER_DESC { Filter: D3D11_FILTER_MIN_MAG_MIP_LINEAR, AddressU: D3D11_TEXTURE_ADDRESS_WRAP, AddressV: D3D11_TEXTURE_ADDRESS_WRAP, AddressW: D3D11_TEXTURE_ADDRESS_WRAP, MipLODBias: 0.0, MaxAnisotropy: 1, ComparisonFunc: D3D11_COMPARISON_ALWAYS, BorderColor: [0.0; 4], MinLOD: 0.0, MaxLOD: D3D11_FLOAT32_MAX, }; let mut output = None; device.CreateSamplerState(&desc, Some(&mut output))?; [output] }; let blend_state = create_blend_state(device)?; Ok(Self { global_params_buffer, sampler, blend_state, }) } } #[derive(Debug, Default)] #[repr(C)] struct GlobalParams { viewport_size: [f32; 2], _pad: u64, } struct PipelineState { label: &'static str, vertex: ID3D11VertexShader, fragment: ID3D11PixelShader, buffer: ID3D11Buffer, buffer_size: usize, view: [Option; 1], _marker: std::marker::PhantomData, } struct PathsPipelineState { vertex: ID3D11VertexShader, fragment: ID3D11PixelShader, buffer: ID3D11Buffer, buffer_size: usize, vertex_buffer: Option, vertex_buffer_size: usize, indirect_draw_buffer: ID3D11Buffer, indirect_buffer_size: usize, input_layout: ID3D11InputLayout, view: [Option; 1], } impl PipelineState { fn new( device: &ID3D11Device, label: &'static str, vertex_entry: &str, fragment_entry: &str, buffer_size: usize, ) -> Result { let vertex = { let shader_blob = shader_resources::build_shader_blob(vertex_entry, "vs_5_0")?; let bytes = unsafe { std::slice::from_raw_parts( shader_blob.GetBufferPointer() as *mut u8, shader_blob.GetBufferSize(), ) }; create_vertex_shader(device, bytes)? }; let fragment = { let shader_blob = shader_resources::build_shader_blob(fragment_entry, "ps_5_0")?; let bytes = unsafe { std::slice::from_raw_parts( shader_blob.GetBufferPointer() as *mut u8, shader_blob.GetBufferSize(), ) }; create_fragment_shader(device, bytes)? }; let buffer = create_buffer(device, std::mem::size_of::(), buffer_size)?; let view = create_buffer_view(device, &buffer)?; Ok(PipelineState { label, vertex, fragment, buffer, buffer_size, view, _marker: std::marker::PhantomData, }) } fn update_buffer( &mut self, device: &ID3D11Device, device_context: &ID3D11DeviceContext, data: &[T], ) -> Result<()> { if self.buffer_size < data.len() { let new_buffer_size = data.len().next_power_of_two(); log::info!( "Updating {} buffer size from {} to {}", self.label, self.buffer_size, new_buffer_size ); let buffer = create_buffer(device, std::mem::size_of::(), new_buffer_size)?; let view = create_buffer_view(device, &buffer)?; self.buffer = buffer; self.view = view; self.buffer_size = new_buffer_size; } update_buffer(device_context, &self.buffer, data) } fn draw( &self, device_context: &ID3D11DeviceContext, viewport: &[D3D11_VIEWPORT], global_params: &[Option], instance_count: u32, ) -> Result<()> { set_pipeline_state( device_context, &self.view, D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP, viewport, &self.vertex, &self.fragment, global_params, ); unsafe { device_context.DrawInstanced(4, instance_count, 0, 0); } Ok(()) } fn draw_with_texture( &self, device_context: &ID3D11DeviceContext, texture: &[Option], viewport: &[D3D11_VIEWPORT], global_params: &[Option], sampler: &[Option], instance_count: u32, ) -> Result<()> { set_pipeline_state( device_context, &self.view, D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP, viewport, &self.vertex, &self.fragment, global_params, ); unsafe { device_context.PSSetSamplers(0, Some(sampler)); device_context.VSSetShaderResources(0, Some(texture)); device_context.PSSetShaderResources(0, Some(texture)); device_context.DrawInstanced(4, instance_count, 0, 0); } Ok(()) } } impl PathsPipelineState { fn new(device: &ID3D11Device) -> Result { let (vertex, vertex_shader) = { let shader_blob = shader_resources::build_shader_blob("paths_vertex", "vs_5_0")?; let bytes = unsafe { std::slice::from_raw_parts( shader_blob.GetBufferPointer() as *mut u8, shader_blob.GetBufferSize(), ) }; (create_vertex_shader(device, bytes)?, shader_blob) }; let fragment = { let shader_blob = shader_resources::build_shader_blob("paths_fragment", "ps_5_0")?; let bytes = unsafe { std::slice::from_raw_parts( shader_blob.GetBufferPointer() as *mut u8, shader_blob.GetBufferSize(), ) }; create_fragment_shader(device, bytes)? }; let buffer = create_buffer(device, std::mem::size_of::(), 32)?; let view = create_buffer_view(device, &buffer)?; let vertex_buffer = Some(create_buffer( device, std::mem::size_of::(), 32, )?); let indirect_draw_buffer = create_indirect_draw_buffer(device, 32)?; // Create input layout let input_layout = unsafe { let shader_bytes = std::slice::from_raw_parts( vertex_shader.GetBufferPointer() as *const u8, vertex_shader.GetBufferSize(), ); let mut layout = None; device.CreateInputLayout( &[ D3D11_INPUT_ELEMENT_DESC { SemanticName: windows::core::s!("POSITION"), SemanticIndex: 0, Format: DXGI_FORMAT_R32G32_FLOAT, InputSlot: 0, AlignedByteOffset: 0, InputSlotClass: D3D11_INPUT_PER_VERTEX_DATA, InstanceDataStepRate: 0, }, D3D11_INPUT_ELEMENT_DESC { SemanticName: windows::core::s!("TEXCOORD"), SemanticIndex: 0, Format: DXGI_FORMAT_R32G32_FLOAT, InputSlot: 0, AlignedByteOffset: 8, InputSlotClass: D3D11_INPUT_PER_VERTEX_DATA, InstanceDataStepRate: 0, }, D3D11_INPUT_ELEMENT_DESC { SemanticName: windows::core::s!("TEXCOORD"), SemanticIndex: 1, Format: DXGI_FORMAT_R32G32_FLOAT, InputSlot: 0, AlignedByteOffset: 16, InputSlotClass: D3D11_INPUT_PER_VERTEX_DATA, InstanceDataStepRate: 0, }, D3D11_INPUT_ELEMENT_DESC { SemanticName: windows::core::s!("GLOBALIDX"), SemanticIndex: 0, Format: DXGI_FORMAT_R32_UINT, InputSlot: 0, AlignedByteOffset: 24, InputSlotClass: D3D11_INPUT_PER_VERTEX_DATA, InstanceDataStepRate: 0, }, ], shader_bytes, Some(&mut layout), )?; layout.unwrap() }; Ok(Self { vertex, fragment, buffer, buffer_size: 32, vertex_buffer, vertex_buffer_size: 32, indirect_draw_buffer, indirect_buffer_size: 32, input_layout, view, }) } fn update_buffer( &mut self, device: &ID3D11Device, device_context: &ID3D11DeviceContext, buffer_data: &[PathSprite], vertices_data: &[DirectXPathVertex], draw_commands: &[DrawInstancedIndirectArgs], ) -> Result<()> { if self.buffer_size < buffer_data.len() { let new_buffer_size = buffer_data.len().next_power_of_two(); log::info!( "Updating Paths Pipeline buffer size from {} to {}", self.buffer_size, new_buffer_size ); let buffer = create_buffer(device, std::mem::size_of::(), new_buffer_size)?; let view = create_buffer_view(device, &buffer)?; self.buffer = buffer; self.view = view; self.buffer_size = new_buffer_size; } update_buffer(device_context, &self.buffer, buffer_data)?; if self.vertex_buffer_size < vertices_data.len() { let new_vertex_buffer_size = vertices_data.len().next_power_of_two(); log::info!( "Updating Paths Pipeline vertex buffer size from {} to {}", self.vertex_buffer_size, new_vertex_buffer_size ); let vertex_buffer = create_buffer( device, std::mem::size_of::(), new_vertex_buffer_size, )?; self.vertex_buffer = Some(vertex_buffer); self.vertex_buffer_size = new_vertex_buffer_size; } update_buffer( device_context, self.vertex_buffer.as_ref().unwrap(), vertices_data, )?; if self.indirect_buffer_size < draw_commands.len() { let new_indirect_buffer_size = draw_commands.len().next_power_of_two(); log::info!( "Updating Paths Pipeline indirect buffer size from {} to {}", self.indirect_buffer_size, new_indirect_buffer_size ); let indirect_draw_buffer = create_indirect_draw_buffer(device, new_indirect_buffer_size)?; self.indirect_draw_buffer = indirect_draw_buffer; self.indirect_buffer_size = new_indirect_buffer_size; } update_buffer(device_context, &self.indirect_draw_buffer, draw_commands)?; Ok(()) } fn draw( &self, device_context: &ID3D11DeviceContext, count: usize, viewport: &[D3D11_VIEWPORT], global_params: &[Option], ) -> Result<()> { set_pipeline_state( device_context, &self.view, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST, viewport, &self.vertex, &self.fragment, global_params, ); unsafe { const STRIDE: u32 = std::mem::size_of::() as u32; device_context.IASetVertexBuffers( 0, 1, Some(&self.vertex_buffer), Some(&STRIDE), Some(&0), ); device_context.IASetInputLayout(&self.input_layout); } for i in 0..count { unsafe { device_context.DrawInstancedIndirect( &self.indirect_draw_buffer, (i * std::mem::size_of::()) as u32, ); } } Ok(()) } } #[repr(C)] struct DirectXPathVertex { xy_position: Point, content_mask: Bounds, sprite_index: u32, } #[derive(Clone, Debug, Eq, PartialEq)] #[repr(C)] struct PathSprite { bounds: Bounds, color: Background, } impl Drop for DirectXContext { fn drop(&mut self) { unsafe { ManuallyDrop::drop(&mut self.render_target); ManuallyDrop::drop(&mut self.swap_chain); } } } #[inline] fn get_dxgi_factory() -> Result { #[cfg(debug_assertions)] let factory_flag = DXGI_CREATE_FACTORY_DEBUG; #[cfg(not(debug_assertions))] let factory_flag = DXGI_CREATE_FACTORY_FLAGS::default(); unsafe { Ok(CreateDXGIFactory2(factory_flag)?) } } fn get_adapter(dxgi_factory: &IDXGIFactory6) -> Result { for adapter_index in 0.. { let adapter: IDXGIAdapter1 = unsafe { dxgi_factory .EnumAdapterByGpuPreference(adapter_index, DXGI_GPU_PREFERENCE_MINIMUM_POWER) }?; { let desc = unsafe { adapter.GetDesc1() }?; println!( "Select GPU: {}", String::from_utf16_lossy(&desc.Description) ); } // Check to see whether the adapter supports Direct3D 11, but don't // create the actual device yet. if get_device(&adapter, None, None).log_err().is_some() { return Ok(adapter); } } unreachable!() } fn get_device( adapter: &IDXGIAdapter1, device: Option<*mut Option>, context: Option<*mut Option>, ) -> Result<()> { #[cfg(debug_assertions)] let device_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_DEBUG; #[cfg(not(debug_assertions))] let device_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; Ok(unsafe { D3D11CreateDevice( adapter, D3D_DRIVER_TYPE_UNKNOWN, HMODULE::default(), device_flags, Some(&[D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_11_1]), D3D11_SDK_VERSION, device, None, context, )? }) } // #[cfg(not(feature = "enable-renderdoc"))] // fn get_comp_device(dxgi_device: &IDXGIDevice) -> Result { // Ok(unsafe { DCompositionCreateDevice(dxgi_device)? }) // } // fn create_swap_chain( // dxgi_factory: &IDXGIFactory6, // device: &ID3D11Device, // transparent: bool, // ) -> Result { // let alpha_mode = if transparent { // DXGI_ALPHA_MODE_PREMULTIPLIED // } else { // DXGI_ALPHA_MODE_IGNORE // }; // let desc = DXGI_SWAP_CHAIN_DESC1 { // Width: 1, // Height: 1, // Format: DXGI_FORMAT_B8G8R8A8_UNORM, // Stereo: false.into(), // SampleDesc: DXGI_SAMPLE_DESC { // Count: 1, // Quality: 0, // }, // BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT, // BufferCount: BUFFER_COUNT as u32, // // Composition SwapChains only support the DXGI_SCALING_STRETCH Scaling. // Scaling: DXGI_SCALING_STRETCH, // SwapEffect: DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, // AlphaMode: alpha_mode, // Flags: 0, // }; // Ok(unsafe { dxgi_factory.CreateSwapChainForComposition(device, &desc, None)? }) // } // #[cfg(feature = "enable-renderdoc")] fn create_swap_chain_default( dxgi_factory: &IDXGIFactory6, device: &ID3D11Device, hwnd: HWND, _transparent: bool, ) -> Result> { use windows::Win32::Graphics::Dxgi::DXGI_MWA_NO_ALT_ENTER; let desc = DXGI_SWAP_CHAIN_DESC1 { Width: 1, Height: 1, Format: RENDER_TARGET_FORMAT, Stereo: false.into(), SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0, }, BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT, BufferCount: BUFFER_COUNT as u32, Scaling: DXGI_SCALING_STRETCH, SwapEffect: DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, AlphaMode: DXGI_ALPHA_MODE_IGNORE, Flags: 0, }; let swap_chain = unsafe { dxgi_factory.CreateSwapChainForHwnd(device, hwnd, &desc, None, None) }?; unsafe { dxgi_factory.MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER) }?; Ok(ManuallyDrop::new(swap_chain)) } #[inline] fn create_render_target_and_its_view( swap_chain: &IDXGISwapChain1, device: &ID3D11Device, ) -> Result<( ManuallyDrop, [Option; 1], )> { let render_target: ID3D11Texture2D = unsafe { swap_chain.GetBuffer(0) }?; let mut render_target_view = None; unsafe { device.CreateRenderTargetView(&render_target, None, Some(&mut render_target_view))? }; Ok(( ManuallyDrop::new(render_target), [Some(render_target_view.unwrap())], )) } #[inline] fn set_render_target_view( swap_chain: &IDXGISwapChain1, device: &ID3D11Device, device_context: &ID3D11DeviceContext, ) -> Result { // In dx11, ID3D11RenderTargetView is supposed to always point to the new back buffer. // https://stackoverflow.com/questions/65246961/does-the-backbuffer-that-a-rendertargetview-points-to-automagically-change-after let back_buffer = unsafe { let resource: ID3D11Texture2D = swap_chain.GetBuffer(0)?; let mut buffer: Option = None; device.CreateRenderTargetView(&resource, None, Some(&mut buffer))?; buffer.unwrap() }; unsafe { device_context.OMSetRenderTargets(Some(&[Some(back_buffer.clone())]), None) }; Ok(back_buffer) } #[inline] fn create_msaa_target_and_its_view( device: &ID3D11Device, width: u32, height: u32, ) -> Result<(ID3D11Texture2D, ID3D11RenderTargetView)> { let msaa_target = unsafe { let mut output = None; let desc = D3D11_TEXTURE2D_DESC { Width: width, Height: height, MipLevels: 1, ArraySize: 1, Format: RENDER_TARGET_FORMAT, SampleDesc: DXGI_SAMPLE_DESC { Count: 4, Quality: D3D11_STANDARD_MULTISAMPLE_PATTERN.0 as u32, }, Usage: D3D11_USAGE_DEFAULT, BindFlags: D3D11_BIND_RENDER_TARGET.0 as u32, CPUAccessFlags: 0, MiscFlags: 0, }; device.CreateTexture2D(&desc, None, Some(&mut output))?; output.unwrap() }; let msaa_view = unsafe { let mut output = None; device.CreateRenderTargetView(&msaa_target, None, Some(&mut output))?; output.unwrap() }; Ok((msaa_target, msaa_view)) } #[inline] fn set_viewport( device_context: &ID3D11DeviceContext, width: f32, height: f32, ) -> [D3D11_VIEWPORT; 1] { let viewport = [D3D11_VIEWPORT { TopLeftX: 0.0, TopLeftY: 0.0, Width: width, Height: height, MinDepth: 0.0, MaxDepth: 1.0, }]; unsafe { device_context.RSSetViewports(Some(&viewport)) }; viewport } #[inline] fn set_rasterizer_state(device: &ID3D11Device, device_context: &ID3D11DeviceContext) -> Result<()> { let desc = D3D11_RASTERIZER_DESC { FillMode: D3D11_FILL_SOLID, CullMode: D3D11_CULL_NONE, FrontCounterClockwise: false.into(), DepthBias: 0, DepthBiasClamp: 0.0, SlopeScaledDepthBias: 0.0, DepthClipEnable: true.into(), ScissorEnable: false.into(), // MultisampleEnable: false.into(), MultisampleEnable: true.into(), AntialiasedLineEnable: false.into(), }; let rasterizer_state = unsafe { let mut state = None; device.CreateRasterizerState(&desc, Some(&mut state))?; state.unwrap() }; unsafe { device_context.RSSetState(&rasterizer_state) }; Ok(()) } // https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ns-d3d11-d3d11_blend_desc #[inline] fn create_blend_state(device: &ID3D11Device) -> Result { // If the feature level is set to greater than D3D_FEATURE_LEVEL_9_3, the display // device performs the blend in linear space, which is ideal. let mut desc = D3D11_BLEND_DESC::default(); desc.RenderTarget[0].BlendEnable = true.into(); desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ONE; desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8; unsafe { let mut state = None; device.CreateBlendState(&desc, Some(&mut state))?; Ok(state.unwrap()) } } #[inline] fn create_vertex_shader(device: &ID3D11Device, bytes: &[u8]) -> Result { unsafe { let mut shader = None; device.CreateVertexShader(bytes, None, Some(&mut shader))?; Ok(shader.unwrap()) } } #[inline] fn create_fragment_shader(device: &ID3D11Device, bytes: &[u8]) -> Result { unsafe { let mut shader = None; device.CreatePixelShader(bytes, None, Some(&mut shader))?; Ok(shader.unwrap()) } } #[inline] fn create_buffer( device: &ID3D11Device, element_size: usize, buffer_size: usize, ) -> Result { let desc = D3D11_BUFFER_DESC { ByteWidth: (element_size * buffer_size) as u32, Usage: D3D11_USAGE_DYNAMIC, BindFlags: D3D11_BIND_SHADER_RESOURCE.0 as u32, CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32, MiscFlags: D3D11_RESOURCE_MISC_BUFFER_STRUCTURED.0 as u32, StructureByteStride: element_size as u32, }; let mut buffer = None; unsafe { device.CreateBuffer(&desc, None, Some(&mut buffer)) }?; Ok(buffer.unwrap()) } #[inline] fn create_buffer_view( device: &ID3D11Device, buffer: &ID3D11Buffer, ) -> Result<[Option; 1]> { let mut view = None; unsafe { device.CreateShaderResourceView(buffer, None, Some(&mut view)) }?; Ok([view]) } #[inline] fn create_indirect_draw_buffer(device: &ID3D11Device, buffer_size: usize) -> Result { let desc = D3D11_BUFFER_DESC { ByteWidth: (std::mem::size_of::() * buffer_size) as u32, Usage: D3D11_USAGE_DYNAMIC, BindFlags: D3D11_BIND_SHADER_RESOURCE.0 as u32, CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32, MiscFlags: D3D11_RESOURCE_MISC_DRAWINDIRECT_ARGS.0 as u32, StructureByteStride: std::mem::size_of::() as u32, }; let mut buffer = None; unsafe { device.CreateBuffer(&desc, None, Some(&mut buffer)) }?; Ok(buffer.unwrap()) } #[inline] fn pre_draw( device_context: &ID3D11DeviceContext, global_params_buffer: &[Option; 1], view_port: &[D3D11_VIEWPORT; 1], render_target_view: &[Option; 1], clear_color: [f32; 4], blend_state: &ID3D11BlendState, ) -> Result<()> { let global_params = global_params_buffer[0].as_ref().unwrap(); update_buffer( device_context, global_params, &[GlobalParams { viewport_size: [view_port[0].Width, view_port[0].Height], ..Default::default() }], )?; unsafe { device_context.RSSetViewports(Some(view_port)); device_context.OMSetRenderTargets(Some(render_target_view), None); device_context.ClearRenderTargetView(render_target_view[0].as_ref().unwrap(), &clear_color); device_context.OMSetBlendState(blend_state, None, 0xFFFFFFFF); } Ok(()) } #[inline] fn update_buffer( device_context: &ID3D11DeviceContext, buffer: &ID3D11Buffer, data: &[T], ) -> Result<()> { unsafe { let mut dest = std::mem::zeroed(); device_context.Map(buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, Some(&mut dest))?; std::ptr::copy_nonoverlapping(data.as_ptr(), dest.pData as _, data.len()); device_context.Unmap(buffer, 0); } Ok(()) } #[inline] fn set_pipeline_state( device_context: &ID3D11DeviceContext, buffer_view: &[Option], topology: D3D_PRIMITIVE_TOPOLOGY, viewport: &[D3D11_VIEWPORT], vertex_shader: &ID3D11VertexShader, fragment_shader: &ID3D11PixelShader, global_params: &[Option], ) { unsafe { device_context.VSSetShaderResources(1, Some(buffer_view)); device_context.PSSetShaderResources(1, Some(buffer_view)); device_context.IASetPrimitiveTopology(topology); device_context.RSSetViewports(Some(viewport)); device_context.VSSetShader(vertex_shader, None); device_context.PSSetShader(fragment_shader, None); device_context.VSSetConstantBuffers(0, Some(global_params)); device_context.PSSetConstantBuffers(0, Some(global_params)); } } const BUFFER_COUNT: usize = 3; mod shader_resources { use anyhow::Result; use windows::Win32::Graphics::Direct3D::{ Fxc::{D3DCOMPILE_DEBUG, D3DCOMPILE_SKIP_OPTIMIZATION, D3DCompileFromFile}, ID3DBlob, }; use windows_core::{HSTRING, PCSTR}; pub(super) fn build_shader_blob(entry: &str, target: &str) -> Result { unsafe { let mut entry = entry.to_owned(); let mut target = target.to_owned(); let mut compile_blob = None; let mut error_blob = None; let shader_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("src/platform/windows/shaders.hlsl") .canonicalize() .unwrap(); entry.push_str("\0"); target.push_str("\0"); let entry_point = PCSTR::from_raw(entry.as_ptr()); let target_cstr = PCSTR::from_raw(target.as_ptr()); #[cfg(debug_assertions)] let compile_flag = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; #[cfg(not(debug_assertions))] let compile_flag = 0; let ret = D3DCompileFromFile( &HSTRING::from(shader_path.to_str().unwrap()), None, None, entry_point, target_cstr, compile_flag, 0, &mut compile_blob, Some(&mut error_blob), ); if ret.is_err() { let Some(error_blob) = error_blob else { return Err(anyhow::anyhow!("{ret:?}")); }; let string_len = error_blob.GetBufferSize(); let error_string_encode = Vec::from_raw_parts( error_blob.GetBufferPointer() as *mut u8, string_len, string_len, ); let error_string = String::from_utf8_lossy(&error_string_encode).to_string(); log::error!("Shader compile error: {}", error_string); return Err(anyhow::anyhow!("Compile error: {}", error_string)); } Ok(compile_blob.unwrap()) } } }