diff --git a/.gitignore b/.gitignore index 573461a..25d73e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +/.cache /.idea -/target -/cmake-build-* \ No newline at end of file +/build +/cmake-build-* +/target \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 89fc06b..67c4f11 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,39 +1,38 @@ # NOTE: This file is _not_ used as part of the main build process. # It exists to help IDE tools provide auto-hinting while developing the C++ bridge cmake_minimum_required(VERSION 3.22) -project(slang_compiler) +project(slang-tools) find_program(CXXBRIDGE cxxbridge) -set(SLANG_COMPILER_CXXBRIDGE_DIR ${CMAKE_CURRENT_BINARY_DIR}/cxxbridge) +set(SLANG_TOOLS_CXXBRIDGE_DIR ${CMAKE_CURRENT_BINARY_DIR}/cxxbridge) # Generate the Rust bridge stubs header -# This is used by the C++ header to define an interface for Rust -file(MAKE_DIRECTORY ${SLANG_COMPILER_CXXBRIDGE_DIR}/rust) -set(SLANG_COMPILER_CXXBRIDGE_RUST ${SLANG_COMPILER_CXXBRIDGE_DIR}/rust/cxx.h) +# This is used by the C++ header while defining the interface for Rust +file(MAKE_DIRECTORY ${SLANG_TOOLS_CXXBRIDGE_DIR}/rust) +set(SLANG_TOOLS_CXXBRIDGE_RUST ${SLANG_TOOLS_CXXBRIDGE_DIR}/rust/cxx.h) add_custom_command( - OUTPUT ${SLANG_COMPILER_CXXBRIDGE_RUST} - COMMAND ${CXXBRIDGE} --header -o ${SLANG_COMPILER_CXXBRIDGE_RUST} + OUTPUT ${SLANG_TOOLS_CXXBRIDGE_RUST} + COMMAND ${CXXBRIDGE} --header -o ${SLANG_TOOLS_CXXBRIDGE_RUST} ) # Generate the Rust bridge header # This is used by the C++ implementation to interact with Rust -file(MAKE_DIRECTORY ${SLANG_COMPILER_CXXBRIDGE_DIR}/slang-compiler-sys/src) -set(SLANG_COMPILER_CXXBRIDGE_INPUT ${CMAKE_CURRENT_LIST_DIR}/src/lib.rs) -set(SLANG_COMPILER_CXXBRIDGE_OUTPUT ${SLANG_COMPILER_CXXBRIDGE_DIR}/slang-compiler-sys/src/lib.rs.h) +file(MAKE_DIRECTORY ${SLANG_TOOLS_CXXBRIDGE_DIR}/slang-tools/src) +set(SLANG_TOOLS_CXXBRIDGE_INPUT ${CMAKE_CURRENT_LIST_DIR}/src/lib.rs) +set(SLANG_TOOLS_CXXBRIDGE_OUTPUT ${SLANG_TOOLS_CXXBRIDGE_DIR}/slang-tools/src/lib.rs.h) add_custom_command( - OUTPUT ${SLANG_COMPILER_CXXBRIDGE_OUTPUT} - COMMAND ${CXXBRIDGE} ${SLANG_COMPILER_CXXBRIDGE_INPUT} --header -o ${SLANG_COMPILER_CXXBRIDGE_OUTPUT} - DEPENDS ${SLANG_COMPILER_CXXBRIDGE_INPUT} + OUTPUT ${SLANG_TOOLS_CXXBRIDGE_OUTPUT} + COMMAND ${CXXBRIDGE} ${SLANG_TOOLS_CXXBRIDGE_INPUT} --header -o ${SLANG_TOOLS_CXXBRIDGE_OUTPUT} + DEPENDS ${SLANG_TOOLS_CXXBRIDGE_INPUT} ) -add_custom_target(slang_compiler_bridge DEPENDS ${SLANG_COMPILER_CXXBRIDGE_RUST} ${SLANG_COMPILER_CXXBRIDGE_OUTPUT}) - -add_library(slang_compiler "src/lib.cpp") +add_custom_target(slang_tools_bridge DEPENDS ${SLANG_TOOLS_CXXBRIDGE_RUST} ${SLANG_TOOLS_CXXBRIDGE_OUTPUT}) +add_library(slang_tools "src/lib.cpp") # Find the bridge headers -add_dependencies(slang_compiler slang_compiler_bridge) -target_include_directories(slang_compiler PRIVATE ${CMAKE_CURRENT_LIST_DIR}/src ${SLANG_COMPILER_CXXBRIDGE_DIR}) +add_dependencies(slang_tools slang_tools_bridge) +target_include_directories(slang_tools PRIVATE ${CMAKE_CURRENT_LIST_DIR}/src ${SLANG_TOOLS_CXXBRIDGE_DIR}) # Find the slang headers add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/slang" EXCLUDE_FROM_ALL) -target_link_libraries(slang_compiler PRIVATE slang) \ No newline at end of file +target_link_libraries(slang_tools PRIVATE slang) \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index e5077c6..43cdb30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,7 +173,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] -name = "slang-compiler-sys" +name = "slang-tools" version = "0.1.0" dependencies = [ "cmake", diff --git a/Cargo.toml b/Cargo.toml index 12de9d2..1cb5624 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "slang-compiler-sys" +name = "slang-tools" version = "0.1.0" edition = "2021" resolver = "2" diff --git a/src/lib.cpp b/src/lib.cpp index fb2afb5..38a1396 100644 --- a/src/lib.cpp +++ b/src/lib.cpp @@ -3,7 +3,33 @@ // #include "lib.h" -#include "slang-compiler-sys/src/lib.rs.h" +#include +#include + +#include "slang-tools/src/lib.rs.h" + +namespace slang_compiler { + +class slang_exception final : public std::exception { +public: + slang_exception(SlangResult result) : result_{result} {} + + const char *what() const noexcept override { return what_.c_str(); } + +private: + SlangResult result_; + std::string what_; +}; + +} // namespace slang_compiler + +#define SLANG_TOOLS_CHECK(expr) \ + [&]() { \ + const SlangResult result = expr; \ + if (SLANG_FAILED(result)) { \ + throw slang_compiler::slang_exception(result); \ + } \ + }(); namespace slang_compiler { @@ -11,8 +37,110 @@ GlobalSession::GlobalSession() { slang::createGlobalSession(global_session_.writeRef()); } -std::unique_ptr create_global_session() { - return std::make_unique(); +slang::IGlobalSession &GlobalSession::getSlangSession() { + return *global_session_; +} + +SlangResult GlobalSession::createSession(const slang::SessionDesc &desc, + slang::ISession **outSession) const { + return global_session_->createSession(desc, outSession); +} + +std::shared_ptr create_global_session() { + return std::make_shared(); +} + +Session::Session(std::shared_ptr global_session, + Slang::ComPtr &&session) + : global_session_{global_session}, session_{session} {} + +int64_t Session::get_loaded_module_count() const noexcept { + return session_->getLoadedModuleCount(); +} + +::SlangProfileID +get_slang_profile_id(slang::IGlobalSession &global_session, + const SlangProfileID_rs slang_profile_id) { + switch (slang_profile_id) { + case SlangProfileID_rs::spirv_1_0: + return global_session.findProfile("spirv_1_0"); + } + + return SLANG_PROFILE_UNKNOWN; +} + +::SlangTargetFlags +get_slang_target_flags(const rust::Vec &flags) noexcept { + uint32_t value = 0; + std::for_each(flags.cbegin(), flags.cend(), [&value](const auto &flag) { + value |= static_cast>(flag); + }); + return value; +} + +std::unique_ptr +create_session(SessionDesc session_desc, + std::shared_ptr global_session) { + // The Slang session descriptor wants unowned pointers, + // so we copy the values from Rust into storage that + // survives at least through the call to `createSession`. + // Slang will maintain its own copy once as part of the session. + // I'm sure there's a better way to do this, but this works for now. + + // First, convert the compiler options for each target descriptor + std::vector targets_options_strings{}; + std::vector> targets_options{}; + for (const auto &target : session_desc.targets) { + std::vector target_options; + for (const CompilerOptionEntry &target_option : target) { + auto& string_value_0 = targets_options_strings.emplace_back(target_option.value.string_value_0); + auto& string_value_1 = targets_options_strings.emplace_back(target_option.value.string_value_1); + auto slang_option = slang::CompilerOptionEntry{ + .name = target_option.name, + .value = slang::CompilerOptionValue{ + .kind = target_option.value.kind, + .intValue0 = target_option.value.int_value_0, + .intValue1 = target_option.value.int_value_1, + .stringValue0 = string_value_0.c_str(), + .stringValue1 = string_value_1.c_str() + } + }; + target_options.push_back(slang_option); + } + targets_options.push_back(std::move(target_options)); + } + + // Second, convert the target descriptors + std::vector target_descs; + for (size_t i = 0; i < session_desc.targets.size(); ++i) { + const auto& target = session_desc.targets[i]; + auto& target_options = targets_options[i]; + + const auto target_desc = slang::TargetDesc{ + .format = target.format, + .profile = get_slang_profile_id(global_session->getSlangSession(), + target.profile), + .floatingPointMode = target.floating_point_mode, + .lineDirectiveMode = target.line_directive_mode, + .forceGLSLScalarBufferLayout = target.force_glsl_scalar_buffer_layout, + .compilerOptionEntries = target_options.data(), + .compilerOptionEntryCount = static_cast(target_options.size()), + }; + target_descs.push_back(target_desc); + } + + // Finally, build the session descriptor for slang. + const auto slang_session_desc = slang::SessionDesc{ + .targets = target_descs.data(), + .targetCount = static_cast(target_descs.size()), + }; + + Slang::ComPtr session_ptr; + SLANG_TOOLS_CHECK(global_session->createSession(slang_session_desc, + session_ptr.writeRef())); + + return std::make_unique(std::move(global_session), + std::move(session_ptr)); } } // namespace slang_compiler diff --git a/src/lib.h b/src/lib.h index c5e7c42..037bfbb 100644 --- a/src/lib.h +++ b/src/lib.h @@ -15,11 +15,27 @@ namespace slang_compiler { class GlobalSession { public: explicit GlobalSession(); + [[nodiscard]] SlangResult createSession(const slang::SessionDesc& desc, slang::ISession** outSession) const; + + slang::IGlobalSession& getSlangSession(); private: Slang::ComPtr global_session_; }; -std::unique_ptr create_global_session(); +std::shared_ptr create_global_session(); + +struct SessionDesc; +class Session { +public: + Session(std::shared_ptr global_session, Slang::ComPtr&& session); + [[nodiscard]] int64_t get_loaded_module_count() const noexcept; + +private: + const std::shared_ptr global_session_; + Slang::ComPtr session_; +}; + +std::unique_ptr create_session(SessionDesc session_desc, std::shared_ptr global_session); } // namespace slang_compiler diff --git a/src/lib.rs b/src/lib.rs index 491f734..604fc3e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,19 +1,179 @@ +use crate::ffi::{SessionDesc, SlangMatrixLayoutMode}; + #[cxx::bridge(namespace = "slang_compiler")] mod ffi { + #[namespace = ""] + #[repr(i32)] + enum SlangCompileTarget { + SLANG_TARGET_UNKNOWN, + SLANG_TARGET_NONE, + SLANG_GLSL, + SLANG_HLSL = 5, + SLANG_SPIRV + } + + // SlangProfileID is a combination of anonymous enum and preprocessor hack ultimately used + // to generate switch statements for the various supported profiles - see `slang-profile-defs.h` + // We'll define the names here and let C++ deal with mapping them to the ID + #[namespace = ""] + #[repr(u32)] + enum SlangProfileID_rs { + spirv_1_0, + } + + // SlangTargetFlags is an anonymous enum in C++, use a shared enum (in the `slang_compiler` namespace) + // here as a nicer API. Also means we need to manage the values by hand instead of relying on cxx. + // https://stackoverflow.com/a/7147049 + #[repr(u32)] + enum SlangTargetFlags { + SLANG_TARGET_FLAG_PARAMETER_BLOCKS_USE_REGISTER_SPACES = 32, + SLANG_TARGET_FLAG_GENERATE_WHOLE_PROGRAM = 256, + SLANG_TARGET_FLAG_DUMP_IR = 512, + SLANG_TARGET_FLAG_GENERATE_SPIRV_DIRECTLY = 1024, + } + + #[namespace = ""] + #[repr(u32)] + enum SlangFloatingPointMode { + SLANG_FLOATING_POINT_MODE_DEFAULT, + SLANG_FLOATING_POINT_MODE_FAST, + SLANG_FLOATING_POINT_MODE_PRECISE, + } + + #[namespace = ""] + #[repr(u32)] + enum SlangLineDirectiveMode { + SLANG_LINE_DIRECTIVE_MODE_DEFAULT, + SLANG_LINE_DIRECTIVE_MODE_NONE, + SLANG_LINE_DIRECTIVE_MODE_STANDARD, + SLANG_LINE_DIRECTIVE_MODE_GLSL, + SLANG_LINE_DIRECTIVE_MODE_SOURCE_MAP, + } + + #[namespace = ""] + #[repr(u32)] + enum SlangMatrixLayoutMode { + SLANG_MATRIX_LAYOUT_MODE_UNKNOWN, + SLANG_MATRIX_LAYOUT_ROW_MAJOR, + SLANG_MATRIX_LAYOUT_COLUMN_MAJOR, + } + + // Some slang enums are defined in the root namespace + #[namespace = ""] + unsafe extern "C++" { + include!("slang.h"); + type SlangCompileTarget; + type SlangFloatingPointMode; + type SlangLineDirectiveMode; + type SlangMatrixLayoutMode; + } + + #[namespace = "slang"] + #[repr(u32)] + enum CompilerOptionName { + MacroDefine + } + + #[namespace = "slang"] + #[repr(u32)] + enum CompilerOptionValueKind { + Int, + String, + } + + #[namespace = "slang"] + unsafe extern "C++" { + include!("slang.h"); + type CompilerOptionName; + type CompilerOptionValueKind; + } + + #[derive(Clone)] + struct CompilerOptionValue { + kind: CompilerOptionValueKind, + int_value_0: i32, + int_value_1: i32, + string_value_0: String, + string_value_1: String, + } + + #[derive(Clone)] + struct CompilerOptionEntry { + name: CompilerOptionName, + value: CompilerOptionValue, + } + + #[derive(Clone)] + struct TargetDesc { + format: SlangCompileTarget, + profile: SlangProfileID_rs, + flags: Vec, + floating_point_mode: SlangFloatingPointMode, + line_directive_mode: SlangLineDirectiveMode, + force_glsl_scalar_buffer_layout: bool, + compiler_option_entries: Vec, + } + + #[derive(Clone)] + struct PreprocessorMacroDesc { + name: String, + value: String, + } + + #[derive(Clone)] + struct SessionDesc { + targets: Vec, + default_matrix_layout_mode: SlangMatrixLayoutMode, + search_paths: Vec, + preprocessor_macros: Vec, + enable_effect_annotations: bool, + allow_glsl_syntax: bool, + compiler_option_entries: Vec, + } + unsafe extern "C++" { include!("lib.h"); type GlobalSession; - fn create_global_session() -> UniquePtr; + fn create_global_session() -> SharedPtr; + + type Session; + #[must_use] + fn get_loaded_module_count(self: &Session) -> i64; + + #[must_use] + fn create_session(session_desc: SessionDesc, global_session: SharedPtr) -> Result>; + } +} + +impl Default for SessionDesc { + fn default() -> Self { + SessionDesc { + targets: vec![], + default_matrix_layout_mode: SlangMatrixLayoutMode::SLANG_MATRIX_LAYOUT_ROW_MAJOR, + search_paths: vec![], + preprocessor_macros: vec![], + enable_effect_annotations: false, + allow_glsl_syntax: false, + compiler_option_entries: vec![], + } } } #[cfg(test)] mod tests { - use crate::ffi::create_global_session; + use crate::ffi::{create_global_session, create_session}; #[test] fn can_create_global_session() { let _global_session = create_global_session(); } + + #[test] + fn can_create_session() { + let global_session = create_global_session(); + let session = create_session(Default::default(), global_session).expect("unable to create session"); + + assert_eq!(session.get_loaded_module_count(), 0); + } } \ No newline at end of file