From 5abe4533baa7502c3c99c191785351d241f7089d Mon Sep 17 00:00:00 2001 From: Lauro Oyen Date: Sat, 2 Mar 2024 10:51:46 +0100 Subject: [PATCH] Initial commit --- .gitignore | 6 ++ Cargo.toml | 14 +++ LICENSE-APACHE | 201 ++++++++++++++++++++++++++++++++++++++ LICENSE-MIT | 19 ++++ README.md | 45 +++++++++ slang-sys/Cargo.toml | 10 ++ slang-sys/build.rs | 119 ++++++++++++++++++++++ slang-sys/src/lib.rs | 123 +++++++++++++++++++++++ src/lib.rs | 228 +++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 765 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 README.md create mode 100644 slang-sys/Cargo.toml create mode 100644 slang-sys/build.rs create mode 100644 slang-sys/src/lib.rs create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7067353 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +debug/ +target/ +Cargo.lock + +.cargo/ +.vscode/ diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..66858b5 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "slang" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +publish = false + +[dependencies] +slang-sys = { path = "slang-sys" } + +[workspace] +members = [ + "slang-sys" +] diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..f49a4e1 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..6802bc4 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,19 @@ +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..39f5549 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# Slang Rust Bindings + +Rust bindings for the [Slang](https://github.com/shader-slang/slang/) shader language compiler. In contrast to existing bindings, these internally use Slang's COM/C++ API because the old C API is soon to be deprecated. + +Currently mostly reflects the needs of our own [engine](https://github.com/FloatyMonkey/engine) but issues and pull requests are more than welcome. + +## Example + +```rust +let session = slang::GlobalSession::new(); + +let mut compile_request = session.create_compile_request(); + +compile_request + .set_codegen_target(slang::CompileTarget::Dxil) + .set_target_profile(session.find_profile("sm_6_5")); + +let entry_point = compile_request + .add_translation_unit(slang::SourceLanguage::Slang, None) + .add_source_file(filepath) + .add_entry_point("main", slang::Stage::Compute); + +let shader_bytecode = compile_request + .compile() + .expect("Shader compilation failed.") + .get_entry_point_code(entry_point); +``` + +## Installation + +Add the following to the `[dependencies]` section of your `Cargo.toml`: + +```toml +slang = { git = "https://github.com/FloatyMonkey/slang-rs.git" } +``` + +Set the `SLANG_DIR` environment variable to the path of your Slang installation. Download the latest release from their [releases page](https://github.com/shader-slang/slang/releases). Copy `slang.dll` to your executable's directory. + +To compile to DXIL bytecode you need the Microsoft DirectXShaderCompiler. Download the latest release from their [releases page](https://github.com/microsoft/DirectXShaderCompiler/releases). Copy `dxil.dll` and `dxcompiler.dll` to your executable's directory. + +## Credits + +Maintained by Lauro Oyen ([@laurooyen](https://github.com/laurooyen)). + +Licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE). diff --git a/slang-sys/Cargo.toml b/slang-sys/Cargo.toml new file mode 100644 index 0000000..48e0237 --- /dev/null +++ b/slang-sys/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "slang-sys" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +publish = false +build = "build.rs" + +[build-dependencies] +bindgen = "0.69.4" diff --git a/slang-sys/build.rs b/slang-sys/build.rs new file mode 100644 index 0000000..c1ba9cb --- /dev/null +++ b/slang-sys/build.rs @@ -0,0 +1,119 @@ +extern crate bindgen; + +use std::env; +use std::path::{Path, PathBuf}; + +fn main() { + let slang_dir = env::var("SLANG_DIR") + .map(PathBuf::from) + .expect("Environment variable `SLANG_DIR` should be set to the directory of a Slang installation. \ + This directory should contain `slang.h` and a `bin` subdirectory."); + + let out_dir = env::var("OUT_DIR") + .map(PathBuf::from) + .expect("Couldn't determine output directory."); + + link_libraries(&slang_dir); + + bindgen::builder() + .header(slang_dir.join("slang.h").to_str().unwrap()) + .clang_arg("-v") + .clang_arg("-xc++") + .clang_arg("-std=c++14") + .allowlist_function("slang_.*") + .allowlist_type("slang.*") + .allowlist_var("SLANG_.*") + .with_codegen_config( + bindgen::CodegenConfig::FUNCTIONS + | bindgen::CodegenConfig::TYPES + | bindgen::CodegenConfig::VARS, + ) + .parse_callbacks(Box::new(ParseCallback {})) + .default_enum_style(bindgen::EnumVariation::Rust { + non_exhaustive: true, + }) + .vtable_generation(true) + .layout_tests(false) + .derive_copy(true) + .generate() + .expect("Couldn't generate bindings.") + .write_to_file(out_dir.join("bindings.rs")) + .expect("Couldn't write bindings."); +} + +fn link_libraries(slang_dir: &Path) { + let target_os = env::var("CARGO_CFG_TARGET_OS") + .expect("Couldn't determine target OS."); + + let target_arch = env::var("CARGO_CFG_TARGET_ARCH") + .expect("Couldn't determine target architecture."); + + let target = match(&*target_os, &*target_arch) { + ("windows", "x86") => "windows-x86", + ("windows", "x86_64") => "windows-x64", + ("windows", "aarch64") => "windows-aarch64", + ("linux", "x86_64") => "linux-x64", + ("linux", "aarch64") => "linux-aarch64", + ("macos", "x86_64") => "macosx-x64", + + (os, arch) => panic!("Unsupported OS or architecture: {os} {arch}") + }; + + let bin_dir = slang_dir.join(format!("bin/{target}/release")); + + if !bin_dir.is_dir() { + panic!(" + Could not find the target-specific `bin` subdirectory (bin/{target}/release) in the Slang installation directory. \ + The Slang installation may not match the target this crate is being compiled for. + ") + } + + println!("cargo:rustc-link-search=native={}", bin_dir.display()); + println!("cargo:rustc-link-lib=dylib=slang"); +} + +#[derive(Debug)] +struct ParseCallback {} + +impl bindgen::callbacks::ParseCallbacks for ParseCallback { + fn enum_variant_name( + &self, + enum_name: Option<&str>, + original_variant_name: &str, + _variant_value: bindgen::callbacks::EnumVariantValue, + ) -> Option { + let enum_name = enum_name?; + + // Map enum names to the part of their variant names that needs to be trimmed. + // When an enum name is not in this map the code below will try to trim the enum name itself. + let mut map = std::collections::HashMap::new(); + map.insert("SlangMatrixLayoutMode", "SlangMatrixLayout"); + map.insert("SlangCompileTarget", "Slang"); + + let trim = map.get(enum_name).unwrap_or(&enum_name); + let new_variant_name = pascal_case_from_snake_case(original_variant_name); + let new_variant_name = new_variant_name.trim_start_matches(trim); + Some(new_variant_name.to_string()) + } +} + +/// Converts `snake_case` or `SNAKE_CASE` to `PascalCase`. +fn pascal_case_from_snake_case(snake_case: &str) -> String { + let mut result = String::new(); + let mut capitalize_next = true; + + for c in snake_case.chars() { + if c == '_' { + capitalize_next = true; + } else { + if capitalize_next { + result.push(c.to_ascii_uppercase()); + capitalize_next = false; + } else { + result.push(c.to_ascii_lowercase()); + } + } + } + + result +} diff --git a/slang-sys/src/lib.rs b/slang-sys/src/lib.rs new file mode 100644 index 0000000..6d4ea5d --- /dev/null +++ b/slang-sys/src/lib.rs @@ -0,0 +1,123 @@ +#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)] +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); + +use std::ffi::{c_char, c_int, c_void}; + +// The vtables below are manually implemented since bindgen does not yet support +// generating vtables for types with base classes, a critical part of COM interfaces. + +// Based on Slang version 2024.0.10 + +#[repr(C)] +pub struct IGlobalSessionVtable { + pub _base: ISlangUnknown__bindgen_vtable, + + pub createSession: unsafe extern "stdcall" fn(*mut c_void, desc: *const slang_SessionDesc, outSession: *mut *mut slang_ISession) -> SlangResult, + pub findProfile: unsafe extern "stdcall" fn(*mut c_void, name: *const c_char) -> SlangProfileID, + pub setDownstreamCompilerPath: unsafe extern "stdcall" fn(*mut c_void, passThrough: SlangPassThrough, path: *const c_char), + #[deprecated( note = "Use setLanguagePrelude instead")] + pub setDownstreamCompilerPrelude: unsafe extern "stdcall" fn(*mut c_void, passThrough: SlangPassThrough, preludeText: *const c_char), + #[deprecated( note = "Use getLanguagePrelude instead")] + pub getDownstreamCompilerPrelude: unsafe extern "stdcall" fn(*mut c_void, passThrough: SlangPassThrough, outPrelude: *mut *mut ISlangBlob), + pub getBuildTagString: unsafe extern "stdcall" fn(*mut c_void) -> *const c_char, + pub setDefaultDownstreamCompiler: unsafe extern "stdcall" fn(*mut c_void, sourceLanguage: SlangSourceLanguage, defaultCompiler: SlangPassThrough) -> SlangResult, + pub getDefaultDownstreamCompiler: unsafe extern "stdcall" fn(*mut c_void, sourceLanguage: SlangSourceLanguage) -> SlangPassThrough, + pub setLanguagePrelude: unsafe extern "stdcall" fn(*mut c_void, sourceLanguage: SlangSourceLanguage, preludeText: *const c_char), + pub getLanguagePrelude: unsafe extern "stdcall" fn(*mut c_void, sourceLanguage: SlangSourceLanguage, outPrelude: *mut *mut ISlangBlob), + pub createCompileRequest: unsafe extern "stdcall" fn(*mut c_void, *mut *mut c_void) -> SlangResult, + pub addBuiltins: unsafe extern "stdcall" fn(*mut c_void, sourcePath: *const c_char, sourceString: *const c_char), + pub setSharedLibraryLoader: unsafe extern "stdcall" fn(*mut c_void, loader: *mut ISlangSharedLibraryLoader), + pub getSharedLibraryLoader: unsafe extern "stdcall" fn(*mut c_void) -> *mut ISlangSharedLibraryLoader, + pub checkCompileTargetSupport: unsafe extern "stdcall" fn(*mut c_void, target: SlangCompileTarget) -> SlangResult, + pub checkPassThroughSupport: unsafe extern "stdcall" fn(*mut c_void, passThrough: SlangPassThrough) -> SlangResult, + pub compileStdLib: unsafe extern "stdcall" fn(*mut c_void, flags: slang_CompileStdLibFlags) -> SlangResult, + pub loadStdLib: unsafe extern "stdcall" fn(*mut c_void, stdLib: *const c_void, stdLibSizeInBytes: usize) -> SlangResult, + pub saveStdLib: unsafe extern "stdcall" fn(*mut c_void, archiveType: SlangArchiveType, outBlob: *mut *mut ISlangBlob) -> SlangResult, + pub findCapability: unsafe extern "stdcall" fn(*mut c_void, name: *const c_char) -> SlangCapabilityID, + pub setDownstreamCompilerForTransition: unsafe extern "stdcall" fn(*mut c_void, source: SlangCompileTarget, target: SlangCompileTarget, compiler: SlangPassThrough), + pub getDownstreamCompilerForTransition: unsafe extern "stdcall" fn(*mut c_void, source: SlangCompileTarget, target: SlangCompileTarget) -> SlangPassThrough, + pub getCompilerElapsedTime: unsafe extern "stdcall" fn(*mut c_void, outTotalTime: *mut f64, outDownstreamTime: *mut f64), + pub setSPIRVCoreGrammar: unsafe extern "stdcall" fn(*mut c_void, jsonPath: *const c_char) -> SlangResult, + pub parseCommandLineArguments: unsafe extern "stdcall" fn(*mut c_void, argc: c_int, argv: *const *const c_char, outSessionDesc: *mut slang_SessionDesc, outAuxAllocation: *mut *mut ISlangUnknown) -> SlangResult, +} + +#[repr(C)] +pub struct ICompileRequestVtable { + pub _base: ISlangUnknown__bindgen_vtable, + + pub setFileSystem: unsafe extern "stdcall" fn(*mut c_void, fileSystem: *mut ISlangFileSystem), + pub setCompileFlags: unsafe extern "stdcall" fn(*mut c_void, flags: SlangCompileFlags), + pub getCompileFlags: unsafe extern "stdcall" fn(*mut c_void) -> SlangCompileFlags, + pub setDumpIntermediates: unsafe extern "stdcall" fn(*mut c_void, enable: c_int), + pub setDumpIntermediatePrefix: unsafe extern "stdcall" fn(*mut c_void, prefix: *const c_char), + pub setLineDirectiveMode: unsafe extern "stdcall" fn(*mut c_void, mode: SlangLineDirectiveMode), + pub setCodeGenTarget: unsafe extern "stdcall" fn(*mut c_void, target: SlangCompileTarget), + pub addCodeGenTarget: unsafe extern "stdcall" fn(*mut c_void, target: SlangCompileTarget) -> c_int, + pub setTargetProfile: unsafe extern "stdcall" fn(*mut c_void, targetIndex: c_int, profile: SlangProfileID), + pub setTargetFlags: unsafe extern "stdcall" fn(*mut c_void, targetIndex: c_int, flags: SlangTargetFlags), + pub setTargetFloatingPointMode: unsafe extern "stdcall" fn(*mut c_void, targetIndex: c_int, mode: SlangFloatingPointMode), + #[deprecated( note = "Use setMatrixLayoutMode instead")] + pub setTargetMatrixLayoutMode: unsafe extern "stdcall" fn(*mut c_void, target: c_int, mode: SlangMatrixLayoutMode), + pub setMatrixLayoutMode: unsafe extern "stdcall" fn(*mut c_void, mode: SlangMatrixLayoutMode), + pub setDebugInfoLevel: unsafe extern "stdcall" fn(*mut c_void, level: SlangDebugInfoLevel), + pub setOptimizationLevel: unsafe extern "stdcall" fn(*mut c_void, level: SlangOptimizationLevel), + pub setOutputContainerFormat: unsafe extern "stdcall" fn(*mut c_void, format: SlangContainerFormat), + pub setPassThrough: unsafe extern "stdcall" fn(*mut c_void, passThrough: SlangPassThrough), + pub setDiagnosticCallback: unsafe extern "stdcall" fn(*mut c_void, callback: SlangDiagnosticCallback, userData: *const c_void), + pub setWriter: unsafe extern "stdcall" fn(*mut c_void, channel: SlangWriterChannel, writer: *mut ISlangWriter), + pub getWriter: unsafe extern "stdcall" fn(*mut c_void, channel: SlangWriterChannel) -> *mut ISlangWriter, + pub addSearchPath: unsafe extern "stdcall" fn(*mut c_void, searchDir: *const c_char), + pub addPreprocessorDefine: unsafe extern "stdcall" fn(*mut c_void, key: *const c_char, value: *const c_char), + pub processCommandLineArguments: unsafe extern "stdcall" fn(*mut c_void, args: *const *const c_char, argCount: c_int) -> SlangResult, + pub addTranslationUnit: unsafe extern "stdcall" fn(*mut c_void, language: SlangSourceLanguage, name: *const c_char) -> c_int, + pub setDefaultModuleName: unsafe extern "stdcall" fn(*mut c_void, defaultModuleName: *const c_char), + pub addTranslationUnitPreprocessorDefine: unsafe extern "stdcall" fn(*mut c_void, translationUnitIndex: c_int, key: *const c_char, value: *const c_char), + pub addTranslationUnitSourceFile: unsafe extern "stdcall" fn(*mut c_void, translationUnitIndex: c_int, path: *const c_char), + pub addTranslationUnitSourceString: unsafe extern "stdcall" fn(*mut c_void, translationUnitIndex: c_int, path: *const c_char, source: *const c_char), + pub addLibraryReference: unsafe extern "stdcall" fn(*mut c_void, basePath: *const c_char, libData: *const c_void, libDataSize: usize) -> SlangResult, + pub addTranslationUnitSourceStringSpan: unsafe extern "stdcall" fn(*mut c_void, translationUnitIndex: c_int, path: *const c_char, sourceBegin: *const c_char, sourceEnd: *const c_char), + pub addTranslationUnitSourceBlob: unsafe extern "stdcall" fn(*mut c_void, translationUnitIndex: c_int, path: *const c_char, sourceBlob: *mut ISlangBlob), + pub addEntryPoint: unsafe extern "stdcall" fn(*mut c_void, translationUnitIndex: c_int, name: *const c_char, stage: SlangStage) -> c_int, + pub addEntryPointEx: unsafe extern "stdcall" fn(*mut c_void, translationUnitIndex: c_int, name: *const c_char, stage: SlangStage, genericArgCount: c_int, genericArgs: *const *const c_char) -> c_int, + pub setGlobalGenericArgs: unsafe extern "stdcall" fn(*mut c_void, genericArgCount: c_int, genericArgs: *const *const c_char) -> SlangResult, + pub setTypeNameForGlobalExistentialTypeParam: unsafe extern "stdcall" fn(*mut c_void, slotIndex: c_int, typeName: *const c_char) -> SlangResult, + pub setTypeNameForEntryPointExistentialTypeParam: unsafe extern "stdcall" fn(*mut c_void, entryPointIndex: c_int, slotIndex: c_int, typeName: *const c_char) -> SlangResult, + pub setAllowGLSLInput: unsafe extern "stdcall" fn(*mut c_void, value: bool), + pub compile: unsafe extern "stdcall" fn(*mut c_void) -> SlangResult, + pub getDiagnosticOutput: unsafe extern "stdcall" fn(*mut c_void) -> *const c_char, + pub getDiagnosticOutputBlob: unsafe extern "stdcall" fn(*mut c_void, outBlob: *mut *mut ISlangBlob) -> SlangResult, + pub getDependencyFileCount: unsafe extern "stdcall" fn(*mut c_void) -> c_int, + pub getDependencyFilePath: unsafe extern "stdcall" fn(*mut c_void, index: c_int) -> *const c_char, + pub getTranslationUnitCount: unsafe extern "stdcall" fn(*mut c_void) -> c_int, + pub getEntryPointSource: unsafe extern "stdcall" fn(*mut c_void, entryPointIndex: c_int) -> *const c_char, + pub getEntryPointCode: unsafe extern "stdcall" fn(*mut c_void, entryPointIndex: c_int, outSize: *mut usize) -> *const c_void, + pub getEntryPointCodeBlob: unsafe extern "stdcall" fn(*mut c_void, entryPointIndex: c_int, targetIndex: c_int, outBlob: *mut *mut ISlangBlob) -> SlangResult, + pub getEntryPointHostCallable: unsafe extern "stdcall" fn(*mut c_void, entryPointIndex: c_int, targetIndex: c_int, outSharedLibrary: *mut *mut ISlangSharedLibrary) -> SlangResult, + pub getTargetCodeBlob: unsafe extern "stdcall" fn(*mut c_void, targetIndex: c_int, outBlob: *mut *mut ISlangBlob) -> SlangResult, + pub getTargetHostCallable: unsafe extern "stdcall" fn(*mut c_void, targetIndex: c_int, outSharedLibrary: *mut *mut ISlangSharedLibrary) -> SlangResult, + pub getCompileRequestCode: unsafe extern "stdcall" fn(*mut c_void, outSize: *mut usize) -> *const c_void, + pub getCompileRequestResultAsFileSystem: unsafe extern "stdcall" fn(*mut c_void) -> *mut ISlangMutableFileSystem, + pub getContainerCode: unsafe extern "stdcall" fn(*mut c_void, outBlob: *mut *mut ISlangBlob) -> SlangResult, + pub loadRepro: unsafe extern "stdcall" fn(*mut c_void, fileSystem: *mut ISlangFileSystem, data: *const c_void, size: usize) -> SlangResult, + pub saveRepro: unsafe extern "stdcall" fn(*mut c_void, outBlob: *mut *mut ISlangBlob) -> SlangResult, + pub enableReproCapture: unsafe extern "stdcall" fn(*mut c_void) -> SlangResult, + pub getProgram: unsafe extern "stdcall" fn(*mut c_void, outProgram: *mut *mut slang_IComponentType) -> SlangResult, + pub getEntryPoint: unsafe extern "stdcall" fn(*mut c_void, entryPointIndex: c_int, outEntryPoint: *mut *mut slang_IComponentType) -> SlangResult, + pub getModule: unsafe extern "stdcall" fn(*mut c_void, translationUnitIndex: c_int, outModule: *mut *mut slang_IModule) -> SlangResult, + pub getSession: unsafe extern "stdcall" fn(*mut c_void, outSession: *mut *mut slang_ISession) -> SlangResult, + pub getReflection: unsafe extern "stdcall" fn(*mut c_void) -> *mut SlangReflection, + pub setCommandLineCompilerMode: unsafe extern "stdcall" fn(*mut c_void), + pub addTargetCapability: unsafe extern "stdcall" fn(*mut c_void, targetIndex: c_int, capability: SlangCapabilityID) -> SlangResult, + pub getProgramWithEntryPoints: unsafe extern "stdcall" fn(*mut c_void, outProgram: *mut *mut slang_IComponentType) -> SlangResult, + pub isParameterLocationUsed: unsafe extern "stdcall" fn(*mut c_void, entryPointIndex: c_int, targetIndex: c_int, category: SlangParameterCategory, spaceIndex: SlangUInt, registerIndex: SlangUInt, outUsed: *mut bool) -> SlangResult, + pub setTargetLineDirectiveMode: unsafe extern "stdcall" fn(*mut c_void, targetIndex: c_int, mode: SlangLineDirectiveMode), + pub setTargetForceGLSLScalarBufferLayout: unsafe extern "stdcall" fn(*mut c_void, targetIndex: c_int, forceScalarLayout: bool), + pub overrideDiagnosticSeverity: unsafe extern "stdcall" fn(*mut c_void, messageID: SlangInt, overrideSeverity: SlangSeverity), + pub getDiagnosticFlags: unsafe extern "stdcall" fn(*mut c_void) -> SlangDiagnosticFlags, + pub setDiagnosticFlags: unsafe extern "stdcall" fn(*mut c_void, flags: SlangDiagnosticFlags), + pub setDebugInfoFormat: unsafe extern "stdcall" fn(*mut c_void, debugFormat: SlangDebugInfoFormat), + pub setEnableEffectAnnotations: unsafe extern "stdcall" fn(*mut c_void, value: bool), + pub setReportDownstreamTime: unsafe extern "stdcall" fn(*mut c_void, value: bool), + pub setReportPerfBenchmark: unsafe extern "stdcall" fn(*mut c_void, value: bool), + pub setSkipSPIRVValidation: unsafe extern "stdcall" fn(*mut c_void, value: bool), +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..de8c876 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,228 @@ +use slang_sys as sys; +use std::ffi::{CStr, CString}; +use std::path::Path; +use std::slice; + +pub use sys::SlangUUID as UUID; +pub use sys::SlangCompileTarget as CompileTarget; +pub use sys::SlangMatrixLayoutMode as MatrixLayoutMode; +pub use sys::SlangOptimizationLevel as OptimizationLevel; +pub use sys::SlangSourceLanguage as SourceLanguage; +pub use sys::SlangStage as Stage; +pub use sys::SlangProfileID as ProfileID; + +macro_rules! vcall { + ($self:expr, $method:ident($($args:expr),*)) => { + unsafe { ($self.vtable().$method)($self.as_raw(), $($args),*) } + }; +} + +const fn uuid(data1: u32, data2: u16, data3: u16, data4: [u8; 8]) -> UUID { + UUID { data1, data2, data3, data4 } +} + +unsafe trait Interface: Sized { + type Vtable; + const IID: UUID; + + #[inline(always)] + unsafe fn vtable(&self) -> &Self::Vtable { + &**(self.as_raw() as *mut *mut Self::Vtable) + } + + #[inline(always)] + unsafe fn as_raw(&self) -> *mut T { + std::mem::transmute_copy(self) + } +} + +#[repr(transparent)] +pub struct IUnknown(std::ptr::NonNull); + +unsafe impl Interface for IUnknown { + type Vtable = sys::ISlangUnknown__bindgen_vtable; + const IID: UUID = uuid(0x00000000, 0x0000, 0x0000, [0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46]); +} + +impl Clone for IUnknown { + fn clone(&self) -> Self { + vcall!(self, ISlangUnknown_addRef()); + Self(self.0) + } +} + +impl Drop for IUnknown { + fn drop(&mut self) { + vcall!(self, ISlangUnknown_release()); + } +} + +#[repr(transparent)] +#[derive(Clone)] +pub struct GlobalSession(IUnknown); + +unsafe impl Interface for GlobalSession { + type Vtable = sys::IGlobalSessionVtable; + const IID: UUID = uuid(0xc140b5fd, 0xc78, 0x452e, [0xba, 0x7c, 0x1a, 0x1e, 0x70, 0xc7, 0xf7, 0x1c]); +} + +impl GlobalSession { + pub fn new() -> GlobalSession { + unsafe { + let mut global_session = std::ptr::null_mut(); + sys::slang_createGlobalSession(sys::SLANG_API_VERSION as _, &mut global_session); + GlobalSession(IUnknown(std::ptr::NonNull::new(global_session as *mut _).unwrap())) + } + } + + pub fn new_without_std_lib() -> GlobalSession { + unsafe { + let mut global_session = std::ptr::null_mut(); + sys::slang_createGlobalSessionWithoutStdLib(sys::SLANG_API_VERSION as _, &mut global_session); + GlobalSession(IUnknown(std::ptr::NonNull::new(global_session as *mut _).unwrap())) + } + } + + pub fn create_compile_request(&self) -> CompileRequest { + let mut compile_request = std::ptr::null_mut(); + vcall!(self, createCompileRequest(&mut compile_request)); + CompileRequest(IUnknown(std::ptr::NonNull::new(compile_request).unwrap())) + } + + pub fn find_profile(&self, name: &str) -> ProfileID { + let name = CString::new(name).unwrap(); + vcall!(self, findProfile(name.as_ptr())) + } +} + +#[repr(transparent)] +#[derive(Clone)] +pub struct CompileRequest(IUnknown); + +unsafe impl Interface for CompileRequest { + type Vtable = sys::ICompileRequestVtable; + const IID: UUID = uuid(0x96d33993, 0x317c, 0x4db5, [0xaf, 0xd8, 0x66, 0x6e, 0xe7, 0x72, 0x48, 0xe2]); +} + +impl CompileRequest { + pub fn set_codegen_target(&mut self, target: CompileTarget) -> &mut Self { + vcall!(self, setCodeGenTarget(target)); + self + } + + pub fn set_matrix_layout_mode(&mut self, layout: MatrixLayoutMode) -> &mut Self { + vcall!(self, setMatrixLayoutMode(layout)); + self + } + + pub fn set_optimization_level(&mut self, level: OptimizationLevel) -> &mut Self { + vcall!(self, setOptimizationLevel(level)); + self + } + + pub fn set_target_profile(&mut self, profile: ProfileID) ->&mut Self { + vcall!(self, setTargetProfile(0, profile)); + self + } + + pub fn add_preprocessor_define(&mut self, key: &str, value: &str) -> &mut Self { + let key = CString::new(key).unwrap(); + let value = CString::new(value).unwrap(); + vcall!(self, addPreprocessorDefine(key.as_ptr(), value.as_ptr())); + self + } + + pub fn add_search_path(&mut self, path: impl AsRef) -> &mut Self { + let path = CString::new(path.as_ref().to_str().unwrap()).unwrap(); + vcall!(self, addSearchPath(path.as_ptr())); + self + } + + pub fn add_translation_unit(&mut self, source_language: SourceLanguage, name: Option<&str>) -> TranslationUnit { + let name = CString::new(name.unwrap_or("")).unwrap(); + let index = vcall!(self, addTranslationUnit(source_language, name.as_ptr())); + + TranslationUnit { + request: self, + index, + } + } + + pub fn compile(self) -> Result { + let r = vcall!(self, compile()); + + if r < 0 { + let out = vcall!(self, getDiagnosticOutput()); + let errors = unsafe { CStr::from_ptr(out).to_str().unwrap().to_string() }; + + Err(CompilationErrors { errors }) + } else { + Ok(CompiledRequest { request: self }) + } + } +} + +pub struct TranslationUnit<'a> { + request: &'a mut CompileRequest, + index: i32, +} + +impl<'a> TranslationUnit<'a> { + pub fn add_preprocessor_define(&mut self, key: &str, value: &str) -> &mut Self { + let key = CString::new(key).unwrap(); + let value = CString::new(value).unwrap(); + vcall!(self.request, addTranslationUnitPreprocessorDefine(self.index, key.as_ptr(), value.as_ptr())); + self + } + + pub fn add_source_file(&mut self, path: impl AsRef) -> &mut Self { + let path = CString::new(path.as_ref().to_str().unwrap()).unwrap(); + vcall!(self.request, addTranslationUnitSourceFile(self.index, path.as_ptr())); + self + } + + pub fn add_source_string(&mut self, path: impl AsRef, source: &str) -> &mut Self { + let path = CString::new(path.as_ref().to_str().unwrap()).unwrap(); + let source = CString::new(source).unwrap(); + vcall!(self.request, addTranslationUnitSourceString(self.index, path.as_ptr(), source.as_ptr())); + self + } + + pub fn add_entry_point(&mut self, name: &str, stage: Stage) -> EntryPointIndex { + let name = CString::new(name).unwrap(); + let index = vcall!(self.request, addEntryPoint(self.index, name.as_ptr(), stage)); + EntryPointIndex(index) + } +} + +pub struct CompiledRequest { + request: CompileRequest, +} + +impl CompiledRequest { + pub fn get_entry_point_code(&self, index: EntryPointIndex) -> &[u8] { + let mut out_size = 0; + let ptr = vcall!(self.request, getEntryPointCode(index.0, &mut out_size)); + unsafe { slice::from_raw_parts(ptr as *const u8, out_size) } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct EntryPointIndex(pub i32); + +pub struct CompilationErrors { + errors: String, +} + +impl std::fmt::Debug for CompilationErrors { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("\n")?; + f.write_str(&self.errors) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn compiles() {} +}