use crate::auxil::{self, dxgi::result::HResult as _}; use super::{conv, descriptor, view}; use parking_lot::Mutex; use std::{ffi, mem, num::NonZeroU32, ptr, sync::Arc}; use winapi::{ shared::{dxgiformat, dxgitype, minwindef::BOOL, winerror}, um::{d3d12 as d3d12_ty, synchapi, winbase}, Interface, }; // this has to match Naga's HLSL backend, and also needs to be null-terminated const NAGA_LOCATION_SEMANTIC: &[u8] = b"LOC\0"; impl super::Device { pub(super) fn new( raw: d3d12::Device, present_queue: d3d12::CommandQueue, private_caps: super::PrivateCapabilities, library: &Arc, dx12_shader_compiler: wgt::Dx12Compiler, ) -> Result { let mem_allocator = super::suballocation::create_allocator_wrapper(&raw)?; let dxc_container = match dx12_shader_compiler { wgt::Dx12Compiler::Dxc { dxil_path, dxc_path, } => super::shader_compilation::get_dxc_container(dxc_path, dxil_path)?, wgt::Dx12Compiler::Fxc => None, }; let mut idle_fence = d3d12::Fence::null(); let hr = unsafe { profiling::scope!("ID3D12Device::CreateFence"); raw.CreateFence( 0, d3d12_ty::D3D12_FENCE_FLAG_NONE, &d3d12_ty::ID3D12Fence::uuidof(), idle_fence.mut_void(), ) }; hr.into_device_result("Idle fence creation")?; let mut zero_buffer = d3d12::Resource::null(); unsafe { let raw_desc = d3d12_ty::D3D12_RESOURCE_DESC { Dimension: d3d12_ty::D3D12_RESOURCE_DIMENSION_BUFFER, Alignment: 0, Width: super::ZERO_BUFFER_SIZE, Height: 1, DepthOrArraySize: 1, MipLevels: 1, Format: dxgiformat::DXGI_FORMAT_UNKNOWN, SampleDesc: dxgitype::DXGI_SAMPLE_DESC { Count: 1, Quality: 0, }, Layout: d3d12_ty::D3D12_TEXTURE_LAYOUT_ROW_MAJOR, Flags: d3d12_ty::D3D12_RESOURCE_FLAG_NONE, }; let heap_properties = d3d12_ty::D3D12_HEAP_PROPERTIES { Type: d3d12_ty::D3D12_HEAP_TYPE_CUSTOM, CPUPageProperty: d3d12_ty::D3D12_CPU_PAGE_PROPERTY_NOT_AVAILABLE, MemoryPoolPreference: match private_caps.memory_architecture { super::MemoryArchitecture::Unified { .. } => d3d12_ty::D3D12_MEMORY_POOL_L0, super::MemoryArchitecture::NonUnified => d3d12_ty::D3D12_MEMORY_POOL_L1, }, CreationNodeMask: 0, VisibleNodeMask: 0, }; profiling::scope!("Zero Buffer Allocation"); raw.CreateCommittedResource( &heap_properties, d3d12_ty::D3D12_HEAP_FLAG_NONE, &raw_desc, d3d12_ty::D3D12_RESOURCE_STATE_COMMON, ptr::null(), &d3d12_ty::ID3D12Resource::uuidof(), zero_buffer.mut_void(), ) .into_device_result("Zero buffer creation")?; // Note: without `D3D12_HEAP_FLAG_CREATE_NOT_ZEROED` // this resource is zeroed by default. }; // maximum number of CBV/SRV/UAV descriptors in heap for Tier 1 let capacity_views = 1_000_000; let capacity_samplers = 2_048; let shared = super::DeviceShared { zero_buffer, cmd_signatures: super::CommandSignatures { draw: raw .create_command_signature( d3d12::RootSignature::null(), &[d3d12::IndirectArgument::draw()], mem::size_of::() as u32, 0, ) .into_device_result("Command (draw) signature creation")?, draw_indexed: raw .create_command_signature( d3d12::RootSignature::null(), &[d3d12::IndirectArgument::draw_indexed()], mem::size_of::() as u32, 0, ) .into_device_result("Command (draw_indexed) signature creation")?, dispatch: raw .create_command_signature( d3d12::RootSignature::null(), &[d3d12::IndirectArgument::dispatch()], mem::size_of::() as u32, 0, ) .into_device_result("Command (dispatch) signature creation")?, }, heap_views: descriptor::GeneralHeap::new( raw, d3d12::DescriptorHeapType::CbvSrvUav, capacity_views, )?, heap_samplers: descriptor::GeneralHeap::new( raw, d3d12::DescriptorHeapType::Sampler, capacity_samplers, )?, }; let mut rtv_pool = descriptor::CpuPool::new(raw, d3d12::DescriptorHeapType::Rtv); let null_rtv_handle = rtv_pool.alloc_handle(); // A null pResource is used to initialize a null descriptor, // which guarantees D3D11-like null binding behavior (reading 0s, writes are discarded) raw.create_render_target_view( d3d12::WeakPtr::null(), &d3d12::RenderTargetViewDesc::texture_2d( winapi::shared::dxgiformat::DXGI_FORMAT_R8G8B8A8_UNORM, 0, 0, ), null_rtv_handle.raw, ); Ok(super::Device { raw, present_queue, idler: super::Idler { fence: idle_fence, event: d3d12::Event::create(false, false), }, private_caps, shared: Arc::new(shared), rtv_pool: Mutex::new(rtv_pool), dsv_pool: Mutex::new(descriptor::CpuPool::new( raw, d3d12::DescriptorHeapType::Dsv, )), srv_uav_pool: Mutex::new(descriptor::CpuPool::new( raw, d3d12::DescriptorHeapType::CbvSrvUav, )), sampler_pool: Mutex::new(descriptor::CpuPool::new( raw, d3d12::DescriptorHeapType::Sampler, )), library: Arc::clone(library), #[cfg(feature = "renderdoc")] render_doc: Default::default(), null_rtv_handle, mem_allocator, dxc_container, }) } pub(super) unsafe fn wait_idle(&self) -> Result<(), crate::DeviceError> { let cur_value = self.idler.fence.get_value(); if cur_value == !0 { return Err(crate::DeviceError::Lost); } let value = cur_value + 1; log::info!("Waiting for idle with value {}", value); self.present_queue.signal(self.idler.fence, value); let hr = self .idler .fence .set_event_on_completion(self.idler.event, value); hr.into_device_result("Set event")?; unsafe { synchapi::WaitForSingleObject(self.idler.event.0, winbase::INFINITE) }; Ok(()) } fn load_shader( &self, stage: &crate::ProgrammableStage, layout: &super::PipelineLayout, naga_stage: naga::ShaderStage, ) -> Result { use naga::back::hlsl; let stage_bit = crate::auxil::map_naga_stage(naga_stage); let module = &stage.module.naga.module; //TODO: reuse the writer let mut source = String::new(); let mut writer = hlsl::Writer::new(&mut source, &layout.naga_options); let reflection_info = { profiling::scope!("naga::back::hlsl::write"); writer .write(module, &stage.module.naga.info) .map_err(|e| crate::PipelineError::Linkage(stage_bit, format!("HLSL: {e:?}")))? }; let full_stage = format!( "{}_{}\0", naga_stage.to_hlsl_str(), layout.naga_options.shader_model.to_str() ); let ep_index = module .entry_points .iter() .position(|ep| ep.stage == naga_stage && ep.name == stage.entry_point) .ok_or(crate::PipelineError::EntryPoint(naga_stage))?; let raw_ep = reflection_info.entry_point_names[ep_index] .as_ref() .map_err(|e| crate::PipelineError::Linkage(stage_bit, format!("{e}")))?; let source_name = stage .module .raw_name .as_ref() .and_then(|cstr| cstr.to_str().ok()) .unwrap_or_default(); // Compile with DXC if available, otherwise fall back to FXC let (result, log_level) = if let Some(ref dxc_container) = self.dxc_container { super::shader_compilation::compile_dxc( self, &source, source_name, raw_ep, stage_bit, full_stage, dxc_container, ) } else { super::shader_compilation::compile_fxc( self, &source, source_name, &ffi::CString::new(raw_ep.as_str()).unwrap(), stage_bit, full_stage, ) }; log::log!( log_level, "Naga generated shader for {:?} at {:?}:\n{}", raw_ep, naga_stage, source ); result } pub fn raw_device(&self) -> &d3d12::Device { &self.raw } pub fn raw_queue(&self) -> &d3d12::CommandQueue { &self.present_queue } pub unsafe fn texture_from_raw( resource: d3d12::Resource, format: wgt::TextureFormat, dimension: wgt::TextureDimension, size: wgt::Extent3d, mip_level_count: u32, sample_count: u32, ) -> super::Texture { super::Texture { resource, format, dimension, size, mip_level_count, sample_count, allocation: None, } } } impl crate::Device for super::Device { unsafe fn exit(mut self, queue: super::Queue) { self.rtv_pool.lock().free_handle(self.null_rtv_handle); unsafe { self.rtv_pool.into_inner().destroy() }; unsafe { self.dsv_pool.into_inner().destroy() }; unsafe { self.srv_uav_pool.into_inner().destroy() }; unsafe { self.sampler_pool.into_inner().destroy() }; unsafe { self.shared.destroy() }; unsafe { self.idler.destroy() }; self.mem_allocator = None; unsafe { queue.raw.destroy() }; } unsafe fn create_buffer( &self, desc: &crate::BufferDescriptor, ) -> Result { let mut resource = d3d12::Resource::null(); let mut size = desc.size; if desc.usage.contains(crate::BufferUses::UNIFORM) { let align_mask = d3d12_ty::D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT as u64 - 1; size = ((size - 1) | align_mask) + 1; } let raw_desc = d3d12_ty::D3D12_RESOURCE_DESC { Dimension: d3d12_ty::D3D12_RESOURCE_DIMENSION_BUFFER, Alignment: 0, Width: size, Height: 1, DepthOrArraySize: 1, MipLevels: 1, Format: dxgiformat::DXGI_FORMAT_UNKNOWN, SampleDesc: dxgitype::DXGI_SAMPLE_DESC { Count: 1, Quality: 0, }, Layout: d3d12_ty::D3D12_TEXTURE_LAYOUT_ROW_MAJOR, Flags: conv::map_buffer_usage_to_resource_flags(desc.usage), }; let (hr, allocation) = super::suballocation::create_buffer_resource(self, desc, raw_desc, &mut resource)?; hr.into_device_result("Buffer creation")?; if let Some(label) = desc.label { let cwstr = conv::map_label(label); unsafe { resource.SetName(cwstr.as_ptr()) }; } Ok(super::Buffer { resource, size, allocation, }) } unsafe fn destroy_buffer(&self, mut buffer: super::Buffer) { unsafe { buffer.resource.destroy() }; // Only happens when it's using the windows_rs feature and there's an allocation if let Some(alloc) = buffer.allocation.take() { super::suballocation::free_buffer_allocation( alloc, // SAFETY: for allocations to exist, the allocator must exist unsafe { self.mem_allocator.as_ref().unwrap_unchecked() }, ); } } unsafe fn map_buffer( &self, buffer: &super::Buffer, range: crate::MemoryRange, ) -> Result { let mut ptr = ptr::null_mut(); // TODO: 0 for subresource should be fine here until map and unmap buffer is subresource aware? let hr = unsafe { (*buffer.resource).Map(0, ptr::null(), &mut ptr) }; hr.into_device_result("Map buffer")?; Ok(crate::BufferMapping { ptr: ptr::NonNull::new(unsafe { ptr.offset(range.start as isize).cast::() }) .unwrap(), //TODO: double-check this. Documentation is a bit misleading - // it implies that Map/Unmap is needed to invalidate/flush memory. is_coherent: true, }) } unsafe fn unmap_buffer(&self, buffer: &super::Buffer) -> Result<(), crate::DeviceError> { unsafe { (*buffer.resource).Unmap(0, ptr::null()) }; Ok(()) } unsafe fn flush_mapped_ranges(&self, _buffer: &super::Buffer, _ranges: I) {} unsafe fn invalidate_mapped_ranges(&self, _buffer: &super::Buffer, _ranges: I) {} unsafe fn create_texture( &self, desc: &crate::TextureDescriptor, ) -> Result { use super::suballocation::create_texture_resource; let mut resource = d3d12::Resource::null(); let raw_desc = d3d12_ty::D3D12_RESOURCE_DESC { Dimension: conv::map_texture_dimension(desc.dimension), Alignment: 0, Width: desc.size.width as u64, Height: desc.size.height, DepthOrArraySize: desc.size.depth_or_array_layers as u16, MipLevels: desc.mip_level_count as u16, Format: auxil::dxgi::conv::map_texture_format_for_resource( desc.format, desc.usage, !desc.view_formats.is_empty(), self.private_caps.casting_fully_typed_format_supported, ), SampleDesc: dxgitype::DXGI_SAMPLE_DESC { Count: desc.sample_count, Quality: 0, }, Layout: d3d12_ty::D3D12_TEXTURE_LAYOUT_UNKNOWN, Flags: conv::map_texture_usage_to_resource_flags(desc.usage), }; let (hr, allocation) = create_texture_resource(self, desc, raw_desc, &mut resource)?; hr.into_device_result("Texture creation")?; if let Some(label) = desc.label { let cwstr = conv::map_label(label); unsafe { resource.SetName(cwstr.as_ptr()) }; } Ok(super::Texture { resource, format: desc.format, dimension: desc.dimension, size: desc.size, mip_level_count: desc.mip_level_count, sample_count: desc.sample_count, allocation, }) } unsafe fn destroy_texture(&self, mut texture: super::Texture) { unsafe { texture.resource.destroy() }; if let Some(alloc) = texture.allocation.take() { super::suballocation::free_texture_allocation( alloc, // SAFETY: for allocations to exist, the allocator must exist unsafe { self.mem_allocator.as_ref().unwrap_unchecked() }, ); } } unsafe fn create_texture_view( &self, texture: &super::Texture, desc: &crate::TextureViewDescriptor, ) -> Result { let view_desc = desc.to_internal(texture); Ok(super::TextureView { raw_format: view_desc.rtv_dsv_format, aspects: view_desc.aspects, target_base: ( texture.resource, texture.calc_subresource(desc.range.base_mip_level, desc.range.base_array_layer, 0), ), handle_srv: if desc.usage.intersects(crate::TextureUses::RESOURCE) { let raw_desc = unsafe { view_desc.to_srv() }; raw_desc.map(|raw_desc| { let handle = self.srv_uav_pool.lock().alloc_handle(); unsafe { self.raw.CreateShaderResourceView( texture.resource.as_mut_ptr(), &raw_desc, handle.raw, ) }; handle }) } else { None }, handle_uav: if desc.usage.intersects( crate::TextureUses::STORAGE_READ | crate::TextureUses::STORAGE_READ_WRITE, ) { let raw_desc = unsafe { view_desc.to_uav() }; raw_desc.map(|raw_desc| { let handle = self.srv_uav_pool.lock().alloc_handle(); unsafe { self.raw.CreateUnorderedAccessView( texture.resource.as_mut_ptr(), ptr::null_mut(), &raw_desc, handle.raw, ) }; handle }) } else { None }, handle_rtv: if desc.usage.intersects(crate::TextureUses::COLOR_TARGET) { let raw_desc = unsafe { view_desc.to_rtv() }; let handle = self.rtv_pool.lock().alloc_handle(); unsafe { self.raw.CreateRenderTargetView( texture.resource.as_mut_ptr(), &raw_desc, handle.raw, ) }; Some(handle) } else { None }, handle_dsv_ro: if desc .usage .intersects(crate::TextureUses::DEPTH_STENCIL_READ) { let raw_desc = unsafe { view_desc.to_dsv(true) }; let handle = self.dsv_pool.lock().alloc_handle(); unsafe { self.raw.CreateDepthStencilView( texture.resource.as_mut_ptr(), &raw_desc, handle.raw, ) }; Some(handle) } else { None }, handle_dsv_rw: if desc .usage .intersects(crate::TextureUses::DEPTH_STENCIL_WRITE) { let raw_desc = unsafe { view_desc.to_dsv(false) }; let handle = self.dsv_pool.lock().alloc_handle(); unsafe { self.raw.CreateDepthStencilView( texture.resource.as_mut_ptr(), &raw_desc, handle.raw, ) }; Some(handle) } else { None }, }) } unsafe fn destroy_texture_view(&self, view: super::TextureView) { if view.handle_srv.is_some() || view.handle_uav.is_some() { let mut pool = self.srv_uav_pool.lock(); if let Some(handle) = view.handle_srv { pool.free_handle(handle); } if let Some(handle) = view.handle_uav { pool.free_handle(handle); } } if let Some(handle) = view.handle_rtv { self.rtv_pool.lock().free_handle(handle); } if view.handle_dsv_ro.is_some() || view.handle_dsv_rw.is_some() { let mut pool = self.dsv_pool.lock(); if let Some(handle) = view.handle_dsv_ro { pool.free_handle(handle); } if let Some(handle) = view.handle_dsv_rw { pool.free_handle(handle); } } } unsafe fn create_sampler( &self, desc: &crate::SamplerDescriptor, ) -> Result { let handle = self.sampler_pool.lock().alloc_handle(); let reduction = match desc.compare { Some(_) => d3d12_ty::D3D12_FILTER_REDUCTION_TYPE_COMPARISON, None => d3d12_ty::D3D12_FILTER_REDUCTION_TYPE_STANDARD, }; let mut filter = conv::map_filter_mode(desc.min_filter) << d3d12_ty::D3D12_MIN_FILTER_SHIFT | conv::map_filter_mode(desc.mag_filter) << d3d12_ty::D3D12_MAG_FILTER_SHIFT | conv::map_filter_mode(desc.mipmap_filter) << d3d12_ty::D3D12_MIP_FILTER_SHIFT | reduction << d3d12_ty::D3D12_FILTER_REDUCTION_TYPE_SHIFT; if desc.anisotropy_clamp != 1 { filter |= d3d12_ty::D3D12_FILTER_ANISOTROPIC; }; let border_color = conv::map_border_color(desc.border_color); self.raw.create_sampler( handle.raw, filter, [ conv::map_address_mode(desc.address_modes[0]), conv::map_address_mode(desc.address_modes[1]), conv::map_address_mode(desc.address_modes[2]), ], 0.0, desc.anisotropy_clamp as u32, conv::map_comparison(desc.compare.unwrap_or(wgt::CompareFunction::Always)), border_color, desc.lod_clamp.clone(), ); Ok(super::Sampler { handle }) } unsafe fn destroy_sampler(&self, sampler: super::Sampler) { self.sampler_pool.lock().free_handle(sampler.handle); } unsafe fn create_command_encoder( &self, desc: &crate::CommandEncoderDescriptor, ) -> Result { let allocator = self .raw .create_command_allocator(d3d12::CmdListType::Direct) .into_device_result("Command allocator creation")?; if let Some(label) = desc.label { let cwstr = conv::map_label(label); unsafe { allocator.SetName(cwstr.as_ptr()) }; } Ok(super::CommandEncoder { allocator, device: self.raw, shared: Arc::clone(&self.shared), null_rtv_handle: self.null_rtv_handle, list: None, free_lists: Vec::new(), pass: super::PassState::new(), temp: super::Temp::default(), }) } unsafe fn destroy_command_encoder(&self, encoder: super::CommandEncoder) { if let Some(list) = encoder.list { list.close(); unsafe { list.destroy() }; } for list in encoder.free_lists { unsafe { list.destroy() }; } unsafe { encoder.allocator.destroy() }; } unsafe fn create_bind_group_layout( &self, desc: &crate::BindGroupLayoutDescriptor, ) -> Result { let (mut num_buffer_views, mut num_samplers, mut num_texture_views) = (0, 0, 0); for entry in desc.entries.iter() { let count = entry.count.map_or(1, NonZeroU32::get); match entry.ty { wgt::BindingType::Buffer { has_dynamic_offset: true, .. } => {} wgt::BindingType::Buffer { .. } => num_buffer_views += count, wgt::BindingType::Texture { .. } | wgt::BindingType::StorageTexture { .. } => { num_texture_views += count } wgt::BindingType::Sampler { .. } => num_samplers += count, } } let num_views = num_buffer_views + num_texture_views; Ok(super::BindGroupLayout { entries: desc.entries.to_vec(), cpu_heap_views: if num_views != 0 { let heap = descriptor::CpuHeap::new( self.raw, d3d12::DescriptorHeapType::CbvSrvUav, num_views, )?; Some(heap) } else { None }, cpu_heap_samplers: if num_samplers != 0 { let heap = descriptor::CpuHeap::new( self.raw, d3d12::DescriptorHeapType::Sampler, num_samplers, )?; Some(heap) } else { None }, copy_counts: vec![1; num_views.max(num_samplers) as usize], }) } unsafe fn destroy_bind_group_layout(&self, bg_layout: super::BindGroupLayout) { if let Some(cpu_heap) = bg_layout.cpu_heap_views { unsafe { cpu_heap.destroy() }; } if let Some(cpu_heap) = bg_layout.cpu_heap_samplers { unsafe { cpu_heap.destroy() }; } } unsafe fn create_pipeline_layout( &self, desc: &crate::PipelineLayoutDescriptor, ) -> Result { use naga::back::hlsl; // Pipeline layouts are implemented as RootSignature for D3D12. // // Push Constants are implemented as root constants. // // Each descriptor set layout will be one table entry of the root signature. // We have the additional restriction that SRV/CBV/UAV and samplers need to be // separated, so each set layout will actually occupy up to 2 entries! // SRV/CBV/UAV tables are added to the signature first, then Sampler tables, // and finally dynamic uniform descriptors. // // Buffers with dynamic offsets are implemented as root descriptors. // This is easier than trying to patch up the offset on the shader side. // // Root signature layout: // Root Constants: Parameter=0, Space=0 // ... // (bind group [0]) - Space=0 // View descriptor table, if any // Sampler descriptor table, if any // Root descriptors (for dynamic offset buffers) // (bind group [1]) - Space=0 // ... // (bind group [2]) - Space=0 // Special constant buffer: Space=0 //TODO: put lower bind group indices futher down the root signature. See: // https://microsoft.github.io/DirectX-Specs/d3d/ResourceBinding.html#binding-model // Currently impossible because wgpu-core only re-binds the descriptor sets based // on Vulkan-like layout compatibility rules. fn native_binding(bt: &hlsl::BindTarget) -> d3d12::Binding { d3d12::Binding { space: bt.space as u32, register: bt.register, } } log::debug!( "Creating Root Signature '{}'", desc.label.unwrap_or_default() ); let mut binding_map = hlsl::BindingMap::default(); let (mut bind_cbv, mut bind_srv, mut bind_uav, mut bind_sampler) = ( hlsl::BindTarget::default(), hlsl::BindTarget::default(), hlsl::BindTarget::default(), hlsl::BindTarget::default(), ); let mut parameters = Vec::new(); let mut push_constants_target = None; let mut root_constant_info = None; let mut pc_start = u32::MAX; let mut pc_end = u32::MIN; for pc in desc.push_constant_ranges.iter() { pc_start = pc_start.min(pc.range.start); pc_end = pc_end.max(pc.range.end); } if pc_start != u32::MAX && pc_end != u32::MIN { let parameter_index = parameters.len(); let size = (pc_end - pc_start) / 4; log::debug!( "\tParam[{}] = push constant (count = {})", parameter_index, size, ); parameters.push(d3d12::RootParameter::constants( d3d12::ShaderVisibility::All, native_binding(&bind_cbv), size, )); let binding = bind_cbv.clone(); bind_cbv.register += 1; root_constant_info = Some(super::RootConstantInfo { root_index: parameter_index as u32, range: (pc_start / 4)..(pc_end / 4), }); push_constants_target = Some(binding); bind_cbv.space += 1; } // Collect the whole number of bindings we will create upfront. // It allows us to preallocate enough storage to avoid reallocation, // which could cause invalid pointers. let total_non_dynamic_entries = desc .bind_group_layouts .iter() .flat_map(|bgl| { bgl.entries.iter().map(|entry| match entry.ty { wgt::BindingType::Buffer { has_dynamic_offset: true, .. } => 0, _ => 1, }) }) .sum(); let mut ranges = Vec::with_capacity(total_non_dynamic_entries); let mut bind_group_infos = arrayvec::ArrayVec::::default(); for (index, bgl) in desc.bind_group_layouts.iter().enumerate() { let mut info = super::BindGroupInfo { tables: super::TableTypes::empty(), base_root_index: parameters.len() as u32, dynamic_buffers: Vec::new(), }; let mut visibility_view_static = wgt::ShaderStages::empty(); let mut visibility_view_dynamic = wgt::ShaderStages::empty(); let mut visibility_sampler = wgt::ShaderStages::empty(); for entry in bgl.entries.iter() { match entry.ty { wgt::BindingType::Sampler { .. } => visibility_sampler |= entry.visibility, wgt::BindingType::Buffer { has_dynamic_offset: true, .. } => visibility_view_dynamic |= entry.visibility, _ => visibility_view_static |= entry.visibility, } } // SRV/CBV/UAV descriptor tables let mut range_base = ranges.len(); for entry in bgl.entries.iter() { let range_ty = match entry.ty { wgt::BindingType::Buffer { has_dynamic_offset: true, .. } => continue, ref other => conv::map_binding_type(other), }; let bt = match range_ty { d3d12::DescriptorRangeType::CBV => &mut bind_cbv, d3d12::DescriptorRangeType::SRV => &mut bind_srv, d3d12::DescriptorRangeType::UAV => &mut bind_uav, d3d12::DescriptorRangeType::Sampler => continue, }; binding_map.insert( naga::ResourceBinding { group: index as u32, binding: entry.binding, }, hlsl::BindTarget { binding_array_size: entry.count.map(NonZeroU32::get), ..bt.clone() }, ); ranges.push(d3d12::DescriptorRange::new( range_ty, entry.count.map_or(1, |count| count.get()), native_binding(bt), d3d12_ty::D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND, )); bt.register += entry.count.map(NonZeroU32::get).unwrap_or(1); } if ranges.len() > range_base { log::debug!( "\tParam[{}] = views (vis = {:?}, count = {})", parameters.len(), visibility_view_static, ranges.len() - range_base, ); parameters.push(d3d12::RootParameter::descriptor_table( conv::map_visibility(visibility_view_static), &ranges[range_base..], )); info.tables |= super::TableTypes::SRV_CBV_UAV; } // Sampler descriptor tables range_base = ranges.len(); for entry in bgl.entries.iter() { let range_ty = match entry.ty { wgt::BindingType::Sampler { .. } => d3d12::DescriptorRangeType::Sampler, _ => continue, }; binding_map.insert( naga::ResourceBinding { group: index as u32, binding: entry.binding, }, hlsl::BindTarget { binding_array_size: entry.count.map(NonZeroU32::get), ..bind_sampler.clone() }, ); ranges.push(d3d12::DescriptorRange::new( range_ty, entry.count.map_or(1, |count| count.get()), native_binding(&bind_sampler), d3d12_ty::D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND, )); bind_sampler.register += entry.count.map(NonZeroU32::get).unwrap_or(1); } if ranges.len() > range_base { log::debug!( "\tParam[{}] = samplers (vis = {:?}, count = {})", parameters.len(), visibility_sampler, ranges.len() - range_base, ); parameters.push(d3d12::RootParameter::descriptor_table( conv::map_visibility(visibility_sampler), &ranges[range_base..], )); info.tables |= super::TableTypes::SAMPLERS; } // Root (dynamic) descriptor tables let dynamic_buffers_visibility = conv::map_visibility(visibility_view_dynamic); for entry in bgl.entries.iter() { let buffer_ty = match entry.ty { wgt::BindingType::Buffer { has_dynamic_offset: true, ty, .. } => ty, _ => continue, }; let (kind, parameter_ty, bt) = match buffer_ty { wgt::BufferBindingType::Uniform => ( super::BufferViewKind::Constant, d3d12_ty::D3D12_ROOT_PARAMETER_TYPE_CBV, &mut bind_cbv, ), wgt::BufferBindingType::Storage { read_only: true } => ( super::BufferViewKind::ShaderResource, d3d12_ty::D3D12_ROOT_PARAMETER_TYPE_SRV, &mut bind_srv, ), wgt::BufferBindingType::Storage { read_only: false } => ( super::BufferViewKind::UnorderedAccess, d3d12_ty::D3D12_ROOT_PARAMETER_TYPE_UAV, &mut bind_uav, ), }; binding_map.insert( naga::ResourceBinding { group: index as u32, binding: entry.binding, }, hlsl::BindTarget { binding_array_size: entry.count.map(NonZeroU32::get), ..bt.clone() }, ); info.dynamic_buffers.push(kind); log::debug!( "\tParam[{}] = dynamic {:?} (vis = {:?})", parameters.len(), buffer_ty, dynamic_buffers_visibility, ); parameters.push(d3d12::RootParameter::descriptor( parameter_ty, dynamic_buffers_visibility, native_binding(bt), )); bt.register += entry.count.map_or(1, NonZeroU32::get); } bind_group_infos.push(info); } // Ensure that we didn't reallocate! debug_assert_eq!(ranges.len(), total_non_dynamic_entries); let (special_constants_root_index, special_constants_binding) = if desc.flags.intersects( crate::PipelineLayoutFlags::BASE_VERTEX_INSTANCE | crate::PipelineLayoutFlags::NUM_WORK_GROUPS, ) { let parameter_index = parameters.len(); log::debug!("\tParam[{}] = special", parameter_index); parameters.push(d3d12::RootParameter::constants( d3d12::ShaderVisibility::All, // really needed for VS and CS only native_binding(&bind_cbv), 3, // 0 = base vertex, 1 = base instance, 2 = other )); let binding = bind_cbv.clone(); bind_cbv.register += 1; (Some(parameter_index as u32), Some(binding)) } else { (None, None) }; log::trace!("{:#?}", parameters); log::trace!("Bindings {:#?}", binding_map); let (blob, error) = self .library .serialize_root_signature( d3d12::RootSignatureVersion::V1_0, ¶meters, &[], d3d12::RootSignatureFlags::ALLOW_IA_INPUT_LAYOUT, ) .map_err(|e| { log::error!("Unable to find serialization function: {:?}", e); crate::DeviceError::Lost })? .into_device_result("Root signature serialization")?; if !error.is_null() { log::error!( "Root signature serialization error: {:?}", unsafe { error.as_c_str() }.to_str().unwrap() ); unsafe { error.destroy() }; return Err(crate::DeviceError::Lost); } let raw = self .raw .create_root_signature(blob, 0) .into_device_result("Root signature creation")?; unsafe { blob.destroy() }; log::debug!("\traw = {:?}", raw); if let Some(label) = desc.label { let cwstr = conv::map_label(label); unsafe { raw.SetName(cwstr.as_ptr()) }; } Ok(super::PipelineLayout { shared: super::PipelineLayoutShared { signature: raw, total_root_elements: parameters.len() as super::RootIndex, special_constants_root_index, root_constant_info, }, bind_group_infos, naga_options: hlsl::Options { shader_model: match self.dxc_container { // DXC Some(_) => hlsl::ShaderModel::V6_0, // FXC doesn't support SM 6.0 None => hlsl::ShaderModel::V5_1, }, binding_map, fake_missing_bindings: false, special_constants_binding, push_constants_target, zero_initialize_workgroup_memory: true, }, }) } unsafe fn destroy_pipeline_layout(&self, pipeline_layout: super::PipelineLayout) { unsafe { pipeline_layout.shared.signature.destroy() }; } unsafe fn create_bind_group( &self, desc: &crate::BindGroupDescriptor, ) -> Result { let mut cpu_views = desc .layout .cpu_heap_views .as_ref() .map(|cpu_heap| cpu_heap.inner.lock()); if let Some(ref mut inner) = cpu_views { inner.stage.clear(); } let mut cpu_samplers = desc .layout .cpu_heap_samplers .as_ref() .map(|cpu_heap| cpu_heap.inner.lock()); if let Some(ref mut inner) = cpu_samplers { inner.stage.clear(); } let mut dynamic_buffers = Vec::new(); for (layout, entry) in desc.layout.entries.iter().zip(desc.entries.iter()) { match layout.ty { wgt::BindingType::Buffer { has_dynamic_offset: true, .. } => { let start = entry.resource_index as usize; let end = start + entry.count as usize; for data in &desc.buffers[start..end] { dynamic_buffers.push(data.resolve_address()); } } wgt::BindingType::Buffer { ty, .. } => { let start = entry.resource_index as usize; let end = start + entry.count as usize; for data in &desc.buffers[start..end] { let gpu_address = data.resolve_address(); let size = data.resolve_size() as u32; let inner = cpu_views.as_mut().unwrap(); let cpu_index = inner.stage.len() as u32; let handle = desc.layout.cpu_heap_views.as_ref().unwrap().at(cpu_index); match ty { wgt::BufferBindingType::Uniform => { let size_mask = d3d12_ty::D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT - 1; let raw_desc = d3d12_ty::D3D12_CONSTANT_BUFFER_VIEW_DESC { BufferLocation: gpu_address, SizeInBytes: ((size - 1) | size_mask) + 1, }; unsafe { self.raw.CreateConstantBufferView(&raw_desc, handle) }; } wgt::BufferBindingType::Storage { read_only: true } => { let mut raw_desc = d3d12_ty::D3D12_SHADER_RESOURCE_VIEW_DESC { Format: dxgiformat::DXGI_FORMAT_R32_TYPELESS, Shader4ComponentMapping: view::D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING, ViewDimension: d3d12_ty::D3D12_SRV_DIMENSION_BUFFER, u: unsafe { mem::zeroed() }, }; unsafe { *raw_desc.u.Buffer_mut() = d3d12_ty::D3D12_BUFFER_SRV { FirstElement: data.offset / 4, NumElements: size / 4, StructureByteStride: 0, Flags: d3d12_ty::D3D12_BUFFER_SRV_FLAG_RAW, } }; unsafe { self.raw.CreateShaderResourceView( data.buffer.resource.as_mut_ptr(), &raw_desc, handle, ) }; } wgt::BufferBindingType::Storage { read_only: false } => { let mut raw_desc = d3d12_ty::D3D12_UNORDERED_ACCESS_VIEW_DESC { Format: dxgiformat::DXGI_FORMAT_R32_TYPELESS, ViewDimension: d3d12_ty::D3D12_UAV_DIMENSION_BUFFER, u: unsafe { mem::zeroed() }, }; unsafe { *raw_desc.u.Buffer_mut() = d3d12_ty::D3D12_BUFFER_UAV { FirstElement: data.offset / 4, NumElements: size / 4, StructureByteStride: 0, CounterOffsetInBytes: 0, Flags: d3d12_ty::D3D12_BUFFER_UAV_FLAG_RAW, } }; unsafe { self.raw.CreateUnorderedAccessView( data.buffer.resource.as_mut_ptr(), ptr::null_mut(), &raw_desc, handle, ) }; } } inner.stage.push(handle); } } wgt::BindingType::Texture { .. } => { let start = entry.resource_index as usize; let end = start + entry.count as usize; for data in &desc.textures[start..end] { let handle = data.view.handle_srv.unwrap(); cpu_views.as_mut().unwrap().stage.push(handle.raw); } } wgt::BindingType::StorageTexture { .. } => { let start = entry.resource_index as usize; let end = start + entry.count as usize; for data in &desc.textures[start..end] { let handle = data.view.handle_uav.unwrap(); cpu_views.as_mut().unwrap().stage.push(handle.raw); } } wgt::BindingType::Sampler { .. } => { let start = entry.resource_index as usize; let end = start + entry.count as usize; for data in &desc.samplers[start..end] { cpu_samplers.as_mut().unwrap().stage.push(data.handle.raw); } } } } let handle_views = match cpu_views { Some(inner) => { let dual = unsafe { descriptor::upload( self.raw, &inner, &self.shared.heap_views, &desc.layout.copy_counts, ) }?; Some(dual) } None => None, }; let handle_samplers = match cpu_samplers { Some(inner) => { let dual = unsafe { descriptor::upload( self.raw, &inner, &self.shared.heap_samplers, &desc.layout.copy_counts, ) }?; Some(dual) } None => None, }; Ok(super::BindGroup { handle_views, handle_samplers, dynamic_buffers, }) } unsafe fn destroy_bind_group(&self, group: super::BindGroup) { if let Some(dual) = group.handle_views { self.shared.heap_views.free_slice(dual); } if let Some(dual) = group.handle_samplers { self.shared.heap_samplers.free_slice(dual); } } unsafe fn create_shader_module( &self, desc: &crate::ShaderModuleDescriptor, shader: crate::ShaderInput, ) -> Result { let raw_name = desc.label.and_then(|label| ffi::CString::new(label).ok()); match shader { crate::ShaderInput::Naga(naga) => Ok(super::ShaderModule { naga, raw_name }), crate::ShaderInput::SpirV(_) => { panic!("SPIRV_SHADER_PASSTHROUGH is not enabled for this backend") } } } unsafe fn destroy_shader_module(&self, _module: super::ShaderModule) { // just drop } unsafe fn create_render_pipeline( &self, desc: &crate::RenderPipelineDescriptor, ) -> Result { let (topology_class, topology) = conv::map_topology(desc.primitive.topology); let mut shader_stages = wgt::ShaderStages::VERTEX; let blob_vs = self.load_shader(&desc.vertex_stage, desc.layout, naga::ShaderStage::Vertex)?; let blob_fs = match desc.fragment_stage { Some(ref stage) => { shader_stages |= wgt::ShaderStages::FRAGMENT; Some(self.load_shader(stage, desc.layout, naga::ShaderStage::Fragment)?) } None => None, }; let mut vertex_strides = [None; crate::MAX_VERTEX_BUFFERS]; let mut input_element_descs = Vec::new(); for (i, (stride, vbuf)) in vertex_strides .iter_mut() .zip(desc.vertex_buffers) .enumerate() { *stride = NonZeroU32::new(vbuf.array_stride as u32); let (slot_class, step_rate) = match vbuf.step_mode { wgt::VertexStepMode::Vertex => { (d3d12_ty::D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0) } wgt::VertexStepMode::Instance => { (d3d12_ty::D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1) } }; for attribute in vbuf.attributes { input_element_descs.push(d3d12_ty::D3D12_INPUT_ELEMENT_DESC { SemanticName: NAGA_LOCATION_SEMANTIC.as_ptr() as *const _, SemanticIndex: attribute.shader_location, Format: auxil::dxgi::conv::map_vertex_format(attribute.format), InputSlot: i as u32, AlignedByteOffset: attribute.offset as u32, InputSlotClass: slot_class, InstanceDataStepRate: step_rate, }); } } let mut rtv_formats = [dxgiformat::DXGI_FORMAT_UNKNOWN; d3d12_ty::D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT as usize]; for (rtv_format, ct) in rtv_formats.iter_mut().zip(desc.color_targets) { if let Some(ct) = ct.as_ref() { *rtv_format = auxil::dxgi::conv::map_texture_format(ct.format); } } let bias = desc .depth_stencil .as_ref() .map(|ds| ds.bias) .unwrap_or_default(); let raw_rasterizer = d3d12_ty::D3D12_RASTERIZER_DESC { FillMode: conv::map_polygon_mode(desc.primitive.polygon_mode), CullMode: match desc.primitive.cull_mode { None => d3d12_ty::D3D12_CULL_MODE_NONE, Some(wgt::Face::Front) => d3d12_ty::D3D12_CULL_MODE_FRONT, Some(wgt::Face::Back) => d3d12_ty::D3D12_CULL_MODE_BACK, }, FrontCounterClockwise: match desc.primitive.front_face { wgt::FrontFace::Cw => 0, wgt::FrontFace::Ccw => 1, }, DepthBias: bias.constant, DepthBiasClamp: bias.clamp, SlopeScaledDepthBias: bias.slope_scale, DepthClipEnable: BOOL::from(!desc.primitive.unclipped_depth), MultisampleEnable: BOOL::from(desc.multisample.count > 1), ForcedSampleCount: 0, AntialiasedLineEnable: 0, ConservativeRaster: if desc.primitive.conservative { d3d12_ty::D3D12_CONSERVATIVE_RASTERIZATION_MODE_ON } else { d3d12_ty::D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF }, }; let raw_desc = d3d12_ty::D3D12_GRAPHICS_PIPELINE_STATE_DESC { pRootSignature: desc.layout.shared.signature.as_mut_ptr(), VS: *blob_vs.create_native_shader(), PS: match blob_fs { Some(ref shader) => *shader.create_native_shader(), None => *d3d12::Shader::null(), }, GS: *d3d12::Shader::null(), DS: *d3d12::Shader::null(), HS: *d3d12::Shader::null(), StreamOutput: d3d12_ty::D3D12_STREAM_OUTPUT_DESC { pSODeclaration: ptr::null(), NumEntries: 0, pBufferStrides: ptr::null(), NumStrides: 0, RasterizedStream: 0, }, BlendState: d3d12_ty::D3D12_BLEND_DESC { AlphaToCoverageEnable: BOOL::from(desc.multisample.alpha_to_coverage_enabled), IndependentBlendEnable: 1, RenderTarget: conv::map_render_targets(desc.color_targets), }, SampleMask: desc.multisample.mask as u32, RasterizerState: raw_rasterizer, DepthStencilState: match desc.depth_stencil { Some(ref ds) => conv::map_depth_stencil(ds), None => unsafe { mem::zeroed() }, }, InputLayout: d3d12_ty::D3D12_INPUT_LAYOUT_DESC { pInputElementDescs: if input_element_descs.is_empty() { ptr::null() } else { input_element_descs.as_ptr() }, NumElements: input_element_descs.len() as u32, }, IBStripCutValue: match desc.primitive.strip_index_format { Some(wgt::IndexFormat::Uint16) => { d3d12_ty::D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_0xFFFF } Some(wgt::IndexFormat::Uint32) => { d3d12_ty::D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_0xFFFFFFFF } None => d3d12_ty::D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_DISABLED, }, PrimitiveTopologyType: topology_class, NumRenderTargets: desc.color_targets.len() as u32, RTVFormats: rtv_formats, DSVFormat: desc .depth_stencil .as_ref() .map_or(dxgiformat::DXGI_FORMAT_UNKNOWN, |ds| { auxil::dxgi::conv::map_texture_format(ds.format) }), SampleDesc: dxgitype::DXGI_SAMPLE_DESC { Count: desc.multisample.count, Quality: 0, }, NodeMask: 0, CachedPSO: d3d12_ty::D3D12_CACHED_PIPELINE_STATE { pCachedBlob: ptr::null(), CachedBlobSizeInBytes: 0, }, Flags: d3d12_ty::D3D12_PIPELINE_STATE_FLAG_NONE, }; let mut raw = d3d12::PipelineState::null(); let hr = { profiling::scope!("ID3D12Device::CreateGraphicsPipelineState"); unsafe { self.raw.CreateGraphicsPipelineState( &raw_desc, &d3d12_ty::ID3D12PipelineState::uuidof(), raw.mut_void(), ) } }; unsafe { blob_vs.destroy() }; if let Some(blob_fs) = blob_fs { unsafe { blob_fs.destroy() }; }; hr.into_result() .map_err(|err| crate::PipelineError::Linkage(shader_stages, err.into_owned()))?; if let Some(name) = desc.label { let cwstr = conv::map_label(name); unsafe { raw.SetName(cwstr.as_ptr()) }; } Ok(super::RenderPipeline { raw, layout: desc.layout.shared.clone(), topology, vertex_strides, }) } unsafe fn destroy_render_pipeline(&self, pipeline: super::RenderPipeline) { unsafe { pipeline.raw.destroy() }; } unsafe fn create_compute_pipeline( &self, desc: &crate::ComputePipelineDescriptor, ) -> Result { let blob_cs = self.load_shader(&desc.stage, desc.layout, naga::ShaderStage::Compute)?; let pair = { profiling::scope!("ID3D12Device::CreateComputePipelineState"); self.raw.create_compute_pipeline_state( desc.layout.shared.signature, blob_cs.create_native_shader(), 0, d3d12::CachedPSO::null(), d3d12::PipelineStateFlags::empty(), ) }; unsafe { blob_cs.destroy() }; let raw = pair.into_result().map_err(|err| { crate::PipelineError::Linkage(wgt::ShaderStages::COMPUTE, err.into_owned()) })?; if let Some(name) = desc.label { let cwstr = conv::map_label(name); unsafe { raw.SetName(cwstr.as_ptr()) }; } Ok(super::ComputePipeline { raw, layout: desc.layout.shared.clone(), }) } unsafe fn destroy_compute_pipeline(&self, pipeline: super::ComputePipeline) { unsafe { pipeline.raw.destroy() }; } unsafe fn create_query_set( &self, desc: &wgt::QuerySetDescriptor, ) -> Result { let (heap_ty, raw_ty) = match desc.ty { wgt::QueryType::Occlusion => ( d3d12::QueryHeapType::Occlusion, d3d12_ty::D3D12_QUERY_TYPE_BINARY_OCCLUSION, ), wgt::QueryType::PipelineStatistics(_) => ( d3d12::QueryHeapType::PipelineStatistics, d3d12_ty::D3D12_QUERY_TYPE_PIPELINE_STATISTICS, ), wgt::QueryType::Timestamp => ( d3d12::QueryHeapType::Timestamp, d3d12_ty::D3D12_QUERY_TYPE_TIMESTAMP, ), }; let raw = self .raw .create_query_heap(heap_ty, desc.count, 0) .into_device_result("Query heap creation")?; if let Some(label) = desc.label { let cwstr = conv::map_label(label); unsafe { raw.SetName(cwstr.as_ptr()) }; } Ok(super::QuerySet { raw, raw_ty }) } unsafe fn destroy_query_set(&self, set: super::QuerySet) { unsafe { set.raw.destroy() }; } unsafe fn create_fence(&self) -> Result { let mut raw = d3d12::Fence::null(); let hr = unsafe { self.raw.CreateFence( 0, d3d12_ty::D3D12_FENCE_FLAG_NONE, &d3d12_ty::ID3D12Fence::uuidof(), raw.mut_void(), ) }; hr.into_device_result("Fence creation")?; Ok(super::Fence { raw }) } unsafe fn destroy_fence(&self, fence: super::Fence) { unsafe { fence.raw.destroy() }; } unsafe fn get_fence_value( &self, fence: &super::Fence, ) -> Result { Ok(unsafe { fence.raw.GetCompletedValue() }) } unsafe fn wait( &self, fence: &super::Fence, value: crate::FenceValue, timeout_ms: u32, ) -> Result { if unsafe { fence.raw.GetCompletedValue() } >= value { return Ok(true); } let hr = fence.raw.set_event_on_completion(self.idler.event, value); hr.into_device_result("Set event")?; match unsafe { synchapi::WaitForSingleObject(self.idler.event.0, timeout_ms) } { winbase::WAIT_ABANDONED | winbase::WAIT_FAILED => Err(crate::DeviceError::Lost), winbase::WAIT_OBJECT_0 => Ok(true), winerror::WAIT_TIMEOUT => Ok(false), other => { log::error!("Unexpected wait status: 0x{:x}", other); Err(crate::DeviceError::Lost) } } } unsafe fn start_capture(&self) -> bool { #[cfg(feature = "renderdoc")] { unsafe { self.render_doc .start_frame_capture(self.raw.as_mut_ptr() as *mut _, ptr::null_mut()) } } #[cfg(not(feature = "renderdoc"))] false } unsafe fn stop_capture(&self) { #[cfg(feature = "renderdoc")] unsafe { self.render_doc .end_frame_capture(self.raw.as_mut_ptr() as *mut _, ptr::null_mut()) } } }