diff options
Diffstat (limited to 'third_party/rust/gfx-backend-dx12/src/device.rs')
-rw-r--r-- | third_party/rust/gfx-backend-dx12/src/device.rs | 3690 |
1 files changed, 3690 insertions, 0 deletions
diff --git a/third_party/rust/gfx-backend-dx12/src/device.rs b/third_party/rust/gfx-backend-dx12/src/device.rs new file mode 100644 index 0000000000..61f48aea9b --- /dev/null +++ b/third_party/rust/gfx-backend-dx12/src/device.rs @@ -0,0 +1,3690 @@ +use std::{ + borrow::Borrow, + collections::{hash_map::Entry, BTreeMap}, + ffi, iter, mem, + ops::Range, + ptr, slice, + sync::Arc, +}; + +use range_alloc::RangeAllocator; +use smallvec::SmallVec; +use spirv_cross::{hlsl, spirv, ErrorCode as SpirvErrorCode}; + +use winapi::{ + shared::{ + dxgi, dxgi1_2, dxgi1_4, dxgiformat, dxgitype, + minwindef::{FALSE, TRUE, UINT}, + windef, winerror, + }, + um::{d3d12, d3dcompiler, synchapi, winbase, winnt}, + Interface, +}; + +use auxil::{spirv_cross_specialize_ast, ShaderStage}; +use hal::{ + buffer, device as d, format, + format::Aspects, + image, memory, + memory::Requirements, + pass, + pool::CommandPoolCreateFlags, + pso, + pso::VertexInputRate, + query, + queue::{CommandQueue as _, QueueFamilyId}, + window as w, +}; + +use crate::{ + command as cmd, conv, descriptors_cpu, pool::CommandPool, resource as r, root_constants, + root_constants::RootConstant, window::Swapchain, Backend as B, Device, MemoryGroup, + MAX_VERTEX_BUFFERS, NUM_HEAP_PROPERTIES, QUEUE_FAMILIES, +}; +use native::{PipelineStateSubobject, Subobject}; + +// Register space used for root constants. +const ROOT_CONSTANT_SPACE: u32 = 0; + +const MEM_TYPE_MASK: u32 = 0x7; +const MEM_TYPE_SHIFT: u32 = 3; + +const MEM_TYPE_UNIVERSAL_SHIFT: u32 = MEM_TYPE_SHIFT * MemoryGroup::Universal as u32; +const MEM_TYPE_BUFFER_SHIFT: u32 = MEM_TYPE_SHIFT * MemoryGroup::BufferOnly as u32; +const MEM_TYPE_IMAGE_SHIFT: u32 = MEM_TYPE_SHIFT * MemoryGroup::ImageOnly as u32; +const MEM_TYPE_TARGET_SHIFT: u32 = MEM_TYPE_SHIFT * MemoryGroup::TargetOnly as u32; + +pub const IDENTITY_MAPPING: UINT = 0x1688; // D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING + +fn wide_cstr(name: &str) -> Vec<u16> { + name.encode_utf16().chain(iter::once(0)).collect() +} + +/// Emit error during shader module creation. Used if we don't expect an error +/// but might panic due to an exception in SPIRV-Cross. +fn gen_unexpected_error(err: SpirvErrorCode) -> d::ShaderError { + let msg = match err { + SpirvErrorCode::CompilationError(msg) => msg, + SpirvErrorCode::Unhandled => "Unexpected error".into(), + }; + d::ShaderError::CompilationFailed(msg) +} + +/// Emit error during shader module creation. Used if we execute an query command. +fn gen_query_error(err: SpirvErrorCode) -> d::ShaderError { + let msg = match err { + SpirvErrorCode::CompilationError(msg) => msg, + SpirvErrorCode::Unhandled => "Unknown query error".into(), + }; + d::ShaderError::CompilationFailed(msg) +} + +#[derive(Clone, Debug)] +pub(crate) struct ViewInfo { + pub(crate) resource: native::Resource, + pub(crate) kind: image::Kind, + pub(crate) caps: image::ViewCapabilities, + pub(crate) view_kind: image::ViewKind, + pub(crate) format: dxgiformat::DXGI_FORMAT, + pub(crate) component_mapping: UINT, + pub(crate) levels: Range<image::Level>, + pub(crate) layers: Range<image::Layer>, +} + +pub(crate) enum CommandSignature { + Draw, + DrawIndexed, + Dispatch, +} + +/// Compile a single shader entry point from a HLSL text shader +pub(crate) fn compile_shader( + stage: ShaderStage, + shader_model: hlsl::ShaderModel, + features: &hal::Features, + entry: &str, + code: &[u8], +) -> Result<native::Blob, d::ShaderError> { + let stage_str = match stage { + ShaderStage::Vertex => "vs", + ShaderStage::Fragment => "ps", + ShaderStage::Compute => "cs", + _ => unimplemented!(), + }; + let model_str = match shader_model { + hlsl::ShaderModel::V5_0 => "5_0", + hlsl::ShaderModel::V5_1 => "5_1", + hlsl::ShaderModel::V6_0 => "6_0", + _ => unimplemented!(), + }; + let full_stage = format!("{}_{}\0", stage_str, model_str); + + let mut shader_data = native::Blob::null(); + let mut error = native::Blob::null(); + let entry = ffi::CString::new(entry).unwrap(); + let mut compile_flags = d3dcompiler::D3DCOMPILE_ENABLE_STRICTNESS; + if cfg!(debug_assertions) { + compile_flags |= d3dcompiler::D3DCOMPILE_DEBUG; + } + if features.contains(hal::Features::UNSIZED_DESCRIPTOR_ARRAY) { + compile_flags |= d3dcompiler::D3DCOMPILE_ENABLE_UNBOUNDED_DESCRIPTOR_TABLES; + } + let hr = unsafe { + d3dcompiler::D3DCompile( + code.as_ptr() as *const _, + code.len(), + ptr::null(), + ptr::null(), + ptr::null_mut(), + entry.as_ptr() as *const _, + full_stage.as_ptr() as *const i8, + compile_flags, + 0, + shader_data.mut_void() as *mut *mut _, + error.mut_void() as *mut *mut _, + ) + }; + if !winerror::SUCCEEDED(hr) { + error!("D3DCompile error {:x}", hr); + let message = unsafe { + let pointer = error.GetBufferPointer(); + let size = error.GetBufferSize(); + let slice = slice::from_raw_parts(pointer as *const u8, size as usize); + String::from_utf8_lossy(slice).into_owned() + }; + unsafe { + error.destroy(); + } + Err(d::ShaderError::CompilationFailed(message)) + } else { + Ok(shader_data) + } +} + +#[repr(C)] +struct GraphicsPipelineStateSubobjectStream { + root_signature: PipelineStateSubobject<*mut d3d12::ID3D12RootSignature>, + vs: PipelineStateSubobject<d3d12::D3D12_SHADER_BYTECODE>, + ps: PipelineStateSubobject<d3d12::D3D12_SHADER_BYTECODE>, + ds: PipelineStateSubobject<d3d12::D3D12_SHADER_BYTECODE>, + hs: PipelineStateSubobject<d3d12::D3D12_SHADER_BYTECODE>, + gs: PipelineStateSubobject<d3d12::D3D12_SHADER_BYTECODE>, + stream_output: PipelineStateSubobject<d3d12::D3D12_STREAM_OUTPUT_DESC>, + blend: PipelineStateSubobject<d3d12::D3D12_BLEND_DESC>, + sample_mask: PipelineStateSubobject<UINT>, + rasterizer: PipelineStateSubobject<d3d12::D3D12_RASTERIZER_DESC>, + depth_stencil: PipelineStateSubobject<d3d12::D3D12_DEPTH_STENCIL_DESC1>, + input_layout: PipelineStateSubobject<d3d12::D3D12_INPUT_LAYOUT_DESC>, + ib_strip_cut_value: PipelineStateSubobject<d3d12::D3D12_INDEX_BUFFER_STRIP_CUT_VALUE>, + primitive_topology: PipelineStateSubobject<d3d12::D3D12_PRIMITIVE_TOPOLOGY_TYPE>, + render_target_formats: PipelineStateSubobject<d3d12::D3D12_RT_FORMAT_ARRAY>, + depth_stencil_format: PipelineStateSubobject<dxgiformat::DXGI_FORMAT>, + sample_desc: PipelineStateSubobject<dxgitype::DXGI_SAMPLE_DESC>, + node_mask: PipelineStateSubobject<UINT>, + cached_pso: PipelineStateSubobject<d3d12::D3D12_CACHED_PIPELINE_STATE>, + flags: PipelineStateSubobject<d3d12::D3D12_PIPELINE_STATE_FLAGS>, +} + +impl GraphicsPipelineStateSubobjectStream { + fn new( + pso_desc: &d3d12::D3D12_GRAPHICS_PIPELINE_STATE_DESC, + depth_bounds_test_enable: bool, + ) -> Self { + GraphicsPipelineStateSubobjectStream { + root_signature: PipelineStateSubobject::new( + Subobject::RootSignature, + pso_desc.pRootSignature, + ), + vs: PipelineStateSubobject::new(Subobject::VS, pso_desc.VS), + ps: PipelineStateSubobject::new(Subobject::PS, pso_desc.PS), + ds: PipelineStateSubobject::new(Subobject::DS, pso_desc.DS), + hs: PipelineStateSubobject::new(Subobject::HS, pso_desc.HS), + gs: PipelineStateSubobject::new(Subobject::GS, pso_desc.GS), + stream_output: PipelineStateSubobject::new( + Subobject::StreamOutput, + pso_desc.StreamOutput, + ), + blend: PipelineStateSubobject::new(Subobject::Blend, pso_desc.BlendState), + sample_mask: PipelineStateSubobject::new(Subobject::SampleMask, pso_desc.SampleMask), + rasterizer: PipelineStateSubobject::new( + Subobject::Rasterizer, + pso_desc.RasterizerState, + ), + depth_stencil: PipelineStateSubobject::new( + Subobject::DepthStencil1, + d3d12::D3D12_DEPTH_STENCIL_DESC1 { + DepthEnable: pso_desc.DepthStencilState.DepthEnable, + DepthWriteMask: pso_desc.DepthStencilState.DepthWriteMask, + DepthFunc: pso_desc.DepthStencilState.DepthFunc, + StencilEnable: pso_desc.DepthStencilState.StencilEnable, + StencilReadMask: pso_desc.DepthStencilState.StencilReadMask, + StencilWriteMask: pso_desc.DepthStencilState.StencilWriteMask, + FrontFace: pso_desc.DepthStencilState.FrontFace, + BackFace: pso_desc.DepthStencilState.BackFace, + DepthBoundsTestEnable: depth_bounds_test_enable as _, + }, + ), + input_layout: PipelineStateSubobject::new(Subobject::InputLayout, pso_desc.InputLayout), + ib_strip_cut_value: PipelineStateSubobject::new( + Subobject::IBStripCut, + pso_desc.IBStripCutValue, + ), + primitive_topology: PipelineStateSubobject::new( + Subobject::PrimitiveTopology, + pso_desc.PrimitiveTopologyType, + ), + render_target_formats: PipelineStateSubobject::new( + Subobject::RTFormats, + d3d12::D3D12_RT_FORMAT_ARRAY { + RTFormats: pso_desc.RTVFormats, + NumRenderTargets: pso_desc.NumRenderTargets, + }, + ), + depth_stencil_format: PipelineStateSubobject::new( + Subobject::DSFormat, + pso_desc.DSVFormat, + ), + sample_desc: PipelineStateSubobject::new(Subobject::SampleDesc, pso_desc.SampleDesc), + node_mask: PipelineStateSubobject::new(Subobject::NodeMask, pso_desc.NodeMask), + cached_pso: PipelineStateSubobject::new(Subobject::CachedPSO, pso_desc.CachedPSO), + flags: PipelineStateSubobject::new(Subobject::Flags, pso_desc.Flags), + } + } +} + +impl Device { + fn parse_spirv(raw_data: &[u32]) -> Result<spirv::Ast<hlsl::Target>, d::ShaderError> { + let module = spirv::Module::from_words(raw_data); + + spirv::Ast::parse(&module).map_err(|err| { + let msg = match err { + SpirvErrorCode::CompilationError(msg) => msg, + SpirvErrorCode::Unhandled => "Unknown parsing error".into(), + }; + d::ShaderError::CompilationFailed(msg) + }) + } + + /// Introspects the input attributes of given SPIR-V shader and returns an optional vertex semantic remapping. + /// + /// The returned hashmap has attribute location as a key and an Optional remapping to a two part semantic. + /// + /// eg. + /// `2 -> None` means use default semantic `TEXCOORD2` + /// `2 -> Some((0, 2))` means use two part semantic `TEXCOORD0_2`. This is how matrices are represented by spirv-cross. + /// + /// This is a temporary workaround for https://github.com/KhronosGroup/SPIRV-Cross/issues/1512. + /// + /// This workaround also exists under the same name in the DX11 backend. + pub(crate) fn introspect_spirv_vertex_semantic_remapping( + raw_data: &[u32], + ) -> Result<auxil::FastHashMap<u32, Option<(u32, u32)>>, hal::device::ShaderError> { + // This is inefficient as we already parse it once before. This is a temporary workaround only called + // on vertex shaders. If this becomes permanent or shows up in profiles, deduplicate these as first course of action. + let ast = Self::parse_spirv(raw_data)?; + + let mut map = auxil::FastHashMap::default(); + + let inputs = ast + .get_shader_resources() + .map_err(gen_query_error)? + .stage_inputs; + for input in inputs { + let idx = ast + .get_decoration(input.id, spirv::Decoration::Location) + .map_err(gen_query_error)?; + + let ty = ast.get_type(input.type_id).map_err(gen_query_error)?; + + match ty { + spirv::Type::Boolean { columns, .. } + | spirv::Type::Int { columns, .. } + | spirv::Type::UInt { columns, .. } + | spirv::Type::Half { columns, .. } + | spirv::Type::Float { columns, .. } + | spirv::Type::Double { columns, .. } + if columns > 1 => + { + for col in 0..columns { + if let Some(_) = map.insert(idx + col, Some((idx, col))) { + return Err(hal::device::ShaderError::CompilationFailed(format!( + "Shader has overlapping input attachments at location {}", + idx + ))); + } + } + } + _ => { + if let Some(_) = map.insert(idx, None) { + return Err(hal::device::ShaderError::CompilationFailed(format!( + "Shader has overlapping input attachments at location {}", + idx + ))); + } + } + } + } + + Ok(map) + } + + fn patch_spirv_resources( + ast: &mut spirv::Ast<hlsl::Target>, + layout: &r::PipelineLayout, + ) -> Result<(), d::ShaderError> { + // Move the descriptor sets away to yield for the root constants at "space0". + let space_offset = if layout.shared.constants.is_empty() { + 0 + } else { + 1 + }; + let shader_resources = ast.get_shader_resources().map_err(gen_query_error)?; + + if space_offset != 0 { + for image in &shader_resources.separate_images { + let set = ast + .get_decoration(image.id, spirv::Decoration::DescriptorSet) + .map_err(gen_query_error)?; + ast.set_decoration( + image.id, + spirv::Decoration::DescriptorSet, + space_offset + set, + ) + .map_err(gen_unexpected_error)?; + } + } + + if space_offset != 0 { + for uniform_buffer in &shader_resources.uniform_buffers { + let set = ast + .get_decoration(uniform_buffer.id, spirv::Decoration::DescriptorSet) + .map_err(gen_query_error)?; + ast.set_decoration( + uniform_buffer.id, + spirv::Decoration::DescriptorSet, + space_offset + set, + ) + .map_err(gen_unexpected_error)?; + } + } + + for storage_buffer in &shader_resources.storage_buffers { + let set = ast + .get_decoration(storage_buffer.id, spirv::Decoration::DescriptorSet) + .map_err(gen_query_error)?; + let binding = ast + .get_decoration(storage_buffer.id, spirv::Decoration::Binding) + .map_err(gen_query_error)?; + if space_offset != 0 { + ast.set_decoration( + storage_buffer.id, + spirv::Decoration::DescriptorSet, + space_offset + set, + ) + .map_err(gen_unexpected_error)?; + } + if !layout.elements[set as usize] + .mutable_bindings + .contains(&binding) + { + ast.set_decoration(storage_buffer.id, spirv::Decoration::NonWritable, 1) + .map_err(gen_unexpected_error)? + } + } + + for image in &shader_resources.storage_images { + let set = ast + .get_decoration(image.id, spirv::Decoration::DescriptorSet) + .map_err(gen_query_error)?; + let binding = ast + .get_decoration(image.id, spirv::Decoration::Binding) + .map_err(gen_query_error)?; + if space_offset != 0 { + ast.set_decoration( + image.id, + spirv::Decoration::DescriptorSet, + space_offset + set, + ) + .map_err(gen_unexpected_error)?; + } + if !layout.elements[set as usize] + .mutable_bindings + .contains(&binding) + { + ast.set_decoration(image.id, spirv::Decoration::NonWritable, 1) + .map_err(gen_unexpected_error)? + } + } + + if space_offset != 0 { + for sampler in &shader_resources.separate_samplers { + let set = ast + .get_decoration(sampler.id, spirv::Decoration::DescriptorSet) + .map_err(gen_query_error)?; + ast.set_decoration( + sampler.id, + spirv::Decoration::DescriptorSet, + space_offset + set, + ) + .map_err(gen_unexpected_error)?; + } + } + + if space_offset != 0 { + for image in &shader_resources.sampled_images { + let set = ast + .get_decoration(image.id, spirv::Decoration::DescriptorSet) + .map_err(gen_query_error)?; + ast.set_decoration( + image.id, + spirv::Decoration::DescriptorSet, + space_offset + set, + ) + .map_err(gen_unexpected_error)?; + } + } + + if space_offset != 0 { + for input in &shader_resources.subpass_inputs { + let set = ast + .get_decoration(input.id, spirv::Decoration::DescriptorSet) + .map_err(gen_query_error)?; + ast.set_decoration( + input.id, + spirv::Decoration::DescriptorSet, + space_offset + set, + ) + .map_err(gen_unexpected_error)?; + } + } + + Ok(()) + } + + fn translate_spirv( + ast: &mut spirv::Ast<hlsl::Target>, + shader_model: hlsl::ShaderModel, + layout: &r::PipelineLayout, + stage: ShaderStage, + features: &hal::Features, + entry_point: &str, + ) -> Result<String, d::ShaderError> { + let mut compile_options = hlsl::CompilerOptions::default(); + compile_options.shader_model = shader_model; + compile_options.vertex.invert_y = !features.contains(hal::Features::NDC_Y_UP); + compile_options.force_zero_initialized_variables = true; + compile_options.nonwritable_uav_texture_as_srv = true; + // these are technically incorrect, but better than panicking + compile_options.point_size_compat = true; + compile_options.point_coord_compat = true; + compile_options.entry_point = Some((entry_point.to_string(), conv::map_stage(stage))); + + let stage_flag = stage.to_flag(); + let root_constant_layout = layout + .shared + .constants + .iter() + .filter_map(|constant| { + if constant.stages.contains(stage_flag) { + Some(hlsl::RootConstant { + start: constant.range.start * 4, + end: constant.range.end * 4, + binding: constant.range.start, + space: 0, + }) + } else { + None + } + }) + .collect(); + ast.set_compiler_options(&compile_options) + .map_err(gen_unexpected_error)?; + ast.set_root_constant_layout(root_constant_layout) + .map_err(gen_unexpected_error)?; + ast.compile().map_err(|err| { + let msg = match err { + SpirvErrorCode::CompilationError(msg) => msg, + SpirvErrorCode::Unhandled => "Unknown compile error".into(), + }; + d::ShaderError::CompilationFailed(msg) + }) + } + + // Extract entry point from shader module on pipeline creation. + // Returns compiled shader blob and bool to indicate if the shader should be + // destroyed after pipeline creation + fn extract_entry_point( + stage: ShaderStage, + source: &pso::EntryPoint<B>, + layout: &r::PipelineLayout, + features: &hal::Features, + ) -> Result<(native::Blob, bool), d::ShaderError> { + match *source.module { + r::ShaderModule::Compiled(ref shaders) => { + // TODO: do we need to check for specialization constants? + // Use precompiled shader, ignore specialization or layout. + shaders + .get(source.entry) + .map(|src| (*src, false)) + .ok_or(d::ShaderError::MissingEntryPoint(source.entry.into())) + } + r::ShaderModule::Spirv(ref raw_data) => { + let mut ast = Self::parse_spirv(raw_data)?; + spirv_cross_specialize_ast(&mut ast, &source.specialization)?; + Self::patch_spirv_resources(&mut ast, layout)?; + + let execution_model = conv::map_stage(stage); + let shader_model = hlsl::ShaderModel::V5_1; + let shader_code = Self::translate_spirv( + &mut ast, + shader_model, + layout, + stage, + features, + source.entry, + )?; + debug!("SPIRV-Cross generated shader:\n{}", shader_code); + + let real_name = ast + .get_cleansed_entry_point_name(source.entry, execution_model) + .map_err(gen_query_error)?; + + let stage = conv::map_execution_model(execution_model); + let shader = compile_shader( + stage, + shader_model, + features, + &real_name, + shader_code.as_bytes(), + )?; + Ok((shader, true)) + } + } + } + + /// Create a shader module from HLSL with a single entry point + pub fn create_shader_module_from_source( + &self, + stage: ShaderStage, + hlsl_entry: &str, + entry_point: &str, + code: &[u8], + ) -> Result<r::ShaderModule, d::ShaderError> { + let mut shader_map = BTreeMap::new(); + let blob = compile_shader( + stage, + hlsl::ShaderModel::V5_1, + &self.features, + hlsl_entry, + code, + )?; + shader_map.insert(entry_point.into(), blob); + Ok(r::ShaderModule::Compiled(shader_map)) + } + + pub(crate) fn create_command_signature( + device: native::Device, + ty: CommandSignature, + ) -> native::CommandSignature { + let (arg, stride) = match ty { + CommandSignature::Draw => (native::IndirectArgument::draw(), 16), + CommandSignature::DrawIndexed => (native::IndirectArgument::draw_indexed(), 20), + CommandSignature::Dispatch => (native::IndirectArgument::dispatch(), 12), + }; + + let (signature, hr) = + device.create_command_signature(native::RootSignature::null(), &[arg], stride, 0); + + if !winerror::SUCCEEDED(hr) { + error!("error on command signature creation: {:x}", hr); + } + signature + } + + pub(crate) fn create_descriptor_heap_impl( + device: native::Device, + heap_type: native::DescriptorHeapType, + shader_visible: bool, + capacity: usize, + ) -> r::DescriptorHeap { + assert_ne!(capacity, 0); + + let (heap, _hr) = device.create_descriptor_heap( + capacity as _, + heap_type, + if shader_visible { + native::DescriptorHeapFlags::SHADER_VISIBLE + } else { + native::DescriptorHeapFlags::empty() + }, + 0, + ); + + let descriptor_size = device.get_descriptor_increment_size(heap_type); + let cpu_handle = heap.start_cpu_descriptor(); + let gpu_handle = heap.start_gpu_descriptor(); + + r::DescriptorHeap { + raw: heap, + handle_size: descriptor_size as _, + total_handles: capacity as _, + start: r::DualHandle { + cpu: cpu_handle, + gpu: gpu_handle, + size: 0, + }, + } + } + + pub(crate) fn view_image_as_render_target_impl( + device: native::Device, + handle: d3d12::D3D12_CPU_DESCRIPTOR_HANDLE, + info: &ViewInfo, + ) -> Result<(), image::ViewCreationError> { + #![allow(non_snake_case)] + + let mut desc = d3d12::D3D12_RENDER_TARGET_VIEW_DESC { + Format: info.format, + ViewDimension: 0, + u: unsafe { mem::zeroed() }, + }; + + let MipSlice = info.levels.start as _; + let FirstArraySlice = info.layers.start as _; + let ArraySize = (info.layers.end - info.layers.start) as _; + let is_msaa = info.kind.num_samples() > 1; + if info.levels.start + 1 != info.levels.end { + return Err(image::ViewCreationError::Level(info.levels.start)); + } + if info.layers.end > info.kind.num_layers() { + return Err(image::ViewCreationError::Layer( + image::LayerError::OutOfBounds, + )); + } + + match info.view_kind { + image::ViewKind::D1 => { + assert_eq!(info.layers, 0..1); + desc.ViewDimension = d3d12::D3D12_RTV_DIMENSION_TEXTURE1D; + *unsafe { desc.u.Texture1D_mut() } = d3d12::D3D12_TEX1D_RTV { MipSlice } + } + image::ViewKind::D1Array => { + desc.ViewDimension = d3d12::D3D12_RTV_DIMENSION_TEXTURE1DARRAY; + *unsafe { desc.u.Texture1DArray_mut() } = d3d12::D3D12_TEX1D_ARRAY_RTV { + MipSlice, + FirstArraySlice, + ArraySize, + } + } + image::ViewKind::D2 if is_msaa => { + assert_eq!(info.layers, 0..1); + desc.ViewDimension = d3d12::D3D12_RTV_DIMENSION_TEXTURE2DMS; + *unsafe { desc.u.Texture2DMS_mut() } = d3d12::D3D12_TEX2DMS_RTV { + UnusedField_NothingToDefine: 0, + } + } + image::ViewKind::D2 => { + assert_eq!(info.layers, 0..1); + desc.ViewDimension = d3d12::D3D12_RTV_DIMENSION_TEXTURE2D; + *unsafe { desc.u.Texture2D_mut() } = d3d12::D3D12_TEX2D_RTV { + MipSlice, + PlaneSlice: 0, //TODO + } + } + image::ViewKind::D2Array if is_msaa => { + desc.ViewDimension = d3d12::D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY; + *unsafe { desc.u.Texture2DMSArray_mut() } = d3d12::D3D12_TEX2DMS_ARRAY_RTV { + FirstArraySlice, + ArraySize, + } + } + image::ViewKind::D2Array => { + desc.ViewDimension = d3d12::D3D12_RTV_DIMENSION_TEXTURE2DARRAY; + *unsafe { desc.u.Texture2DArray_mut() } = d3d12::D3D12_TEX2D_ARRAY_RTV { + MipSlice, + FirstArraySlice, + ArraySize, + PlaneSlice: 0, //TODO + } + } + image::ViewKind::D3 => { + assert_eq!(info.layers, 0..1); + desc.ViewDimension = d3d12::D3D12_RTV_DIMENSION_TEXTURE3D; + *unsafe { desc.u.Texture3D_mut() } = d3d12::D3D12_TEX3D_RTV { + MipSlice, + FirstWSlice: 0, + WSize: info.kind.extent().depth as _, + } + } + image::ViewKind::Cube | image::ViewKind::CubeArray => { + desc.ViewDimension = d3d12::D3D12_RTV_DIMENSION_TEXTURE2DARRAY; + //TODO: double-check if any *6 are needed + *unsafe { desc.u.Texture2DArray_mut() } = d3d12::D3D12_TEX2D_ARRAY_RTV { + MipSlice, + FirstArraySlice, + ArraySize, + PlaneSlice: 0, //TODO + } + } + }; + + unsafe { + device.CreateRenderTargetView(info.resource.as_mut_ptr(), &desc, handle); + } + + Ok(()) + } + + fn view_image_as_render_target( + &self, + info: &ViewInfo, + ) -> Result<descriptors_cpu::Handle, image::ViewCreationError> { + let handle = self.rtv_pool.lock().alloc_handle(); + Self::view_image_as_render_target_impl(self.raw, handle.raw, info).map(|_| handle) + } + + pub(crate) fn view_image_as_depth_stencil_impl( + device: native::Device, + handle: d3d12::D3D12_CPU_DESCRIPTOR_HANDLE, + info: &ViewInfo, + ) -> Result<(), image::ViewCreationError> { + #![allow(non_snake_case)] + + let mut desc = d3d12::D3D12_DEPTH_STENCIL_VIEW_DESC { + Format: info.format, + ViewDimension: 0, + Flags: 0, + u: unsafe { mem::zeroed() }, + }; + + let MipSlice = info.levels.start as _; + let FirstArraySlice = info.layers.start as _; + let ArraySize = (info.layers.end - info.layers.start) as _; + let is_msaa = info.kind.num_samples() > 1; + if info.levels.start + 1 != info.levels.end { + return Err(image::ViewCreationError::Level(info.levels.start)); + } + if info.layers.end > info.kind.num_layers() { + return Err(image::ViewCreationError::Layer( + image::LayerError::OutOfBounds, + )); + } + + match info.view_kind { + image::ViewKind::D1 => { + assert_eq!(info.layers, 0..1); + desc.ViewDimension = d3d12::D3D12_DSV_DIMENSION_TEXTURE1D; + *unsafe { desc.u.Texture1D_mut() } = d3d12::D3D12_TEX1D_DSV { MipSlice } + } + image::ViewKind::D1Array => { + desc.ViewDimension = d3d12::D3D12_DSV_DIMENSION_TEXTURE1DARRAY; + *unsafe { desc.u.Texture1DArray_mut() } = d3d12::D3D12_TEX1D_ARRAY_DSV { + MipSlice, + FirstArraySlice, + ArraySize, + } + } + image::ViewKind::D2 if is_msaa => { + assert_eq!(info.layers, 0..1); + desc.ViewDimension = d3d12::D3D12_DSV_DIMENSION_TEXTURE2DMS; + *unsafe { desc.u.Texture2DMS_mut() } = d3d12::D3D12_TEX2DMS_DSV { + UnusedField_NothingToDefine: 0, + } + } + image::ViewKind::D2 => { + assert_eq!(info.layers, 0..1); + desc.ViewDimension = d3d12::D3D12_DSV_DIMENSION_TEXTURE2D; + *unsafe { desc.u.Texture2D_mut() } = d3d12::D3D12_TEX2D_DSV { MipSlice } + } + image::ViewKind::D2Array if is_msaa => { + desc.ViewDimension = d3d12::D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY; + *unsafe { desc.u.Texture2DMSArray_mut() } = d3d12::D3D12_TEX2DMS_ARRAY_DSV { + FirstArraySlice, + ArraySize, + } + } + image::ViewKind::D2Array => { + desc.ViewDimension = d3d12::D3D12_DSV_DIMENSION_TEXTURE2DARRAY; + *unsafe { desc.u.Texture2DArray_mut() } = d3d12::D3D12_TEX2D_ARRAY_DSV { + MipSlice, + FirstArraySlice, + ArraySize, + } + } + image::ViewKind::D3 | image::ViewKind::Cube | image::ViewKind::CubeArray => { + unimplemented!() + } + }; + + unsafe { + device.CreateDepthStencilView(info.resource.as_mut_ptr(), &desc, handle); + } + + Ok(()) + } + + fn view_image_as_depth_stencil( + &self, + info: &ViewInfo, + ) -> Result<descriptors_cpu::Handle, image::ViewCreationError> { + let handle = self.dsv_pool.lock().alloc_handle(); + Self::view_image_as_depth_stencil_impl(self.raw, handle.raw, info).map(|_| handle) + } + + pub(crate) fn build_image_as_shader_resource_desc( + info: &ViewInfo, + ) -> Result<d3d12::D3D12_SHADER_RESOURCE_VIEW_DESC, image::ViewCreationError> { + #![allow(non_snake_case)] + + let mut desc = d3d12::D3D12_SHADER_RESOURCE_VIEW_DESC { + Format: info.format, + ViewDimension: 0, + Shader4ComponentMapping: info.component_mapping, + u: unsafe { mem::zeroed() }, + }; + + let MostDetailedMip = info.levels.start as _; + let MipLevels = (info.levels.end - info.levels.start) as _; + let FirstArraySlice = info.layers.start as _; + let ArraySize = (info.layers.end - info.layers.start) as _; + + if info.layers.end > info.kind.num_layers() { + return Err(image::ViewCreationError::Layer( + image::LayerError::OutOfBounds, + )); + } + let is_msaa = info.kind.num_samples() > 1; + let is_cube = info.caps.contains(image::ViewCapabilities::KIND_CUBE); + + match info.view_kind { + image::ViewKind::D1 => { + assert_eq!(info.layers, 0..1); + desc.ViewDimension = d3d12::D3D12_SRV_DIMENSION_TEXTURE1D; + *unsafe { desc.u.Texture1D_mut() } = d3d12::D3D12_TEX1D_SRV { + MostDetailedMip, + MipLevels, + ResourceMinLODClamp: 0.0, + } + } + image::ViewKind::D1Array => { + desc.ViewDimension = d3d12::D3D12_SRV_DIMENSION_TEXTURE1DARRAY; + *unsafe { desc.u.Texture1DArray_mut() } = d3d12::D3D12_TEX1D_ARRAY_SRV { + MostDetailedMip, + MipLevels, + FirstArraySlice, + ArraySize, + ResourceMinLODClamp: 0.0, + } + } + image::ViewKind::D2 if is_msaa => { + assert_eq!(info.layers, 0..1); + desc.ViewDimension = d3d12::D3D12_SRV_DIMENSION_TEXTURE2DMS; + *unsafe { desc.u.Texture2DMS_mut() } = d3d12::D3D12_TEX2DMS_SRV { + UnusedField_NothingToDefine: 0, + } + } + image::ViewKind::D2 => { + assert_eq!(info.layers, 0..1); + desc.ViewDimension = d3d12::D3D12_SRV_DIMENSION_TEXTURE2D; + *unsafe { desc.u.Texture2D_mut() } = d3d12::D3D12_TEX2D_SRV { + MostDetailedMip, + MipLevels, + PlaneSlice: 0, //TODO + ResourceMinLODClamp: 0.0, + } + } + image::ViewKind::D2Array if is_msaa => { + desc.ViewDimension = d3d12::D3D12_SRV_DIMENSION_TEXTURE2DMSARRAY; + *unsafe { desc.u.Texture2DMSArray_mut() } = d3d12::D3D12_TEX2DMS_ARRAY_SRV { + FirstArraySlice, + ArraySize, + } + } + image::ViewKind::D2Array => { + desc.ViewDimension = d3d12::D3D12_SRV_DIMENSION_TEXTURE2DARRAY; + *unsafe { desc.u.Texture2DArray_mut() } = d3d12::D3D12_TEX2D_ARRAY_SRV { + MostDetailedMip, + MipLevels, + FirstArraySlice, + ArraySize, + PlaneSlice: 0, //TODO + ResourceMinLODClamp: 0.0, + } + } + image::ViewKind::D3 => { + assert_eq!(info.layers, 0..1); + desc.ViewDimension = d3d12::D3D12_SRV_DIMENSION_TEXTURE3D; + *unsafe { desc.u.Texture3D_mut() } = d3d12::D3D12_TEX3D_SRV { + MostDetailedMip, + MipLevels, + ResourceMinLODClamp: 0.0, + } + } + image::ViewKind::Cube if is_cube => { + desc.ViewDimension = d3d12::D3D12_SRV_DIMENSION_TEXTURECUBE; + *unsafe { desc.u.TextureCube_mut() } = d3d12::D3D12_TEXCUBE_SRV { + MostDetailedMip, + MipLevels, + ResourceMinLODClamp: 0.0, + } + } + image::ViewKind::CubeArray if is_cube => { + assert_eq!(0, ArraySize % 6); + desc.ViewDimension = d3d12::D3D12_SRV_DIMENSION_TEXTURECUBEARRAY; + *unsafe { desc.u.TextureCubeArray_mut() } = d3d12::D3D12_TEXCUBE_ARRAY_SRV { + MostDetailedMip, + MipLevels, + First2DArrayFace: FirstArraySlice, + NumCubes: ArraySize / 6, + ResourceMinLODClamp: 0.0, + } + } + image::ViewKind::Cube | image::ViewKind::CubeArray => { + error!( + "Cube views are not supported for the image, kind: {:?}", + info.kind + ); + return Err(image::ViewCreationError::BadKind(info.view_kind)); + } + } + + Ok(desc) + } + + fn view_image_as_shader_resource( + &self, + info: &ViewInfo, + ) -> Result<descriptors_cpu::Handle, image::ViewCreationError> { + let desc = Self::build_image_as_shader_resource_desc(&info)?; + let handle = self.srv_uav_pool.lock().alloc_handle(); + unsafe { + self.raw + .CreateShaderResourceView(info.resource.as_mut_ptr(), &desc, handle.raw); + } + + Ok(handle) + } + + fn view_image_as_storage( + &self, + info: &ViewInfo, + ) -> Result<descriptors_cpu::Handle, image::ViewCreationError> { + #![allow(non_snake_case)] + + // Cannot make a storage image over multiple mips + if info.levels.start + 1 != info.levels.end { + return Err(image::ViewCreationError::Unsupported); + } + + let mut desc = d3d12::D3D12_UNORDERED_ACCESS_VIEW_DESC { + Format: info.format, + ViewDimension: 0, + u: unsafe { mem::zeroed() }, + }; + + let MipSlice = info.levels.start as _; + let FirstArraySlice = info.layers.start as _; + let ArraySize = (info.layers.end - info.layers.start) as _; + + if info.layers.end > info.kind.num_layers() { + return Err(image::ViewCreationError::Layer( + image::LayerError::OutOfBounds, + )); + } + if info.kind.num_samples() > 1 { + error!("MSAA images can't be viewed as UAV"); + return Err(image::ViewCreationError::Unsupported); + } + + match info.view_kind { + image::ViewKind::D1 => { + assert_eq!(info.layers, 0..1); + desc.ViewDimension = d3d12::D3D12_UAV_DIMENSION_TEXTURE1D; + *unsafe { desc.u.Texture1D_mut() } = d3d12::D3D12_TEX1D_UAV { MipSlice } + } + image::ViewKind::D1Array => { + desc.ViewDimension = d3d12::D3D12_UAV_DIMENSION_TEXTURE1DARRAY; + *unsafe { desc.u.Texture1DArray_mut() } = d3d12::D3D12_TEX1D_ARRAY_UAV { + MipSlice, + FirstArraySlice, + ArraySize, + } + } + image::ViewKind::D2 => { + assert_eq!(info.layers, 0..1); + desc.ViewDimension = d3d12::D3D12_UAV_DIMENSION_TEXTURE2D; + *unsafe { desc.u.Texture2D_mut() } = d3d12::D3D12_TEX2D_UAV { + MipSlice, + PlaneSlice: 0, //TODO + } + } + image::ViewKind::D2Array => { + desc.ViewDimension = d3d12::D3D12_UAV_DIMENSION_TEXTURE2DARRAY; + *unsafe { desc.u.Texture2DArray_mut() } = d3d12::D3D12_TEX2D_ARRAY_UAV { + MipSlice, + FirstArraySlice, + ArraySize, + PlaneSlice: 0, //TODO + } + } + image::ViewKind::D3 => { + assert_eq!(info.layers, 0..1); + desc.ViewDimension = d3d12::D3D12_UAV_DIMENSION_TEXTURE3D; + *unsafe { desc.u.Texture3D_mut() } = d3d12::D3D12_TEX3D_UAV { + MipSlice, + FirstWSlice: 0, + WSize: info.kind.extent().depth as _, + } + } + image::ViewKind::Cube | image::ViewKind::CubeArray => { + error!("Cubic images can't be viewed as UAV"); + return Err(image::ViewCreationError::Unsupported); + } + } + + let handle = self.srv_uav_pool.lock().alloc_handle(); + unsafe { + self.raw.CreateUnorderedAccessView( + info.resource.as_mut_ptr(), + ptr::null_mut(), + &desc, + handle.raw, + ); + } + + Ok(handle) + } + + pub(crate) fn create_raw_fence(&self, signalled: bool) -> native::Fence { + let mut handle = native::Fence::null(); + assert_eq!(winerror::S_OK, unsafe { + self.raw.CreateFence( + if signalled { 1 } else { 0 }, + d3d12::D3D12_FENCE_FLAG_NONE, + &d3d12::ID3D12Fence::uuidof(), + handle.mut_void(), + ) + }); + handle + } + + pub(crate) fn create_swapchain_impl( + &self, + config: &w::SwapchainConfig, + window_handle: windef::HWND, + factory: native::WeakPtr<dxgi1_4::IDXGIFactory4>, + ) -> Result< + ( + native::WeakPtr<dxgi1_4::IDXGISwapChain3>, + dxgiformat::DXGI_FORMAT, + ), + w::CreationError, + > { + let mut swap_chain1 = native::WeakPtr::<dxgi1_2::IDXGISwapChain1>::null(); + + //TODO: proper error type? + let non_srgb_format = conv::map_format_nosrgb(config.format).unwrap(); + + let mut flags = dxgi::DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; + if config.present_mode.contains(w::PresentMode::IMMEDIATE) { + flags |= dxgi::DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + } + + // TODO: double-check values + let desc = dxgi1_2::DXGI_SWAP_CHAIN_DESC1 { + AlphaMode: dxgi1_2::DXGI_ALPHA_MODE_IGNORE, + BufferCount: config.image_count, + Width: config.extent.width, + Height: config.extent.height, + Format: non_srgb_format, + Flags: flags, + BufferUsage: dxgitype::DXGI_USAGE_RENDER_TARGET_OUTPUT, + SampleDesc: dxgitype::DXGI_SAMPLE_DESC { + Count: 1, + Quality: 0, + }, + Scaling: dxgi1_2::DXGI_SCALING_STRETCH, + Stereo: FALSE, + SwapEffect: dxgi::DXGI_SWAP_EFFECT_FLIP_DISCARD, + }; + + unsafe { + let hr = factory.CreateSwapChainForHwnd( + self.present_queue.as_mut_ptr() as *mut _, + window_handle, + &desc, + ptr::null(), + ptr::null_mut(), + swap_chain1.mut_void() as *mut *mut _, + ); + + if !winerror::SUCCEEDED(hr) { + error!("error on swapchain creation 0x{:x}", hr); + } + + let (swap_chain3, hr3) = swap_chain1.cast::<dxgi1_4::IDXGISwapChain3>(); + if !winerror::SUCCEEDED(hr3) { + error!("error on swapchain cast 0x{:x}", hr3); + } + + swap_chain1.destroy(); + Ok((swap_chain3, non_srgb_format)) + } + } + + pub(crate) fn wrap_swapchain( + &self, + inner: native::WeakPtr<dxgi1_4::IDXGISwapChain3>, + config: &w::SwapchainConfig, + ) -> Swapchain { + let waitable = unsafe { + inner.SetMaximumFrameLatency(config.image_count); + inner.GetFrameLatencyWaitableObject() + }; + + let rtv_desc = d3d12::D3D12_RENDER_TARGET_VIEW_DESC { + Format: conv::map_format(config.format).unwrap(), + ViewDimension: d3d12::D3D12_RTV_DIMENSION_TEXTURE2D, + ..unsafe { mem::zeroed() } + }; + let rtv_heap = Device::create_descriptor_heap_impl( + self.raw, + native::DescriptorHeapType::Rtv, + false, + config.image_count as _, + ); + + let mut resources = vec![native::Resource::null(); config.image_count as usize]; + for (i, res) in resources.iter_mut().enumerate() { + let rtv_handle = rtv_heap.at(i as _, 0).cpu; + unsafe { + inner.GetBuffer(i as _, &d3d12::ID3D12Resource::uuidof(), res.mut_void()); + self.raw + .CreateRenderTargetView(res.as_mut_ptr(), &rtv_desc, rtv_handle); + } + } + + Swapchain { + inner, + rtv_heap, + resources, + waitable, + usage: config.image_usage, + acquired_count: 0, + } + } +} + +impl d::Device<B> for Device { + unsafe fn allocate_memory( + &self, + mem_type: hal::MemoryTypeId, + size: u64, + ) -> Result<r::Memory, d::AllocationError> { + let mem_type = mem_type.0; + let mem_base_id = mem_type % NUM_HEAP_PROPERTIES; + let heap_property = &self.heap_properties[mem_base_id]; + + let properties = d3d12::D3D12_HEAP_PROPERTIES { + Type: d3d12::D3D12_HEAP_TYPE_CUSTOM, + CPUPageProperty: heap_property.page_property, + MemoryPoolPreference: heap_property.memory_pool, + CreationNodeMask: 0, + VisibleNodeMask: 0, + }; + + // Exposed memory types are grouped according to their capabilities. + // See `MemoryGroup` for more details. + let mem_group = mem_type / NUM_HEAP_PROPERTIES; + + let desc = d3d12::D3D12_HEAP_DESC { + SizeInBytes: size, + Properties: properties, + Alignment: d3d12::D3D12_DEFAULT_MSAA_RESOURCE_PLACEMENT_ALIGNMENT as _, // TODO: not always..? + Flags: match mem_group { + 0 => d3d12::D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES, + 1 => d3d12::D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS, + 2 => d3d12::D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES, + 3 => d3d12::D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES, + _ => unreachable!(), + }, + }; + + let mut heap = native::Heap::null(); + let hr = self + .raw + .clone() + .CreateHeap(&desc, &d3d12::ID3D12Heap::uuidof(), heap.mut_void()); + if hr == winerror::E_OUTOFMEMORY { + return Err(d::OutOfMemory::Device.into()); + } + assert_eq!(winerror::S_OK, hr); + + // The first memory heap of each group corresponds to the default heap, which is can never + // be mapped. + // Devices supporting heap tier 1 can only created buffers on mem group 1 (ALLOW_ONLY_BUFFERS). + // Devices supporting heap tier 2 always expose only mem group 0 and don't have any further restrictions. + let is_mapable = mem_base_id != 0 + && (mem_group == MemoryGroup::Universal as _ + || mem_group == MemoryGroup::BufferOnly as _); + + // Create a buffer resource covering the whole memory slice to be able to map the whole memory. + let resource = if is_mapable { + let mut resource = native::Resource::null(); + let desc = d3d12::D3D12_RESOURCE_DESC { + Dimension: d3d12::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::D3D12_TEXTURE_LAYOUT_ROW_MAJOR, + Flags: d3d12::D3D12_RESOURCE_FLAG_NONE, + }; + + assert_eq!( + winerror::S_OK, + self.raw.clone().CreatePlacedResource( + heap.as_mut_ptr(), + 0, + &desc, + d3d12::D3D12_RESOURCE_STATE_COMMON, + ptr::null(), + &d3d12::ID3D12Resource::uuidof(), + resource.mut_void(), + ) + ); + + Some(resource) + } else { + None + }; + + Ok(r::Memory { + heap, + type_id: mem_type, + size, + resource, + }) + } + + unsafe fn create_command_pool( + &self, + family: QueueFamilyId, + create_flags: CommandPoolCreateFlags, + ) -> Result<CommandPool, d::OutOfMemory> { + let list_type = QUEUE_FAMILIES[family.0].native_type(); + Ok(CommandPool::new( + self.raw, + list_type, + &self.shared, + create_flags, + )) + } + + unsafe fn destroy_command_pool(&self, _pool: CommandPool) {} + + unsafe fn create_render_pass<'a, IA, IS, ID>( + &self, + attachments: IA, + subpasses: IS, + dependencies: ID, + ) -> Result<r::RenderPass, d::OutOfMemory> + where + IA: IntoIterator, + IA::Item: Borrow<pass::Attachment>, + IS: IntoIterator, + IS::Item: Borrow<pass::SubpassDesc<'a>>, + ID: IntoIterator, + ID::Item: Borrow<pass::SubpassDependency>, + { + #[derive(Copy, Clone, Debug, PartialEq)] + enum SubState { + New(d3d12::D3D12_RESOURCE_STATES), + // Color attachment which will be resolved at the end of the subpass + Resolve(d3d12::D3D12_RESOURCE_STATES), + Preserve, + Undefined, + } + /// Temporary information about every sub-pass + struct SubInfo<'a> { + desc: pass::SubpassDesc<'a>, + /// States before the render-pass (in self.start) + /// and after the render-pass (in self.end). + external_dependencies: Range<image::Access>, + /// Counts the number of dependencies that need to be resolved + /// before starting this subpass. + unresolved_dependencies: u16, + } + struct AttachmentInfo { + sub_states: Vec<SubState>, + last_state: d3d12::D3D12_RESOURCE_STATES, + barrier_start_index: usize, + } + + let attachments = attachments + .into_iter() + .map(|attachment| attachment.borrow().clone()) + .collect::<SmallVec<[_; 5]>>(); + let mut sub_infos = subpasses + .into_iter() + .map(|desc| SubInfo { + desc: desc.borrow().clone(), + external_dependencies: image::Access::empty()..image::Access::empty(), + unresolved_dependencies: 0, + }) + .collect::<SmallVec<[_; 1]>>(); + let dependencies = dependencies.into_iter().collect::<SmallVec<[_; 2]>>(); + + let mut att_infos = (0..attachments.len()) + .map(|_| AttachmentInfo { + sub_states: vec![SubState::Undefined; sub_infos.len()], + last_state: d3d12::D3D12_RESOURCE_STATE_COMMON, // is to be overwritten + barrier_start_index: 0, + }) + .collect::<SmallVec<[_; 5]>>(); + + for dep in &dependencies { + let dep = dep.borrow(); + match dep.passes { + Range { + start: None, + end: None, + } => { + error!("Unexpected external-external dependency!"); + } + Range { + start: None, + end: Some(sid), + } => { + sub_infos[sid as usize].external_dependencies.start |= dep.accesses.start; + } + Range { + start: Some(sid), + end: None, + } => { + sub_infos[sid as usize].external_dependencies.end |= dep.accesses.end; + } + Range { + start: Some(from_sid), + end: Some(sid), + } => { + //Note: self-dependencies are ignored + if from_sid != sid { + sub_infos[sid as usize].unresolved_dependencies += 1; + } + } + } + } + + // Fill out subpass known layouts + for (sid, sub_info) in sub_infos.iter().enumerate() { + let sub = &sub_info.desc; + for (i, &(id, _layout)) in sub.colors.iter().enumerate() { + let target_state = d3d12::D3D12_RESOURCE_STATE_RENDER_TARGET; + let state = match sub.resolves.get(i) { + Some(_) => SubState::Resolve(target_state), + None => SubState::New(target_state), + }; + let old = mem::replace(&mut att_infos[id].sub_states[sid], state); + debug_assert_eq!(SubState::Undefined, old); + } + for &(id, layout) in sub.depth_stencil { + let state = SubState::New(match layout { + image::Layout::DepthStencilAttachmentOptimal => { + d3d12::D3D12_RESOURCE_STATE_DEPTH_WRITE + } + image::Layout::DepthStencilReadOnlyOptimal => { + d3d12::D3D12_RESOURCE_STATE_DEPTH_READ + } + image::Layout::General => d3d12::D3D12_RESOURCE_STATE_DEPTH_WRITE, + _ => { + error!("Unexpected depth/stencil layout: {:?}", layout); + d3d12::D3D12_RESOURCE_STATE_COMMON + } + }); + let old = mem::replace(&mut att_infos[id].sub_states[sid], state); + debug_assert_eq!(SubState::Undefined, old); + } + for &(id, _layout) in sub.inputs { + let state = SubState::New(d3d12::D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + let old = mem::replace(&mut att_infos[id].sub_states[sid], state); + debug_assert_eq!(SubState::Undefined, old); + } + for &(id, _layout) in sub.resolves { + let state = SubState::New(d3d12::D3D12_RESOURCE_STATE_RESOLVE_DEST); + let old = mem::replace(&mut att_infos[id].sub_states[sid], state); + debug_assert_eq!(SubState::Undefined, old); + } + for &id in sub.preserves { + let old = mem::replace(&mut att_infos[id].sub_states[sid], SubState::Preserve); + debug_assert_eq!(SubState::Undefined, old); + } + } + + let mut rp = r::RenderPass { + attachments: attachments.iter().cloned().collect(), + subpasses: Vec::new(), + post_barriers: Vec::new(), + raw_name: Vec::new(), + }; + + while let Some(sid) = sub_infos + .iter() + .position(|si| si.unresolved_dependencies == 0) + { + for dep in &dependencies { + let dep = dep.borrow(); + if dep.passes.start != dep.passes.end + && dep.passes.start == Some(sid as pass::SubpassId) + { + if let Some(other) = dep.passes.end { + sub_infos[other as usize].unresolved_dependencies -= 1; + } + } + } + + let si = &mut sub_infos[sid]; + si.unresolved_dependencies = !0; // mark as done + + // Subpass barriers + let mut pre_barriers = Vec::new(); + let mut post_barriers = Vec::new(); + for (att_id, (ai, att)) in att_infos.iter_mut().zip(attachments.iter()).enumerate() { + // Attachment wasn't used before, figure out the initial state + if ai.barrier_start_index == 0 { + //Note: the external dependencies are provided for all attachments that are + // first used in this sub-pass, so they may contain more states than we expect + // for this particular attachment. + ai.last_state = conv::map_image_resource_state( + si.external_dependencies.start, + att.layouts.start, + ); + } + // Barrier from previous subpass to current or following subpasses. + match ai.sub_states[sid] { + SubState::Preserve => { + ai.barrier_start_index = rp.subpasses.len() + 1; + } + SubState::New(state) if state != ai.last_state => { + let barrier = r::BarrierDesc::new(att_id, ai.last_state..state); + match rp.subpasses.get_mut(ai.barrier_start_index) { + Some(past_subpass) => { + let split = barrier.split(); + past_subpass.pre_barriers.push(split.start); + pre_barriers.push(split.end); + } + None => pre_barriers.push(barrier), + } + ai.last_state = state; + ai.barrier_start_index = rp.subpasses.len() + 1; + } + SubState::Resolve(state) => { + // 1. Standard pre barrier to update state from previous pass into desired substate. + if state != ai.last_state { + let barrier = r::BarrierDesc::new(att_id, ai.last_state..state); + match rp.subpasses.get_mut(ai.barrier_start_index) { + Some(past_subpass) => { + let split = barrier.split(); + past_subpass.pre_barriers.push(split.start); + pre_barriers.push(split.end); + } + None => pre_barriers.push(barrier), + } + } + + // 2. Post Barrier at the end of the subpass into RESOLVE_SOURCE. + let resolve_state = d3d12::D3D12_RESOURCE_STATE_RESOLVE_SOURCE; + let barrier = r::BarrierDesc::new(att_id, state..resolve_state); + post_barriers.push(barrier); + + ai.last_state = resolve_state; + ai.barrier_start_index = rp.subpasses.len() + 1; + } + SubState::Undefined | SubState::New(_) => {} + }; + } + + rp.subpasses.push(r::SubpassDesc { + color_attachments: si.desc.colors.iter().cloned().collect(), + depth_stencil_attachment: si.desc.depth_stencil.cloned(), + input_attachments: si.desc.inputs.iter().cloned().collect(), + resolve_attachments: si.desc.resolves.iter().cloned().collect(), + pre_barriers, + post_barriers, + }); + } + // if this fails, our graph has cycles + assert_eq!(rp.subpasses.len(), sub_infos.len()); + assert!(sub_infos.iter().all(|si| si.unresolved_dependencies == !0)); + + // take care of the post-pass transitions at the end of the renderpass. + for (att_id, (ai, att)) in att_infos.iter().zip(attachments.iter()).enumerate() { + let state_dst = if ai.barrier_start_index == 0 { + // attachment wasn't used in any sub-pass? + continue; + } else { + let si = &sub_infos[ai.barrier_start_index - 1]; + conv::map_image_resource_state(si.external_dependencies.end, att.layouts.end) + }; + if state_dst == ai.last_state { + continue; + } + let barrier = r::BarrierDesc::new(att_id, ai.last_state..state_dst); + match rp.subpasses.get_mut(ai.barrier_start_index) { + Some(past_subpass) => { + let split = barrier.split(); + past_subpass.pre_barriers.push(split.start); + rp.post_barriers.push(split.end); + } + None => rp.post_barriers.push(barrier), + } + } + + Ok(rp) + } + + unsafe fn create_pipeline_layout<IS, IR>( + &self, + sets: IS, + push_constant_ranges: IR, + ) -> Result<r::PipelineLayout, d::OutOfMemory> + where + IS: IntoIterator, + IS::Item: Borrow<r::DescriptorSetLayout>, + IR: IntoIterator, + IR::Item: Borrow<(pso::ShaderStageFlags, Range<u32>)>, + { + // 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. + // + // Dynamic uniform buffers are implemented as root descriptors. + // This allows to handle the dynamic offsets properly, which would not be feasible + // with a combination of root constant and descriptor table. + // + // Root signature layout: + // Root Constants: Register: Offest/4, Space: 0 + // ... + // DescriptorTable0: Space: 1 (SrvCbvUav) + // DescriptorTable0: Space: 1 (Sampler) + // Root Descriptors 0 + // DescriptorTable1: Space: 2 (SrvCbvUav) + // Root Descriptors 1 + // ... + + let sets = sets.into_iter().collect::<Vec<_>>(); + + let mut root_offset = 0u32; + let root_constants = root_constants::split(push_constant_ranges) + .iter() + .map(|constant| { + assert!(constant.range.start <= constant.range.end); + root_offset += constant.range.end - constant.range.start; + + RootConstant { + stages: constant.stages, + range: constant.range.start..constant.range.end, + } + }) + .collect::<Vec<_>>(); + + info!( + "Creating a pipeline layout with {} sets and {} root constants", + sets.len(), + root_constants.len() + ); + + // Number of elements in the root signature. + // Guarantees that no re-allocation is done, and our pointers are valid + let mut parameters = Vec::with_capacity(root_constants.len() + sets.len() * 2); + let mut parameter_offsets = Vec::with_capacity(parameters.capacity()); + + // Convert root signature descriptions into root signature parameters. + for root_constant in root_constants.iter() { + debug!( + "\tRoot constant set={} range {:?}", + ROOT_CONSTANT_SPACE, root_constant.range + ); + parameter_offsets.push(root_constant.range.start); + parameters.push(native::RootParameter::constants( + conv::map_shader_visibility(root_constant.stages), + native::Binding { + register: root_constant.range.start as _, + space: ROOT_CONSTANT_SPACE, + }, + (root_constant.range.end - root_constant.range.start) as _, + )); + } + + // Offest of `spaceN` for descriptor tables. Root constants will be in + // `space0`. + // This has to match `patch_spirv_resources` logic. + let root_space_offset = if !root_constants.is_empty() { 1 } else { 0 }; + + // 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 = sets + .iter() + .map(|desc_set| { + let mut sum = 0; + for binding in desc_set.borrow().bindings.iter() { + let content = r::DescriptorContent::from(binding.ty); + if !content.is_dynamic() { + sum += content.bits().count_ones() as usize; + } + } + sum + }) + .sum(); + let mut ranges = Vec::with_capacity(total); + + let elements = sets + .iter() + .enumerate() + .map(|(i, set)| { + let set = set.borrow(); + let space = (root_space_offset + i) as u32; + let mut table_type = r::SetTableTypes::empty(); + let root_table_offset = root_offset; + + //TODO: split between sampler and non-sampler tables + let visibility = conv::map_shader_visibility( + set.bindings + .iter() + .fold(pso::ShaderStageFlags::empty(), |u, bind| { + u | bind.stage_flags + }), + ); + + for bind in set.bindings.iter() { + debug!("\tRange {:?} at space={}", bind, space); + } + + let describe = |bind: &pso::DescriptorSetLayoutBinding, ty| { + native::DescriptorRange::new( + ty, + bind.count as _, + native::Binding { + register: bind.binding as _, + space, + }, + d3d12::D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND, + ) + }; + + let mut mutable_bindings = auxil::FastHashSet::default(); + + // SRV/CBV/UAV descriptor tables + let mut range_base = ranges.len(); + for bind in set.bindings.iter() { + let content = r::DescriptorContent::from(bind.ty); + if !content.is_dynamic() { + // Descriptor table ranges + if content.contains(r::DescriptorContent::CBV) { + ranges.push(describe(bind, native::DescriptorRangeType::CBV)); + } + if content.contains(r::DescriptorContent::SRV) { + ranges.push(describe(bind, native::DescriptorRangeType::SRV)); + } + if content.contains(r::DescriptorContent::UAV) { + ranges.push(describe(bind, native::DescriptorRangeType::UAV)); + mutable_bindings.insert(bind.binding); + } + } + } + if ranges.len() > range_base { + parameter_offsets.push(root_offset); + parameters.push(native::RootParameter::descriptor_table( + visibility, + &ranges[range_base..], + )); + table_type |= r::SRV_CBV_UAV; + root_offset += 1; + } + + // Sampler descriptor tables + range_base = ranges.len(); + for bind in set.bindings.iter() { + let content = r::DescriptorContent::from(bind.ty); + if content.contains(r::DescriptorContent::SAMPLER) { + ranges.push(describe(bind, native::DescriptorRangeType::Sampler)); + } + } + if ranges.len() > range_base { + parameter_offsets.push(root_offset); + parameters.push(native::RootParameter::descriptor_table( + visibility, + &ranges[range_base..], + )); + table_type |= r::SAMPLERS; + root_offset += 1; + } + + // Root (dynamic) descriptor tables + for bind in set.bindings.iter() { + let content = r::DescriptorContent::from(bind.ty); + if content.is_dynamic() { + let binding = native::Binding { + register: bind.binding as _, + space, + }; + + if content.contains(r::DescriptorContent::CBV) { + parameter_offsets.push(root_offset); + parameters + .push(native::RootParameter::cbv_descriptor(visibility, binding)); + root_offset += 2; // root CBV costs 2 words + } + if content.contains(r::DescriptorContent::SRV) { + parameter_offsets.push(root_offset); + parameters + .push(native::RootParameter::srv_descriptor(visibility, binding)); + root_offset += 2; // root SRV costs 2 words + } + if content.contains(r::DescriptorContent::UAV) { + parameter_offsets.push(root_offset); + parameters + .push(native::RootParameter::uav_descriptor(visibility, binding)); + root_offset += 2; // root UAV costs 2 words + } + } + } + + r::RootElement { + table: r::RootTable { + ty: table_type, + offset: root_table_offset as _, + }, + mutable_bindings, + } + }) + .collect(); + + // Ensure that we didn't reallocate! + debug_assert_eq!(ranges.len(), total); + assert_eq!(parameters.len(), parameter_offsets.len()); + + // TODO: error handling + let (signature_raw, error) = match self.library.serialize_root_signature( + native::RootSignatureVersion::V1_0, + ¶meters, + &[], + native::RootSignatureFlags::ALLOW_IA_INPUT_LAYOUT, + ) { + Ok((pair, hr)) if winerror::SUCCEEDED(hr) => pair, + Ok((_, hr)) => panic!("Can't serialize root signature: {:?}", hr), + Err(e) => panic!("Can't find serialization function: {:?}", e), + }; + + if !error.is_null() { + error!( + "Root signature serialization error: {:?}", + error.as_c_str().to_str().unwrap() + ); + error.destroy(); + } + + // TODO: error handling + let (signature, _hr) = self.raw.create_root_signature(signature_raw, 0); + signature_raw.destroy(); + + Ok(r::PipelineLayout { + shared: Arc::new(r::PipelineShared { + signature, + constants: root_constants, + parameter_offsets, + total_slots: root_offset, + }), + elements, + }) + } + + unsafe fn create_pipeline_cache(&self, _data: Option<&[u8]>) -> Result<(), d::OutOfMemory> { + Ok(()) + } + + unsafe fn get_pipeline_cache_data(&self, _cache: &()) -> Result<Vec<u8>, d::OutOfMemory> { + //empty + Ok(Vec::new()) + } + + unsafe fn destroy_pipeline_cache(&self, _: ()) { + //empty + } + + unsafe fn merge_pipeline_caches<I>(&self, _: &(), _: I) -> Result<(), d::OutOfMemory> + where + I: IntoIterator, + I::Item: Borrow<()>, + { + //empty + Ok(()) + } + + unsafe fn create_graphics_pipeline<'a>( + &self, + desc: &pso::GraphicsPipelineDesc<'a, B>, + _cache: Option<&()>, + ) -> Result<r::GraphicsPipeline, pso::CreationError> { + enum ShaderBc { + Owned(native::Blob), + Borrowed(native::Blob), + None, + } + let features = &self.features; + impl ShaderBc { + pub fn shader(&self) -> native::Shader { + match *self { + ShaderBc::Owned(ref bc) | ShaderBc::Borrowed(ref bc) => { + native::Shader::from_blob(*bc) + } + ShaderBc::None => native::Shader::null(), + } + } + } + + let build_shader = |stage: ShaderStage, source: Option<&pso::EntryPoint<'a, B>>| { + let source = match source { + Some(src) => src, + None => return Ok(ShaderBc::None), + }; + + match Self::extract_entry_point(stage, source, desc.layout, features) { + Ok((shader, true)) => Ok(ShaderBc::Owned(shader)), + Ok((shader, false)) => Ok(ShaderBc::Borrowed(shader)), + Err(err) => Err(pso::CreationError::Shader(err)), + } + }; + + let vertex_buffers: Vec<pso::VertexBufferDesc> = Vec::new(); + let attributes: Vec<pso::AttributeDesc> = Vec::new(); + let mesh_input_assembler = pso::InputAssemblerDesc::new(pso::Primitive::TriangleList); + let (vertex_buffers, attributes, input_assembler, vs, gs, hs, ds, _, _) = + match desc.primitive_assembler { + pso::PrimitiveAssemblerDesc::Vertex { + buffers, + attributes, + ref input_assembler, + ref vertex, + ref tessellation, + ref geometry, + } => { + let (hs, ds) = if let Some(ts) = tessellation { + (Some(&ts.0), Some(&ts.1)) + } else { + (None, None) + }; + + ( + buffers, + attributes, + input_assembler, + Some(vertex), + geometry.as_ref(), + hs, + ds, + None, + None, + ) + } + pso::PrimitiveAssemblerDesc::Mesh { ref task, ref mesh } => ( + &vertex_buffers[..], + &attributes[..], + &mesh_input_assembler, + None, + None, + None, + None, + task.as_ref(), + Some(mesh), + ), + }; + + let vertex_semantic_remapping = if let Some(ref vs) = vs { + // If we have a pre-compiled shader, we've lost the information we need to recover + // this information, so just pretend like this workaround never existed and hope + // for the best. + if let crate::resource::ShaderModule::Spirv(ref spv) = vs.module { + Some( + Self::introspect_spirv_vertex_semantic_remapping(spv) + .map_err(pso::CreationError::Shader)?, + ) + } else { + None + } + } else { + None + }; + + let vs = build_shader(ShaderStage::Vertex, vs)?; + let gs = build_shader(ShaderStage::Geometry, gs)?; + let hs = build_shader(ShaderStage::Domain, hs)?; + let ds = build_shader(ShaderStage::Hull, ds)?; + let ps = build_shader(ShaderStage::Fragment, desc.fragment.as_ref())?; + + // Rebind vertex buffers, see native.rs for more details. + let mut vertex_bindings = [None; MAX_VERTEX_BUFFERS]; + let mut vertex_strides = [0; MAX_VERTEX_BUFFERS]; + + for buffer in vertex_buffers { + vertex_strides[buffer.binding as usize] = buffer.stride; + } + // Fill in identity mapping where we don't need to adjust anything. + for attrib in attributes { + let binding = attrib.binding as usize; + let stride = vertex_strides[attrib.binding as usize]; + if attrib.element.offset < stride { + vertex_bindings[binding] = Some(r::VertexBinding { + stride: vertex_strides[attrib.binding as usize], + offset: 0, + mapped_binding: binding, + }); + } + } + + // See [`introspect_spirv_vertex_semantic_remapping`] for details of why this is needed. + let semantics: Vec<_> = attributes + .iter() + .map(|attrib| { + let semantics = vertex_semantic_remapping + .as_ref() + .and_then(|map| map.get(&attrib.location)); + match semantics { + Some(Some((major, minor))) => { + let name = std::borrow::Cow::Owned(format!("TEXCOORD{}_\0", major)); + let location = *minor; + (name, location) + } + _ => { + let name = std::borrow::Cow::Borrowed("TEXCOORD\0"); + let location = attrib.location; + (name, location) + } + } + }) + .collect(); + + // Define input element descriptions + let input_element_descs = attributes + .iter() + .zip(semantics.iter()) + .filter_map(|(attrib, (semantic_name, semantic_index))| { + let buffer_desc = match vertex_buffers + .iter() + .find(|buffer_desc| buffer_desc.binding == attrib.binding) + { + Some(buffer_desc) => buffer_desc, + None => { + error!( + "Couldn't find associated vertex buffer description {:?}", + attrib.binding + ); + return Some(Err(pso::CreationError::Other)); + } + }; + + let (slot_class, step_rate) = match buffer_desc.rate { + VertexInputRate::Vertex => { + (d3d12::D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0) + } + VertexInputRate::Instance(divisor) => { + (d3d12::D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, divisor) + } + }; + let format = attrib.element.format; + + // Check if we need to add a new remapping in-case the offset is + // higher than the vertex stride. + // In this case we rebase the attribute to zero offset. + let binding = attrib.binding as usize; + let stride = vertex_strides[binding]; + let offset = attrib.element.offset; + let (input_slot, offset) = if stride <= offset { + // Number of input attributes may not exceed bindings, see limits. + // We will always find at least one free binding. + let mapping = vertex_bindings.iter().position(Option::is_none).unwrap(); + vertex_bindings[mapping] = Some(r::VertexBinding { + stride: vertex_strides[binding], + offset: offset, + mapped_binding: binding, + }); + + (mapping, 0) + } else { + (binding, offset) + }; + + Some(Ok(d3d12::D3D12_INPUT_ELEMENT_DESC { + SemanticName: semantic_name.as_ptr() as *const _, // Semantic name used by SPIRV-Cross + SemanticIndex: *semantic_index, + Format: match conv::map_format(format) { + Some(fm) => fm, + None => { + error!("Unable to find DXGI format for {:?}", format); + return Some(Err(pso::CreationError::Other)); + } + }, + InputSlot: input_slot as _, + AlignedByteOffset: offset, + InputSlotClass: slot_class, + InstanceDataStepRate: step_rate as _, + })) + }) + .collect::<Result<Vec<_>, _>>()?; + + // TODO: check maximum number of rtvs + // Get associated subpass information + let pass = { + let subpass = &desc.subpass; + match subpass.main_pass.subpasses.get(subpass.index as usize) { + Some(subpass) => subpass, + None => return Err(pso::CreationError::InvalidSubpass(subpass.index)), + } + }; + + // Get color attachment formats from subpass + let (rtvs, num_rtvs) = { + let mut rtvs = [dxgiformat::DXGI_FORMAT_UNKNOWN; + d3d12::D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT as usize]; + let mut num_rtvs = 0; + for (rtv, target) in rtvs.iter_mut().zip(pass.color_attachments.iter()) { + let format = desc.subpass.main_pass.attachments[target.0].format; + *rtv = format + .and_then(conv::map_format) + .unwrap_or(dxgiformat::DXGI_FORMAT_UNKNOWN); + num_rtvs += 1; + } + (rtvs, num_rtvs) + }; + + let sample_desc = dxgitype::DXGI_SAMPLE_DESC { + Count: match desc.multisampling { + Some(ref ms) => ms.rasterization_samples as _, + None => 1, + }, + Quality: 0, + }; + + // Setup pipeline description + let pso_desc = d3d12::D3D12_GRAPHICS_PIPELINE_STATE_DESC { + pRootSignature: desc.layout.shared.signature.as_mut_ptr(), + VS: *vs.shader(), + PS: *ps.shader(), + GS: *gs.shader(), + DS: *ds.shader(), + HS: *hs.shader(), + StreamOutput: d3d12::D3D12_STREAM_OUTPUT_DESC { + pSODeclaration: ptr::null(), + NumEntries: 0, + pBufferStrides: ptr::null(), + NumStrides: 0, + RasterizedStream: 0, + }, + BlendState: d3d12::D3D12_BLEND_DESC { + AlphaToCoverageEnable: desc.multisampling.as_ref().map_or(FALSE, |ms| { + if ms.alpha_coverage { + TRUE + } else { + FALSE + } + }), + IndependentBlendEnable: TRUE, + RenderTarget: conv::map_render_targets(&desc.blender.targets), + }, + SampleMask: match desc.multisampling { + Some(ref ms) => ms.sample_mask as u32, + None => UINT::max_value(), + }, + RasterizerState: conv::map_rasterizer(&desc.rasterizer, desc.multisampling.is_some()), + DepthStencilState: conv::map_depth_stencil(&desc.depth_stencil), + InputLayout: d3d12::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 input_assembler.restart_index { + Some(hal::IndexType::U16) => d3d12::D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_0xFFFF, + Some(hal::IndexType::U32) => d3d12::D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_0xFFFFFFFF, + None => d3d12::D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_DISABLED, + }, + PrimitiveTopologyType: conv::map_topology_type(input_assembler.primitive), + NumRenderTargets: num_rtvs, + RTVFormats: rtvs, + DSVFormat: pass + .depth_stencil_attachment + .and_then(|att_ref| { + desc.subpass.main_pass.attachments[att_ref.0] + .format + .and_then(|f| conv::map_format_dsv(f.base_format().0)) + }) + .unwrap_or(dxgiformat::DXGI_FORMAT_UNKNOWN), + SampleDesc: sample_desc, + NodeMask: 0, + CachedPSO: d3d12::D3D12_CACHED_PIPELINE_STATE { + pCachedBlob: ptr::null(), + CachedBlobSizeInBytes: 0, + }, + Flags: d3d12::D3D12_PIPELINE_STATE_FLAG_NONE, + }; + let topology = conv::map_topology(input_assembler); + + // Create PSO + let mut pipeline = native::PipelineState::null(); + let hr = if desc.depth_stencil.depth_bounds { + // The DepthBoundsTestEnable option isn't available in the original D3D12_GRAPHICS_PIPELINE_STATE_DESC struct. + // Instead, we must use the newer subobject stream method. + let (device2, hr) = self.raw.cast::<d3d12::ID3D12Device2>(); + if winerror::SUCCEEDED(hr) { + let mut pss_stream = GraphicsPipelineStateSubobjectStream::new(&pso_desc, true); + let pss_desc = d3d12::D3D12_PIPELINE_STATE_STREAM_DESC { + SizeInBytes: mem::size_of_val(&pss_stream), + pPipelineStateSubobjectStream: &mut pss_stream as *mut _ as _, + }; + device2.CreatePipelineState( + &pss_desc, + &d3d12::ID3D12PipelineState::uuidof(), + pipeline.mut_void(), + ) + } else { + hr + } + } else { + self.raw.clone().CreateGraphicsPipelineState( + &pso_desc, + &d3d12::ID3D12PipelineState::uuidof(), + pipeline.mut_void(), + ) + }; + + let destroy_shader = |shader: ShaderBc| { + if let ShaderBc::Owned(bc) = shader { + bc.destroy(); + } + }; + + destroy_shader(vs); + destroy_shader(ps); + destroy_shader(gs); + destroy_shader(hs); + destroy_shader(ds); + + if winerror::SUCCEEDED(hr) { + let mut baked_states = desc.baked_states.clone(); + if !desc.depth_stencil.depth_bounds { + baked_states.depth_bounds = None; + } + + Ok(r::GraphicsPipeline { + raw: pipeline, + shared: Arc::clone(&desc.layout.shared), + topology, + vertex_bindings, + baked_states, + }) + } else { + error!("Failed to build shader: {:x}", hr); + Err(pso::CreationError::Other) + } + } + + unsafe fn create_compute_pipeline<'a>( + &self, + desc: &pso::ComputePipelineDesc<'a, B>, + _cache: Option<&()>, + ) -> Result<r::ComputePipeline, pso::CreationError> { + let (cs, cs_destroy) = Self::extract_entry_point( + ShaderStage::Compute, + &desc.shader, + desc.layout, + &self.features, + ) + .map_err(|err| pso::CreationError::Shader(err))?; + + let (pipeline, hr) = self.raw.create_compute_pipeline_state( + desc.layout.shared.signature, + native::Shader::from_blob(cs), + 0, + native::CachedPSO::null(), + native::PipelineStateFlags::empty(), + ); + + if cs_destroy { + cs.destroy(); + } + + if winerror::SUCCEEDED(hr) { + Ok(r::ComputePipeline { + raw: pipeline, + shared: Arc::clone(&desc.layout.shared), + }) + } else { + error!("Failed to build shader: {:x}", hr); + Err(pso::CreationError::Other) + } + } + + unsafe fn create_framebuffer<I>( + &self, + _renderpass: &r::RenderPass, + attachments: I, + extent: image::Extent, + ) -> Result<r::Framebuffer, d::OutOfMemory> + where + I: IntoIterator, + I::Item: Borrow<r::ImageView>, + { + Ok(r::Framebuffer { + attachments: attachments.into_iter().map(|att| *att.borrow()).collect(), + layers: extent.depth as _, + }) + } + + unsafe fn create_shader_module( + &self, + raw_data: &[u32], + ) -> Result<r::ShaderModule, d::ShaderError> { + Ok(r::ShaderModule::Spirv(raw_data.into())) + } + + unsafe fn create_buffer( + &self, + mut size: u64, + usage: buffer::Usage, + ) -> Result<r::Buffer, buffer::CreationError> { + if usage.contains(buffer::Usage::UNIFORM) { + // Constant buffer view sizes need to be aligned. + // Coupled with the offset alignment we can enforce an aligned CBV size + // on descriptor updates. + let mask = d3d12::D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT as u64 - 1; + size = (size + mask) & !mask; + } + if usage.contains(buffer::Usage::TRANSFER_DST) { + // minimum of 1 word for the clear UAV + size = size.max(4); + } + + let type_mask_shift = if self.private_caps.heterogeneous_resource_heaps { + MEM_TYPE_UNIVERSAL_SHIFT + } else { + MEM_TYPE_BUFFER_SHIFT + }; + + let requirements = memory::Requirements { + size, + alignment: d3d12::D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT as u64, + type_mask: MEM_TYPE_MASK << type_mask_shift, + }; + + Ok(r::Buffer::Unbound(r::BufferUnbound { + requirements, + usage, + name: None, + })) + } + + unsafe fn get_buffer_requirements(&self, buffer: &r::Buffer) -> Requirements { + match buffer { + r::Buffer::Unbound(b) => b.requirements, + r::Buffer::Bound(b) => b.requirements, + } + } + + unsafe fn bind_buffer_memory( + &self, + memory: &r::Memory, + offset: u64, + buffer: &mut r::Buffer, + ) -> Result<(), d::BindError> { + let buffer_unbound = buffer.expect_unbound(); + if buffer_unbound.requirements.type_mask & (1 << memory.type_id) == 0 { + error!( + "Bind memory failure: supported mask 0x{:x}, given id {}", + buffer_unbound.requirements.type_mask, memory.type_id + ); + return Err(d::BindError::WrongMemory); + } + if offset + buffer_unbound.requirements.size > memory.size { + return Err(d::BindError::OutOfBounds); + } + + let mut resource = native::Resource::null(); + let desc = d3d12::D3D12_RESOURCE_DESC { + Dimension: d3d12::D3D12_RESOURCE_DIMENSION_BUFFER, + Alignment: 0, + Width: buffer_unbound.requirements.size, + Height: 1, + DepthOrArraySize: 1, + MipLevels: 1, + Format: dxgiformat::DXGI_FORMAT_UNKNOWN, + SampleDesc: dxgitype::DXGI_SAMPLE_DESC { + Count: 1, + Quality: 0, + }, + Layout: d3d12::D3D12_TEXTURE_LAYOUT_ROW_MAJOR, + Flags: conv::map_buffer_flags(buffer_unbound.usage), + }; + + assert_eq!( + winerror::S_OK, + self.raw.clone().CreatePlacedResource( + memory.heap.as_mut_ptr(), + offset, + &desc, + d3d12::D3D12_RESOURCE_STATE_COMMON, + ptr::null(), + &d3d12::ID3D12Resource::uuidof(), + resource.mut_void(), + ) + ); + + if let Some(ref name) = buffer_unbound.name { + resource.SetName(name.as_ptr()); + } + + let clear_uav = if buffer_unbound.usage.contains(buffer::Usage::TRANSFER_DST) { + let handle = self.srv_uav_pool.lock().alloc_handle(); + let mut view_desc = d3d12::D3D12_UNORDERED_ACCESS_VIEW_DESC { + Format: dxgiformat::DXGI_FORMAT_R32_TYPELESS, + ViewDimension: d3d12::D3D12_UAV_DIMENSION_BUFFER, + u: mem::zeroed(), + }; + + *view_desc.u.Buffer_mut() = d3d12::D3D12_BUFFER_UAV { + FirstElement: 0, + NumElements: (buffer_unbound.requirements.size / 4) as _, + StructureByteStride: 0, + CounterOffsetInBytes: 0, + Flags: d3d12::D3D12_BUFFER_UAV_FLAG_RAW, + }; + + self.raw.CreateUnorderedAccessView( + resource.as_mut_ptr(), + ptr::null_mut(), + &view_desc, + handle.raw, + ); + Some(handle) + } else { + None + }; + + *buffer = r::Buffer::Bound(r::BufferBound { + resource, + requirements: buffer_unbound.requirements, + clear_uav, + }); + + Ok(()) + } + + unsafe fn create_buffer_view( + &self, + buffer: &r::Buffer, + format: Option<format::Format>, + sub: buffer::SubRange, + ) -> Result<r::BufferView, buffer::ViewCreationError> { + let buffer = buffer.expect_bound(); + let buffer_features = { + let idx = format.map(|fmt| fmt as usize).unwrap_or(0); + self.format_properties + .resolve(idx) + .properties + .buffer_features + }; + let (format, format_desc) = match format.and_then(conv::map_format) { + Some(fmt) => (fmt, format.unwrap().surface_desc()), + None => return Err(buffer::ViewCreationError::UnsupportedFormat(format)), + }; + + let start = sub.offset; + let size = sub.size.unwrap_or(buffer.requirements.size - start); + + let bytes_per_texel = (format_desc.bits / 8) as u64; + // Check if it adheres to the texel buffer offset limit + assert_eq!(start % bytes_per_texel, 0); + let first_element = start / bytes_per_texel; + let num_elements = size / bytes_per_texel; // rounds down to next smaller size + + let handle_srv = if buffer_features.contains(format::BufferFeature::UNIFORM_TEXEL) { + let mut desc = d3d12::D3D12_SHADER_RESOURCE_VIEW_DESC { + Format: format, + ViewDimension: d3d12::D3D12_SRV_DIMENSION_BUFFER, + Shader4ComponentMapping: IDENTITY_MAPPING, + u: mem::zeroed(), + }; + + *desc.u.Buffer_mut() = d3d12::D3D12_BUFFER_SRV { + FirstElement: first_element, + NumElements: num_elements as _, + StructureByteStride: bytes_per_texel as _, + Flags: d3d12::D3D12_BUFFER_SRV_FLAG_NONE, + }; + + let handle = self.srv_uav_pool.lock().alloc_handle(); + self.raw.clone().CreateShaderResourceView( + buffer.resource.as_mut_ptr(), + &desc, + handle.raw, + ); + Some(handle) + } else { + None + }; + + let handle_uav = if buffer_features.intersects( + format::BufferFeature::STORAGE_TEXEL | format::BufferFeature::STORAGE_TEXEL_ATOMIC, + ) { + let mut desc = d3d12::D3D12_UNORDERED_ACCESS_VIEW_DESC { + Format: format, + ViewDimension: d3d12::D3D12_UAV_DIMENSION_BUFFER, + u: mem::zeroed(), + }; + + *desc.u.Buffer_mut() = d3d12::D3D12_BUFFER_UAV { + FirstElement: first_element, + NumElements: num_elements as _, + StructureByteStride: bytes_per_texel as _, + Flags: d3d12::D3D12_BUFFER_UAV_FLAG_NONE, + CounterOffsetInBytes: 0, + }; + + let handle = self.srv_uav_pool.lock().alloc_handle(); + self.raw.clone().CreateUnorderedAccessView( + buffer.resource.as_mut_ptr(), + ptr::null_mut(), + &desc, + handle.raw, + ); + Some(handle) + } else { + None + }; + + return Ok(r::BufferView { + handle_srv, + handle_uav, + }); + } + + unsafe fn create_image( + &self, + kind: image::Kind, + mip_levels: image::Level, + format: format::Format, + tiling: image::Tiling, + usage: image::Usage, + view_caps: image::ViewCapabilities, + ) -> Result<r::Image, image::CreationError> { + assert!(mip_levels <= kind.compute_num_levels()); + + let base_format = format.base_format(); + let format_desc = base_format.0.desc(); + let bytes_per_block = (format_desc.bits / 8) as _; + let block_dim = format_desc.dim; + let view_format = conv::map_format(format); + let extent = kind.extent(); + + let format_info = self.format_properties.resolve(format as usize); + let (layout, features) = match tiling { + image::Tiling::Optimal => ( + d3d12::D3D12_TEXTURE_LAYOUT_UNKNOWN, + format_info.properties.optimal_tiling, + ), + image::Tiling::Linear => ( + d3d12::D3D12_TEXTURE_LAYOUT_ROW_MAJOR, + format_info.properties.linear_tiling, + ), + }; + if format_info.sample_count_mask & kind.num_samples() == 0 { + return Err(image::CreationError::Samples(kind.num_samples())); + } + + let desc = d3d12::D3D12_RESOURCE_DESC { + Dimension: match kind { + image::Kind::D1(..) => d3d12::D3D12_RESOURCE_DIMENSION_TEXTURE1D, + image::Kind::D2(..) => d3d12::D3D12_RESOURCE_DIMENSION_TEXTURE2D, + image::Kind::D3(..) => d3d12::D3D12_RESOURCE_DIMENSION_TEXTURE3D, + }, + Alignment: 0, + Width: extent.width as _, + Height: extent.height as _, + DepthOrArraySize: if extent.depth > 1 { + extent.depth as _ + } else { + kind.num_layers() as _ + }, + MipLevels: mip_levels as _, + Format: if format_desc.is_compressed() { + view_format.unwrap() + } else { + match conv::map_surface_type(base_format.0) { + Some(format) => format, + None => return Err(image::CreationError::Format(format)), + } + }, + SampleDesc: dxgitype::DXGI_SAMPLE_DESC { + Count: kind.num_samples() as _, + Quality: 0, + }, + Layout: layout, + Flags: conv::map_image_flags(usage, features), + }; + + let alloc_info = self.raw.clone().GetResourceAllocationInfo(0, 1, &desc); + + // Image flags which require RT/DS heap due to internal implementation. + let target_flags = d3d12::D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET + | d3d12::D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; + let type_mask_shift = if self.private_caps.heterogeneous_resource_heaps { + MEM_TYPE_UNIVERSAL_SHIFT + } else if desc.Flags & target_flags != 0 { + MEM_TYPE_TARGET_SHIFT + } else { + MEM_TYPE_IMAGE_SHIFT + }; + + Ok(r::Image::Unbound(r::ImageUnbound { + view_format, + dsv_format: conv::map_format_dsv(base_format.0), + desc, + requirements: memory::Requirements { + size: alloc_info.SizeInBytes, + alignment: alloc_info.Alignment, + type_mask: MEM_TYPE_MASK << type_mask_shift, + }, + format, + kind, + mip_levels, + usage, + tiling, + view_caps, + bytes_per_block, + block_dim, + name: None, + })) + } + + unsafe fn get_image_requirements(&self, image: &r::Image) -> Requirements { + match image { + r::Image::Bound(i) => i.requirements, + r::Image::Unbound(i) => i.requirements, + } + } + + unsafe fn get_image_subresource_footprint( + &self, + image: &r::Image, + sub: image::Subresource, + ) -> image::SubresourceFootprint { + let mut num_rows = 0; + let mut total_bytes = 0; + let _desc = match image { + r::Image::Bound(i) => i.descriptor, + r::Image::Unbound(i) => i.desc, + }; + let footprint = { + let mut footprint = mem::zeroed(); + self.raw.GetCopyableFootprints( + image.get_desc(), + image.calc_subresource(sub.level as _, sub.layer as _, 0), + 1, + 0, + &mut footprint, + &mut num_rows, + ptr::null_mut(), // row size in bytes + &mut total_bytes, + ); + footprint + }; + + let depth_pitch = (footprint.Footprint.RowPitch * num_rows) as buffer::Offset; + let array_pitch = footprint.Footprint.Depth as buffer::Offset * depth_pitch; + image::SubresourceFootprint { + slice: footprint.Offset..footprint.Offset + total_bytes, + row_pitch: footprint.Footprint.RowPitch as _, + depth_pitch, + array_pitch, + } + } + + unsafe fn bind_image_memory( + &self, + memory: &r::Memory, + offset: u64, + image: &mut r::Image, + ) -> Result<(), d::BindError> { + use self::image::Usage; + + let image_unbound = image.expect_unbound(); + if image_unbound.requirements.type_mask & (1 << memory.type_id) == 0 { + error!( + "Bind memory failure: supported mask 0x{:x}, given id {}", + image_unbound.requirements.type_mask, memory.type_id + ); + return Err(d::BindError::WrongMemory); + } + if offset + image_unbound.requirements.size > memory.size { + return Err(d::BindError::OutOfBounds); + } + + let mut resource = native::Resource::null(); + let num_layers = image_unbound.kind.num_layers(); + + assert_eq!( + winerror::S_OK, + self.raw.clone().CreatePlacedResource( + memory.heap.as_mut_ptr(), + offset, + &image_unbound.desc, + d3d12::D3D12_RESOURCE_STATE_COMMON, + ptr::null(), + &d3d12::ID3D12Resource::uuidof(), + resource.mut_void(), + ) + ); + + if let Some(ref name) = image_unbound.name { + resource.SetName(name.as_ptr()); + } + + let info = ViewInfo { + resource, + kind: image_unbound.kind, + caps: image::ViewCapabilities::empty(), + view_kind: match image_unbound.kind { + image::Kind::D1(..) => image::ViewKind::D1Array, + image::Kind::D2(..) => image::ViewKind::D2Array, + image::Kind::D3(..) => image::ViewKind::D3, + }, + format: image_unbound.desc.Format, + component_mapping: IDENTITY_MAPPING, + levels: 0..1, + layers: 0..0, + }; + + //TODO: the clear_Xv is incomplete. We should support clearing images created without XXX_ATTACHMENT usage. + // for this, we need to check the format and force the `RENDER_TARGET` flag behind the user's back + // if the format supports being rendered into, allowing us to create clear_Xv + let format_properties = self + .format_properties + .resolve(image_unbound.format as usize) + .properties; + let props = match image_unbound.tiling { + image::Tiling::Optimal => format_properties.optimal_tiling, + image::Tiling::Linear => format_properties.linear_tiling, + }; + let can_clear_color = image_unbound + .usage + .intersects(Usage::TRANSFER_DST | Usage::COLOR_ATTACHMENT) + && props.contains(format::ImageFeature::COLOR_ATTACHMENT); + let can_clear_depth = image_unbound + .usage + .intersects(Usage::TRANSFER_DST | Usage::DEPTH_STENCIL_ATTACHMENT) + && props.contains(format::ImageFeature::DEPTH_STENCIL_ATTACHMENT); + let aspects = image_unbound.format.surface_desc().aspects; + + *image = r::Image::Bound(r::ImageBound { + resource: resource, + place: r::Place::Heap { + raw: memory.heap.clone(), + offset, + }, + surface_type: image_unbound.format.base_format().0, + kind: image_unbound.kind, + mip_levels: image_unbound.mip_levels, + usage: image_unbound.usage, + default_view_format: image_unbound.view_format, + view_caps: image_unbound.view_caps, + descriptor: image_unbound.desc, + clear_cv: if aspects.contains(Aspects::COLOR) && can_clear_color { + let format = image_unbound.view_format.unwrap(); + (0..num_layers) + .map(|layer| { + self.view_image_as_render_target(&ViewInfo { + format, + layers: layer..layer + 1, + ..info.clone() + }) + .unwrap() + }) + .collect() + } else { + Vec::new() + }, + clear_dv: if aspects.contains(Aspects::DEPTH) && can_clear_depth { + let format = image_unbound.dsv_format.unwrap(); + (0..num_layers) + .map(|layer| { + self.view_image_as_depth_stencil(&ViewInfo { + format, + layers: layer..layer + 1, + ..info.clone() + }) + .unwrap() + }) + .collect() + } else { + Vec::new() + }, + clear_sv: if aspects.contains(Aspects::STENCIL) && can_clear_depth { + let format = image_unbound.dsv_format.unwrap(); + (0..num_layers) + .map(|layer| { + self.view_image_as_depth_stencil(&ViewInfo { + format, + layers: layer..layer + 1, + ..info.clone() + }) + .unwrap() + }) + .collect() + } else { + Vec::new() + }, + requirements: image_unbound.requirements, + }); + + Ok(()) + } + + unsafe fn create_image_view( + &self, + image: &r::Image, + view_kind: image::ViewKind, + format: format::Format, + swizzle: format::Swizzle, + range: image::SubresourceRange, + ) -> Result<r::ImageView, image::ViewCreationError> { + let image = image.expect_bound(); + let is_array = image.kind.num_layers() > 1; + let mip_levels = ( + range.level_start, + range.level_start + range.resolve_level_count(image.mip_levels), + ); + let layers = ( + range.layer_start, + range.layer_start + range.resolve_layer_count(image.kind.num_layers()), + ); + let surface_format = format.base_format().0; + + let info = ViewInfo { + resource: image.resource, + kind: image.kind, + caps: image.view_caps, + // D3D12 doesn't allow looking at a single slice of an array as a non-array + view_kind: if is_array && view_kind == image::ViewKind::D2 { + image::ViewKind::D2Array + } else if is_array && view_kind == image::ViewKind::D1 { + image::ViewKind::D1Array + } else { + view_kind + }, + format: conv::map_format(format).ok_or(image::ViewCreationError::BadFormat(format))?, + component_mapping: conv::map_swizzle(swizzle), + levels: mip_levels.0..mip_levels.1, + layers: layers.0..layers.1, + }; + + //Note: we allow RTV/DSV/SRV/UAV views to fail to be created here, + // because we don't know if the user will even need to use them. + + Ok(r::ImageView { + resource: image.resource, + handle_srv: if image + .usage + .intersects(image::Usage::SAMPLED | image::Usage::INPUT_ATTACHMENT) + { + let info = if range.aspects.contains(format::Aspects::DEPTH) { + conv::map_format_shader_depth(surface_format).map(|format| ViewInfo { + format, + ..info.clone() + }) + } else if range.aspects.contains(format::Aspects::STENCIL) { + // Vulkan/gfx expects stencil to be read from the R channel, + // while DX12 exposes it in "G" always. + let new_swizzle = conv::swizzle_rg(swizzle); + conv::map_format_shader_stencil(surface_format).map(|format| ViewInfo { + format, + component_mapping: conv::map_swizzle(new_swizzle), + ..info.clone() + }) + } else { + Some(info.clone()) + }; + if let Some(ref info) = info { + self.view_image_as_shader_resource(&info).ok() + } else { + None + } + } else { + None + }, + handle_rtv: if image.usage.contains(image::Usage::COLOR_ATTACHMENT) { + // This view is not necessarily going to be rendered to, even + // if the image supports that in general. + match self.view_image_as_render_target(&info) { + Ok(handle) => r::RenderTargetHandle::Pool(handle), + Err(_) => r::RenderTargetHandle::None, + } + } else { + r::RenderTargetHandle::None + }, + handle_uav: if image.usage.contains(image::Usage::STORAGE) { + self.view_image_as_storage(&info).ok() + } else { + None + }, + handle_dsv: if image.usage.contains(image::Usage::DEPTH_STENCIL_ATTACHMENT) { + match conv::map_format_dsv(surface_format) { + Some(dsv_format) => self + .view_image_as_depth_stencil(&ViewInfo { + format: dsv_format, + ..info + }) + .ok(), + None => None, + } + } else { + None + }, + dxgi_format: image.default_view_format.unwrap(), + num_levels: image.descriptor.MipLevels as image::Level, + mip_levels, + layers, + kind: info.kind, + }) + } + + unsafe fn create_sampler( + &self, + info: &image::SamplerDesc, + ) -> Result<r::Sampler, d::AllocationError> { + if !info.normalized { + warn!("Sampler with unnormalized coordinates is not supported!"); + } + let handle = match self.samplers.map.lock().entry(info.clone()) { + Entry::Occupied(e) => *e.get(), + Entry::Vacant(e) => { + let handle = self.samplers.pool.lock().alloc_handle(); + let info = e.key(); + let op = match info.comparison { + Some(_) => d3d12::D3D12_FILTER_REDUCTION_TYPE_COMPARISON, + None => d3d12::D3D12_FILTER_REDUCTION_TYPE_STANDARD, + }; + self.raw.create_sampler( + handle.raw, + conv::map_filter( + info.mag_filter, + info.min_filter, + info.mip_filter, + op, + info.anisotropy_clamp, + ), + [ + conv::map_wrap(info.wrap_mode.0), + conv::map_wrap(info.wrap_mode.1), + conv::map_wrap(info.wrap_mode.2), + ], + info.lod_bias.0, + info.anisotropy_clamp.map_or(0, |aniso| aniso as u32), + conv::map_comparison(info.comparison.unwrap_or(pso::Comparison::Always)), + info.border.into(), + info.lod_range.start.0..info.lod_range.end.0, + ); + *e.insert(handle) + } + }; + Ok(r::Sampler { handle }) + } + + unsafe fn create_descriptor_pool<I>( + &self, + max_sets: usize, + descriptor_pools: I, + _flags: pso::DescriptorPoolCreateFlags, + ) -> Result<r::DescriptorPool, d::OutOfMemory> + where + I: IntoIterator, + I::Item: Borrow<pso::DescriptorRangeDesc>, + { + // Descriptor pools are implemented as slices of the global descriptor heaps. + // A descriptor pool will occupy a contiguous space in each heap (CBV/SRV/UAV and Sampler) depending + // on the total requested amount of descriptors. + + let mut num_srv_cbv_uav = 0; + let mut num_samplers = 0; + + let descriptor_pools = descriptor_pools + .into_iter() + .map(|desc| *desc.borrow()) + .collect::<Vec<_>>(); + + info!("create_descriptor_pool with {} max sets", max_sets); + for desc in &descriptor_pools { + let content = r::DescriptorContent::from(desc.ty); + debug!("\tcontent {:?}", content); + if content.contains(r::DescriptorContent::CBV) { + num_srv_cbv_uav += desc.count; + } + if content.contains(r::DescriptorContent::SRV) { + num_srv_cbv_uav += desc.count; + } + if content.contains(r::DescriptorContent::UAV) { + num_srv_cbv_uav += desc.count; + } + if content.contains(r::DescriptorContent::SAMPLER) { + num_samplers += desc.count; + } + } + + info!( + "total {} views and {} samplers", + num_srv_cbv_uav, num_samplers + ); + + // Allocate slices of the global GPU descriptor heaps. + let heap_srv_cbv_uav = { + let view_heap = &self.heap_srv_cbv_uav.0; + + let range = match num_srv_cbv_uav { + 0 => 0..0, + _ => self + .heap_srv_cbv_uav + .1 + .lock() + .allocate_range(num_srv_cbv_uav as _) + .map_err(|e| { + warn!("View pool allocation error: {:?}", e); + d::OutOfMemory::Host + })?, + }; + + r::DescriptorHeapSlice { + heap: view_heap.raw.clone(), + handle_size: view_heap.handle_size as _, + range_allocator: RangeAllocator::new(range), + start: view_heap.start, + } + }; + + Ok(r::DescriptorPool { + heap_srv_cbv_uav, + heap_raw_sampler: self.samplers.heap.raw, + pools: descriptor_pools, + max_size: max_sets as _, + }) + } + + unsafe fn create_descriptor_set_layout<I, J>( + &self, + bindings: I, + _immutable_samplers: J, + ) -> Result<r::DescriptorSetLayout, d::OutOfMemory> + where + I: IntoIterator, + I::Item: Borrow<pso::DescriptorSetLayoutBinding>, + J: IntoIterator, + J::Item: Borrow<r::Sampler>, + { + Ok(r::DescriptorSetLayout { + bindings: bindings.into_iter().map(|b| b.borrow().clone()).collect(), + }) + } + + unsafe fn write_descriptor_sets<'a, I, J>(&self, write_iter: I) + where + I: IntoIterator<Item = pso::DescriptorSetWrite<'a, B, J>>, + J: IntoIterator, + J::Item: Borrow<pso::Descriptor<'a, B>>, + { + let mut descriptor_updater = self.descriptor_updater.lock(); + descriptor_updater.reset(); + + let mut accum = descriptors_cpu::MultiCopyAccumulator::default(); + debug!("write_descriptor_sets"); + + for write in write_iter { + let mut offset = write.array_offset as u64; + let mut target_binding = write.binding as usize; + let mut bind_info = &write.set.binding_infos[target_binding]; + debug!( + "\t{:?} binding {} array offset {}", + bind_info, target_binding, offset + ); + let base_sampler_offset = write.set.sampler_offset(write.binding, write.array_offset); + trace!("\tsampler offset {}", base_sampler_offset); + let mut sampler_offset = base_sampler_offset; + let mut desc_samplers = write.set.sampler_origins.borrow_mut(); + + for descriptor in write.descriptors { + // spill over the writes onto the next binding + while offset >= bind_info.count { + assert_eq!(offset, bind_info.count); + target_binding += 1; + bind_info = &write.set.binding_infos[target_binding]; + offset = 0; + } + let mut src_cbv = None; + let mut src_srv = None; + let mut src_uav = None; + + match *descriptor.borrow() { + pso::Descriptor::Buffer(buffer, ref sub) => { + let buffer = buffer.expect_bound(); + + if bind_info.content.is_dynamic() { + // Root Descriptor + let buffer_address = (*buffer.resource).GetGPUVirtualAddress(); + // Descriptor sets need to be externally synchronized according to specification + let dynamic_descriptors = &mut *bind_info.dynamic_descriptors.get(); + dynamic_descriptors[offset as usize].gpu_buffer_location = + buffer_address + sub.offset; + } else { + // Descriptor table + let size = sub.size_to(buffer.requirements.size); + + if bind_info.content.contains(r::DescriptorContent::CBV) { + // Making the size field of buffer requirements for uniform + // buffers a multiple of 256 and setting the required offset + // alignment to 256 allows us to patch the size here. + // We can always enforce the size to be aligned to 256 for + // CBVs without going out-of-bounds. + let mask = + d3d12::D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT - 1; + let desc = d3d12::D3D12_CONSTANT_BUFFER_VIEW_DESC { + BufferLocation: (*buffer.resource).GetGPUVirtualAddress() + + sub.offset, + SizeInBytes: (size as u32 + mask) as u32 & !mask, + }; + let handle = descriptor_updater.alloc_handle(self.raw); + self.raw.CreateConstantBufferView(&desc, handle); + src_cbv = Some(handle); + } + if bind_info.content.contains(r::DescriptorContent::SRV) { + assert_eq!(size % 4, 0); + let mut desc = d3d12::D3D12_SHADER_RESOURCE_VIEW_DESC { + Format: dxgiformat::DXGI_FORMAT_R32_TYPELESS, + Shader4ComponentMapping: IDENTITY_MAPPING, + ViewDimension: d3d12::D3D12_SRV_DIMENSION_BUFFER, + u: mem::zeroed(), + }; + *desc.u.Buffer_mut() = d3d12::D3D12_BUFFER_SRV { + FirstElement: sub.offset as _, + NumElements: (size / 4) as _, + StructureByteStride: 0, + Flags: d3d12::D3D12_BUFFER_SRV_FLAG_RAW, + }; + let handle = descriptor_updater.alloc_handle(self.raw); + self.raw.CreateShaderResourceView( + buffer.resource.as_mut_ptr(), + &desc, + handle, + ); + src_srv = Some(handle); + } + if bind_info.content.contains(r::DescriptorContent::UAV) { + assert_eq!(size % 4, 0); + let mut desc = d3d12::D3D12_UNORDERED_ACCESS_VIEW_DESC { + Format: dxgiformat::DXGI_FORMAT_R32_TYPELESS, + ViewDimension: d3d12::D3D12_UAV_DIMENSION_BUFFER, + u: mem::zeroed(), + }; + *desc.u.Buffer_mut() = d3d12::D3D12_BUFFER_UAV { + FirstElement: sub.offset as _, + NumElements: (size / 4) as _, + StructureByteStride: 0, + CounterOffsetInBytes: 0, + Flags: d3d12::D3D12_BUFFER_UAV_FLAG_RAW, + }; + let handle = descriptor_updater.alloc_handle(self.raw); + self.raw.CreateUnorderedAccessView( + buffer.resource.as_mut_ptr(), + ptr::null_mut(), + &desc, + handle, + ); + src_uav = Some(handle); + } + } + } + pso::Descriptor::Image(image, _layout) => { + if bind_info.content.contains(r::DescriptorContent::SRV) { + src_srv = image.handle_srv.map(|h| h.raw); + } + if bind_info.content.contains(r::DescriptorContent::UAV) { + src_uav = image.handle_uav.map(|h| h.raw); + } + } + pso::Descriptor::CombinedImageSampler(image, _layout, sampler) => { + src_srv = image.handle_srv.map(|h| h.raw); + desc_samplers[sampler_offset] = sampler.handle.raw; + sampler_offset += 1; + } + pso::Descriptor::Sampler(sampler) => { + desc_samplers[sampler_offset] = sampler.handle.raw; + sampler_offset += 1; + } + pso::Descriptor::TexelBuffer(buffer_view) => { + if bind_info.content.contains(r::DescriptorContent::SRV) { + let handle = buffer_view.handle_srv + .expect("SRV handle of the storage texel buffer is zero (not supported by specified format)"); + src_srv = Some(handle.raw); + } + if bind_info.content.contains(r::DescriptorContent::UAV) { + let handle = buffer_view.handle_uav + .expect("UAV handle of the storage texel buffer is zero (not supported by specified format)"); + src_uav = Some(handle.raw); + } + } + } + + if let Some(handle) = src_cbv { + trace!("\tcbv offset {}", offset); + accum.src_views.add(handle, 1); + accum + .dst_views + .add(bind_info.view_range.as_ref().unwrap().at(offset), 1); + } + if let Some(handle) = src_srv { + trace!("\tsrv offset {}", offset); + accum.src_views.add(handle, 1); + accum + .dst_views + .add(bind_info.view_range.as_ref().unwrap().at(offset), 1); + } + if let Some(handle) = src_uav { + let uav_offset = if bind_info.content.contains(r::DescriptorContent::SRV) { + bind_info.count + offset + } else { + offset + }; + trace!("\tuav offset {}", uav_offset); + accum.src_views.add(handle, 1); + accum + .dst_views + .add(bind_info.view_range.as_ref().unwrap().at(uav_offset), 1); + } + + offset += 1; + } + + if sampler_offset != base_sampler_offset { + drop(desc_samplers); + write + .set + .update_samplers(&self.samplers.heap, &self.samplers.origins, &mut accum); + } + } + + accum.flush(self.raw); + } + + unsafe fn copy_descriptor_sets<'a, I>(&self, copy_iter: I) + where + I: IntoIterator, + I::Item: Borrow<pso::DescriptorSetCopy<'a, B>>, + { + let mut accum = descriptors_cpu::MultiCopyAccumulator::default(); + + for copy_wrap in copy_iter { + let copy = copy_wrap.borrow(); + let src_info = ©.src_set.binding_infos[copy.src_binding as usize]; + let dst_info = ©.dst_set.binding_infos[copy.dst_binding as usize]; + + if let (Some(src_range), Some(dst_range)) = + (src_info.view_range.as_ref(), dst_info.view_range.as_ref()) + { + assert!(copy.src_array_offset + copy.count <= src_range.handle.size as usize); + assert!(copy.dst_array_offset + copy.count <= dst_range.handle.size as usize); + let count = copy.count as u32; + accum + .src_views + .add(src_range.at(copy.src_array_offset as _), count); + accum + .dst_views + .add(dst_range.at(copy.dst_array_offset as _), count); + + if (src_info.content & dst_info.content) + .contains(r::DescriptorContent::SRV | r::DescriptorContent::UAV) + { + assert!( + src_info.count as usize + copy.src_array_offset + copy.count + <= src_range.handle.size as usize + ); + assert!( + dst_info.count as usize + copy.dst_array_offset + copy.count + <= dst_range.handle.size as usize + ); + accum.src_views.add( + src_range.at(src_info.count + copy.src_array_offset as u64), + count, + ); + accum.dst_views.add( + dst_range.at(dst_info.count + copy.dst_array_offset as u64), + count, + ); + } + } + + if dst_info.content.contains(r::DescriptorContent::SAMPLER) { + let src_offset = copy + .src_set + .sampler_offset(copy.src_binding, copy.src_array_offset); + let dst_offset = copy + .dst_set + .sampler_offset(copy.dst_binding, copy.dst_array_offset); + let src_samplers = copy.src_set.sampler_origins.borrow(); + let mut dst_samplers = copy.dst_set.sampler_origins.borrow_mut(); + dst_samplers[dst_offset..dst_offset + copy.count] + .copy_from_slice(&src_samplers[src_offset..src_offset + copy.count]); + drop(dst_samplers); + + copy.dst_set.update_samplers( + &self.samplers.heap, + &self.samplers.origins, + &mut accum, + ); + } + } + + accum.flush(self.raw.clone()); + } + + unsafe fn map_memory( + &self, + memory: &r::Memory, + segment: memory::Segment, + ) -> Result<*mut u8, d::MapError> { + let mem = memory + .resource + .expect("Memory not created with a memory type exposing `CPU_VISIBLE`"); + let mut ptr = ptr::null_mut(); + assert_eq!( + winerror::S_OK, + (*mem).Map(0, &d3d12::D3D12_RANGE { Begin: 0, End: 0 }, &mut ptr) + ); + ptr = ptr.offset(segment.offset as isize); + Ok(ptr as *mut _) + } + + unsafe fn unmap_memory(&self, memory: &r::Memory) { + if let Some(mem) = memory.resource { + (*mem).Unmap(0, &d3d12::D3D12_RANGE { Begin: 0, End: 0 }); + } + } + + unsafe fn flush_mapped_memory_ranges<'a, I>(&self, ranges: I) -> Result<(), d::OutOfMemory> + where + I: IntoIterator, + I::Item: Borrow<(&'a r::Memory, memory::Segment)>, + { + for range in ranges { + let &(ref memory, ref segment) = range.borrow(); + if let Some(mem) = memory.resource { + // map and immediately unmap, hoping that dx12 drivers internally cache + // currently mapped buffers. + assert_eq!( + winerror::S_OK, + (*mem).Map(0, &d3d12::D3D12_RANGE { Begin: 0, End: 0 }, ptr::null_mut()) + ); + + let start = segment.offset; + let end = segment.size.map_or(memory.size, |s| start + s); // TODO: only need to be end of current mapping + + (*mem).Unmap( + 0, + &d3d12::D3D12_RANGE { + Begin: start as _, + End: end as _, + }, + ); + } + } + + Ok(()) + } + + unsafe fn invalidate_mapped_memory_ranges<'a, I>(&self, ranges: I) -> Result<(), d::OutOfMemory> + where + I: IntoIterator, + I::Item: Borrow<(&'a r::Memory, memory::Segment)>, + { + for range in ranges { + let &(ref memory, ref segment) = range.borrow(); + if let Some(mem) = memory.resource { + let start = segment.offset; + let end = segment.size.map_or(memory.size, |s| start + s); // TODO: only need to be end of current mapping + + // map and immediately unmap, hoping that dx12 drivers internally cache + // currently mapped buffers. + assert_eq!( + winerror::S_OK, + (*mem).Map( + 0, + &d3d12::D3D12_RANGE { + Begin: start as _, + End: end as _, + }, + ptr::null_mut(), + ) + ); + + (*mem).Unmap(0, &d3d12::D3D12_RANGE { Begin: 0, End: 0 }); + } + } + + Ok(()) + } + + fn create_semaphore(&self) -> Result<r::Semaphore, d::OutOfMemory> { + let fence = self.create_fence(false)?; + Ok(r::Semaphore { raw: fence.raw }) + } + + fn create_fence(&self, signalled: bool) -> Result<r::Fence, d::OutOfMemory> { + Ok(r::Fence { + raw: self.create_raw_fence(signalled), + }) + } + + unsafe fn reset_fence(&self, fence: &r::Fence) -> Result<(), d::OutOfMemory> { + assert_eq!(winerror::S_OK, fence.raw.signal(0)); + Ok(()) + } + + unsafe fn wait_for_fences<I>( + &self, + fences: I, + wait: d::WaitFor, + timeout_ns: u64, + ) -> Result<bool, d::OomOrDeviceLost> + where + I: IntoIterator, + I::Item: Borrow<r::Fence>, + { + let fences = fences.into_iter().collect::<Vec<_>>(); + let mut events = self.events.lock(); + for _ in events.len()..fences.len() { + events.push(native::Event::create(false, false)); + } + + for (&event, fence) in events.iter().zip(fences.iter()) { + synchapi::ResetEvent(event.0); + assert_eq!( + winerror::S_OK, + fence.borrow().raw.set_event_on_completion(event, 1) + ); + } + + let all = match wait { + d::WaitFor::Any => FALSE, + d::WaitFor::All => TRUE, + }; + + let hr = { + // This block handles overflow when converting to u32 and always rounds up + // The Vulkan specification allows to wait more than specified + let timeout_ms = { + if timeout_ns > (<u32>::max_value() as u64) * 1_000_000 { + <u32>::max_value() + } else { + ((timeout_ns + 999_999) / 1_000_000) as u32 + } + }; + + synchapi::WaitForMultipleObjects( + fences.len() as u32, + events.as_ptr() as *const _, + all, + timeout_ms, + ) + }; + + const WAIT_OBJECT_LAST: u32 = winbase::WAIT_OBJECT_0 + winnt::MAXIMUM_WAIT_OBJECTS; + const WAIT_ABANDONED_LAST: u32 = winbase::WAIT_ABANDONED_0 + winnt::MAXIMUM_WAIT_OBJECTS; + match hr { + winbase::WAIT_OBJECT_0..=WAIT_OBJECT_LAST => Ok(true), + winbase::WAIT_ABANDONED_0..=WAIT_ABANDONED_LAST => Ok(true), //TODO? + winerror::WAIT_TIMEOUT => Ok(false), + _ => panic!("Unexpected wait status 0x{:X}", hr), + } + } + + unsafe fn get_fence_status(&self, fence: &r::Fence) -> Result<bool, d::DeviceLost> { + match fence.raw.GetCompletedValue() { + 0 => Ok(false), + 1 => Ok(true), + _ => Err(d::DeviceLost), + } + } + + fn create_event(&self) -> Result<(), d::OutOfMemory> { + unimplemented!() + } + + unsafe fn get_event_status(&self, _event: &()) -> Result<bool, d::OomOrDeviceLost> { + unimplemented!() + } + + unsafe fn set_event(&self, _event: &()) -> Result<(), d::OutOfMemory> { + unimplemented!() + } + + unsafe fn reset_event(&self, _event: &()) -> Result<(), d::OutOfMemory> { + unimplemented!() + } + + unsafe fn free_memory(&self, memory: r::Memory) { + memory.heap.destroy(); + if let Some(buffer) = memory.resource { + buffer.destroy(); + } + } + + unsafe fn create_query_pool( + &self, + query_ty: query::Type, + count: query::Id, + ) -> Result<r::QueryPool, query::CreationError> { + let heap_ty = match query_ty { + query::Type::Occlusion => native::QueryHeapType::Occlusion, + query::Type::PipelineStatistics(_) => native::QueryHeapType::PipelineStatistics, + query::Type::Timestamp => native::QueryHeapType::Timestamp, + }; + + let (query_heap, hr) = self.raw.create_query_heap(heap_ty, count, 0); + assert_eq!(winerror::S_OK, hr); + + Ok(r::QueryPool { + raw: query_heap, + ty: heap_ty, + }) + } + + unsafe fn destroy_query_pool(&self, pool: r::QueryPool) { + pool.raw.destroy(); + } + + unsafe fn get_query_pool_results( + &self, + _pool: &r::QueryPool, + _queries: Range<query::Id>, + _data: &mut [u8], + _stride: buffer::Offset, + _flags: query::ResultFlags, + ) -> Result<bool, d::OomOrDeviceLost> { + unimplemented!() + } + + unsafe fn destroy_shader_module(&self, shader_lib: r::ShaderModule) { + if let r::ShaderModule::Compiled(shaders) = shader_lib { + for (_, blob) in shaders { + blob.destroy(); + } + } + } + + unsafe fn destroy_render_pass(&self, _rp: r::RenderPass) { + // Just drop + } + + unsafe fn destroy_pipeline_layout(&self, layout: r::PipelineLayout) { + layout.shared.signature.destroy(); + } + + unsafe fn destroy_graphics_pipeline(&self, pipeline: r::GraphicsPipeline) { + pipeline.raw.destroy(); + } + + unsafe fn destroy_compute_pipeline(&self, pipeline: r::ComputePipeline) { + pipeline.raw.destroy(); + } + + unsafe fn destroy_framebuffer(&self, _fb: r::Framebuffer) { + // Just drop + } + + unsafe fn destroy_buffer(&self, buffer: r::Buffer) { + match buffer { + r::Buffer::Bound(buffer) => { + if let Some(handle) = buffer.clear_uav { + self.srv_uav_pool.lock().free_handle(handle); + } + buffer.resource.destroy(); + } + r::Buffer::Unbound(_) => {} + } + } + + unsafe fn destroy_buffer_view(&self, view: r::BufferView) { + 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); + } + } + + unsafe fn destroy_image(&self, image: r::Image) { + match image { + r::Image::Bound(image) => { + let mut dsv_pool = self.dsv_pool.lock(); + for handle in image.clear_cv { + self.rtv_pool.lock().free_handle(handle); + } + for handle in image.clear_dv { + dsv_pool.free_handle(handle); + } + for handle in image.clear_sv { + dsv_pool.free_handle(handle); + } + image.resource.destroy(); + } + r::Image::Unbound(_) => {} + } + } + + unsafe fn destroy_image_view(&self, view: r::ImageView) { + if let Some(handle) = view.handle_srv { + self.srv_uav_pool.lock().free_handle(handle); + } + if let Some(handle) = view.handle_uav { + self.srv_uav_pool.lock().free_handle(handle); + } + if let r::RenderTargetHandle::Pool(handle) = view.handle_rtv { + self.rtv_pool.lock().free_handle(handle); + } + if let Some(handle) = view.handle_dsv { + self.dsv_pool.lock().free_handle(handle); + } + } + + unsafe fn destroy_sampler(&self, _sampler: r::Sampler) { + // We don't destroy samplers, they are permanently cached + } + + unsafe fn destroy_descriptor_pool(&self, pool: r::DescriptorPool) { + let view_range = pool.heap_srv_cbv_uav.range_allocator.initial_range(); + if view_range.start < view_range.end { + self.heap_srv_cbv_uav + .1 + .lock() + .free_range(view_range.clone()); + } + } + + unsafe fn destroy_descriptor_set_layout(&self, _layout: r::DescriptorSetLayout) { + // Just drop + } + + unsafe fn destroy_fence(&self, fence: r::Fence) { + fence.raw.destroy(); + } + + unsafe fn destroy_semaphore(&self, semaphore: r::Semaphore) { + semaphore.raw.destroy(); + } + + unsafe fn destroy_event(&self, _event: ()) { + unimplemented!() + } + + fn wait_idle(&self) -> Result<(), d::OutOfMemory> { + for queue in &self.queues { + queue.wait_idle()?; + } + Ok(()) + } + + unsafe fn set_image_name(&self, image: &mut r::Image, name: &str) { + let cwstr = wide_cstr(name); + match *image { + r::Image::Unbound(ref mut image) => image.name = Some(cwstr), + r::Image::Bound(ref image) => { + image.resource.SetName(cwstr.as_ptr()); + } + } + } + + unsafe fn set_buffer_name(&self, buffer: &mut r::Buffer, name: &str) { + let cwstr = wide_cstr(name); + match *buffer { + r::Buffer::Unbound(ref mut buffer) => buffer.name = Some(cwstr), + r::Buffer::Bound(ref buffer) => { + buffer.resource.SetName(cwstr.as_ptr()); + } + } + } + + unsafe fn set_command_buffer_name(&self, command_buffer: &mut cmd::CommandBuffer, name: &str) { + let cwstr = wide_cstr(name); + command_buffer.raw.SetName(cwstr.as_ptr()); + } + + unsafe fn set_semaphore_name(&self, semaphore: &mut r::Semaphore, name: &str) { + let cwstr = wide_cstr(name); + semaphore.raw.SetName(cwstr.as_ptr()); + } + + unsafe fn set_fence_name(&self, fence: &mut r::Fence, name: &str) { + let cwstr = wide_cstr(name); + fence.raw.SetName(cwstr.as_ptr()); + } + + unsafe fn set_framebuffer_name(&self, _framebuffer: &mut r::Framebuffer, _name: &str) { + // ignored + } + + unsafe fn set_render_pass_name(&self, render_pass: &mut r::RenderPass, name: &str) { + render_pass.raw_name = wide_cstr(name); + } + + unsafe fn set_descriptor_set_name(&self, descriptor_set: &mut r::DescriptorSet, name: &str) { + descriptor_set.raw_name = wide_cstr(name); + } + + unsafe fn set_descriptor_set_layout_name( + &self, + _descriptor_set_layout: &mut r::DescriptorSetLayout, + _name: &str, + ) { + // ignored + } + + unsafe fn set_pipeline_layout_name(&self, pipeline_layout: &mut r::PipelineLayout, name: &str) { + let cwstr = wide_cstr(name); + pipeline_layout.shared.signature.SetName(cwstr.as_ptr()); + } + + unsafe fn set_compute_pipeline_name(&self, pipeline: &mut r::ComputePipeline, name: &str) { + let cwstr = wide_cstr(name); + pipeline.raw.SetName(cwstr.as_ptr()); + } + + unsafe fn set_graphics_pipeline_name(&self, pipeline: &mut r::GraphicsPipeline, name: &str) { + let cwstr = wide_cstr(name); + pipeline.raw.SetName(cwstr.as_ptr()); + } +} + +#[test] +fn test_identity_mapping() { + assert_eq!(conv::map_swizzle(format::Swizzle::NO), IDENTITY_MAPPING); +} |