diff --git a/hecl/blender/BlenderConnection.cpp b/hecl/blender/BlenderConnection.cpp index 30ec96f65..e70a318c9 100644 --- a/hecl/blender/BlenderConnection.cpp +++ b/hecl/blender/BlenderConnection.cpp @@ -458,6 +458,7 @@ void BlenderConnection::PyOutStream::linkBlend(const std::string& target, } BlenderConnection::DataStream::Mesh::Mesh(BlenderConnection& conn, int skinSlotCount) +: aabbMin(conn), aabbMax(conn) { uint32_t matSetCount; conn._readBuf(&matSetCount, 4); diff --git a/hecl/blender/BlenderConnection.hpp b/hecl/blender/BlenderConnection.hpp index becec5ad7..c1770d500 100644 --- a/hecl/blender/BlenderConnection.hpp +++ b/hecl/blender/BlenderConnection.hpp @@ -19,6 +19,7 @@ #include #include "HECL/HECL.hpp" +#include namespace HECL { @@ -283,6 +284,26 @@ public: /* Intermediate mesh representation prepared by blender from a single mesh object */ struct Mesh { + struct Vector2f + { + atVec2f val; + Vector2f(BlenderConnection& conn) {conn._readBuf(&val, 8);} + }; + struct Vector3f + { + atVec3f val; + Vector3f(BlenderConnection& conn) {conn._readBuf(&val, 12);} + }; + struct Index + { + uint32_t val; + Index(BlenderConnection& conn) {conn._readBuf(&val, 4);} + }; + + /* Cumulative AABB */ + Vector3f aabbMin; + Vector3f aabbMax; + /* HECL source of each material */ struct Material { @@ -294,21 +315,6 @@ public: std::vector> materialSets; /* Vertex buffer data */ - struct Vector2f - { - float val[2]; - Vector2f(BlenderConnection& conn) {conn._readBuf(val, 8);} - }; - struct Vector3f - { - float val[3]; - Vector3f(BlenderConnection& conn) {conn._readBuf(val, 12);} - }; - struct Index - { - uint32_t val; - Index(BlenderConnection& conn) {conn._readBuf(&val, 4);} - }; std::vector pos; std::vector norm; uint32_t colorLayerCount = 0; diff --git a/hecl/blender/hecl/hmdl/__init__.py b/hecl/blender/hecl/hmdl/__init__.py index 996250581..9cf2e4a03 100644 --- a/hecl/blender/hecl/hmdl/__init__.py +++ b/hecl/blender/hecl/hmdl/__init__.py @@ -94,6 +94,13 @@ def cook(writebuf, mesh_obj, max_skin_banks, max_octant_length=None): copy_mesh.calc_normals_split() rna_loops = copy_mesh.loops + # Filter out useless AABB points and send data + aabb = bytearray() + for comp in copy_obj.bound_box[0]: + writebuf(struct.pack('f', comp)) + for comp in copy_obj.bound_box[6]: + writebuf(struct.pack('f', comp)) + # Create master BMesh and VertPool bm_master = bmesh.new() bm_master.from_mesh(copy_obj.data) @@ -177,13 +184,6 @@ def cook(writebuf, mesh_obj, max_skin_banks, max_octant_length=None): # No more surfaces writebuf(struct.pack('B', 0)) - # Filter out useless AABB points and generate data array - #aabb = bytearray() - #for comp in copy_obj.bound_box[0]: - # aabb += struct.pack('f', comp) - #for comp in copy_obj.bound_box[6]: - # aabb += struct.pack('f', comp) - # Delete copied mesh from scene bm_master.free() bpy.context.scene.objects.unlink(copy_obj) diff --git a/hecl/extern/Athena b/hecl/extern/Athena index f4716070d..e6dedd0e6 160000 --- a/hecl/extern/Athena +++ b/hecl/extern/Athena @@ -1 +1 @@ -Subproject commit f4716070dd33f910e4854997a91a249e40922229 +Subproject commit e6dedd0e6cd72117cc3b06ec1b42aec0371d5790 diff --git a/hecl/extern/LogVisor b/hecl/extern/LogVisor index 8b9dd5695..189e04797 160000 --- a/hecl/extern/LogVisor +++ b/hecl/extern/LogVisor @@ -1 +1 @@ -Subproject commit 8b9dd56955f0ada6cb89e77eb7ea65fb15efdb0c +Subproject commit 189e047977b138b711259ad84d94471f5d006ffb diff --git a/hecl/include/HECL/Frontend.hpp b/hecl/include/HECL/Frontend.hpp index 6fdf15cdf..c775a5a8f 100644 --- a/hecl/include/HECL/Frontend.hpp +++ b/hecl/include/HECL/Frontend.hpp @@ -1,4 +1,195 @@ #ifndef HECLFRONTEND_HPP #define HECLFRONTEND_HPP +#include +#include +#include +#include + +namespace HECL +{ +namespace Frontend +{ + +struct SourceLocation +{ + int line = -1; + int col = -1; + SourceLocation() = default; + SourceLocation(int l, int c) : line(l), col(c) {} +}; + +class Diagnostics +{ + std::string m_name; +public: + void setName(const std::string& name) {m_name = name;} + void reportParserErr(const SourceLocation& l, const char* format, ...); + void reportLexerErr(const SourceLocation& l, const char* format, ...); +}; + +class Parser +{ +public: + enum TokenType + { + TokenNone, + TokenSourceBegin, + TokenSourceEnd, + TokenNumLiteral, + TokenVectorSwizzle, + TokenEvalGroupStart, + TokenEvalGroupEnd, + TokenFunctionStart, + TokenFunctionEnd, + TokenFunctionArgDelim, + TokenArithmeticOp, + }; +private: + Diagnostics& m_diag; + const std::string* m_source = nullptr; + std::string::const_iterator m_sourceIt; + std::vector m_parenStack; + bool m_reset = false; + void skipWhitespace(std::string::const_iterator& it); +public: + struct Token + { + TokenType m_type; + SourceLocation m_location; + std::string m_tokenString; + int m_tokenInt = 0; + float m_tokenFloat = 0.0; + Token() : m_type(TokenNone) {} + Token(TokenType type, SourceLocation loc) : m_type(type), m_location(loc) {} + }; + void reset(const std::string& source); + Token consumeToken(); + SourceLocation getLocation() const; + + Parser(Diagnostics& diag) : m_diag(diag) {} +}; + +struct IR +{ + enum OpType + { + OpNone, /**< NOP */ + OpCall, /**< Deferred function insertion for HECL backend using specified I/O regs */ + OpLoadImm, /**< Load a constant (numeric literal) into register */ + OpArithmetic, /**< Perform binary arithmetic between registers */ + OpSwizzle /**< Vector insertion/extraction/swizzling operation */ + }; + + enum RegType + { + RegNone, + RegFloat, + RegVec3, + RegVec4 + }; + + struct RegID + { + RegType m_type = RegNone; + unsigned m_idx = 0; + }; + + struct Instruction + { + OpType m_op = OpNone; + + struct + { + std::vector m_callRegs; + RegID m_target; + } m_call; + + struct + { + atVec4f m_immVec; + RegID m_target; + } m_loadImm; + + struct + { + enum ArithmeticOpType + { + ArithmeticOpNone, + ArithmeticOpAdd, + ArithmeticOpSubtract, + ArithmeticOpMultiply, + ArithmeticOpDivide + } m_op = ArithmeticOpNone; + RegID m_a; + RegID m_b; + RegID m_target; + } m_arithmetic; + + struct + { + RegID m_source; + int m_sourceIdxs[4] = {-1}; + RegID m_target; + int m_targetIdxs[4] = {-1}; + } m_swizzle; + }; + + unsigned m_floatRegCount = 0; + unsigned m_vec3RegCount = 0; + unsigned m_vec4RegCount = 0; + std::vector m_instructions; +}; + +class Lexer +{ + friend class OperationNode; + Diagnostics& m_diag; + + /* Intermediate tree-node for organizing tokens into operations */ + struct OperationNode + { + Parser::Token m_tok; + OperationNode* m_prev = nullptr; + OperationNode* m_next = nullptr; + OperationNode* m_sub = nullptr; + + OperationNode() {} + OperationNode(Parser::Token&& tok) : m_tok(std::move(tok)) {} + }; + + /* Pool of nodes to keep ownership (forward_list so pointers aren't invalidated) */ + std::forward_list m_pool; + + /* Final lexed root function (IR comes from this) */ + OperationNode* m_root = nullptr; + +public: + void reset(); + void consumeAllTokens(Parser& parser); + IR compileIR() const; + + Lexer(Diagnostics& diag) : m_diag(diag) {} +}; + +class Frontend +{ + Diagnostics m_diag; + Parser m_parser; + Lexer m_lexer; +public: + IR compileSource(const std::string& source, const std::string& diagName) + { + m_diag.setName(diagName); + m_parser.reset(source); + m_lexer.consumeAllTokens(m_parser); + return m_lexer.compileIR(); + } + + Frontend() : m_parser(m_diag), m_lexer(m_diag) {} +}; + +} +} + #endif // HECLFRONTEND_HPP diff --git a/hecl/lib/Frontend/CHECLIR.cpp b/hecl/lib/Frontend/CHECLIR.cpp deleted file mode 100644 index e69de29bb..000000000 diff --git a/hecl/lib/Frontend/CHECLLexer.cpp b/hecl/lib/Frontend/CHECLLexer.cpp deleted file mode 100644 index e69de29bb..000000000 diff --git a/hecl/lib/Frontend/CMakeLists.txt b/hecl/lib/Frontend/CMakeLists.txt index 759d539e9..8e4c7b16d 100644 --- a/hecl/lib/Frontend/CMakeLists.txt +++ b/hecl/lib/Frontend/CMakeLists.txt @@ -1,3 +1,4 @@ add_library(HECLFrontend - CHECLIR.cpp - CHECLLexer.cpp) + Parser.cpp + Lexer.cpp + Diagnostics.cpp) diff --git a/hecl/lib/Frontend/Diagnostics.cpp b/hecl/lib/Frontend/Diagnostics.cpp new file mode 100644 index 000000000..344d6565b --- /dev/null +++ b/hecl/lib/Frontend/Diagnostics.cpp @@ -0,0 +1,65 @@ +#include "HECL/HECL.hpp" +#include "HECL/Frontend.hpp" + +#include + +/* ANSI sequences */ +#define RED "\x1b[1;31m" +#define YELLOW "\x1b[1;33m" +#define GREEN "\x1b[1;32m" +#define MAGENTA "\x1b[1;35m" +#define CYAN "\x1b[1;36m" +#define BOLD "\x1b[1m" +#define NORMAL "\x1b[0m" + +namespace HECL +{ +namespace Frontend +{ + +void Diagnostics::reportParserErr(const SourceLocation& l, const char* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + char* result = nullptr; +#ifdef _WIN32 + int length = _vscprintf(fmt, ap); + result = (char*)malloc(length); + vsnprintf(result, length, fmt, ap); +#else + vasprintf(&result, fmt, ap); +#endif + va_end(ap); + if (LogVisor::XtermColor) + LogModule.report(LogVisor::FatalError, RED "Error parsing" NORMAL " '%s' " YELLOW "@%d:%d " NORMAL "\n%s", + m_name.c_str(), l.line, l.col, result); + else + LogModule.report(LogVisor::FatalError, "Error parsing '%s' @%d:%d\n%s", + m_name.c_str(), l.line, l.col, result); + free(result); +} + +void Diagnostics::reportLexerErr(const SourceLocation& l, const char* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + char* result = nullptr; +#ifdef _WIN32 + int length = _vscprintf(fmt, ap); + result = (char*)malloc(length); + vsnprintf(result, length, fmt, ap); +#else + vasprintf(&result, fmt, ap); +#endif + va_end(ap); + if (LogVisor::XtermColor) + LogModule.report(LogVisor::FatalError, RED "Error lexing" NORMAL " '%s' " YELLOW "@%d:%d " NORMAL "\n%s", + m_name.c_str(), l.line, l.col, result); + else + LogModule.report(LogVisor::FatalError, "Error lexing '%s' @%d:%d\n%s", + m_name.c_str(), l.line, l.col, result); + free(result); +} + +} +} diff --git a/hecl/lib/Frontend/Lexer.cpp b/hecl/lib/Frontend/Lexer.cpp new file mode 100644 index 000000000..bc2937820 --- /dev/null +++ b/hecl/lib/Frontend/Lexer.cpp @@ -0,0 +1,268 @@ +#include "HECL/HECL.hpp" +#include "HECL/Frontend.hpp" + +namespace HECL +{ +namespace Frontend +{ + +void Lexer::reset() +{ + m_root = nullptr; + m_pool.clear(); +} + +void Lexer::consumeAllTokens(Parser& parser) +{ + reset(); + Parser::Token firstTok = parser.consumeToken(); + if (firstTok.m_type != Parser::TokenSourceBegin) + { + m_diag.reportLexerErr(firstTok.m_location, "expected start token"); + return; + } + + m_pool.emplace_front(std::move(firstTok)); + Lexer::OperationNode* firstNode = &m_pool.front(); + Lexer::OperationNode* lastNode = firstNode; + + /* Build linked-list of nodes parsed in-order */ + { + std::vector funcStack; + std::vector groupStack; + while (lastNode->m_tok.m_type != Parser::TokenSourceEnd) + { + Parser::Token tok = parser.consumeToken(); + switch (tok.m_type) + { + case Parser::TokenEvalGroupStart: + groupStack.push_back(tok.m_location); + break; + case Parser::TokenEvalGroupEnd: + if (groupStack.empty()) + { + m_diag.reportLexerErr(tok.m_location, "unbalanced group detected"); + return; + } + groupStack.pop_back(); + break; + case Parser::TokenFunctionStart: + funcStack.push_back(tok.m_location); + break; + case Parser::TokenFunctionEnd: + if (funcStack.empty()) + { + m_diag.reportLexerErr(tok.m_location, "unbalanced function detected"); + return; + } + funcStack.pop_back(); + break; + case Parser::TokenSourceEnd: + case Parser::TokenNumLiteral: + case Parser::TokenVectorSwizzle: + case Parser::TokenFunctionArgDelim: + case Parser::TokenArithmeticOp: + break; + default: + m_diag.reportLexerErr(tok.m_location, "invalid token"); + return; + } + m_pool.emplace_front(std::move(tok)); + lastNode->m_next = &m_pool.front(); + m_pool.front().m_prev = lastNode; + lastNode = &m_pool.front(); + } + + /* Ensure functions and groups are balanced */ + if (funcStack.size()) + { + m_diag.reportLexerErr(funcStack.back(), "unclosed function detected"); + return; + } + if (groupStack.size()) + { + m_diag.reportLexerErr(groupStack.back(), "unclosed group detected"); + return; + } + } + + /* Ensure first non-start node is a function */ + if (firstNode->m_next->m_tok.m_type != Parser::TokenFunctionStart) + { + m_diag.reportLexerErr(firstNode->m_tok.m_location, "expected root function"); + return; + } + + /* Organize marked function args into implicit groups */ + for (Lexer::OperationNode* n = firstNode ; n != lastNode ; n = n->m_next) + { + if (n->m_tok.m_type == Parser::TokenFunctionStart) + { + if (n->m_next->m_tok.m_type != Parser::TokenFunctionEnd) + { + if (n->m_next->m_tok.m_type == Parser::TokenFunctionArgDelim) + { + m_diag.reportLexerErr(n->m_next->m_tok.m_location, "empty function arg"); + return; + } + m_pool.emplace_front(std::move( + Parser::Token(Parser::TokenEvalGroupStart, n->m_next->m_tok.m_location))); + Lexer::OperationNode* grp = &m_pool.front(); + grp->m_next = n->m_next; + grp->m_prev = n; + n->m_next->m_prev = grp; + n->m_next = grp; + } + } + else if (n->m_tok.m_type == Parser::TokenFunctionEnd) + { + if (n->m_prev->m_tok.m_type != Parser::TokenEvalGroupStart) + { + m_pool.emplace_front(std::move( + Parser::Token(Parser::TokenEvalGroupEnd, n->m_tok.m_location))); + Lexer::OperationNode* grp = &m_pool.front(); + grp->m_next = n; + grp->m_prev = n->m_prev; + n->m_prev->m_next = grp; + n->m_prev = grp; + } + } + else if (n->m_tok.m_type == Parser::TokenFunctionArgDelim) + { + if (n->m_next->m_tok.m_type == Parser::TokenFunctionArgDelim || + n->m_next->m_tok.m_type == Parser::TokenFunctionEnd) + { + m_diag.reportLexerErr(n->m_next->m_tok.m_location, "empty function arg"); + return; + } + + m_pool.emplace_front(std::move( + Parser::Token(Parser::TokenEvalGroupEnd, n->m_tok.m_location))); + Lexer::OperationNode* egrp = &m_pool.front(); + + m_pool.emplace_front(std::move( + Parser::Token(Parser::TokenEvalGroupStart, n->m_next->m_tok.m_location))); + Lexer::OperationNode* sgrp = &m_pool.front(); + + egrp->m_next = sgrp; + sgrp->m_prev = egrp; + + sgrp->m_next = n->m_next; + egrp->m_prev = n->m_prev; + n->m_next->m_prev = sgrp; + n->m_prev->m_next = egrp; + } + } + + /* Organize marked groups into tree-hierarchy */ + { + std::vector groupStack; + for (Lexer::OperationNode* n = firstNode ; n != lastNode ; n = n->m_next) + { + if (n->m_tok.m_type == Parser::TokenEvalGroupStart) + groupStack.push_back(n); + else if (n->m_tok.m_type == Parser::TokenEvalGroupEnd) + { + Lexer::OperationNode* start = groupStack.back(); + groupStack.pop_back(); + if (n->m_prev == start) + { + m_diag.reportLexerErr(start->m_tok.m_location, "empty group"); + return; + } + start->m_sub = start->m_next; + start->m_next = n->m_next; + if (n->m_next) + n->m_next->m_prev = start; + n->m_prev->m_next = nullptr; + } + } + } + + /* Organize functions into tree-hierarchy */ + for (Lexer::OperationNode& n : m_pool) + { + if (n.m_tok.m_type == Parser::TokenFunctionStart) + { + for (Lexer::OperationNode* sn = n.m_next ; sn ; sn = sn->m_next) + { + if (sn->m_tok.m_type == Parser::TokenFunctionEnd) + { + n.m_sub = n.m_next; + n.m_next = sn->m_next; + sn->m_next->m_prev = &n; + n.m_sub->m_prev = nullptr; + sn->m_prev->m_next = nullptr; + break; + } + } + } + } + + /* Organize vector swizzles into tree-hierarchy */ + for (Lexer::OperationNode& n : m_pool) + { + if (n.m_tok.m_type == Parser::TokenVectorSwizzle) + { + if (n.m_prev->m_tok.m_type != Parser::TokenFunctionStart) + { + m_diag.reportLexerErr(n.m_tok.m_location, + "vector swizzles may only follow functions"); + return; + } + Lexer::OperationNode* func = n.m_prev; + n.m_sub = func; + n.m_prev = func->m_prev; + func->m_prev->m_next = &n; + func->m_next = nullptr; + func->m_prev = nullptr; + } + } + + /* Ensure evaluation groups have proper arithmetic usage */ + for (Lexer::OperationNode& n : m_pool) + { + if (n.m_tok.m_type == Parser::TokenEvalGroupStart) + { + int idx = 0; + for (Lexer::OperationNode* sn = n.m_sub ; sn ; sn = sn->m_next, ++idx) + { + if ((sn->m_tok.m_type == Parser::TokenArithmeticOp && !(idx & 1)) || + (sn->m_tok.m_type != Parser::TokenArithmeticOp && (idx & 1))) + { + m_diag.reportLexerErr(sn->m_tok.m_location, "improper arithmetic expression"); + return; + } + } + } + } + + /* Done! */ + m_root = firstNode->m_next; +} + +IR Lexer::compileIR() const +{ + if (!m_root) + LogModule.report(LogVisor::FatalError, "unable to compile HECL-IR for invalid source"); + + IR ir; + + /* Determine maximum float regs */ + for (const Lexer::OperationNode& n : m_pool) + { + if (n.m_tok.m_type == Parser::TokenFunctionStart) + { + for (Lexer::OperationNode* sn = n.m_sub ; sn ; sn = sn->m_next) + { + + } + } + } + + return ir; +} + +} +} + diff --git a/hecl/lib/Frontend/Parser.cpp b/hecl/lib/Frontend/Parser.cpp new file mode 100644 index 000000000..73acf2f9a --- /dev/null +++ b/hecl/lib/Frontend/Parser.cpp @@ -0,0 +1,192 @@ +#include "HECL/HECL.hpp" +#include "HECL/Frontend.hpp" +#include + +namespace HECL +{ +namespace Frontend +{ + +void Parser::skipWhitespace(std::string::const_iterator& it) +{ + while (true) + { + while (isspace(*it) && it != m_source->cend()) + ++it; + + /* Skip comment line */ + if (*it == '#') + { + while (*it != '\n' && it != m_source->cend()) + ++it; + if (*it == '\n') + ++it; + continue; + } + break; + } +} + +void Parser::reset(const std::string& source) +{ + m_source = &source; + m_sourceIt = m_source->cbegin(); + m_parenStack.clear(); + m_reset = true; +} + +Parser::Token Parser::consumeToken() +{ + if (!m_source) + return Parser::Token(TokenNone, SourceLocation()); + + /* If parser has just been reset, emit begin token */ + if (m_reset) + { + m_reset = false; + return Parser::Token(TokenSourceBegin, getLocation()); + } + + /* Skip whitespace */ + skipWhitespace(m_sourceIt); + + /* Check for source end */ + if (m_sourceIt == m_source->cend()) + return Parser::Token(TokenSourceEnd, getLocation()); + + /* Check for numeric literal */ + { + char* strEnd; + float val = std::strtof(&*m_sourceIt, &strEnd); + if (&*m_sourceIt != strEnd) + { + Parser::Token tok(TokenNumLiteral, getLocation()); + tok.m_tokenFloat = val; + m_sourceIt += (strEnd - &*m_sourceIt); + return tok; + } + } + + /* Check for swizzle op */ + if (*m_sourceIt == '.') + { + int count = 0; + std::string::const_iterator tmp = m_sourceIt + 1; + if (tmp != m_source->cend()) + { + for (int i=0 ; i<4 ; ++i) + { + std::string::const_iterator tmp2 = tmp + i; + if (tmp2 == m_source->cend()) + break; + char ch = tolower(*tmp2); + if (ch >= 'w' && ch <= 'z') + ++count; + else if (ch == 'r' || ch == 'g' || ch == 'b' || ch == 'a') + ++count; + else + break; + } + } + if (count) + { + Parser::Token tok(TokenVectorSwizzle, getLocation()); + for (int i=0 ; icend() && (isalnum(*tmp) || *tmp == '_') && *tmp != '(') + ++tmp; + std::string::const_iterator nameEnd = tmp; + skipWhitespace(tmp); + if (*tmp == '(') + { + Parser::Token tok(TokenFunctionStart, getLocation()); + tok.m_tokenString.assign(m_sourceIt, nameEnd); + m_sourceIt = tmp + 1; + m_parenStack.push_back(TokenFunctionEnd); + return tok; + } + } + + /* Check for function arg delimitation */ + if (*m_sourceIt == ',') + { + if (m_parenStack.empty() || m_parenStack.back() != TokenFunctionEnd) + { + m_diag.reportParserErr(getLocation(), "unexpected ',' while parsing"); + return Parser::Token(TokenNone, SourceLocation()); + } + Parser::Token tok(TokenFunctionArgDelim, getLocation()); + ++m_sourceIt; + return tok; + } + + /* Error condition if reached */ + m_diag.reportParserErr(getLocation(), "unexpected token while parsing"); + return Parser::Token(TokenNone, SourceLocation()); +} + +SourceLocation Parser::getLocation() const +{ + if (!m_source) + return SourceLocation(); + std::string::const_iterator it = m_source->cbegin(); + int line = 0; + int col = 0; + for (; it != m_sourceIt ; ++it) + { + ++col; + if (*it == '\n') + { + ++line; + col = 0; + } + } + return {line+1, col+1}; +} + +} +}