Awesome
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
andset_*(&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
andDXGI
prefixes have been stripped from all types, functions and enum variants (e.g. this library exposesCommandListType::Direct
instead ofD3D12_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 ofGetGPUDescriptorHandleForHeapStart
). 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 is1.606.3
(aka606
) - PIX markers (they require enabling
pix
feature which is off by default not to introduce a dependency onWinPixEventRuntime.dll
for people who don't need it) - automatic COM object reference counting via
Clone
andDrop
traits implementations with optional logging possibilities (e.g. seeimpl_com_object_refcount_named
macro) - D3D12 debug callback support (please note that
debug_callback
feature needs to be activated explicitly sinceID3D12InfoQueue1
interface is only supported on Windows 11), object autonaming and GPU validation - convenience macros for wrapping API calls (
dx_call!
anddx_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 asunsafe
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. Sounsafe
is reserved for something unsafe that happens on Rust side, e.g. accessing unions (seeClearValue::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:
- hello_triangle
- hello_texture (based on Microsoft sample)
- dynamic_indexing_sm66 (based on Microsoft sample with changes related to using SM6.6 dynamic resources and Agility SDK exports)
- hello_mesh_shaders (loosely based on Microsoft sample)
- heterogeneous_multiadapter (closely follows Microsoft sample, so currently it is the most recommended sample to start exploring these bindings if you want to compare them to C++ code line-by-line)
- interprocess_communication (demonstrates usage of a shared heap by two processes - producer and consumer)
- n_body_gravity (based on Microsoft sample, but uses a different threading model).
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:
- run
python tools/conversion_assist.py struct
- 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, }
- Press
Enter
. - 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) } } }
- Note that the raw untyped enumeration
D3D12_ROOT_DESCRIPTOR_FLAGS
was automatically changed to the correspondent wrapperRootDescriptorFlags
in the signatures of the getter and setters: this is possible since the script parsesenum_wrappers.rs
for the already known types and tries to recognize them. - 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). - Add the final type definition to
struct_wrappers.rs
and open a PR :)
- run
-
to generate enum wrapper:
- run
python tools/conversion_assist.py enum
- 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;
- 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'sD3D12_DESCRIPTOR_RANGE_TYPE_D3D12_DESCRIPTOR_RANGE_TYPE_
- Paste the autogenerated enum definition to src/enum_wrappers.rs.
- run
-
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
.