rs: initial implementation of safe wrapper

This commit is contained in:
chyyran 2023-02-03 19:09:30 -05:00
parent 9afe6b30ca
commit 463a584917
12 changed files with 527 additions and 16 deletions

11
Cargo.lock generated
View file

@ -30,6 +30,12 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bytemuck"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c041d3eab048880cb0b86b256447da3f18859a163c3b8d8893f4e6368abe6393"
[[package]]
name = "cc"
version = "1.0.79"
@ -202,6 +208,11 @@ checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
[[package]]
name = "spirv-to-dxil"
version = "0.1.0"
dependencies = [
"bitflags",
"bytemuck",
"spirv-to-dxil-sys",
]
[[package]]
name = "spirv-to-dxil-sys"

View file

@ -4,8 +4,6 @@ version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[build-dependencies]
bindgen = "0.63.0"

View file

@ -1,5 +1,5 @@
use std::{fs::File, env, path::PathBuf};
use cmake::Config;
use std::{env, fs::File, path::PathBuf};
fn main() {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
@ -9,9 +9,7 @@ fn main() {
return;
}
let cmake_dst = Config::new("native")
.build_target("mesa")
.build();
let cmake_dst = Config::new("native").build_target("mesa").build();
let object_dst = cmake_dst.join("build/mesa/lib");
@ -36,5 +34,4 @@ fn main() {
bindings
.write_to_file(out_dir.join("bindings.rs"))
.expect("Couldn't write bindings!");
}

View file

@ -5,4 +5,11 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
[dependencies]
spirv-to-dxil-sys = { version = "0.1.0", path = "../spirv-to-dxil-sys" }
bitflags = "1.3.2"
[dev-dependencies]
bytemuck = "1.13.0"

View file

@ -0,0 +1,80 @@
/// Configuration options for CBVs
#[derive(Default)]
pub struct ConstantBufferConfig {
pub register_space: u32,
pub base_shader_register: u32,
}
/// Configuration options for SPIR-V YZ flip mode.
#[derive(Default)]
pub struct FlipConfig {
pub mode: crate::FlipMode,
pub y_mask: u16,
pub z_mask: u16,
}
/// Runtime configuration options for the SPIR-V compilation.
pub struct RuntimeConfig {
pub runtime_data_cbv: ConstantBufferConfig,
pub push_constant_cbv: ConstantBufferConfig,
/// Set true if vertex and instance ids have already been converted to
/// zero-based. Otherwise, runtime_data will be required to lower them.
pub zero_based_vertex_instance_id: bool,
/// Set true if workgroup base is known to be zero
pub zero_based_compute_workgroup_id: bool,
pub yz_flip: FlipConfig,
/// The caller supports read-only images to be turned into SRV accesses,
/// which allows us to run the nir_opt_access() pass
pub read_only_images_as_srvs: bool,
/// Force sample rate shading on a fragment shader
pub force_sample_rate_shading: bool,
/// View index needs to be lowered to a UBO lookup
pub lower_view_index: bool,
/// View index also needs to be forwarded to RT layer output
pub lower_view_index_to_rt_layer: bool,
}
impl Default for RuntimeConfig {
fn default() -> Self {
RuntimeConfig {
runtime_data_cbv: ConstantBufferConfig {
register_space: 31,
base_shader_register: 0,
},
push_constant_cbv: Default::default(),
zero_based_vertex_instance_id: true,
zero_based_compute_workgroup_id: false,
yz_flip: Default::default(),
read_only_images_as_srvs: false,
force_sample_rate_shading: false,
lower_view_index: false,
lower_view_index_to_rt_layer: false,
}
}
}
impl From<RuntimeConfig> for spirv_to_dxil_sys::dxil_spirv_runtime_conf {
fn from(value: RuntimeConfig) -> Self {
spirv_to_dxil_sys::dxil_spirv_runtime_conf {
runtime_data_cbv: spirv_to_dxil_sys::dxil_spirv_runtime_conf__bindgen_ty_1 {
register_space: value.runtime_data_cbv.register_space,
base_shader_register: value.runtime_data_cbv.base_shader_register,
},
push_constant_cbv: spirv_to_dxil_sys::dxil_spirv_runtime_conf__bindgen_ty_2 {
register_space: value.push_constant_cbv.register_space,
base_shader_register: value.push_constant_cbv.base_shader_register,
},
zero_based_vertex_instance_id: value.zero_based_vertex_instance_id,
zero_based_compute_workgroup_id: value.zero_based_compute_workgroup_id,
yz_flip: spirv_to_dxil_sys::dxil_spirv_runtime_conf__bindgen_ty_3 {
mode: value.yz_flip.mode.bits(),
y_mask: value.yz_flip.y_mask,
z_mask: value.yz_flip.z_mask,
},
read_only_images_as_srvs: value.read_only_images_as_srvs,
force_sample_rate_shading: value.force_sample_rate_shading,
lower_view_index: value.lower_view_index,
lower_view_index_to_rt_layer: value.lower_view_index_to_rt_layer,
}
}
}

130
spirv-to-dxil/src/enums.rs Normal file
View file

@ -0,0 +1,130 @@
use bitflags::bitflags;
pub enum ShaderStage {
None,
Vertex,
TesselationControl,
TesselationEvaluation,
Geometry,
Fragment,
Compute,
Kernel,
}
impl From<ShaderStage> for spirv_to_dxil_sys::dxil_spirv_shader_stage {
fn from(value: ShaderStage) -> Self {
match value {
ShaderStage::None => spirv_to_dxil_sys::dxil_spirv_shader_stage_DXIL_SPIRV_SHADER_NONE,
ShaderStage::Vertex => {
spirv_to_dxil_sys::dxil_spirv_shader_stage_DXIL_SPIRV_SHADER_VERTEX
}
ShaderStage::TesselationControl => {
spirv_to_dxil_sys::dxil_spirv_shader_stage_DXIL_SPIRV_SHADER_TESS_CTRL
}
ShaderStage::TesselationEvaluation => {
spirv_to_dxil_sys::dxil_spirv_shader_stage_DXIL_SPIRV_SHADER_TESS_EVAL
}
ShaderStage::Geometry => {
spirv_to_dxil_sys::dxil_spirv_shader_stage_DXIL_SPIRV_SHADER_GEOMETRY
}
ShaderStage::Fragment => {
spirv_to_dxil_sys::dxil_spirv_shader_stage_DXIL_SPIRV_SHADER_FRAGMENT
}
ShaderStage::Compute => {
spirv_to_dxil_sys::dxil_spirv_shader_stage_DXIL_SPIRV_SHADER_COMPUTE
}
ShaderStage::Kernel => {
spirv_to_dxil_sys::dxil_spirv_shader_stage_DXIL_SPIRV_SHADER_KERNEL
}
}
}
}
#[non_exhaustive]
pub enum ShaderModel {
ShaderModel6_0,
ShaderModel6_1,
ShaderModel6_2,
ShaderModel6_3,
ShaderModel6_4,
ShaderModel6_5,
ShaderModel6_6,
ShaderModel6_7,
}
impl From<ShaderModel> for spirv_to_dxil_sys::dxil_shader_model {
fn from(value: ShaderModel) -> Self {
match value {
ShaderModel::ShaderModel6_0 => spirv_to_dxil_sys::dxil_shader_model_SHADER_MODEL_6_0,
ShaderModel::ShaderModel6_1 => spirv_to_dxil_sys::dxil_shader_model_SHADER_MODEL_6_1,
ShaderModel::ShaderModel6_2 => spirv_to_dxil_sys::dxil_shader_model_SHADER_MODEL_6_2,
ShaderModel::ShaderModel6_3 => spirv_to_dxil_sys::dxil_shader_model_SHADER_MODEL_6_3,
ShaderModel::ShaderModel6_4 => spirv_to_dxil_sys::dxil_shader_model_SHADER_MODEL_6_4,
ShaderModel::ShaderModel6_5 => spirv_to_dxil_sys::dxil_shader_model_SHADER_MODEL_6_5,
ShaderModel::ShaderModel6_6 => spirv_to_dxil_sys::dxil_shader_model_SHADER_MODEL_6_6,
ShaderModel::ShaderModel6_7 => spirv_to_dxil_sys::dxil_shader_model_SHADER_MODEL_6_7,
}
}
}
#[non_exhaustive]
pub enum ValidatorVersion {
None,
Validator1_0,
Validator1_1,
Validator1_2,
Validator1_3,
Validator1_4,
Validator1_5,
Validator1_6,
Validator1_7,
}
impl From<ValidatorVersion> for spirv_to_dxil_sys::dxil_validator_version {
fn from(value: ValidatorVersion) -> Self {
match value {
ValidatorVersion::None => spirv_to_dxil_sys::dxil_validator_version_NO_DXIL_VALIDATION,
ValidatorVersion::Validator1_0 => {
spirv_to_dxil_sys::dxil_validator_version_DXIL_VALIDATOR_1_0
}
ValidatorVersion::Validator1_1 => {
spirv_to_dxil_sys::dxil_validator_version_DXIL_VALIDATOR_1_1
}
ValidatorVersion::Validator1_2 => {
spirv_to_dxil_sys::dxil_validator_version_DXIL_VALIDATOR_1_2
}
ValidatorVersion::Validator1_3 => {
spirv_to_dxil_sys::dxil_validator_version_DXIL_VALIDATOR_1_3
}
ValidatorVersion::Validator1_4 => {
spirv_to_dxil_sys::dxil_validator_version_DXIL_VALIDATOR_1_4
}
ValidatorVersion::Validator1_5 => {
spirv_to_dxil_sys::dxil_validator_version_DXIL_VALIDATOR_1_5
}
ValidatorVersion::Validator1_6 => {
spirv_to_dxil_sys::dxil_validator_version_DXIL_VALIDATOR_1_6
}
ValidatorVersion::Validator1_7 => {
spirv_to_dxil_sys::dxil_validator_version_DXIL_VALIDATOR_1_7
}
}
}
}
bitflags! {
#[derive(Default)]
pub struct FlipMode: spirv_to_dxil_sys::dxil_spirv_yz_flip_mode {
/// No YZ flip
const NONE = spirv_to_dxil_sys::dxil_spirv_yz_flip_mode_DXIL_SPIRV_YZ_FLIP_NONE;
/// Y-flip is unconditional: pos.y = -pos.y
const Y_FLIP_UNCONDITIONAL = spirv_to_dxil_sys::dxil_spirv_yz_flip_mode_DXIL_SPIRV_Y_FLIP_UNCONDITIONAL;
/// Z-flip is unconditional: pos.z = -pos.z + 1.0f
const Z_FLIP_UNCONDITIONAL = spirv_to_dxil_sys::dxil_spirv_yz_flip_mode_DXIL_SPIRV_Z_FLIP_UNCONDITIONAL;
const YZ_FLIP_UNCONDITIONAL = spirv_to_dxil_sys::dxil_spirv_yz_flip_mode_DXIL_SPIRV_YZ_FLIP_UNCONDITIONAL;
const Y_FLIP_CONDITIONAL = spirv_to_dxil_sys::dxil_spirv_yz_flip_mode_DXIL_SPIRV_Y_FLIP_CONDITIONAL;
/// Z-flip is unconditional: pos.z = -pos.z + 1.0f
const Z_FLIP_CONDITIONAL = spirv_to_dxil_sys::dxil_spirv_yz_flip_mode_DXIL_SPIRV_Z_FLIP_CONDITIONAL;
const YZ_FLIP_CONDITIONAL = spirv_to_dxil_sys::dxil_spirv_yz_flip_mode_DXIL_SPIRV_YZ_FLIP_CONDITIONAL;
}
}

View file

@ -1,5 +1,134 @@
pub fn add(left: usize, right: usize) -> usize {
left + right
mod config;
mod enums;
mod object;
mod specialization;
mod logger;
pub use config::*;
pub use enums::*;
pub use object::*;
pub use specialization::*;
use std::mem::MaybeUninit;
use spirv_to_dxil_sys::dxil_spirv_object;
pub use spirv_to_dxil_sys::DXIL_SPIRV_MAX_VIEWPORT;
use crate::logger::Logger;
fn spirv_to_dxil_inner(
spirv_words: &[u32],
specializations: Option<&[Specialization]>,
entry_point: &[u8],
stage: ShaderStage,
shader_model_max: ShaderModel,
validator_version_max: ValidatorVersion,
runtime_conf: RuntimeConfig,
dump_nir: bool,
logger: &spirv_to_dxil_sys::dxil_spirv_logger,
out: &mut MaybeUninit<dxil_spirv_object>,
) -> bool {
let num_specializations = specializations.map(|o| o.len()).unwrap_or(0) as u32;
let mut specializations: Option<Vec<spirv_to_dxil_sys::dxil_spirv_specialization>> =
specializations.map(|o| o.into_iter().map(|o| (*o).into()).collect());
let runtime_conf: spirv_to_dxil_sys::dxil_spirv_runtime_conf =
runtime_conf.into();
let debug = spirv_to_dxil_sys::dxil_spirv_debug_options { dump_nir };
unsafe {
spirv_to_dxil_sys::spirv_to_dxil(
spirv_words.as_ptr(),
spirv_words.len(),
specializations
.as_mut()
.map_or(std::ptr::null_mut(), |x| x.as_mut_ptr()),
num_specializations,
stage.into(),
entry_point.as_ptr().cast(),
shader_model_max.into(),
validator_version_max.into(),
&debug,
&runtime_conf,
logger,
out.as_mut_ptr(),
)
}
}
/// Dump NIR to stdout.
pub fn dump_nir(
spirv_words: &[u32],
specializations: Option<&[Specialization]>,
entry_point: impl AsRef<str>,
stage: ShaderStage,
shader_model_max: ShaderModel,
validator_version_max: ValidatorVersion,
runtime_conf: RuntimeConfig,
) -> bool {
let entry_point = entry_point.as_ref();
let mut entry_point = String::from(entry_point).into_bytes();
entry_point.push(0);
let mut out = MaybeUninit::uninit();
spirv_to_dxil_inner(
spirv_words,
specializations,
&entry_point,
stage,
shader_model_max,
validator_version_max,
runtime_conf,
true,
&logger::DEBUG_LOGGER,
&mut out,
)
}
/// Compile SPIR-V words to a DXIL blob.
pub fn spirv_to_dxil(
spirv_words: &[u32],
specializations: Option<&[Specialization]>,
entry_point: impl AsRef<str>,
stage: ShaderStage,
shader_model_max: ShaderModel,
validator_version_max: ValidatorVersion,
runtime_conf: RuntimeConfig,
) -> Result<DxilObject, String> {
let entry_point = entry_point.as_ref();
let mut entry_point = String::from(entry_point).into_bytes();
entry_point.push(0);
let logger = Logger::new();
let logger = logger.into_logger();
let mut out = MaybeUninit::uninit();
let result = spirv_to_dxil_inner(
spirv_words,
specializations,
&entry_point,
stage,
shader_model_max,
validator_version_max,
runtime_conf,
false,
&logger,
&mut out,
);
let logger = unsafe {
Logger::finalize(logger)
};
if result {
let out = unsafe { out.assume_init() };
Ok(DxilObject::new(out))
} else {
Err(logger)
}
}
#[cfg(test)]
@ -7,8 +136,29 @@ mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
fn dump_nir() {
let fragment: &[u8] = include_bytes!("../test/fragment.spv");
let fragment = Vec::from(fragment);
let fragment = bytemuck::cast_slice(&fragment);
super::dump_nir(&fragment,
None, "main",
ShaderStage::Fragment,
ShaderModel::ShaderModel6_0, ValidatorVersion::None,
RuntimeConfig::default());
}
#[test]
fn test_compile() {
let fragment: &[u8] = include_bytes!("../test/fragment.spv");
let fragment = Vec::from(fragment);
let fragment = bytemuck::cast_slice(&fragment);
super::spirv_to_dxil(&fragment,
None, "main",
ShaderStage::Fragment,
ShaderModel::ShaderModel6_0, ValidatorVersion::None,
RuntimeConfig::default())
.expect("failed to compile");
}
}

View file

@ -0,0 +1,56 @@
use std::ffi::CStr;
use std::os::raw::{c_char, c_void};
extern "C" fn stdout_logger(_: *mut c_void, msg: *const c_char) {
let msg = unsafe {
CStr::from_ptr(msg)
};
println!("[spirv-to-dxil] {:?}", msg)
}
pub(crate) const DEBUG_LOGGER: spirv_to_dxil_sys::dxil_spirv_logger = spirv_to_dxil_sys::dxil_spirv_logger {
priv_: std::ptr::null_mut(),
log: Some(stdout_logger),
};
extern "C" fn string_logger(out: *mut c_void, msg: *const c_char) {
let logger = out.cast::<Logger>();
let str = unsafe {
std::ptr::addr_of_mut!((*logger).msg)
.as_mut().unwrap()
};
let msg = unsafe {
CStr::from_ptr(msg)
};
str.push_str(msg.to_string_lossy().as_ref())
}
pub(crate) struct Logger {
msg: String
}
impl Logger {
pub fn new() -> Logger {
Logger {
msg: String::new()
}
}
pub fn into_logger(self) -> spirv_to_dxil_sys::dxil_spirv_logger {
spirv_to_dxil_sys::dxil_spirv_logger {
priv_: Box::into_raw(Box::new(self)).cast(),
log: Some(string_logger),
}
}
pub unsafe fn finalize(logger: spirv_to_dxil_sys::dxil_spirv_logger) -> String {
let logger: Box<Logger> = unsafe {
Box::from_raw(logger.priv_.cast())
};
logger.msg
}
}

View file

@ -0,0 +1,33 @@
use std::ops::Deref;
/// A compiled DXIL artifact.
pub struct DxilObject {
inner: spirv_to_dxil_sys::dxil_spirv_object,
}
impl Drop for DxilObject {
fn drop(&mut self) {
unsafe {
// SAFETY:
// spirv_to_dxil_free frees only the interior buffer.
// https://gitlab.freedesktop.org/mesa/mesa/-/blob/7b0d00034201f8284a41370c0c3326736ae1134c/src/microsoft/spirv_to_dxil/spirv_to_dxil.c#L118
spirv_to_dxil_sys::spirv_to_dxil_free(&mut self.inner)
}
}
}
impl DxilObject {
pub(crate) fn new(raw: spirv_to_dxil_sys::dxil_spirv_object) -> Self {
Self { inner: raw }
}
}
impl Deref for DxilObject {
type Target = [u8];
fn deref(&self) -> &Self::Target {
unsafe {
std::slice::from_raw_parts(self.inner.binary.buffer.cast(), self.inner.binary.size)
}
}
}

View file

@ -0,0 +1,49 @@
#[derive(Debug, Copy, Clone)]
pub enum ConstValue {
Bool(bool),
Float32(f32),
Float64(f64),
Int8(i8),
Uint8(u8),
Int16(i16),
Uint16(u16),
Int32(i32),
Uint32(u32),
Int64(i64),
Uint64(u64),
}
impl From<ConstValue> for spirv_to_dxil_sys::dxil_spirv_const_value {
fn from(value: ConstValue) -> Self {
match value {
ConstValue::Bool(b) => spirv_to_dxil_sys::dxil_spirv_const_value { b },
ConstValue::Float32(f32_) => spirv_to_dxil_sys::dxil_spirv_const_value { f32_ },
ConstValue::Float64(f64_) => spirv_to_dxil_sys::dxil_spirv_const_value { f64_ },
ConstValue::Int8(i8_) => spirv_to_dxil_sys::dxil_spirv_const_value { i8_ },
ConstValue::Uint8(u8_) => spirv_to_dxil_sys::dxil_spirv_const_value { u8_ },
ConstValue::Int16(i16_) => spirv_to_dxil_sys::dxil_spirv_const_value { i16_ },
ConstValue::Uint16(u16_) => spirv_to_dxil_sys::dxil_spirv_const_value { u16_ },
ConstValue::Int32(i32_) => spirv_to_dxil_sys::dxil_spirv_const_value { i32_ },
ConstValue::Uint32(u32_) => spirv_to_dxil_sys::dxil_spirv_const_value { u32_ },
ConstValue::Int64(i64_) => spirv_to_dxil_sys::dxil_spirv_const_value { i64_ },
ConstValue::Uint64(u64_) => spirv_to_dxil_sys::dxil_spirv_const_value { u64_ },
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct Specialization {
pub id: u32,
pub value: ConstValue,
pub defined_on_module: bool,
}
impl From<Specialization> for spirv_to_dxil_sys::dxil_spirv_specialization {
fn from(value: Specialization) -> Self {
Self {
id: value.id,
defined_on_module: value.defined_on_module,
value: value.value.into(),
}
}
}

Binary file not shown.

Binary file not shown.