commit 673e00456e77a1cfbac577e17239e416f0875320 Author: TQ Hirsch Date: Tue May 24 20:19:20 2022 +0200 Initial commit; tgv handled diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..051d09d --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +eval "$(lorri direnv)" diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0abcb37 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.10) +project(Bluebell VERSION 0.1) + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +include(FindPkgConfig) +find_package(LLVM 13 REQUIRED CONFIG) +find_package(Boost 1.78 + COMPONENTS log) +list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}") +include(HandleLLVMOptions) +include(AddLLVM) + +#pkg_check_modules(shaderc REQUIRED IMPORTED_TARGET shaderc) + +message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") + +include_directories(${LLVM_INCLUDE_DIRS}) +add_definitions(${LLVM_DEFINITIONS}) + +add_executable(bluebell + src/bluebell.cpp src/bluebell.hpp + src/pre-compiler.cpp src/pre-compiler.hpp src/compiler.cpp src/compiler.hpp) + +#target_link_libraries(bluebell PkgConfig::shaderc) + +llvm_map_components_to_libnames(llvm_libs core) + +target_link_libraries(bluebell shaderc_shared Boost::boost Boost::log ${llvm_libs}) + diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..2553de5 --- /dev/null +++ b/shell.nix @@ -0,0 +1,24 @@ +{ pkgs ? import {} }: + +pkgs.clangStdenv.mkDerivation { + name = "bluebell"; + buildInputs = with pkgs; [ + + # keep this line if you use bash + bashInteractive + llvm_13 + shaderc + spirv-headers + spirv-tools + boost178 + ]; + + nativeBuildInputs = with pkgs; [ + cmake + ninja + pkg-config + gdb + ]; + + SPIRV_HEADERS = "${pkgs.spirv-headers}"; +} diff --git a/src/bluebell.cpp b/src/bluebell.cpp new file mode 100644 index 0000000..b857219 --- /dev/null +++ b/src/bluebell.cpp @@ -0,0 +1,47 @@ +// +// Created by thequux on 5/23/22. +// + +#include +#include +#include +#include "bluebell.hpp" +#include "pre-compiler.hpp" +#include "compiler.hpp" + +const char* SHADER_PREFIX = "#version 320 es\n\ +precision highp float;\ +layout(binding=0)\ +uniform Shadertoy {\ + uniform vec3 iResolution;\ + uniform float iTime;\ + uniform float iTimeDelta;\ + uniform float iFrame;\ + uniform float iChannelTime[4];\ + uniform vec4 iMouse;\ + uniform vec4 iDate;\ + uniform float iSampleRate;\ + uniform vec3 iChannelResolution[4];\ +};\ +\ +layout(location=0) out vec4 wallshader_out_diffuseColor;"; + +const char* SHADER_SUFFIX = "void main() { \ + mainImage(wallshader_out_diffuseColor, gl_FragCoord.xy); \ + }"; + +int main(int argc, char** argv) { + std::stringstream shader_src; + std::ifstream input(argv[1]); + + shader_src << SHADER_PREFIX; + shader_src << input.rdbuf(); + shader_src << SHADER_SUFFIX; + +// std::cout << shader_src.str(); + + auto shader = pre_compile_shader(shader_src.str()); + Compiler compiler; + compiler.compile(*shader); + +} \ No newline at end of file diff --git a/src/bluebell.hpp b/src/bluebell.hpp new file mode 100644 index 0000000..38f5559 --- /dev/null +++ b/src/bluebell.hpp @@ -0,0 +1,6 @@ +// +// Created by thequux on 5/23/22. +// + +#pragma once + diff --git a/src/compiler.cpp b/src/compiler.cpp new file mode 100644 index 0000000..a110d2f --- /dev/null +++ b/src/compiler.cpp @@ -0,0 +1,542 @@ +// +// Created by thequux on 5/24/22. +// + +#include "compiler.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma GCC diagnostic ignored "-Wunknown-pragmas" + +class SpirvType { +protected: + llvm::Type *llvm = nullptr; + + virtual llvm::Type *build_llvm_type_impl(llvm::LLVMContext& ctx) = 0; +public: + virtual bool is_composite() const { return false; } + virtual bool is_vector() const { return false; } + virtual std::shared_ptr inner_type() const { return nullptr; } + virtual ~SpirvType() = default; + + llvm::Type *get_llvm_type() const { return llvm; } + void build_llvm_type(llvm::LLVMContext &ctx) { + if (llvm == nullptr) { + llvm = build_llvm_type_impl(ctx); + } + }; +}; + +class SpirvVoidType: public SpirvType { + llvm::Type* build_llvm_type_impl(llvm::LLVMContext &ctx) override { + return llvm::Type::getVoidTy(ctx); + } +}; +class SpirvBoolType: public SpirvType { + llvm::Type * build_llvm_type_impl(llvm::LLVMContext &ctx) override { + return llvm::Type::getInt1Ty(ctx); + } +}; +class SpirvIntType: public SpirvType { + llvm::Type * build_llvm_type_impl(llvm::LLVMContext &ctx) override { + switch (width) { + case 32: return llvm::Type::getInt32Ty(ctx); + case 64: return llvm::Type::getInt64Ty(ctx); + default: + BOOST_LOG_TRIVIAL(error) << "Invalid integer width " << width; + // TODO will crash if invalid width is provided + return nullptr; // this will quickly result in crashes, but that's OK for now + } + } +public: + int width; + bool is_signed; + + SpirvIntType(int width, bool isSigned) : width(width), is_signed(isSigned) {} + +}; + +class SpirvFloatType: public SpirvType { +public: + int width; + + SpirvFloatType(int width) : width(width) {} + + llvm::Type * build_llvm_type_impl(llvm::LLVMContext &ctx) override { + switch (width) { + case 16: return llvm::Type::getHalfTy(ctx); + case 32: return llvm::Type::getFloatTy(ctx); + case 64: return llvm::Type::getDoubleTy(ctx); + default: + BOOST_LOG_TRIVIAL(error) << "Invalid float width " << width; + return nullptr; + } + } +}; + +class SpirvSequenceType: public SpirvType { +protected: + std::shared_ptr inner; +public: + std::shared_ptr inner_type() const override { + return inner; + } + + explicit SpirvSequenceType(std::shared_ptr &&inner): inner(inner) {} + explicit SpirvSequenceType(std::shared_ptr &inner): inner(inner) {} +}; + +class SpirvArrayType: public SpirvSequenceType { + int length; +public: + SpirvArrayType(std::shared_ptr &&inner, int length) + : SpirvSequenceType(std::move(inner)), length(length) {} + SpirvArrayType(std::shared_ptr &inner, int length) + : SpirvSequenceType(inner), length(length) {} + llvm::Type *build_llvm_type_impl(llvm::LLVMContext &ctx) override { + inner->build_llvm_type(ctx); + return llvm::ArrayType::get(inner->get_llvm_type(), length); + } +}; + +class SpirvVectorType: public SpirvSequenceType { + int length; +public: + SpirvVectorType(std::shared_ptr &&inner, int length) + : SpirvSequenceType(std::move(inner)), length(length) {} + SpirvVectorType(std::shared_ptr &inner, int length) + : SpirvSequenceType(inner), length(length) {} + + llvm::Type *build_llvm_type_impl(llvm::LLVMContext &ctx) override { + inner->build_llvm_type(ctx); + return llvm::FixedVectorType::get(inner->get_llvm_type(), length); + } +}; + +class SpirvMatrixType: public SpirvSequenceType { + int n_cols; +public: + SpirvMatrixType(std::shared_ptr &&inner, int n_cols) + : SpirvSequenceType(std::move(inner)), n_cols(n_cols) {} + SpirvMatrixType(std::shared_ptr &inner, int n_cols) + : SpirvSequenceType(inner), n_cols(n_cols) {} + + llvm::Type *build_llvm_type_impl(llvm::LLVMContext &ctx) override { + inner->build_llvm_type(ctx); + // TODO: Is this really the right type? + return llvm::ArrayType::get(inner->get_llvm_type(), n_cols); + } +}; + +class SpirvStructType: public SpirvType { +public: + std::vector> members; + + explicit SpirvStructType(std::vector> members) : members(std::move(members)) {} + + llvm::Type * build_llvm_type_impl(llvm::LLVMContext &ctx) override { + std::vector inner_types; + for (auto &inner: members) { + inner->build_llvm_type(ctx); + inner_types.push_back(inner->get_llvm_type()); + } + auto ret = llvm::StructType::create(ctx, llvm::ArrayRef(inner_types)); + return ret; + } +}; + +class SpirvPointerType: public SpirvType { +public: + SpirvPointerType(std::shared_ptr inner) : inner(std::move(inner)) {} + llvm::Type * build_llvm_type_impl(llvm::LLVMContext &ctx) override { + inner->build_llvm_type(ctx); + return inner->get_llvm_type()->getPointerTo(); + } + +private: + std::shared_ptr inner; +}; + +class SpirvFunctionType: public SpirvType { +public: + SpirvFunctionType(std::shared_ptr retType, std::vector> argTypes) + : ret_type(std::move(retType)), arg_types(std::move(argTypes)) {} + +private: + std::shared_ptr ret_type; + std::vector> arg_types; + + llvm::Type * build_llvm_type_impl(llvm::LLVMContext &ctx) override { + ret_type->build_llvm_type(ctx); + + std::vector args; + for (auto &arg: arg_types) { + arg->build_llvm_type(ctx); + args.push_back(arg->get_llvm_type()); + } + + return llvm::FunctionType::get(ret_type->get_llvm_type(), llvm::ArrayRef(args), false); + } +}; + +std::string decode_string_arg(const uint32_t* args) { + std::string result; + for (int i = 0;; i++) { + uint32_t word = args[i]; + for (int j = 0; j < 4; j++) { + char c = (char)(word & 0xff); + if (!c) return result; + result.push_back(c); + word >>= 8; + } + } + return result; +} + +#define OP_BYTES(i) (insn->words + insn->operands[i].offset) +#define OP_WORD(i) (*OP_BYTES(i)) + +struct EntryPoint { + std::string name; + uint32_t func_id; + std::vector interface_vars; + +public: + EntryPoint(const spv_parsed_instruction_t *insn) { + func_id = OP_WORD(1); + name = decode_string_arg(OP_BYTES(2)); + for (int i = 3; i < insn->num_operands; i++) { + interface_vars.push_back(OP_WORD(i)); + } + } +}; + + + +static spv_result_t impl_parse_header( + void* user_data, spv_endianness_t endian, uint32_t magic, uint32_t version, + uint32_t generator, uint32_t id_bound, uint32_t reserved); + +static spv_result_t impl_parse_insn( + void* user_data, const spv_parsed_instruction_t* parsed_instruction); + +#pragma clang diagnostic push +#pragma ide diagnostic ignored "cppcoreguidelines-pro-type-member-init" +struct Constant { + enum class Type { + U32, U64, I32, I64, + F32, F64, + + } type; + + union { + uint64_t ival; + double fval; + }; + + Constant(uint32_t val): type(Type::U32), ival(val) {} + Constant(uint64_t val): type(Type::U64), ival(val) {} + Constant(int32_t val): type(Type::I32), ival(val) {} + Constant(int64_t val): type(Type::I64), ival(val) {} + Constant(float val): type(Type::F32), fval(val) {} + Constant(double val): type(Type::F64), fval(val) {} + Constant(Type type, uint64_t val): type(type), ival(val) {} + Constant(Type type, double val): type(type), fval(val) {} + Constant(): type(Type::I32), ival(0) {} +}; +#pragma clang diagnostic pop + +class Variable { +public: + + void get_llvm_var() { + + } +}; +struct CompilerImpl { + std::unique_ptr ctx; + std::unique_ptr module; + std::unique_ptr> builder; + + std::map ext_insts; + std::map> types; + std::map constants; + std::map> entry_points; + std::map globals; + std::map function_vars; + +public: + CompilerImpl() { + ctx = std::make_unique(); + module = std::make_unique("shader", *ctx); + builder = std::make_unique>(*ctx); + } + + bool process_module(std::vector &spv_module) { + spvtools::Context context(SPV_ENV_OPENGL_4_5); + auto spv_ctx = spvContextCreate(SPV_ENV_UNIVERSAL_1_0); + auto validationResult = spvValidateBinary(spv_ctx, &spv_module[0], spv_module.size(), nullptr); + if (validationResult != SPV_SUCCESS) { + // TODO: collect diagnostics + return false; + } + + // process spv_module + spvBinaryParse(spv_ctx, this, + &spv_module[0], spv_module.size(), + impl_parse_header, + impl_parse_insn, + nullptr); + return true; + } + + spv_result_t + handle_spv_header(spv_endianness_t endianness, uint32_t magic, uint32_t version, + uint32_t generator, + uint32_t id_bound, + uint32_t reserved) { + if (version != 0x00010000) { + return SPV_ERROR_WRONG_VERSION; + } + return SPV_SUCCESS; + // TODO: allocate structures based on id_bound. + } + + std::string format_insn(const spv_parsed_instruction_t* insn) { + + std::stringstream msg_stream; + msg_stream << "Opcode(" << insn->opcode << "):"; + + for (int i = 0; i < insn->num_operands; i++) { + auto operand = &insn->operands[i]; + msg_stream << " " << std::dec << operand->type << std::hex; + for (int j = 0; j < operand->num_words; j++) { + msg_stream << (j == 0 ? "[" : ", ") << insn->words[operand->offset + j]; + } + msg_stream << "]"; + } + return msg_stream.str(); + + } +#pragma clang diagnostic push +#pragma ide diagnostic ignored "bugprone-branch-clone" + spv_result_t handle_spv_insn(const spv_parsed_instruction_t *insn) { + uint32_t rid; + uint32_t rty; + uint32_t first_operand = insn->num_operands; + auto idata = insn->words; + + for (int i = 0; i < insn->num_operands; i++) { + auto operand = &insn->operands[i]; + if (operand->type == SPV_OPERAND_TYPE_RESULT_ID) { + rid = OP_WORD(i); + } else if (operand->type == SPV_OPERAND_TYPE_TYPE_ID) { + rty = OP_WORD(i); + } else { + first_operand = i; + break; + } + } + + using spv::Op; + switch (Op(insn->opcode)) { + case Op::OpNop:break; + //region Header instructions + case Op::OpCapability: + switch (spv::Capability(idata[insn->operands[0].offset])) { + case spv::Capability::Matrix: + case spv::Capability::Float16: + case spv::Capability::Float64: + case spv::Capability::Shader: + // all allowed + break; + default: + return SPV_ERROR_INVALID_CAPABILITY; + } + break; + case Op::OpExtInstImport: { + auto id = decode_string_arg(OP_BYTES(1)); + if (id == "GLSL.std.450") { + ext_insts[rid] = SPV_EXT_INST_TYPE_GLSL_STD_450; + } else { + BOOST_LOG_TRIVIAL(warning) << "Unknown extinst type " << id; + return SPV_ERROR_MISSING_EXTENSION; + } + break; + } + case Op::OpMemoryModel: { + if (spv::AddressingModel(OP_WORD(0)) != spv::AddressingModel::Logical) { + BOOST_LOG_TRIVIAL(error) << "Invalid addressing model"; + return SPV_ERROR_MISSING_EXTENSION; + } + // We always use memory model GLSL450 + break; + } + case Op::OpEntryPoint: { + if (spv::ExecutionModel(OP_WORD(0)) != spv::ExecutionModel::Fragment) { + break; // simply don't handle it + } + auto entry = std::make_shared(insn); + entry_points[entry->name] = std::move(entry); + break; + } + case Op::OpExecutionMode: { + // TODO: Save this for when generating the glue function + break; + } + //endregion + //region Annotations + case Op::OpDecorate: { + // TODO: save these for bindings + break; + } + case Op::OpMemberDecorate: { + // ignore + break; + } + //endregion + //region Types + case Op::OpTypeVoid: { + types[rid] = std::make_shared(); + break; + } + case Op::OpTypeBool: { + types[rid] = std::make_shared(); + break; + } + case Op::OpTypeInt: { + types[rid] = std::make_shared(OP_WORD(1), OP_WORD(2) != 0); + break; + } + case Op::OpTypeFloat: + types[rid] = std::make_shared(OP_WORD(1)); + break; + case Op::OpTypeVector: { + types[rid] = std::make_shared(types[OP_WORD(1)], OP_WORD(2)); + break; + } + case Op::OpTypeMatrix: { + types[rid] = std::make_shared(types[OP_WORD(1)], OP_WORD(2)); + break; + } + case Op::OpTypeArray: { + types[rid] = std::make_shared(types[OP_WORD(1)], constants[OP_WORD(2)].ival); + break; + } + // Op::OpTypeRuntimeArray: {} + case Op::OpTypeStruct: { + std::vector> members; + for (int i = 1; i < insn->num_operands; i++) { + members.push_back(types[OP_WORD(i)]); + } + types[rid] = std::make_shared(std::move(members)); + break; + } + case Op::OpTypePointer: { + // ignore storage class, for now... + types[rid] = std::make_shared(types[OP_WORD(2)]); + break; + } + case Op::OpTypeFunction: { + std::vector> args; + for (int i = 2; i < insn->num_operands; i++) { + args.push_back(types[OP_WORD(i)]); + } + types[rid] = std::make_shared(types[OP_WORD(1)], std::move(args)); + break; + } + //endregion + //region Constants + case Op::OpConstantTrue: { + constants[rid] = Constant(uint32_t(1)); + break; + } + case Op::OpConstantFalse: + constants[rid] = Constant(uint32_t(0)); + break; + case Op::OpConstant: { + auto &type = types[rty]; + auto ftype = std::dynamic_pointer_cast(type); + auto itype = std::dynamic_pointer_cast(type); + if (ftype != nullptr) { + double val = 0; + float fval = 0; + uint64_t bits; + switch (ftype->width) { + case 64: + // we've got a double here... + bits = (uint64_t)OP_BYTES(2)[0] + | ((uint64_t)(OP_BYTES(2)[1]) << 32); + memcpy(&val, &bits, 8); + constants[rid] = val; + break; + case 32: + // float + memcpy(&fval, OP_BYTES(2), 4); + constants[rid] = fval; + break; + case 16: + // half; we don't implement this yet + BOOST_LOG_TRIVIAL(warning) << "Half-precision floats incompletely implemented"; + constants[rid] = fval; + break; + } + } else if (itype != nullptr) { + uint64_t val = 0; + switch (itype->width) { + case 64: + constants[rid] = (uint64_t)OP_BYTES(2)[0] + | ((uint64_t)(OP_BYTES(2)[1]) << 32); + break; + case 32: + constants[rid] = OP_WORD(2); + } + } + break; + } + //endregion + //region Variables + case Op::OpVariable: { + + break; + } + //endregion + //region + //endregion + default:BOOST_LOG_TRIVIAL(info) << "Unhandled instruction " << format_insn(insn); + } + + return SPV_SUCCESS; + } +#pragma clang diagnostic pop + +}; + + +Compiler::Compiler(): impl(std::make_unique()) { +} + +bool Compiler::compile(std::vector &spv_module) { + return impl->process_module(spv_module); +} + +Compiler::~Compiler() = default; + +spv_result_t +impl_parse_header(void *user_data, spv_endianness_t endian, uint32_t magic, uint32_t version, uint32_t generator, + uint32_t id_bound, uint32_t reserved) { + auto compiler = (CompilerImpl*)user_data; + return compiler->handle_spv_header(endian, magic, version, generator, id_bound, reserved); +} + +spv_result_t impl_parse_insn(void *user_data, const spv_parsed_instruction_t *parsed_instruction) { + auto compiler = (CompilerImpl*)user_data; + return compiler->handle_spv_insn(parsed_instruction); +} diff --git a/src/compiler.hpp b/src/compiler.hpp new file mode 100644 index 0000000..aa47087 --- /dev/null +++ b/src/compiler.hpp @@ -0,0 +1,18 @@ +// +// Created by thequux on 5/24/22. +// + +#pragma once + +#include +#include + +struct CompilerImpl; +class Compiler { + std::unique_ptr impl; +public: + Compiler(); + + bool compile(std::vector &spv_module); + virtual ~Compiler(); +}; \ No newline at end of file diff --git a/src/pre-compiler.cpp b/src/pre-compiler.cpp new file mode 100644 index 0000000..c49ca7a --- /dev/null +++ b/src/pre-compiler.cpp @@ -0,0 +1,97 @@ +// +// Created by thequux on 5/23/22. +// + +#include +#include "pre-compiler.hpp" +#include +#include +#include + + +std::shared_ptr> pre_compile_shader(const std::string &src) { + shaderc::Compiler compiler; + shaderc::CompileOptions options; + options.SetSourceLanguage(shaderc_source_language_glsl); + options.SetOptimizationLevel(shaderc_optimization_level_performance); + options.SetTargetSpirv(shaderc_spirv_version_1_0); + options.SetTargetEnvironment( + shaderc_target_env_opengl_compat, + shaderc_env_version::shaderc_env_version_opengl_4_5); + + + auto result = compiler.CompileGlslToSpv( + src, + shaderc_glsl_fragment_shader, + "shader.frag", + "main", + options); + + std::string status_message; + bool compilation_ok = false; + switch (result.GetCompilationStatus()) { + case shaderc_compilation_status_success:status_message = "Pre-compile succeeded"; + compilation_ok = true; + break; + case shaderc_compilation_status_invalid_stage:status_message = "Failed: Invalid stage"; + break; + case shaderc_compilation_status_compilation_error:status_message = "Failed: Compilation error"; + break; + case shaderc_compilation_status_internal_error:status_message = "Failed: Internal error"; + break; + case shaderc_compilation_status_null_result_object:status_message = "Failed: Null result object"; + break; + case shaderc_compilation_status_invalid_assembly:status_message = "Failed: Invalid assembly"; + break; + case shaderc_compilation_status_validation_error:status_message = "Failed: validation error"; + break; + case shaderc_compilation_status_transformation_error:status_message = "Failed: transformation error"; + break; + case shaderc_compilation_status_configuration_error:status_message = "Failed: configuration error"; + break; + }; + + + auto err_msg = result.GetErrorMessage(); + if (!err_msg.empty()) { + status_message += '\n'; + status_message += err_msg; + } + std::cerr << err_msg; + + + + if (compilation_ok) { + auto module = std::make_shared>( + result.cbegin(), result.cend() + ); + + { + std::ofstream shader_color("shader.spv_t"); + + spvtools::SpirvTools tools(SPV_ENV_OPENGL_4_5); + std::string result; + tools.Disassemble(*module, &result, + SPV_BINARY_TO_TEXT_OPTION_COLOR | + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES | + SPV_BINARY_TO_TEXT_OPTION_INDENT | + SPV_BINARY_TO_TEXT_OPTION_COMMENT); + shader_color << result; + shader_color.flush(); + shader_color.close(); + + std::ofstream shader_spv("shader.spv"); + result.clear(); + tools.Disassemble(*module, &result, + SPV_BINARY_TO_TEXT_OPTION_INDENT); + shader_spv << result; + shader_spv.flush(); + shader_spv.close(); + + } + + return module; + } else { + return {}; + } +} diff --git a/src/pre-compiler.hpp b/src/pre-compiler.hpp new file mode 100644 index 0000000..c7a6c74 --- /dev/null +++ b/src/pre-compiler.hpp @@ -0,0 +1,10 @@ +// +// Created by thequux on 5/23/22. +// + +#pragma once +#include +#include +#include + +std::shared_ptr> pre_compile_shader(const std::string& src);