/rusty-d3d12

Primary LanguageRustMIT LicenseMIT

Documentation Crates.io

rusty-d3d12

This project provides low-level bindings for D3D12 API. It utilizes rust-bindgen for generating raw bindings (unlike d3d12-rs crate), but aims for providing idiomatic APIs (unlike the raw D3D12 wrappers from winapi or windows-rs crates).

Features

  • wrappers for ID3D12* interfaces and POD structs. The latter are marked as #[repr(transparent)] so that they can be used as a drop-in replacement for the native types, but expose type-safe getters and setters. The setters have two forms: with_*(mut self, ...) -> Self and set_*(&mut self, ...) -> &mut Self and are intended for building new structures and modifying the existing ones, respectively
  • type-safe wrappers for D3D12 enumerations and bit flags (see enum_wrappers.rs for details)
  • D3D12 and DXGI prefixes have been stripped from all types, functions and enum variants (e.g. this library exposes CommandListType::Direct instead of D3D12_COMMAND_LIST_TYPE_DIRECT) since it's very likely that people who use it already know the name of the API it wraps (it's mentioned in the crate name after all), and do not need to be constantly reminded about it :) Also all type and function names have been reshaped with respect to the official Rust code style (e.g. get_gpu_descriptor_handle_for_heap_start instead of GetGPUDescriptorHandleForHeapStart). Note that most, but not all the enum variant names have been converted yet, so some of them will be changed in future versions
  • D3D12 Agility SDK is integrated into the library and shipped along with it (see heterogeneous_multiadapter.rs for an example of exporting required symbols). Current SDK version is 1.606.3 (aka 606)
  • PIX markers (they require enabling pix feature which is off by default not to introduce a dependency on WinPixEventRuntime.dll for people who don't need it)
  • automatic COM object reference counting via Clone and Drop traits implementations with optional logging possibilities (e.g. see impl_com_object_refcount_named macro)
  • D3D12 debug callback support (please note that debug_callback feature needs to be activated explicitly since ID3D12InfoQueue1 interface is only supported on Windows 11), object autonaming and GPU validation
  • convenience macros for wrapping API calls (dx_call! and dx_try!)
  • not yet covered APIs can be accessed through raw bindings exports, and new APIs can be wrapped in semi-automatic mode with the help of conversion_assist.py script
  • most of the APIs provided by rusty-d3d12 are not marked as unsafe since it pollutes client code while giving little in return: obviously, a lot of bad things can happen due to misusing D3D12, but guarding against something like that is a task for a high-level graphics library or engine. So unsafe is reserved for something unsafe that happens on Rust side, e.g. accessing unions (see ClearValue::color())

Examples

  • create debug controller and enable validations:
let debug_controller = Debug::new().expect("cannot create debug controller");
debug_controller.enable_debug_layer();
debug_controller.enable_gpu_based_validation();
debug_controller.enable_object_auto_name();
  • create a descriptor heap:
let rtv_heap = device
    .create_descriptor_heap(
        &DescriptorHeapDesc::default()
            .with_heap_type(DescriptorHeapType::Rtv)
            .with_num_descriptors(FRAMES_IN_FLIGHT),
    )
    .expect("Cannot create RTV heap");
rtv_heap
    .set_name("RTV heap")
    .expect("Cannot set RTV heap name");
  • check if cross-adapter textures are supported:
let mut feature_data = FeatureDataOptions::default();
device
    .check_feature_support(Feature::D3D12Options, &mut feature_data)
    .expect("Cannot check feature support");

let cross_adapter_textures_supported = feature_data.cross_adapter_row_major_texture_supported();
  • create mesh shader PSO:
let ms_bytecode = ShaderBytecode::new(&mesh_shader);
let ps_bytecode = ShaderBytecode::new(&pixel_shader);

let pso_subobjects_desc = MeshShaderPipelineStateDesc::default()
    .with_root_signature(root_signature)
    .with_ms_bytecode(&ms_bytecode)
    .with_ps_bytecode(&ps_bytecode)
    .with_rasterizer_state(
        RasterizerDesc::default().with_depth_clip_enable(false),
    )
    .with_blend_state(BlendDesc::default())
    .with_depth_stencil_state(
        DepthStencilDesc::default().with_depth_enable(false),
    )
    .with_primitive_topology_type(PrimitiveTopologyType::Triangle)
    .with_rtv_formats(&[Format::R8G8B8A8Unorm]);

let pso_desc = PipelineStateStreamDesc::default()
    .with_pipeline_state_subobject_stream(
        pso_subobjects_desc.as_byte_stream(),
    );

let pso = device
    .create_pipeline_state(&pso_desc)
    .expect("Cannot create PSO");

Several runnable samples can be found in examples directory. Please note their code can be dirty and contains some (non-critical) bugs, so they should not be treated as sane D3D12 tutorials or high-quality Rust code examples since their purpose is just to showcase the API.

Currently implemented examples include:

The next planned goal for this project is to cover DXR APIs and provide the corresponding samples.

API stability

Currently the library is under active development, so breaking changes can happen between minor releases (but should not happen between patch releases). After publishing version 1.0 standard semantic versioning will be applied.

Making changes

As mentioned above, the library is still a work-in-progress, so all contributions are welcome :)

How to add a struct or enum that is missing

