diff --git a/Cargo.lock b/Cargo.lock index 1973f3c..9a0ea84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/spirv-to-dxil-sys/Cargo.toml b/spirv-to-dxil-sys/Cargo.toml index a9f8432..989879d 100644 --- a/spirv-to-dxil-sys/Cargo.toml +++ b/spirv-to-dxil-sys/Cargo.toml @@ -4,8 +4,6 @@ version = "0.1.0" edition = "2021" [lib] -crate-type = ["cdylib"] - [build-dependencies] bindgen = "0.63.0" diff --git a/spirv-to-dxil-sys/build.rs b/spirv-to-dxil-sys/build.rs index ee5db38..8a40fcd 100644 --- a/spirv-to-dxil-sys/build.rs +++ b/spirv-to-dxil-sys/build.rs @@ -1,17 +1,15 @@ -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()); if env::var("DOCS_RS").is_ok() { - println!("cargo:warning=Skipping spirv-to-dxil native build for docs.rs."); - File::create(out_dir.join("bindings.rs")).unwrap(); - return; + println!("cargo:warning=Skipping spirv-to-dxil native build for docs.rs."); + File::create(out_dir.join("bindings.rs")).unwrap(); + 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!"); - -} \ No newline at end of file +} diff --git a/spirv-to-dxil/Cargo.toml b/spirv-to-dxil/Cargo.toml index abfe5a7..91669a2 100644 --- a/spirv-to-dxil/Cargo.toml +++ b/spirv-to-dxil/Cargo.toml @@ -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" \ No newline at end of file diff --git a/spirv-to-dxil/src/config.rs b/spirv-to-dxil/src/config.rs new file mode 100644 index 0000000..fba37ac --- /dev/null +++ b/spirv-to-dxil/src/config.rs @@ -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 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, + } + } +} diff --git a/spirv-to-dxil/src/enums.rs b/spirv-to-dxil/src/enums.rs new file mode 100644 index 0000000..1a6df0d --- /dev/null +++ b/spirv-to-dxil/src/enums.rs @@ -0,0 +1,130 @@ +use bitflags::bitflags; + +pub enum ShaderStage { + None, + Vertex, + TesselationControl, + TesselationEvaluation, + Geometry, + Fragment, + Compute, + Kernel, +} + +impl From 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 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 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; + } +} diff --git a/spirv-to-dxil/src/lib.rs b/spirv-to-dxil/src/lib.rs index 7d12d9a..5b4a511 100644 --- a/spirv-to-dxil/src/lib.rs +++ b/spirv-to-dxil/src/lib.rs @@ -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, +) -> bool { + let num_specializations = specializations.map(|o| o.len()).unwrap_or(0) as u32; + let mut specializations: Option> = + 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, + 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, + stage: ShaderStage, + shader_model_max: ShaderModel, + validator_version_max: ValidatorVersion, + runtime_conf: RuntimeConfig, +) -> Result { + 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"); } } diff --git a/spirv-to-dxil/src/logger.rs b/spirv-to-dxil/src/logger.rs new file mode 100644 index 0000000..b285154 --- /dev/null +++ b/spirv-to-dxil/src/logger.rs @@ -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::(); + 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 = unsafe { + Box::from_raw(logger.priv_.cast()) + }; + + logger.msg + } +} \ No newline at end of file diff --git a/spirv-to-dxil/src/object.rs b/spirv-to-dxil/src/object.rs new file mode 100644 index 0000000..6734f11 --- /dev/null +++ b/spirv-to-dxil/src/object.rs @@ -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) + } + } +} diff --git a/spirv-to-dxil/src/specialization.rs b/spirv-to-dxil/src/specialization.rs new file mode 100644 index 0000000..e18a6b7 --- /dev/null +++ b/spirv-to-dxil/src/specialization.rs @@ -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 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 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(), + } + } +} diff --git a/spirv-to-dxil/test/fragment.spv b/spirv-to-dxil/test/fragment.spv new file mode 100644 index 0000000..1fb90cf Binary files /dev/null and b/spirv-to-dxil/test/fragment.spv differ diff --git a/spirv-to-dxil/test/vertex.spv b/spirv-to-dxil/test/vertex.spv new file mode 100644 index 0000000..67b8491 Binary files /dev/null and b/spirv-to-dxil/test/vertex.spv differ