407 lines
14 KiB
C++
407 lines
14 KiB
C++
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <vector>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <optional>
|
|
#include <functional>
|
|
#include <unordered_map>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
|
|
// ========================
|
|
// External texture view
|
|
// ========================
|
|
struct Texture {
|
|
uint32_t Width, Height;
|
|
const uint32_t* Pixels; // assumed 0xAARRGGBB
|
|
};
|
|
|
|
// ========================
|
|
// Bytecode words are uint8_t (1 byte machine word)
|
|
// TexId is u24 (3 bytes, little-endian)
|
|
// Subprogram refs use off24/len24 in BYTES (<=65535)
|
|
// ========================
|
|
class TexturePipelineProgram {
|
|
public:
|
|
using Word = uint8_t;
|
|
|
|
enum AnimFlags : Word {
|
|
AnimSmooth = 1u << 0,
|
|
AnimHorizontal = 1u << 1,
|
|
AnimGrid = 1u << 2
|
|
};
|
|
|
|
static constexpr uint16_t DefaultAnimFpsQ = uint16_t(8u * 256u);
|
|
static constexpr size_t MaxCodeBytes = (1u << 16) + 1u; // 65537
|
|
|
|
struct OwnedTexture {
|
|
uint32_t Width = 0, Height = 0;
|
|
std::vector<uint32_t> Pixels;
|
|
Texture view() const { return Texture{Width, Height, Pixels.data()}; }
|
|
};
|
|
|
|
using IdResolverFunc = std::function<std::optional<uint32_t>(std::string_view)>;
|
|
using TextureProviderFunc = std::function<std::optional<Texture>(uint32_t)>;
|
|
|
|
// Patch point to 3 consecutive bytes where u24 texId lives (b0,b1,b2)
|
|
struct Patch {
|
|
size_t ByteIndex0 = 0; // Code_[i], Code_[i+1], Code_[i+2]
|
|
std::string Name;
|
|
};
|
|
|
|
bool compile(std::string_view src, std::string* err = nullptr);
|
|
bool link(const IdResolverFunc& resolver, std::string* err = nullptr);
|
|
bool bake(const TextureProviderFunc& provider, OwnedTexture& out, std::string* err = nullptr) const;
|
|
bool bake(const TextureProviderFunc& provider, OwnedTexture& out, double timeSeconds, std::string* err = nullptr) const;
|
|
|
|
const std::vector<Word>& words() const { return Code_; }
|
|
const std::vector<Patch>& patches() const { return Patches_; }
|
|
|
|
std::vector<uint8_t> toBytes() const { return Code_; }
|
|
|
|
struct AnimSpec {
|
|
uint32_t TexId = 0;
|
|
bool HasTexId = false;
|
|
uint16_t FrameW = 0;
|
|
uint16_t FrameH = 0;
|
|
uint16_t FrameCount = 0;
|
|
uint16_t FpsQ = 0;
|
|
uint16_t Flags = 0;
|
|
};
|
|
|
|
static std::vector<AnimSpec> extractAnimationSpecs(const Word* code, size_t size);
|
|
static bool remapTexIds(std::vector<uint8_t>& code, const std::vector<uint32_t>& remap, std::string* err = nullptr);
|
|
|
|
static std::vector<AnimSpec> extractAnimationSpecs(const std::vector<Word>& code) {
|
|
return extractAnimationSpecs(code.data(), code.size());
|
|
}
|
|
|
|
void fromBytes(std::vector<uint8_t> bytes);
|
|
|
|
private:
|
|
// ========================
|
|
// Byte helpers (little-endian)
|
|
// ========================
|
|
static inline uint16_t _rd16(const std::vector<uint8_t>& c, size_t& ip) {
|
|
uint16_t v = uint16_t(c[ip]) | (uint16_t(c[ip+1]) << 8);
|
|
ip += 2;
|
|
return v;
|
|
}
|
|
static inline uint32_t _rd24(const std::vector<uint8_t>& c, size_t& ip) {
|
|
uint32_t v = uint32_t(c[ip]) | (uint32_t(c[ip+1]) << 8) | (uint32_t(c[ip+2]) << 16);
|
|
ip += 3;
|
|
return v;
|
|
}
|
|
static inline uint32_t _rd32(const std::vector<uint8_t>& c, size_t& ip) {
|
|
uint32_t v = uint32_t(c[ip]) |
|
|
(uint32_t(c[ip+1]) << 8) |
|
|
(uint32_t(c[ip+2]) << 16) |
|
|
(uint32_t(c[ip+3]) << 24);
|
|
ip += 4;
|
|
return v;
|
|
}
|
|
|
|
static inline void _wr8 (std::vector<uint8_t>& o, uint32_t v){ o.push_back(uint8_t(v & 0xFFu)); }
|
|
static inline void _wr16(std::vector<uint8_t>& o, uint32_t v){
|
|
o.push_back(uint8_t(v & 0xFFu));
|
|
o.push_back(uint8_t((v >> 8) & 0xFFu));
|
|
}
|
|
static inline void _wr24(std::vector<uint8_t>& o, uint32_t v){
|
|
o.push_back(uint8_t(v & 0xFFu));
|
|
o.push_back(uint8_t((v >> 8) & 0xFFu));
|
|
o.push_back(uint8_t((v >> 16) & 0xFFu));
|
|
}
|
|
static inline void _wr32(std::vector<uint8_t>& o, uint32_t v){
|
|
o.push_back(uint8_t(v & 0xFFu));
|
|
o.push_back(uint8_t((v >> 8) & 0xFFu));
|
|
o.push_back(uint8_t((v >> 16) & 0xFFu));
|
|
o.push_back(uint8_t((v >> 24) & 0xFFu));
|
|
}
|
|
|
|
// ========================
|
|
// SrcRef encoding in bytes (variable length)
|
|
// kind(1) + payload
|
|
// TexId: id24(3) => total 4
|
|
// Sub : off16(3) + len16(3) => total 7
|
|
// ========================
|
|
enum class SrcKind : uint8_t { TexId = 0, Sub = 1 };
|
|
|
|
struct SrcRef {
|
|
SrcKind Kind{};
|
|
uint32_t TexId24 = 0; // for TexId
|
|
uint16_t Off24 = 0; // for Sub
|
|
uint16_t Len24 = 0; // for Sub
|
|
};
|
|
|
|
// ========================
|
|
// Opcodes (1 byte)
|
|
// ========================
|
|
enum class Op : uint8_t {
|
|
End = 0,
|
|
|
|
Base_Tex = 1, // SrcRef(TexId)
|
|
Base_Fill = 2, // w16, h16, color32
|
|
Base_Anim = 3, // SrcRef(TexId), frameW16, frameH16, frames16, fpsQ16, flags8
|
|
|
|
Resize = 10, // w16, h16
|
|
Transform = 11, // t8
|
|
Opacity = 12, // a8
|
|
NoAlpha = 13, // -
|
|
MakeAlpha = 14, // rgb24 (3 bytes) RR,GG,BB
|
|
Invert = 15, // mask8
|
|
Brighten = 16, // -
|
|
Contrast = 17, // cBias8, bBias8 (bias-127)
|
|
Multiply = 18, // color32
|
|
Screen = 19, // color32
|
|
Colorize = 20, // color32, ratio8
|
|
Anim = 21, // frameW16, frameH16, frames16, fpsQ16, flags8
|
|
|
|
Overlay = 30, // SrcRef (var)
|
|
Mask = 31, // SrcRef (var)
|
|
LowPart = 32, // percent8, SrcRef (var)
|
|
|
|
Combine = 40 // w16,h16,n16 then n*(x16,y16,SrcRef) (если понадобится — допишем DSL)
|
|
};
|
|
|
|
// ========================
|
|
// Pixel helpers (assume 0xAARRGGBB)
|
|
// ========================
|
|
static inline uint8_t _a(uint32_t c){ return uint8_t((c >> 24) & 0xFF); }
|
|
static inline uint8_t _r(uint32_t c){ return uint8_t((c >> 16) & 0xFF); }
|
|
static inline uint8_t _g(uint32_t c){ return uint8_t((c >> 8) & 0xFF); }
|
|
static inline uint8_t _b(uint32_t c){ return uint8_t((c >> 0) & 0xFF); }
|
|
static inline uint32_t _pack(uint8_t a,uint8_t r,uint8_t g,uint8_t b){
|
|
return (uint32_t(a)<<24)|(uint32_t(r)<<16)|(uint32_t(g)<<8)|(uint32_t(b));
|
|
}
|
|
static inline uint8_t _clampu8(int v){ return uint8_t(std::min(255, std::max(0, v))); }
|
|
|
|
// ========================
|
|
// VM (executes bytes)
|
|
// ========================
|
|
struct Image {
|
|
uint32_t W=0,H=0;
|
|
std::vector<uint32_t> Px;
|
|
};
|
|
|
|
class VM {
|
|
public:
|
|
using TextureProvider = TexturePipelineProgram::TextureProviderFunc;
|
|
|
|
explicit VM(TextureProvider provider);
|
|
bool run(const std::vector<uint8_t>& code, OwnedTexture& out, double timeSeconds, std::string* err);
|
|
|
|
private:
|
|
TextureProvider Provider_;
|
|
|
|
static bool _bad(std::string* err, const char* msg);
|
|
static bool _readSrc(const std::vector<uint8_t>& code, size_t& ip, SrcRef& out, std::string* err);
|
|
Image _loadTex(uint32_t id, std::unordered_map<uint32_t, Image>& cache, std::string* err);
|
|
Image _loadSub(const std::vector<uint8_t>& code,
|
|
uint32_t off, uint32_t len,
|
|
std::unordered_map<uint32_t, Image>& texCache,
|
|
std::unordered_map<uint64_t, Image>& subCache,
|
|
double timeSeconds,
|
|
std::string* err);
|
|
Image _loadSrc(const std::vector<uint8_t>& code,
|
|
const SrcRef& src,
|
|
std::unordered_map<uint32_t, Image>& texCache,
|
|
std::unordered_map<uint64_t, Image>& subCache,
|
|
double timeSeconds,
|
|
std::string* err);
|
|
|
|
// ---- image ops (как в исходнике) ----
|
|
static Image _makeSolid(uint32_t w, uint32_t h, uint32_t color);
|
|
static Image _resizeNN(const Image& src, uint32_t nw, uint32_t nh);
|
|
static Image _resizeNN_ifNeeded(Image img, uint32_t w, uint32_t h);
|
|
static Image _cropFrame(const Image& sheet, uint32_t index, uint32_t fw, uint32_t fh, bool horizontal);
|
|
static Image _cropFrameGrid(const Image& sheet, uint32_t index, uint32_t fw, uint32_t fh);
|
|
static void _lerp(Image& base, const Image& over, double t);
|
|
static void _alphaOver(Image& base, const Image& over);
|
|
static void _applyMask(Image& base, const Image& mask);
|
|
static void _opacity(Image& img, uint8_t mul);
|
|
static void _noAlpha(Image& img);
|
|
static void _makeAlpha(Image& img, uint32_t rgb24);
|
|
static void _invert(Image& img, uint32_t maskBits);
|
|
static void _brighten(Image& img);
|
|
static void _contrast(Image& img, int c, int br);
|
|
static void _multiply(Image& img, uint32_t color);
|
|
static void _screen(Image& img, uint32_t color);
|
|
static void _colorize(Image& img, uint32_t color, uint8_t ratio);
|
|
static void _lowpart(Image& base, const Image& over, uint32_t percent);
|
|
static Image _transform(const Image& src, uint32_t t);
|
|
};
|
|
|
|
// ========================
|
|
// Minimal DSL Lexer/Parser
|
|
// now supports:
|
|
// name |> op(...)
|
|
// 32x32 "#RRGGBBAA"
|
|
// optional prefix:
|
|
// tex name |> op(...)
|
|
// nested only where op expects a texture arg:
|
|
// overlay( tex other |> ... )
|
|
// Also supports overlay(other) / mask(other) / lowpart(50, other)
|
|
// ========================
|
|
enum class TokKind { End, Ident, Number, String, Pipe, Comma, LParen, RParen, Eq, X };
|
|
|
|
struct Tok {
|
|
TokKind Kind = TokKind::End;
|
|
std::string Text;
|
|
uint32_t U32 = 0;
|
|
};
|
|
|
|
struct Lexer {
|
|
std::string_view S;
|
|
size_t I=0;
|
|
|
|
bool HasBuf = false;
|
|
Tok Buf;
|
|
|
|
static bool isAlpha(char c){ return (c>='a'&&c<='z')||(c>='A'&&c<='Z')||c=='_'; }
|
|
static bool isNum(char c){ return (c>='0'&&c<='9'); }
|
|
static bool isAlnum(char c){ return isAlpha(c)||isNum(c); }
|
|
|
|
void unread(const Tok& t);
|
|
Tok peek();
|
|
void skipWs();
|
|
Tok next();
|
|
};
|
|
|
|
|
|
struct ArgVal {
|
|
enum class ValueKind { U32, Str, Ident };
|
|
ValueKind Kind = ValueKind::U32;
|
|
uint32_t U32 = 0;
|
|
std::string S;
|
|
};
|
|
|
|
struct ParsedOp {
|
|
std::string Name;
|
|
std::vector<ArgVal> Pos;
|
|
std::unordered_map<std::string, ArgVal> Named;
|
|
};
|
|
|
|
// ========================
|
|
// Compiler state
|
|
// ========================
|
|
std::string Source_;
|
|
std::vector<uint8_t> Code_;
|
|
std::vector<Patch> Patches_;
|
|
|
|
// ---- emit helpers (target = arbitrary out vector) ----
|
|
static inline void _emitOp(std::vector<uint8_t>& out, Op op) { _wr8(out, uint8_t(op)); }
|
|
static inline void _emitU8(std::vector<uint8_t>& out, uint32_t v){ _wr8(out, v); }
|
|
static inline void _emitU16(std::vector<uint8_t>& out, uint32_t v){ _wr16(out, v); }
|
|
static inline void _emitU24(std::vector<uint8_t>& out, uint32_t v){ _wr24(out, v); }
|
|
static inline void _emitU32(std::vector<uint8_t>& out, uint32_t v){ _wr32(out, v); }
|
|
|
|
// reserve 3 bytes for u24 texId and register patch (absolute or relative)
|
|
struct RelPatch { size_t Rel0; std::string Name; };
|
|
|
|
static void _emitTexPatchU24(std::vector<uint8_t>& out,
|
|
std::vector<Patch>* absPatches,
|
|
std::vector<RelPatch>* relPatches,
|
|
const std::string& name) {
|
|
const size_t idx = out.size();
|
|
out.push_back(0); out.push_back(0); out.push_back(0);
|
|
if(absPatches) absPatches->push_back(Patch{idx, name});
|
|
if(relPatches) relPatches->push_back(RelPatch{idx, name});
|
|
}
|
|
|
|
static void _emitSrcTexName(std::vector<uint8_t>& out,
|
|
std::vector<Patch>* absPatches,
|
|
std::vector<RelPatch>* relPatches,
|
|
const std::string& name) {
|
|
_emitU8(out, uint8_t(SrcKind::TexId));
|
|
_emitTexPatchU24(out, absPatches, relPatches, name);
|
|
}
|
|
|
|
static void _emitSrcSub(std::vector<uint8_t>& out, uint32_t off24, uint32_t len24) {
|
|
_emitU8(out, uint8_t(SrcKind::Sub));
|
|
_emitU24(out, off24);
|
|
_emitU24(out, len24);
|
|
}
|
|
|
|
// ========================
|
|
// Color parsing: #RRGGBB or #RRGGBBAA -> 0xAARRGGBB
|
|
// ========================
|
|
static bool _parseHexColor(std::string_view s, uint32_t& outARGB);
|
|
|
|
// ========================
|
|
// Parsing entry: full program
|
|
// ========================
|
|
bool _parseProgram(std::string* err);
|
|
|
|
// ========================
|
|
// Base compilation (optionally after 'tex')
|
|
// supports:
|
|
// 1) name
|
|
// 2) "name(.png/.jpg/.jpeg)" (allowed but normalized)
|
|
// 3) anim(...)
|
|
// 4) 32x32 "#RRGGBBAA"
|
|
// optional: all of the above may be prefixed with 'tex'
|
|
// ========================
|
|
bool _compileBaseAfterTex(Lexer& lx,
|
|
std::vector<uint8_t>& out,
|
|
std::vector<Patch>* absPatches,
|
|
std::vector<RelPatch>* relPatches,
|
|
std::string* err);
|
|
|
|
bool _compileBaseFromToken(Lexer& lx,
|
|
const Tok& a,
|
|
std::vector<uint8_t>& out,
|
|
std::vector<Patch>* absPatches,
|
|
std::vector<RelPatch>* relPatches,
|
|
std::string* err);
|
|
|
|
// ========================
|
|
// Args parsing:
|
|
// - normal args: (a,b,key=v)
|
|
// - OR if first token inside '(' is 'tex' => parse nested program until ')'
|
|
// ========================
|
|
bool _parseArgListOrTextureExpr(Lexer& lx, ParsedOp& op, std::string* err);
|
|
|
|
bool _parseArgList(Lexer& lx, ParsedOp& op, std::string* err);
|
|
|
|
bool _tokToVal(const Tok& t, ArgVal& out, std::string* err);
|
|
|
|
// ========================
|
|
// Subprogram compilation:
|
|
// we already consumed 'tex'. Parse base + pipeline until next token is ')'
|
|
// DO NOT consume ')'
|
|
// ========================
|
|
struct PendingSubData {
|
|
std::vector<uint8_t> Bytes;
|
|
std::vector<RelPatch> RelPatches;
|
|
};
|
|
|
|
bool _compileSubProgramFromAlreadySawTex(Lexer& lx, PendingSubData& outSub, std::string* err);
|
|
|
|
// pending subprogram associated with ParsedOp pointer (created during parsing)
|
|
mutable std::unordered_map<const ParsedOp*, PendingSubData> PendingSub_;
|
|
|
|
// Append subprogram to `out` and emit SrcRef(Sub, off16, len16), migrating patches properly.
|
|
static bool _appendSubprogram(std::vector<uint8_t>& out,
|
|
PendingSubData&& sub,
|
|
std::vector<Patch>* absPatches,
|
|
std::vector<RelPatch>* relPatches,
|
|
uint32_t& outOff,
|
|
uint32_t& outLen,
|
|
std::string* err);
|
|
|
|
// ========================
|
|
// Compile operations into arbitrary `out`
|
|
// absPatches != nullptr => patches recorded as absolute for this buffer
|
|
// relPatches != nullptr => patches recorded as relative for this buffer
|
|
// ========================
|
|
bool _compileOpInto(Lexer& lx,
|
|
const ParsedOp& op,
|
|
std::vector<uint8_t>& out,
|
|
std::vector<Patch>* absPatches,
|
|
std::vector<RelPatch>* relPatches,
|
|
std::string* err);
|
|
};
|