If the type in question is already present in the pre-generated d3d12.rs, you can use conversion_assist.py script to generate most (or sometimes all) of the code for you.

  • to generate a struct wrapper:

    1. run python tools/conversion_assist.py struct
    2. paste the raw definition of the struct without attributes and derives (i.e. starting from pub struct) and without impl blocks, e.g.:
    pub struct D3D12_ROOT_DESCRIPTOR1 {
        pub ShaderRegister: UINT,
        pub RegisterSpace: UINT,
        pub Flags: D3D12_ROOT_DESCRIPTOR_FLAGS,
    }
    1. Press Enter.
    2. The script will provide you with the boilerplate wrapper struct definition, e.g.
    /// Wrapper around D3D12_ROOT_DESCRIPTOR1 structure
    #[derive(Default, Debug, Hash, PartialOrd, Ord, PartialEq, Eq, Clone)]
    #[repr(transparent)]
    pub struct RootDescriptor(pub(crate) D3D12_ROOT_DESCRIPTOR1);
    
    impl RootDescriptor {
        pub fn set_shader_register(&mut self, shader_register: u32) -> &mut Self {
            self.0.ShaderRegister = shader_register;
            self
        }
    
        pub fn with_shader_register(mut self, shader_register: u32) -> Self {
            self.set_shader_register(shader_register);
            self
        }
    
        pub fn shader_register(&self) -> u32 {
            self.0.ShaderRegister
        }
    
        pub fn set_register_space(&mut self, register_space: u32) -> &mut Self {
            self.0.RegisterSpace = register_space;
            self
        }
    
        pub fn with_register_space(mut self, register_space: u32) -> Self {
            self.set_register_space(register_space);
            self
        }
    
        pub fn register_space(&self) -> u32 {
            self.0.RegisterSpace
        }
    
        pub fn set_flags(&mut self, flags: RootDescriptorFlags) -> &mut Self {
            self.0.Flags = flags.bits();
            self
        }
    
        pub fn with_flags(mut self, flags: RootDescriptorFlags) -> Self {
            self.set_flags(flags);
            self
        }
    
        pub fn flags(&self) -> RootDescriptorFlags {
            unsafe { RootDescriptorFlags::from_bits_unchecked(self.0.Flags) }
        }
    }
    1. Note that the raw untyped enumeration D3D12_ROOT_DESCRIPTOR_FLAGS was automatically changed to the correspondent wrapper RootDescriptorFlags in the signatures of the getter and setters: this is possible since the script parses enum_wrappers.rs for the already known types and tries to recognize them.
    2. If needed (i.e. if the original struct contains raw pointers), add the PhantomData's with lifetime specifiers (please see src/struct_wrappers.rs for examples).
    3. Add the final type definition to struct_wrappers.rs and open a PR :)
  • to generate enum wrapper:

    1. run python tools/conversion_assist.py enum
    2. paste enum variants and the type alias from d3d12.rs:
    pub const D3D12_DESCRIPTOR_RANGE_TYPE_D3D12_DESCRIPTOR_RANGE_TYPE_SRV:
        D3D12_DESCRIPTOR_RANGE_TYPE = 0;
    pub const D3D12_DESCRIPTOR_RANGE_TYPE_D3D12_DESCRIPTOR_RANGE_TYPE_UAV:
        D3D12_DESCRIPTOR_RANGE_TYPE = 1;
    pub const D3D12_DESCRIPTOR_RANGE_TYPE_D3D12_DESCRIPTOR_RANGE_TYPE_CBV:
        D3D12_DESCRIPTOR_RANGE_TYPE = 2;
    pub const D3D12_DESCRIPTOR_RANGE_TYPE_D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER:
        D3D12_DESCRIPTOR_RANGE_TYPE = 3;
    pub type D3D12_DESCRIPTOR_RANGE_TYPE = ::std::os::raw::c_int;
    1. As you could notice, rust-bindgen duplicates enum name in each of the variants, so the script will ask you about the part you'd like to strip from the variants; in this case it's D3D12_DESCRIPTOR_RANGE_TYPE_D3D12_DESCRIPTOR_RANGE_TYPE_
    2. Paste the autogenerated enum definition to src/enum_wrappers.rs.
  • if your enum variants are not exclusive (i.e. can be OR'ed together etc.), then follow the same procedure, but use python tools/conversion_assist.py flags: the script will generate bitflags definition.

How to add a missing function

Unfortunately conversion_assist.py doesn't support generating function definitions yet, so it should be done manually :( Please refer to lib.rs for examples.

Running rust-bindgen

If the required function or type is not yet present in the shipped d3d12.rs (i.e. the new Agility SDK has come out but has not been integrated into rusty-d3d12 yet), then running rust-bindgen on the workspace is required after updating Agility SDK.

When used as a Cargo dependency, rusty-d3d12 does not generate bindings during build process by default (besides increasing build times, running rust-bindgen requires libclang.dll, which can be absent on some systems, and cannot be vendored via crates.io due to its large size). So as a prerequisite, Cargo should be able to find this DLL under the path set in LIBCLANG_PATH environment variable. After this requirement is met, Cargo feature devel can be activated, and d3d12_bindings.rs and pix_bindings.rs files will be generated from scratch, and included into src/raw_bindings/mod.rs instead of the shipped ones. Of course, enabling this feature and copying libclang.dll is not required if one doesn't need to update Agility SDK headers and just wants to wrap some APIs that are already present in the shipped d3d12.rs but not yet covered by this library.

After generating the new raw bindings file (d3d12_bindings.rs, please see the build script for details) using rust-bindgen it should be copied from $OUT_DIR to src/raw_bindings directory and renamed into d3d12.rs.