Initial commit; tgv handled

This commit is contained in:
2022-05-24 20:19:20 +02:00
commit 673e00456e
9 changed files with 776 additions and 0 deletions

1
.envrc Normal file
View File

@@ -0,0 +1 @@
eval "$(lorri direnv)"

31
CMakeLists.txt Normal file
View File

@@ -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})

24
shell.nix Normal file
View File

@@ -0,0 +1,24 @@
{ pkgs ? import <nixpkgs> {} }:
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}";
}

47
src/bluebell.cpp Normal file
View File

@@ -0,0 +1,47 @@
//
// Created by thequux on 5/23/22.
//
#include <sstream>
#include <iostream>
#include <fstream>
#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);
}

6
src/bluebell.hpp Normal file
View File

@@ -0,0 +1,6 @@
//
// Created by thequux on 5/23/22.
//
#pragma once

542
src/compiler.cpp Normal file
View File

@@ -0,0 +1,542 @@
//
// Created by thequux on 5/24/22.
//
#include "compiler.hpp"
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Module.h>
#include <llvm/IR/IRBuilder.h>
#include <boost/log/trivial.hpp>
#include <spirv-tools/libspirv.hpp>
#include <spirv/1.0/spirv.hpp11>
#include <sstream>
#include <iostream>
#include <utility>
#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<SpirvType> 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<SpirvType> inner;
public:
std::shared_ptr<SpirvType> inner_type() const override {
return inner;
}
explicit SpirvSequenceType(std::shared_ptr<SpirvType> &&inner): inner(inner) {}
explicit SpirvSequenceType(std::shared_ptr<SpirvType> &inner): inner(inner) {}
};
class SpirvArrayType: public SpirvSequenceType {
int length;
public:
SpirvArrayType(std::shared_ptr<SpirvType> &&inner, int length)
: SpirvSequenceType(std::move(inner)), length(length) {}
SpirvArrayType(std::shared_ptr<SpirvType> &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<SpirvType> &&inner, int length)
: SpirvSequenceType(std::move(inner)), length(length) {}
SpirvVectorType(std::shared_ptr<SpirvType> &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<SpirvType> &&inner, int n_cols)
: SpirvSequenceType(std::move(inner)), n_cols(n_cols) {}
SpirvMatrixType(std::shared_ptr<SpirvType> &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<std::shared_ptr<SpirvType>> members;
explicit SpirvStructType(std::vector<std::shared_ptr<SpirvType>> members) : members(std::move(members)) {}
llvm::Type * build_llvm_type_impl(llvm::LLVMContext &ctx) override {
std::vector<llvm::Type*> 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<llvm::Type*>(inner_types));
return ret;
}
};
class SpirvPointerType: public SpirvType {
public:
SpirvPointerType(std::shared_ptr<SpirvType> 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<SpirvType> inner;
};
class SpirvFunctionType: public SpirvType {
public:
SpirvFunctionType(std::shared_ptr<SpirvType> retType, std::vector<std::shared_ptr<SpirvType>> argTypes)
: ret_type(std::move(retType)), arg_types(std::move(argTypes)) {}
private:
std::shared_ptr<SpirvType> ret_type;
std::vector<std::shared_ptr<SpirvType>> arg_types;
llvm::Type * build_llvm_type_impl(llvm::LLVMContext &ctx) override {
ret_type->build_llvm_type(ctx);
std::vector<llvm::Type*> 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<llvm::Type*>(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<uint32_t> 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<llvm::LLVMContext> ctx;
std::unique_ptr<llvm::Module> module;
std::unique_ptr<llvm::IRBuilder<>> builder;
std::map<uint32_t, spv_ext_inst_type_t> ext_insts;
std::map<uint32_t, std::shared_ptr<SpirvType>> types;
std::map<uint32_t, Constant> constants;
std::map<std::string, std::shared_ptr<EntryPoint>> entry_points;
std::map<uint32_t, Variable> globals;
std::map<uint32_t, Variable> function_vars;
public:
CompilerImpl() {
ctx = std::make_unique<llvm::LLVMContext>();
module = std::make_unique<llvm::Module>("shader", *ctx);
builder = std::make_unique<llvm::IRBuilder<>>(*ctx);
}
bool process_module(std::vector<uint32_t> &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<EntryPoint>(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<SpirvVoidType>();
break;
}
case Op::OpTypeBool: {
types[rid] = std::make_shared<SpirvBoolType>();
break;
}
case Op::OpTypeInt: {
types[rid] = std::make_shared<SpirvIntType>(OP_WORD(1), OP_WORD(2) != 0);
break;
}
case Op::OpTypeFloat:
types[rid] = std::make_shared<SpirvFloatType>(OP_WORD(1));
break;
case Op::OpTypeVector: {
types[rid] = std::make_shared<SpirvVectorType>(types[OP_WORD(1)], OP_WORD(2));
break;
}
case Op::OpTypeMatrix: {
types[rid] = std::make_shared<SpirvMatrixType>(types[OP_WORD(1)], OP_WORD(2));
break;
}
case Op::OpTypeArray: {
types[rid] = std::make_shared<SpirvArrayType>(types[OP_WORD(1)], constants[OP_WORD(2)].ival);
break;
}
// Op::OpTypeRuntimeArray: {}
case Op::OpTypeStruct: {
std::vector<std::shared_ptr<SpirvType>> members;
for (int i = 1; i < insn->num_operands; i++) {
members.push_back(types[OP_WORD(i)]);
}
types[rid] = std::make_shared<SpirvStructType>(std::move(members));
break;
}
case Op::OpTypePointer: {
// ignore storage class, for now...
types[rid] = std::make_shared<SpirvPointerType>(types[OP_WORD(2)]);
break;
}
case Op::OpTypeFunction: {
std::vector<std::shared_ptr<SpirvType>> args;
for (int i = 2; i < insn->num_operands; i++) {
args.push_back(types[OP_WORD(i)]);
}
types[rid] = std::make_shared<SpirvFunctionType>(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<SpirvFloatType>(type);
auto itype = std::dynamic_pointer_cast<SpirvIntType>(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<CompilerImpl>()) {
}
bool Compiler::compile(std::vector<uint32_t> &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);
}

18
src/compiler.hpp Normal file
View File

@@ -0,0 +1,18 @@
//
// Created by thequux on 5/24/22.
//
#pragma once
#include <memory>
#include <vector>
struct CompilerImpl;
class Compiler {
std::unique_ptr<CompilerImpl> impl;
public:
Compiler();
bool compile(std::vector<uint32_t> &spv_module);
virtual ~Compiler();
};

97
src/pre-compiler.cpp Normal file
View File

@@ -0,0 +1,97 @@
//
// Created by thequux on 5/23/22.
//
#include <iostream>
#include "pre-compiler.hpp"
#include <shaderc/shaderc.hpp>
#include <spirv-tools/libspirv.hpp>
#include <fstream>
std::shared_ptr<std::vector<uint32_t>> 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<std::vector<uint32_t>>(
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 {};
}
}

10
src/pre-compiler.hpp Normal file
View File

@@ -0,0 +1,10 @@
//
// Created by thequux on 5/23/22.
//
#pragma once
#include <shaderc/shaderc.hpp>
#include <string>
#include <memory>
std::shared_ptr<std::vector<uint32_t>> pre_compile_shader(const std::string& src);