From b61cc9fb036e96efbce839f3dca8c19c84de652a Mon Sep 17 00:00:00 2001 From: DrSocalkwe3n Date: Tue, 6 Jan 2026 18:20:31 +0600 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20TexPipe=20=D0=BD=D0=B0=20cpp=20=D0=B8=20hp?= =?UTF-8?q?p?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Src/Common/TexturePipelineProgram.cpp | 1845 +++++++++++++++++++++++++ Src/Common/TexturePipelineProgram.hpp | 1837 +----------------------- 2 files changed, 1894 insertions(+), 1788 deletions(-) create mode 100644 Src/Common/TexturePipelineProgram.cpp diff --git a/Src/Common/TexturePipelineProgram.cpp b/Src/Common/TexturePipelineProgram.cpp new file mode 100644 index 0000000..06fd0c2 --- /dev/null +++ b/Src/Common/TexturePipelineProgram.cpp @@ -0,0 +1,1845 @@ +#include "Common/TexturePipelineProgram.hpp" + +bool TexturePipelineProgram::compile(std::string_view src, std::string* err) { + Source_ = src; + Code_.clear(); + Patches_.clear(); + PendingSub_.clear(); + return _parseProgram(err); +} + +bool TexturePipelineProgram::link(const IdResolverFunc& resolver, std::string* err) { + for (const auto& p : Patches_) { + auto idOpt = resolver(p.Name); + if(!idOpt) { + if(err) *err = "Не удалось разрешить имя текстуры: " + p.Name; + return false; + } + uint32_t id = *idOpt; + if(id >= (1u << 24)) { + if(err) *err = "TexId выходит за 24 бита (u24): " + p.Name + " => " + std::to_string(id); + return false; + } + if(p.ByteIndex0 + 2 >= Code_.size()) { + if(err) *err = "Внутренняя ошибка: применение идентификатора выходит за рамки кода"; + return false; + } + Code_[p.ByteIndex0 + 0] = uint8_t(id & 0xFFu); + Code_[p.ByteIndex0 + 1] = uint8_t((id >> 8) & 0xFFu); + Code_[p.ByteIndex0 + 2] = uint8_t((id >> 16) & 0xFFu); + } + return true; +} + +bool TexturePipelineProgram::bake(const TextureProviderFunc& provider, OwnedTexture& out, std::string* err) const { + return bake(provider, out, 0.0, err); +} + +bool TexturePipelineProgram::bake(const TextureProviderFunc& provider, OwnedTexture& out, double timeSeconds, std::string* err) const { + VM vm(provider); + return vm.run(Code_, out, timeSeconds, err); +} + +std::vector TexturePipelineProgram::extractAnimationSpecs(const Word* code, size_t size) { + std::vector specs; + if(!code || size == 0) { + return specs; + } + + struct Range { + size_t Start = 0; + size_t End = 0; + }; + + std::vector visited; + + auto read8 = [&](size_t& ip, uint8_t& out)->bool{ + if(ip >= size) return false; + out = code[ip++]; + return true; + }; + auto read16 = [&](size_t& ip, uint16_t& out)->bool{ + if(ip + 1 >= size) return false; + out = uint16_t(code[ip]) | (uint16_t(code[ip + 1]) << 8); + ip += 2; + return true; + }; + auto read24 = [&](size_t& ip, uint32_t& out)->bool{ + if(ip + 2 >= size) return false; + out = uint32_t(code[ip]) | + (uint32_t(code[ip + 1]) << 8) | + (uint32_t(code[ip + 2]) << 16); + ip += 3; + return true; + }; + auto read32 = [&](size_t& ip, uint32_t& out)->bool{ + if(ip + 3 >= size) return false; + out = uint32_t(code[ip]) | + (uint32_t(code[ip + 1]) << 8) | + (uint32_t(code[ip + 2]) << 16) | + (uint32_t(code[ip + 3]) << 24); + ip += 4; + return true; + }; + + struct SrcMeta { + SrcKind Kind = SrcKind::TexId; + uint32_t TexId = 0; + uint32_t Off = 0; + uint32_t Len = 0; + }; + + auto readSrc = [&](size_t& ip, SrcMeta& out)->bool{ + uint8_t kind = 0; + if(!read8(ip, kind)) return false; + out.Kind = static_cast(kind); + if(out.Kind == SrcKind::TexId) { + return read24(ip, out.TexId); + } + if(out.Kind == SrcKind::Sub) { + return read24(ip, out.Off) && read24(ip, out.Len); + } + return false; + }; + + auto scan = [&](auto&& self, size_t start, size_t end) -> void { + if(start >= end || end > size) { + return; + } + for(const auto& r : visited) { + if(r.Start == start && r.End == end) { + return; + } + } + visited.push_back(Range{start, end}); + + size_t ip = start; + while(ip < end) { + uint8_t opByte = 0; + if(!read8(ip, opByte)) return; + Op op = static_cast(opByte); + switch(op) { + case Op::End: + return; + + case Op::Base_Tex: { + SrcMeta src{}; + if(!readSrc(ip, src)) return; + if(src.Kind == SrcKind::Sub) { + size_t subStart = src.Off; + size_t subEnd = subStart + src.Len; + if(subStart < subEnd && subEnd <= size) { + self(self, subStart, subEnd); + } + } + } break; + + case Op::Base_Fill: { + uint16_t tmp16 = 0; + uint32_t tmp32 = 0; + if(!read16(ip, tmp16)) return; + if(!read16(ip, tmp16)) return; + if(!read32(ip, tmp32)) return; + } break; + + case Op::Base_Anim: { + SrcMeta src{}; + if(!readSrc(ip, src)) return; + uint16_t frameW = 0; + uint16_t frameH = 0; + uint16_t frameCount = 0; + uint16_t fpsQ = 0; + uint8_t flags = 0; + if(!read16(ip, frameW)) return; + if(!read16(ip, frameH)) return; + if(!read16(ip, frameCount)) return; + if(!read16(ip, fpsQ)) return; + if(!read8(ip, flags)) return; + + if(src.Kind == SrcKind::TexId) { + AnimSpec spec{}; + spec.TexId = src.TexId; + spec.HasTexId = true; + spec.FrameW = frameW; + spec.FrameH = frameH; + spec.FrameCount = frameCount; + spec.FpsQ = fpsQ; + spec.Flags = flags; + specs.push_back(spec); + } else if(src.Kind == SrcKind::Sub) { + size_t subStart = src.Off; + size_t subEnd = subStart + src.Len; + if(subStart < subEnd && subEnd <= size) { + self(self, subStart, subEnd); + } + } + } break; + + case Op::Resize: { + uint16_t tmp16 = 0; + if(!read16(ip, tmp16)) return; + if(!read16(ip, tmp16)) return; + } break; + + case Op::Transform: + case Op::Opacity: + case Op::Invert: + if(!read8(ip, opByte)) return; + break; + + case Op::NoAlpha: + case Op::Brighten: + break; + + case Op::MakeAlpha: + if(ip + 2 >= size) return; + ip += 3; + break; + + case Op::Contrast: + if(ip + 1 >= size) return; + ip += 2; + break; + + case Op::Multiply: + case Op::Screen: { + uint32_t tmp32 = 0; + if(!read32(ip, tmp32)) return; + } break; + + case Op::Colorize: { + uint32_t tmp32 = 0; + if(!read32(ip, tmp32)) return; + if(!read8(ip, opByte)) return; + } break; + + case Op::Anim: { + uint16_t frameW = 0; + uint16_t frameH = 0; + uint16_t frameCount = 0; + uint16_t fpsQ = 0; + uint8_t flags = 0; + if(!read16(ip, frameW)) return; + if(!read16(ip, frameH)) return; + if(!read16(ip, frameCount)) return; + if(!read16(ip, fpsQ)) return; + if(!read8(ip, flags)) return; + + AnimSpec spec{}; + spec.HasTexId = false; + spec.FrameW = frameW; + spec.FrameH = frameH; + spec.FrameCount = frameCount; + spec.FpsQ = fpsQ; + spec.Flags = flags; + specs.push_back(spec); + } break; + + case Op::Overlay: + case Op::Mask: { + SrcMeta src{}; + if(!readSrc(ip, src)) return; + if(src.Kind == SrcKind::Sub) { + size_t subStart = src.Off; + size_t subEnd = subStart + src.Len; + if(subStart < subEnd && subEnd <= size) { + self(self, subStart, subEnd); + } + } + } break; + + case Op::LowPart: { + if(!read8(ip, opByte)) return; + SrcMeta src{}; + if(!readSrc(ip, src)) return; + if(src.Kind == SrcKind::Sub) { + size_t subStart = src.Off; + size_t subEnd = subStart + src.Len; + if(subStart < subEnd && subEnd <= size) { + self(self, subStart, subEnd); + } + } + } break; + + case Op::Combine: { + uint16_t w = 0, h = 0, n = 0; + if(!read16(ip, w)) return; + if(!read16(ip, h)) return; + if(!read16(ip, n)) return; + for(uint16_t i = 0; i < n; ++i) { + uint16_t tmp16 = 0; + if(!read16(ip, tmp16)) return; + if(!read16(ip, tmp16)) return; + SrcMeta src{}; + if(!readSrc(ip, src)) return; + if(src.Kind == SrcKind::Sub) { + size_t subStart = src.Off; + size_t subEnd = subStart + src.Len; + if(subStart < subEnd && subEnd <= size) { + self(self, subStart, subEnd); + } + } + } + (void)w; (void)h; + } break; + + default: + return; + } + } + }; + + scan(scan, 0, size); + return specs; +} + +bool TexturePipelineProgram::remapTexIds(std::vector& code, const std::vector& remap, std::string* err) { + struct Range { + size_t Start = 0; + size_t End = 0; + }; + + struct SrcMeta { + SrcKind Kind = SrcKind::TexId; + uint32_t TexId = 0; + uint32_t Off = 0; + uint32_t Len = 0; + size_t TexIdOffset = 0; + }; + + const size_t size = code.size(); + std::vector visited; + + auto read8 = [&](size_t& ip, uint8_t& out)->bool{ + if(ip >= size) return false; + out = code[ip++]; + return true; + }; + auto read16 = [&](size_t& ip, uint16_t& out)->bool{ + if(ip + 1 >= size) return false; + out = uint16_t(code[ip]) | (uint16_t(code[ip + 1]) << 8); + ip += 2; + return true; + }; + auto read24 = [&](size_t& ip, uint32_t& out)->bool{ + if(ip + 2 >= size) return false; + out = uint32_t(code[ip]) | + (uint32_t(code[ip + 1]) << 8) | + (uint32_t(code[ip + 2]) << 16); + ip += 3; + return true; + }; + auto read32 = [&](size_t& ip, uint32_t& out)->bool{ + if(ip + 3 >= size) return false; + out = uint32_t(code[ip]) | + (uint32_t(code[ip + 1]) << 8) | + (uint32_t(code[ip + 2]) << 16) | + (uint32_t(code[ip + 3]) << 24); + ip += 4; + return true; + }; + + auto readSrc = [&](size_t& ip, SrcMeta& out)->bool{ + uint8_t kind = 0; + if(!read8(ip, kind)) return false; + out.Kind = static_cast(kind); + if(out.Kind == SrcKind::TexId) { + out.TexIdOffset = ip; + return read24(ip, out.TexId); + } + if(out.Kind == SrcKind::Sub) { + return read24(ip, out.Off) && read24(ip, out.Len); + } + return false; + }; + + auto patchTexId = [&](const SrcMeta& src)->bool{ + if(src.Kind != SrcKind::TexId) + return true; + if(src.TexId >= remap.size()) { + if(err) *err = "Идентификатор текстуры вне диапазона переназначения"; + return false; + } + uint32_t newId = remap[src.TexId]; + if(newId >= (1u << 24)) { + if(err) *err = "TexId выходит за 24 бита (u24)"; + return false; + } + if(src.TexIdOffset + 2 >= code.size()) { + if(err) *err = "Применение идентификатора выходит за рамки кода"; + return false; + } + code[src.TexIdOffset + 0] = uint8_t(newId & 0xFFu); + code[src.TexIdOffset + 1] = uint8_t((newId >> 8) & 0xFFu); + code[src.TexIdOffset + 2] = uint8_t((newId >> 16) & 0xFFu); + return true; + }; + + std::move_only_function scan; + scan = [&](size_t start, size_t end) -> bool { + if(start >= end || end > size) { + return true; + } + for(const auto& r : visited) { + if(r.Start == start && r.End == end) { + return true; + } + } + visited.push_back(Range{start, end}); + + size_t ip = start; + while(ip < end) { + uint8_t opByte = 0; + if(!read8(ip, opByte)) return false; + Op op = static_cast(opByte); + switch(op) { + case Op::End: + return true; + + case Op::Base_Tex: { + SrcMeta src{}; + if(!readSrc(ip, src)) return false; + if(!patchTexId(src)) return false; + if(src.Kind == SrcKind::Sub) { + size_t subStart = src.Off; + size_t subEnd = subStart + src.Len; + if(!scan(subStart, subEnd)) return false; + } + } break; + + case Op::Base_Fill: { + uint16_t tmp16 = 0; + uint32_t tmp32 = 0; + if(!read16(ip, tmp16)) return false; + if(!read16(ip, tmp16)) return false; + if(!read32(ip, tmp32)) return false; + } break; + + case Op::Base_Anim: { + SrcMeta src{}; + if(!readSrc(ip, src)) return false; + if(!patchTexId(src)) return false; + uint16_t frameW = 0; + uint16_t frameH = 0; + uint16_t frameCount = 0; + uint16_t fpsQ = 0; + uint8_t flags = 0; + if(!read16(ip, frameW)) return false; + if(!read16(ip, frameH)) return false; + if(!read16(ip, frameCount)) return false; + if(!read16(ip, fpsQ)) return false; + if(!read8(ip, flags)) return false; + (void)frameW; (void)frameH; (void)frameCount; (void)fpsQ; (void)flags; + if(src.Kind == SrcKind::Sub) { + size_t subStart = src.Off; + size_t subEnd = subStart + src.Len; + if(!scan(subStart, subEnd)) return false; + } + } break; + + case Op::Resize: { + uint16_t tmp16 = 0; + if(!read16(ip, tmp16)) return false; + if(!read16(ip, tmp16)) return false; + } break; + + case Op::Transform: + case Op::Opacity: + case Op::Invert: + if(!read8(ip, opByte)) return false; + break; + + case Op::NoAlpha: + case Op::Brighten: + break; + + case Op::MakeAlpha: + if(ip + 2 >= size) return false; + ip += 3; + break; + + case Op::Contrast: + if(ip + 1 >= size) return false; + ip += 2; + break; + + case Op::Multiply: + case Op::Screen: { + uint32_t tmp32 = 0; + if(!read32(ip, tmp32)) return false; + } break; + + case Op::Colorize: { + uint32_t tmp32 = 0; + if(!read32(ip, tmp32)) return false; + if(!read8(ip, opByte)) return false; + } break; + + case Op::Anim: { + uint16_t frameW = 0; + uint16_t frameH = 0; + uint16_t frameCount = 0; + uint16_t fpsQ = 0; + uint8_t flags = 0; + if(!read16(ip, frameW)) return false; + if(!read16(ip, frameH)) return false; + if(!read16(ip, frameCount)) return false; + if(!read16(ip, fpsQ)) return false; + if(!read8(ip, flags)) return false; + (void)frameW; (void)frameH; (void)frameCount; (void)fpsQ; (void)flags; + } break; + + case Op::Overlay: + case Op::Mask: { + SrcMeta src{}; + if(!readSrc(ip, src)) return false; + if(!patchTexId(src)) return false; + if(src.Kind == SrcKind::Sub) { + size_t subStart = src.Off; + size_t subEnd = subStart + src.Len; + if(!scan(subStart, subEnd)) return false; + } + } break; + + case Op::LowPart: { + if(!read8(ip, opByte)) return false; + SrcMeta src{}; + if(!readSrc(ip, src)) return false; + if(!patchTexId(src)) return false; + if(src.Kind == SrcKind::Sub) { + size_t subStart = src.Off; + size_t subEnd = subStart + src.Len; + if(!scan(subStart, subEnd)) return false; + } + } break; + + case Op::Combine: { + uint16_t w = 0, h = 0, n = 0; + if(!read16(ip, w)) return false; + if(!read16(ip, h)) return false; + if(!read16(ip, n)) return false; + for(uint16_t i = 0; i < n; ++i) { + uint16_t tmp16 = 0; + if(!read16(ip, tmp16)) return false; + if(!read16(ip, tmp16)) return false; + SrcMeta src{}; + if(!readSrc(ip, src)) return false; + if(!patchTexId(src)) return false; + if(src.Kind == SrcKind::Sub) { + size_t subStart = src.Off; + size_t subEnd = subStart + src.Len; + if(!scan(subStart, subEnd)) return false; + } + } + (void)w; (void)h; + } break; + + default: + return false; + } + } + return true; + }; + + return scan(0, size); +} + +void TexturePipelineProgram::fromBytes(std::vector bytes) { + Code_ = std::move(bytes); + Patches_.clear(); + Source_.clear(); + PendingSub_.clear(); +} + +void TexturePipelineProgram::Lexer::unread(const Tok& t) { + // allow only 1-level unread + HasBuf = true; + Buf = t; +} + +TexturePipelineProgram::Tok TexturePipelineProgram::Lexer::peek() { + Tok t = next(); + unread(t); + return t; +} + +void TexturePipelineProgram::Lexer::skipWs() { + while (I < S.size()) { + char c = S[I]; + if(c==' '||c=='\t'||c=='\r'||c=='\n'){ I++; continue; } + if(c=='#'){ while (I= S.size()) return {TokKind::End, {}, 0}; + + if(S[I]=='|' && I+1') { I+=2; return {TokKind::Pipe, "|>",0}; } + char c = S[I]; + if(c==','){ I++; return {TokKind::Comma,",",0}; } + if(c=='('){ I++; return {TokKind::LParen,"(",0}; } + if(c==')'){ I++; return {TokKind::RParen,")",0}; } + if(c=='='){ I++; return {TokKind::Eq,"=",0}; } + if(c=='x' || c=='X'){ I++; return {TokKind::X,"x",0}; } + + if(c=='"') { + I++; + std::string out; + while (I < S.size()) { + char ch = S[I++]; + if(ch=='"') break; + if(ch=='\\' && I& code, OwnedTexture& out, double timeSeconds, std::string* err) { + if(code.empty()) { if(err) *err="Пустой байткод"; return false; } + + Image cur; + std::unordered_map texCache; + std::unordered_map subCache; // key = (off<<24) | len + + size_t ip = 0; + + auto need = [&](size_t n)->bool{ + if(ip + n > code.size()) { if(err) *err="Байткод усечен"; return false; } + return true; + }; + + while(true) { + if(!need(1)) return false; + Op op = static_cast(code[ip++]); + if(op == Op::End) break; + + switch(op) { + case Op::Base_Tex: { + SrcRef src; + if(!_readSrc(code, ip, src, err)) return false; + if(src.Kind != SrcKind::TexId) return _bad(err, "Base_Tex must be TexId"); + cur = _loadTex(src.TexId24, texCache, err); + if(cur.W == 0) return false; + } break; + + case Op::Base_Fill: { + if(!need(2+2+4)) return false; + uint32_t w = _rd16(code, ip); + uint32_t h = _rd16(code, ip); + uint32_t color = _rd32(code, ip); + cur = _makeSolid(w, h, color); + } break; + + case Op::Base_Anim: { + SrcRef src; + if(!_readSrc(code, ip, src, err)) return false; + if(src.Kind != SrcKind::TexId) return _bad(err, "Base_Anim must be TexId"); + if(!need(2+2+2+2+1)) return false; + + uint32_t frameW = _rd16(code, ip); + uint32_t frameH = _rd16(code, ip); + uint32_t frameCount = _rd16(code, ip); + uint32_t fpsQ = _rd16(code, ip); + uint32_t flags = code[ip++]; + + Image sheet = _loadTex(src.TexId24, texCache, err); + if(sheet.W == 0) return false; + + uint32_t fw = frameW ? frameW : sheet.W; + uint32_t fh = frameH ? frameH : sheet.H; + if(fw == 0 || fh == 0) return _bad(err, "Base_Anim invalid frame size"); + + bool useGrid = (flags & AnimGrid) != 0; + bool horizontal = (flags & AnimHorizontal) != 0; + if(frameCount == 0) { + uint32_t avail = 0; + if(useGrid) { + uint32_t cols = sheet.W / fw; + uint32_t rows = sheet.H / fh; + avail = cols * rows; + } else { + avail = horizontal ? (sheet.W / fw) : (sheet.H / fh); + } + frameCount = std::max(1u, avail); + } + + uint32_t fpsQv = fpsQ ? fpsQ : DefaultAnimFpsQ; + double fps = double(fpsQv) / 256.0; + double frameTime = timeSeconds * fps; + if(frameTime < 0.0) frameTime = 0.0; + + uint32_t frameIndex = frameCount ? (uint32_t(frameTime) % frameCount) : 0u; + double frac = frameTime - std::floor(frameTime); + + cur = useGrid ? _cropFrameGrid(sheet, frameIndex, fw, fh) + : _cropFrame(sheet, frameIndex, fw, fh, horizontal); + + if(flags & AnimSmooth) { + uint32_t nextIndex = frameCount ? ((frameIndex + 1u) % frameCount) : 0u; + Image next = useGrid ? _cropFrameGrid(sheet, nextIndex, fw, fh) + : _cropFrame(sheet, nextIndex, fw, fh, horizontal); + _lerp(cur, next, frac); + } + } break; + + case Op::Anim: { + if(!cur.W || !cur.H) return _bad(err, "Anim requires base image"); + if(!need(2+2+2+2+1)) return false; + + uint32_t frameW = _rd16(code, ip); + uint32_t frameH = _rd16(code, ip); + uint32_t frameCount = _rd16(code, ip); + uint32_t fpsQ = _rd16(code, ip); + uint32_t flags = code[ip++]; + + const Image& sheet = cur; + uint32_t fw = frameW ? frameW : sheet.W; + uint32_t fh = frameH ? frameH : sheet.H; + if(fw == 0 || fh == 0) return _bad(err, "Anim invalid frame size"); + + bool useGrid = (flags & AnimGrid) != 0; + bool horizontal = (flags & AnimHorizontal) != 0; + if(frameCount == 0) { + uint32_t avail = 0; + if(useGrid) { + uint32_t cols = sheet.W / fw; + uint32_t rows = sheet.H / fh; + avail = cols * rows; + } else { + avail = horizontal ? (sheet.W / fw) : (sheet.H / fh); + } + frameCount = std::max(1u, avail); + } + + uint32_t fpsQv = fpsQ ? fpsQ : DefaultAnimFpsQ; + double fps = double(fpsQv) / 256.0; + double frameTime = timeSeconds * fps; + if(frameTime < 0.0) frameTime = 0.0; + + uint32_t frameIndex = frameCount ? (uint32_t(frameTime) % frameCount) : 0u; + double frac = frameTime - std::floor(frameTime); + + cur = useGrid ? _cropFrameGrid(sheet, frameIndex, fw, fh) + : _cropFrame(sheet, frameIndex, fw, fh, horizontal); + if(flags & AnimSmooth) { + uint32_t nextIndex = frameCount ? ((frameIndex + 1u) % frameCount) : 0u; + Image next = useGrid ? _cropFrameGrid(sheet, nextIndex, fw, fh) + : _cropFrame(sheet, nextIndex, fw, fh, horizontal); + _lerp(cur, next, frac); + } + } break; + + case Op::Overlay: { + SrcRef src; + if(!_readSrc(code, ip, src, err)) return false; + Image over = _loadSrc(code, src, texCache, subCache, timeSeconds, err); + if(over.W == 0) return false; + if(!cur.W) { cur = std::move(over); break; } + over = _resizeNN_ifNeeded(over, cur.W, cur.H); + _alphaOver(cur, over); + } break; + + case Op::Mask: { + SrcRef src; + if(!_readSrc(code, ip, src, err)) return false; + Image m = _loadSrc(code, src, texCache, subCache, timeSeconds, err); + if(m.W == 0) return false; + if(!cur.W) return _bad(err, "Mask requires base image"); + m = _resizeNN_ifNeeded(m, cur.W, cur.H); + _applyMask(cur, m); + } break; + + case Op::LowPart: { + if(!need(1)) return false; + uint32_t pct = std::min(100u, uint32_t(code[ip++])); + SrcRef src; + if(!_readSrc(code, ip, src, err)) return false; + Image over = _loadSrc(code, src, texCache, subCache, timeSeconds, err); + if(over.W == 0) return false; + if(!cur.W) return _bad(err, "LowPart requires base image"); + over = _resizeNN_ifNeeded(over, cur.W, cur.H); + _lowpart(cur, over, pct); + } break; + + case Op::Resize: { + if(!cur.W) return _bad(err, "Resize requires base image"); + if(!need(2+2)) return false; + uint32_t w = _rd16(code, ip); + uint32_t h = _rd16(code, ip); + cur = _resizeNN(cur, w, h); + } break; + + case Op::Transform: { + if(!cur.W) return _bad(err, "Transform requires base image"); + if(!need(1)) return false; + uint32_t t = code[ip++] & 7u; + cur = _transform(cur, t); + } break; + + case Op::Opacity: { + if(!cur.W) return _bad(err, "Opacity requires base image"); + if(!need(1)) return false; + uint32_t a = code[ip++] & 0xFFu; + _opacity(cur, uint8_t(a)); + } break; + + case Op::NoAlpha: { + if(!cur.W) return _bad(err, "NoAlpha requires base image"); + _noAlpha(cur); + } break; + + case Op::MakeAlpha: { + if(!cur.W) return _bad(err, "MakeAlpha requires base image"); + if(!need(3)) return false; + uint32_t rr = code[ip++], gg = code[ip++], bb = code[ip++]; + uint32_t rgb24 = (rr << 16) | (gg << 8) | bb; + _makeAlpha(cur, rgb24); + } break; + + case Op::Invert: { + if(!cur.W) return _bad(err, "Invert requires base image"); + if(!need(1)) return false; + uint32_t mask = code[ip++] & 0xFu; + _invert(cur, mask); + } break; + + case Op::Brighten: { + if(!cur.W) return _bad(err, "Brighten requires base image"); + _brighten(cur); + } break; + + case Op::Contrast: { + if(!cur.W) return _bad(err, "Contrast requires base image"); + if(!need(2)) return false; + int c = int(code[ip++]) - 127; + int b = int(code[ip++]) - 127; + _contrast(cur, c, b); + } break; + + case Op::Multiply: { + if(!cur.W) return _bad(err, "Multiply requires base image"); + if(!need(4)) return false; + uint32_t color = _rd32(code, ip); + _multiply(cur, color); + } break; + + case Op::Screen: { + if(!cur.W) return _bad(err, "Screen requires base image"); + if(!need(4)) return false; + uint32_t color = _rd32(code, ip); + _screen(cur, color); + } break; + + case Op::Colorize: { + if(!cur.W) return _bad(err, "Colorize requires base image"); + if(!need(4+1)) return false; + uint32_t color = _rd32(code, ip); + uint32_t ratio = code[ip++] & 0xFFu; + _colorize(cur, color, uint8_t(ratio)); + } break; + + default: + return _bad(err, "Unknown opcode (no skip table in this minimal VM)"); + } + } + + out.Width = cur.W; + out.Height = cur.H; + out.Pixels = std::move(cur.Px); + return true; +} + +bool TexturePipelineProgram::VM::_bad(std::string* err, const char* msg) { + if(err) *err = msg; + return false; +} + +bool TexturePipelineProgram::VM::_readSrc(const std::vector& code, size_t& ip, SrcRef& out, std::string* err) { + if(ip >= code.size()) return _bad(err, "Bytecode truncated (SrcRef.kind)"); + out.Kind = static_cast(code[ip++]); + if(out.Kind == SrcKind::TexId) { + if(ip + 3 > code.size()) return _bad(err, "Bytecode truncated (TexId24)"); + out.TexId24 = _rd24(code, ip); + out.Off24 = 0; out.Len24 = 0; + return true; + } + if(out.Kind == SrcKind::Sub) { + if(ip + 6 > code.size()) return _bad(err, "Bytecode truncated (Sub off/len)"); + out.Off24 = _rd24(code, ip); + out.Len24 = _rd24(code, ip); + out.TexId24 = 0; + return true; + } + return _bad(err, "Unknown SrcKind"); +} + +TexturePipelineProgram::Image TexturePipelineProgram::VM::_loadTex(uint32_t id, std::unordered_map& cache, std::string* err) { + auto it = cache.find(id); + if(it != cache.end()) return it->second; + + auto t = Provider_(id); + if(!t || !t->Pixels || !t->Width || !t->Height) { + if(err) *err = "Идентификатор текстуры не найден: " + std::to_string(id); + return {}; + } + Image img; + img.W = t->Width; img.H = t->Height; + img.Px.assign(t->Pixels, t->Pixels + size_t(img.W)*size_t(img.H)); + cache.emplace(id, img); + return img; +} + +TexturePipelineProgram::Image TexturePipelineProgram::VM::_loadSub(const std::vector& code, + uint32_t off, uint32_t len, + std::unordered_map& /*texCache*/, + std::unordered_map& subCache, + double timeSeconds, + std::string* err) { + uint64_t key = (uint32_t(off) << 24) | uint32_t(len); + auto it = subCache.find(key); + if(it != subCache.end()) return it->second; + + size_t start = size_t(off); + size_t end = start + size_t(len); + if(end > code.size()) { if(err) *err="Подпрограмма выходит за пределы"; return {}; } + + std::vector slice(code.begin()+start, code.begin()+end); + OwnedTexture tmp; + VM nested(Provider_); + if(!nested.run(slice, tmp, timeSeconds, err)) return {}; + + Image img; + img.W = tmp.Width; img.H = tmp.Height; img.Px = std::move(tmp.Pixels); + subCache.emplace(key, img); + return img; +} + +TexturePipelineProgram::Image TexturePipelineProgram::VM::_loadSrc(const std::vector& code, + const SrcRef& src, + std::unordered_map& texCache, + std::unordered_map& subCache, + double timeSeconds, + std::string* err) { + if(src.Kind == SrcKind::TexId) return _loadTex(src.TexId24, texCache, err); + if(src.Kind == SrcKind::Sub) return _loadSub(code, src.Off24, src.Len24, texCache, subCache, timeSeconds, err); + if(err) *err = "Неизвестный SrcKind"; + return {}; +} + +TexturePipelineProgram::Image TexturePipelineProgram::VM::_makeSolid(uint32_t w, uint32_t h, uint32_t color) { + Image img; img.W=w; img.H=h; + img.Px.assign(size_t(w)*size_t(h), color); + return img; +} + +TexturePipelineProgram::Image TexturePipelineProgram::VM::_resizeNN(const Image& src, uint32_t nw, uint32_t nh) { + Image dst; dst.W=nw; dst.H=nh; + dst.Px.resize(size_t(nw)*size_t(nh)); + for(uint32_t y=0;y= sheet.H) continue; + for(uint32_t x = 0; x < fw; ++x) { + uint32_t sx = baseX + x; + if(sx >= sheet.W) continue; + out.Px[size_t(y) * fw + x] = sheet.Px[size_t(sy) * sheet.W + sx]; + } + } + return out; +} + +TexturePipelineProgram::Image TexturePipelineProgram::VM::_cropFrameGrid(const Image& sheet, uint32_t index, uint32_t fw, uint32_t fh) { + Image out; + out.W = fw; + out.H = fh; + out.Px.assign(size_t(fw) * size_t(fh), 0u); + if(fw == 0 || fh == 0) return out; + + uint32_t cols = sheet.W / fw; + uint32_t rows = sheet.H / fh; + if(cols == 0 || rows == 0) return out; + + uint32_t col = index % cols; + uint32_t row = index / cols; + if(row >= rows) return out; + + uint32_t baseX = col * fw; + uint32_t baseY = row * fh; + + for(uint32_t y = 0; y < fh; ++y) { + uint32_t sy = baseY + y; + if(sy >= sheet.H) continue; + for(uint32_t x = 0; x < fw; ++x) { + uint32_t sx = baseX + x; + if(sx >= sheet.W) continue; + out.Px[size_t(y) * fw + x] = sheet.Px[size_t(sy) * sheet.W + sx]; + } + } + return out; +} + +void TexturePipelineProgram::VM::_lerp(Image& base, const Image& over, double t) { + if(t <= 0.0) return; + if(t >= 1.0) { base = over; return; } + if(base.W != over.W || base.H != over.H) return; + + const size_t n = base.Px.size(); + for(size_t i = 0; i < n; ++i) { + uint32_t a = base.Px[i]; + uint32_t b = over.Px[i]; + int ar = _r(a), ag = _g(a), ab = _b(a), aa = _a(a); + int br = _r(b), bg = _g(b), bb = _b(b), ba = _a(b); + + uint8_t rr = _clampu8(int(ar + (br - ar) * t)); + uint8_t rg = _clampu8(int(ag + (bg - ag) * t)); + uint8_t rb = _clampu8(int(ab + (bb - ab) * t)); + uint8_t ra = _clampu8(int(aa + (ba - aa) * t)); + + base.Px[i] = _pack(ra, rr, rg, rb); + } +} + +void TexturePipelineProgram::VM::_alphaOver(Image& base, const Image& over) { + const size_t n = base.Px.size(); + for(size_t i=0;i(255, (outRp * 255) / outA)); + outG = uint8_t(std::min(255, (outGp * 255) / outA)); + outB = uint8_t(std::min(255, (outBp * 255) / outA)); + } + base.Px[i] = _pack(uint8_t(outA), outR, outG, outB); + } +} + +void TexturePipelineProgram::VM::_applyMask(Image& base, const Image& mask) { + const size_t n = base.Px.size(); + for(size_t i=0;i> 16) & 0xFF); + uint8_t gg = uint8_t((rgb24 >> 8) & 0xFF); + uint8_t bb = uint8_t((rgb24 >> 0) & 0xFF); + for(auto& p : img.Px) { + if(_r(p)==rr && _g(p)==gg && _b(p)==bb) p = _pack(0, _r(p), _g(p), _b(p)); + } +} + +void TexturePipelineProgram::VM::_invert(Image& img, uint32_t maskBits) { + for(auto& p : img.Px) { + uint8_t a=_a(p), r=_r(p), g=_g(p), b=_b(p); + if(maskBits & 1u) r = 255 - r; + if(maskBits & 2u) g = 255 - g; + if(maskBits & 4u) b = 255 - b; + if(maskBits & 8u) a = 255 - a; + p = _pack(a,r,g,b); + } +} + +void TexturePipelineProgram::VM::_brighten(Image& img) { + for(auto& p : img.Px) { + int r = _r(p), g = _g(p), b = _b(p); + r = r + (255 - r) / 3; + g = g + (255 - g) / 3; + b = b + (255 - b) / 3; + p = _pack(_a(p), _clampu8(r), _clampu8(g), _clampu8(b)); + } +} + +void TexturePipelineProgram::VM::_contrast(Image& img, int c, int br) { + double C = double(std::max(-127, std::min(127, c))); + double factor = (259.0 * (C + 255.0)) / (255.0 * (259.0 - C)); + for(auto& p : img.Px) { + int r = int(factor * (int(_r(p)) - 128) + 128) + br; + int g = int(factor * (int(_g(p)) - 128) + 128) + br; + int b = int(factor * (int(_b(p)) - 128) + 128) + br; + p = _pack(_a(p), _clampu8(r), _clampu8(g), _clampu8(b)); + } +} + +void TexturePipelineProgram::VM::_multiply(Image& img, uint32_t color) { + uint8_t cr=_r(color), cg=_g(color), cb=_b(color); + for(auto& p : img.Px) { + uint8_t r = uint8_t((uint32_t(_r(p)) * cr) / 255); + uint8_t g = uint8_t((uint32_t(_g(p)) * cg) / 255); + uint8_t b = uint8_t((uint32_t(_b(p)) * cb) / 255); + p = _pack(_a(p), r,g,b); + } +} + +void TexturePipelineProgram::VM::_screen(Image& img, uint32_t color) { + uint8_t cr=_r(color), cg=_g(color), cb=_b(color); + for(auto& p : img.Px) { + uint8_t r = uint8_t(255 - ((255 - _r(p)) * (255 - cr)) / 255); + uint8_t g = uint8_t(255 - ((255 - _g(p)) * (255 - cg)) / 255); + uint8_t b = uint8_t(255 - ((255 - _b(p)) * (255 - cb)) / 255); + p = _pack(_a(p), r,g,b); + } +} + +void TexturePipelineProgram::VM::_colorize(Image& img, uint32_t color, uint8_t ratio) { + uint8_t cr=_r(color), cg=_g(color), cb=_b(color); + for(auto& p : img.Px) { + int r = (int(_r(p)) * (255 - ratio) + int(cr) * ratio) / 255; + int g = (int(_g(p)) * (255 - ratio) + int(cg) * ratio) / 255; + int b = (int(_b(p)) * (255 - ratio) + int(cb) * ratio) / 255; + p = _pack(_a(p), uint8_t(r), uint8_t(g), uint8_t(b)); + } +} + +void TexturePipelineProgram::VM::_lowpart(Image& base, const Image& over, uint32_t percent) { + uint32_t startY = base.H - (base.H * percent) / 100; + for(uint32_t y=startY; y(255, (outRp * 255) / outA)); + outG = uint8_t(std::min(255, (outGp * 255) / outA)); + outB = uint8_t(std::min(255, (outBp * 255) / outA)); + } + base.Px[i] = _pack(uint8_t(outA), outR, outG, outB); + } + } +} + +TexturePipelineProgram::Image TexturePipelineProgram::VM::_transform(const Image& src, uint32_t t) { + Image dst; + auto at = [&](uint32_t x, uint32_t y)->uint32_t { return src.Px[size_t(y)*src.W + x]; }; + auto make = [&](uint32_t w, uint32_t h){ + Image d; d.W=w; d.H=h; d.Px.resize(size_t(w)*size_t(h)); + return d; + }; + auto set = [&](Image& im, uint32_t x, uint32_t y, uint32_t v){ + im.Px[size_t(y)*im.W + x] = v; + }; + + switch (t & 7u) { + case 0: return src; + case 1: { dst = make(src.H, src.W); + for(uint32_t y=0;yint{ + if(c>='0'&&c<='9') return c-'0'; + if(c>='a'&&c<='f') return 10+(c-'a'); + if(c>='A'&&c<='F') return 10+(c-'A'); + return -1; + }; + auto byteAt = [&](size_t idx)->std::optional{ + int hi=hex(s[idx]), lo=hex(s[idx+1]); + if(hi<0||lo<0) return std::nullopt; + return uint8_t((hi<<4)|lo); + }; + auto r = byteAt(1), g = byteAt(3), b = byteAt(5); + if(!r||!g||!b) return false; + uint8_t a = 255; + if(s.size()==9) { + auto aa = byteAt(7); + if(!aa) return false; + a = *aa; + } + outARGB = (uint32_t(a)<<24) | (uint32_t(*r)<<16) | (uint32_t(*g)<<8) | (uint32_t(*b)); + return true; +} + +bool TexturePipelineProgram::_parseProgram(std::string* err) { + Lexer lx{Source_}; + Tok t = lx.next(); + bool hasTexPrefix = (t.Kind == TokKind::Ident && t.Text == "tex"); + + // compile base into main Code_ + if(hasTexPrefix) { + if(!_compileBaseAfterTex(lx, Code_, /*abs*/&Patches_, /*rel*/nullptr, err)) return false; + } else { + if(!_compileBaseFromToken(lx, t, Code_, /*abs*/&Patches_, /*rel*/nullptr, err)) return false; + } + + // pipeline: |> op ... + Tok nt = lx.next(); + while (nt.Kind == TokKind::Pipe) { + Tok opName = lx.next(); + if(opName.Kind != TokKind::Ident) { if(err) *err="Ожидалось имя операции после |>"; return false; } + ParsedOp op; op.Name = opName.Text; + + Tok peek = lx.next(); + if(peek.Kind == TokKind::LParen) { + if(!_parseArgListOrTextureExpr(lx, op, err)) return false; + nt = lx.next(); + } else { + nt = peek; // no-arg op + } + + if(!_compileOpInto(lx, op, Code_, /*abs*/&Patches_, /*rel*/nullptr, err)) return false; + } + + _emitOp(Code_, Op::End); + if (Code_.size() > MaxCodeBytes) { + if (err) + *err = "Байткод пайплайна слишком большой: " + std::to_string(Code_.size()) + + " > MaxCodeBytes(" + std::to_string(MaxCodeBytes) + ")"; + return false; + } + + return true; +} + +bool TexturePipelineProgram::_compileBaseAfterTex(Lexer& lx, + std::vector& out, + std::vector* absPatches, + std::vector* relPatches, + std::string* err) { + Tok a = lx.next(); + return _compileBaseFromToken(lx, a, out, absPatches, relPatches, err); +} + +bool TexturePipelineProgram::_compileBaseFromToken(Lexer& lx, + const Tok& a, + std::vector& out, + std::vector* absPatches, + std::vector* relPatches, + std::string* err) { + if(a.Kind == TokKind::End) { + if(err) *err="Ожидалось текстурное выражение"; + return false; + } + + if(a.Kind == TokKind::Ident && a.Text == "anim") { + Tok lp = lx.next(); + if(lp.Kind != TokKind::LParen) { if(err) *err="Ожидалась '(' после anim"; return false; } + + ParsedOp op; op.Name="anim"; + if(!_parseArgList(lx, op, err)) return false; + + auto posU = [&](size_t i)->std::optional{ + if(i >= op.Pos.size()) return std::nullopt; + if(op.Pos[i].Kind != ArgVal::ValueKind::U32) return std::nullopt; + return op.Pos[i].U32; + }; + auto posS = [&](size_t i)->std::optional{ + if(i >= op.Pos.size()) return std::nullopt; + return op.Pos[i].S; + }; + auto namedU = [&](std::string_view k)->std::optional{ + auto it = op.Named.find(std::string(k)); + if(it==op.Named.end() || it->second.Kind!=ArgVal::ValueKind::U32) return std::nullopt; + return it->second.U32; + }; + auto namedS = [&](std::string_view k)->std::optional{ + auto it = op.Named.find(std::string(k)); + if(it==op.Named.end()) return std::nullopt; + return it->second.S; + }; + + std::string tex = namedS("tex").value_or(posS(0).value_or("")); + if(tex.empty()) { if(err) *err="anim требует имя текстуры"; return false; } + + uint32_t frameW = namedU("frame_w").value_or(namedU("w").value_or(posU(1).value_or(0))); + uint32_t frameH = namedU("frame_h").value_or(namedU("h").value_or(posU(2).value_or(0))); + uint32_t frames = namedU("frames").value_or(namedU("count").value_or(posU(3).value_or(0))); + uint32_t fps = namedU("fps").value_or(posU(4).value_or(0)); + uint32_t smooth = namedU("smooth").value_or(posU(5).value_or(0)); + + std::string axis = namedS("axis").value_or(""); + bool axisHorizontal = (!axis.empty() && (axis[0] == 'x' || axis[0] == 'h')); + bool axisVertical = (!axis.empty() && (axis[0] == 'y' || axis[0] == 'v')); + bool useGrid = axis.empty() || axis[0] == 'g'; + + if(frameW > 65535u || frameH > 65535u || frames > 65535u) { + if(err) *err="параметры anim должны помещаться в uint16"; + return false; + } + + uint32_t fpsQ = fps ? std::min(0xFFFFu, fps * 256u) : DefaultAnimFpsQ; + uint32_t flags = (smooth ? AnimSmooth : 0); + if(useGrid) flags |= AnimGrid; + else if(axisHorizontal) flags |= AnimHorizontal; + else if(!axisVertical) flags |= AnimGrid; + + _emitOp(out, Op::Base_Anim); + _emitSrcTexName(out, absPatches, relPatches, tex); + _emitU16(out, frameW); + _emitU16(out, frameH); + _emitU16(out, frames); + _emitU16(out, fpsQ); + _emitU8(out, flags); + return true; + } + + if(a.Kind == TokKind::Ident || a.Kind == TokKind::String) { + // name (or "name.png" => normalized) + _emitOp(out, Op::Base_Tex); + _emitSrcTexName(out, absPatches, relPatches, a.Text); + return true; + } + + if(a.Kind == TokKind::Number) { + // 32x32 "#RRGGBBAA" + Tok xTok = lx.next(); + Tok b = lx.next(); + Tok colTok = lx.next(); + if(xTok.Kind != TokKind::X || b.Kind != TokKind::Number || (colTok.Kind!=TokKind::Ident && colTok.Kind!=TokKind::String)) { + if(err) *err="Ожидалось: x <#color>"; + return false; + } + uint32_t w = a.U32, h = b.U32; + uint32_t color = 0; + if(!_parseHexColor(colTok.Text, color)) { + if(err) *err="Неверный литерал цвета (используйте #RRGGBB или #RRGGBBAA)"; + return false; + } + if(w>65535u || h>65535u) { if(err) *err="w/h должны помещаться в uint16"; return false; } + _emitOp(out, Op::Base_Fill); + _emitU16(out, w); + _emitU16(out, h); + _emitU32(out, color); + return true; + } + + if(err) *err="Некорректное базовое текстурное выражение"; + return false; +} + +bool TexturePipelineProgram::_parseArgListOrTextureExpr(Lexer& lx, ParsedOp& op, std::string* err) { + Tok first = lx.next(); + + if(first.Kind==TokKind::Ident && first.Text=="tex") { + // marker + ArgVal av; av.Kind = ArgVal::ValueKind::Ident; av.S = "__SUBTEX__"; + op.Pos.push_back(av); + + PendingSubData sub; + if(!_compileSubProgramFromAlreadySawTex(lx, sub, err)) return false; + + Tok end = lx.next(); + if(end.Kind != TokKind::RParen) { if(err) *err="Ожидалась ')' после подвыражения текстуры"; return false; } + + PendingSub_[&op] = std::move(sub); + return true; + } + + // otherwise parse as normal arg list, where `first` is first token inside '(' + Tok t = first; + if(t.Kind == TokKind::RParen) return true; + + while (true) { + if(t.Kind == TokKind::Ident) { + Tok maybeEq = lx.next(); + if(maybeEq.Kind == TokKind::Eq) { + Tok v = lx.next(); + ArgVal av; + if(!_tokToVal(v, av, err)) return false; + op.Named[t.Text] = std::move(av); + t = lx.next(); + } else { + ArgVal av; av.Kind = ArgVal::ValueKind::Ident; av.S = t.Text; + op.Pos.push_back(std::move(av)); + t = maybeEq; + } + } else { + ArgVal av; + if(!_tokToVal(t, av, err)) return false; + op.Pos.push_back(std::move(av)); + t = lx.next(); + } + + if(t.Kind == TokKind::Comma) { t = lx.next(); continue; } + if(t.Kind == TokKind::RParen) return true; + + if(err) *err = "Ожидалась ',' или ')' в списке аргументов"; + return false; + } +} + +bool TexturePipelineProgram::_parseArgList(Lexer& lx, ParsedOp& op, std::string* err) { + Tok t = lx.next(); + if(t.Kind == TokKind::RParen) return true; + + while (true) { + if(t.Kind == TokKind::Ident) { + Tok maybeEq = lx.next(); + if(maybeEq.Kind == TokKind::Eq) { + Tok v = lx.next(); + ArgVal av; + if(!_tokToVal(v, av, err)) return false; + op.Named[t.Text] = std::move(av); + t = lx.next(); + } else { + ArgVal av; av.Kind = ArgVal::ValueKind::Ident; av.S = t.Text; + op.Pos.push_back(std::move(av)); + t = maybeEq; + } + } else { + ArgVal av; + if(!_tokToVal(t, av, err)) return false; + op.Pos.push_back(std::move(av)); + t = lx.next(); + } + + if(t.Kind == TokKind::Comma) { t = lx.next(); continue; } + if(t.Kind == TokKind::RParen) return true; + + if(err) *err = "Ожидалась ',' или ')' в списке аргументов"; + return false; + } +} + +bool TexturePipelineProgram::_tokToVal(const Tok& t, ArgVal& out, std::string* err) { + if(t.Kind == TokKind::Number) { out.Kind=ArgVal::ValueKind::U32; out.U32=t.U32; return true; } + if(t.Kind == TokKind::String) { out.Kind=ArgVal::ValueKind::Str; out.S=t.Text; return true; } + if(t.Kind == TokKind::Ident) { out.Kind=ArgVal::ValueKind::Ident; out.S=t.Text; return true; } + if(err) *err = "Ожидалось значение"; + return false; +} + +bool TexturePipelineProgram::_compileSubProgramFromAlreadySawTex(Lexer& lx, PendingSubData& outSub, std::string* err) { + outSub.Bytes.clear(); + outSub.RelPatches.clear(); + + // base + if(!_compileBaseAfterTex(lx, outSub.Bytes, /*abs*/nullptr, /*rel*/&outSub.RelPatches, err)) + return false; + + // pipeline until ')' + while(true) { + // peek + Tok nt = lx.peek(); + if(nt.Kind == TokKind::RParen) break; + if(nt.Kind != TokKind::Pipe) { if(err) *err="Подтекстура: ожидалось '|>' или ')'"; return false; } + + // consume pipe + lx.next(); + Tok opName = lx.next(); + if(opName.Kind != TokKind::Ident) { if(err) *err="Подтекстура: ожидалось имя операции"; return false; } + + ParsedOp op; op.Name = opName.Text; + + Tok lp = lx.next(); + if(lp.Kind == TokKind::LParen) { + if(!_parseArgListOrTextureExpr(lx, op, err)) return false; + } else { + // no-arg op, lp already is next token (pipe or ')'), so we need to "unread" — can't. + // simplest: treat it as next token for outer loop by rewinding lexer state. + // We'll do it by storing the token back via a small hack: rebuild peek? Too heavy. + // Instead: enforce parentheses for ops in subprogram except no-arg ops (brighten/noalpha) which can be without. + // To keep behavior identical to main, we handle no-arg by rewinding I one token is not possible, + // so we accept that in subprogram, no-arg ops must be written as brighten() etc. + if(err) *err="Подтекстура: операции без аргументов должны использовать скобки, напр. brighten()"; + return false; + } + + if(!_compileOpInto(lx, op, outSub.Bytes, /*abs*/nullptr, /*rel*/&outSub.RelPatches, err)) + return false; + } + + // Pipeline until we see ')' + while (true) { + Tok nt = lx.peek(); + if(nt.Kind == TokKind::RParen) break; + if(nt.Kind != TokKind::Pipe) { if(err) *err="Подтекстура: ожидалось '|>' или ')'"; return false; } + + // consume pipe + lx.next(); + + Tok opName = lx.next(); + if(opName.Kind != TokKind::Ident) { if(err) *err="Подтекстура: ожидалось имя операции"; return false; } + + ParsedOp op; op.Name = opName.Text; + + // allow both op and op(...) + Tok maybe = lx.peek(); + if(maybe.Kind == TokKind::LParen) { + lx.next(); // consume '(' + if(!_parseArgListOrTextureExpr(lx, op, err)) return false; + } else { + // no-arg op; nothing to parse + } + + if(!_compileOpInto(lx, op, outSub.Bytes, /*abs*/nullptr, /*rel*/&outSub.RelPatches, err)) + return false; + } + + _emitOp(outSub.Bytes, Op::End); + return true; +} + +bool TexturePipelineProgram::_appendSubprogram(std::vector& out, + PendingSubData&& sub, + std::vector* absPatches, + std::vector* relPatches, + uint32_t& outOff, + uint32_t& outLen, + std::string* err) { + const size_t offset = out.size(); + const size_t len = sub.Bytes.size(); + + if(offset > 0xFFFFFFu || len > 0xFFFFFFu || (offset + len) > 0xFFFFFFu) { + if(err) *err = "Подпрограмма слишком большая (off/len должны влезать в u24 байт)"; + return false; + } + + if(offset + len > MaxCodeBytes) { + if(err) *err = "Байткод пайплайна слишком большой после вставки подпрограммы: " + + std::to_string(offset + len) + " > MaxCodeBytes(" + std::to_string(MaxCodeBytes) + ")"; + return false; + } + + // migrate patches + if(absPatches) { + for(const auto& rp : sub.RelPatches) { + absPatches->push_back(Patch{offset + rp.Rel0, rp.Name}); + } + } + if(relPatches) { + for(const auto& rp : sub.RelPatches) { + relPatches->push_back(RelPatch{offset + rp.Rel0, rp.Name}); + } + } + + out.insert(out.end(), sub.Bytes.begin(), sub.Bytes.end()); + + outOff = uint32_t(offset); + outLen = uint32_t(len); + return true; +} + +bool TexturePipelineProgram::_compileOpInto(Lexer& /*lx*/, + const ParsedOp& op, + std::vector& out, + std::vector* absPatches, + std::vector* relPatches, + std::string* err) { + auto posU = [&](size_t i)->std::optional{ + if(i >= op.Pos.size()) return std::nullopt; + if(op.Pos[i].Kind != ArgVal::ValueKind::U32) return std::nullopt; + return op.Pos[i].U32; + }; + auto posS = [&](size_t i)->std::optional{ + if(i >= op.Pos.size()) return std::nullopt; + return op.Pos[i].S; + }; + auto namedU = [&](std::string_view k)->std::optional{ + auto it = op.Named.find(std::string(k)); + if(it==op.Named.end() || it->second.Kind!=ArgVal::ValueKind::U32) return std::nullopt; + return it->second.U32; + }; + auto namedS = [&](std::string_view k)->std::optional{ + auto it = op.Named.find(std::string(k)); + if(it==op.Named.end()) return std::nullopt; + return it->second.S; + }; + + auto emitSrcFromName = [&](const std::string& n){ + _emitSrcTexName(out, absPatches, relPatches, n); + }; + + auto emitSrcFromPendingSub = [&]()->bool{ + auto it = PendingSub_.find(&op); + if(it == PendingSub_.end()) { if(err) *err="Внутренняя ошибка: отсутствует подпрограмма"; return false; } + uint32_t off=0, len=0; + if(!_appendSubprogram(out, std::move(it->second), absPatches, relPatches, off, len, err)) return false; + PendingSub_.erase(it); + _emitSrcSub(out, off, len); + return true; + }; + + // --- Ops that accept a "texture" argument: overlay/mask/lowpart --- + if(op.Name == "overlay") { + _emitOp(out, Op::Overlay); + if(!op.Pos.empty() && op.Pos[0].S == "__SUBTEX__") return emitSrcFromPendingSub(); + + // allow overlay(name) or overlay(tex=name) + std::string tex = namedS("tex").value_or(posS(0).value_or("")); + if(tex.empty()) { if(err) *err="overlay требует аргумент-текстуру"; return false; } + emitSrcFromName(tex); + return true; + } + + if(op.Name == "mask") { + _emitOp(out, Op::Mask); + if(!op.Pos.empty() && op.Pos[0].S == "__SUBTEX__") return emitSrcFromPendingSub(); + + std::string tex = namedS("tex").value_or(posS(0).value_or("")); + if(tex.empty()) { if(err) *err="mask требует аргумент-текстуру"; return false; } + emitSrcFromName(tex); + return true; + } + + if(op.Name == "lowpart") { + uint32_t pct = namedU("percent").value_or(posU(0).value_or(0)); + if(!pct) { if(err) *err="lowpart требует процент"; return false; } + + _emitOp(out, Op::LowPart); + _emitU8(out, std::min(100u, pct)); + + // 2nd arg can be nested subtex or name + if(op.Pos.size() >= 2 && op.Pos[1].S == "__SUBTEX__") return emitSrcFromPendingSub(); + + std::string tex = namedS("tex").value_or(posS(1).value_or("")); + if(tex.empty()) { if(err) *err="lowpart требует аргумент-текстуру"; return false; } + emitSrcFromName(tex); + return true; + } + + // --- Unary ops --- + if(op.Name == "resize") { + uint32_t w = namedU("w").value_or(posU(0).value_or(0)); + uint32_t h = namedU("h").value_or(posU(1).value_or(0)); + if(!w || !h || w>65535u || h>65535u) { if(err) *err="resize(w,h) должны помещаться в uint16"; return false; } + _emitOp(out, Op::Resize); _emitU16(out, w); _emitU16(out, h); + return true; + } + + if(op.Name == "transform") { + uint32_t t = namedU("t").value_or(posU(0).value_or(0)); + _emitOp(out, Op::Transform); _emitU8(out, t & 7u); + return true; + } + + if(op.Name == "opacity") { + uint32_t a = namedU("a").value_or(posU(0).value_or(255)); + _emitOp(out, Op::Opacity); _emitU8(out, a & 0xFFu); + return true; + } + + if(op.Name == "remove_alpha" || op.Name == "noalpha") { + _emitOp(out, Op::NoAlpha); + return true; + } + + if(op.Name == "make_alpha") { + std::string col = namedS("color").value_or(posS(0).value_or("")); + uint32_t argb=0; + if(!_parseHexColor(col, argb)) { if(err) *err="make_alpha требует цвет #RRGGBB"; return false; } + uint32_t rgb24 = argb & 0x00FFFFFFu; + _emitOp(out, Op::MakeAlpha); + _emitU8(out, (rgb24 >> 16) & 0xFFu); + _emitU8(out, (rgb24 >> 8) & 0xFFu); + _emitU8(out, (rgb24 >> 0) & 0xFFu); + return true; + } + + if(op.Name == "invert") { + std::string ch = namedS("channels").value_or(posS(0).value_or("rgb")); + uint32_t mask=0; + for(char c : ch) { + if(c=='r') mask |= 1; + if(c=='g') mask |= 2; + if(c=='b') mask |= 4; + if(c=='a') mask |= 8; + } + _emitOp(out, Op::Invert); _emitU8(out, mask & 0xFu); + return true; + } + + if(op.Name == "brighten") { + _emitOp(out, Op::Brighten); + return true; + } + + if(op.Name == "contrast") { + int c = int(namedU("value").value_or(posU(0).value_or(0))); + int b = int(namedU("brightness").value_or(posU(1).value_or(0))); + c = std::max(-127, std::min(127, c)); + b = std::max(-127, std::min(127, b)); + _emitOp(out, Op::Contrast); + _emitU8(out, uint32_t(c + 127)); + _emitU8(out, uint32_t(b + 127)); + return true; + } + + auto compileColorOp = [&](Op opcode, bool needsRatio)->bool{ + std::string col = namedS("color").value_or(posS(0).value_or("")); + uint32_t argb=0; + if(!_parseHexColor(col, argb)) { if(err) *err="Неверный литерал цвета"; return false; } + _emitOp(out, opcode); + _emitU32(out, argb); + if(needsRatio) { + uint32_t ratio = namedU("ratio").value_or(posU(1).value_or(255)); + _emitU8(out, ratio & 0xFFu); + } + return true; + }; + + if(op.Name == "multiply") return compileColorOp(Op::Multiply, false); + if(op.Name == "screen") return compileColorOp(Op::Screen, false); + if(op.Name == "colorize") return compileColorOp(Op::Colorize, true); + + if(op.Name == "anim") { + uint32_t frameW = namedU("frame_w").value_or(namedU("w").value_or(posU(0).value_or(0))); + uint32_t frameH = namedU("frame_h").value_or(namedU("h").value_or(posU(1).value_or(0))); + uint32_t frames = namedU("frames").value_or(namedU("count").value_or(posU(2).value_or(0))); + uint32_t fps = namedU("fps").value_or(posU(3).value_or(0)); + uint32_t smooth = namedU("smooth").value_or(posU(4).value_or(0)); + + std::string axis = namedS("axis").value_or(""); + bool axisHorizontal = (!axis.empty() && (axis[0] == 'x' || axis[0] == 'h')); + bool axisVertical = (!axis.empty() && (axis[0] == 'y' || axis[0] == 'v')); + bool useGrid = axis.empty() || axis[0] == 'g'; + + if(frameW > 65535u || frameH > 65535u || frames > 65535u) { + if(err) *err="параметры anim должны помещаться в uint16"; + return false; + } + + uint32_t fpsQ = fps ? std::min(0xFFFFu, fps * 256u) : DefaultAnimFpsQ; + uint32_t flags = (smooth ? AnimSmooth : 0); + if(useGrid) flags |= AnimGrid; + else if(axisHorizontal) flags |= AnimHorizontal; + else if(!axisVertical) flags |= AnimGrid; + + _emitOp(out, Op::Anim); + _emitU16(out, frameW); + _emitU16(out, frameH); + _emitU16(out, frames); + _emitU16(out, fpsQ); + _emitU8(out, flags); + return true; + } + + if(err) *err = "Неизвестная операция: " + op.Name; + return false; +} diff --git a/Src/Common/TexturePipelineProgram.hpp b/Src/Common/TexturePipelineProgram.hpp index e2e83e6..a63696a 100644 --- a/Src/Common/TexturePipelineProgram.hpp +++ b/Src/Common/TexturePipelineProgram.hpp @@ -52,45 +52,10 @@ public: std::string Name; }; - bool compile(std::string_view src, std::string* err = nullptr) { - Source_ = src; - Code_.clear(); - Patches_.clear(); - PendingSub_.clear(); - return _parseProgram(err); - } - - bool link(const IdResolverFunc& resolver, std::string* err = nullptr) { - for (const auto& p : Patches_) { - auto idOpt = resolver(p.Name); - if(!idOpt) { - if(err) *err = "Не удалось разрешить имя текстуры: " + p.Name; - return false; - } - uint32_t id = *idOpt; - if(id >= (1u << 24)) { - if(err) *err = "TexId выходит за 24 бита (u24): " + p.Name + " => " + std::to_string(id); - return false; - } - if(p.ByteIndex0 + 2 >= Code_.size()) { - if(err) *err = "Внутренняя ошибка: применение идентификатора выходит за рамки кода"; - return false; - } - Code_[p.ByteIndex0 + 0] = uint8_t(id & 0xFFu); - Code_[p.ByteIndex0 + 1] = uint8_t((id >> 8) & 0xFFu); - Code_[p.ByteIndex0 + 2] = uint8_t((id >> 16) & 0xFFu); - } - return true; - } - - bool bake(const TextureProviderFunc& provider, OwnedTexture& out, std::string* err = nullptr) const { - return bake(provider, out, 0.0, err); - } - - bool bake(const TextureProviderFunc& provider, OwnedTexture& out, double timeSeconds, std::string* err = nullptr) const { - VM vm(provider); - return vm.run(Code_, out, timeSeconds, err); - } + 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& words() const { return Code_; } const std::vector& patches() const { return Patches_; } @@ -107,520 +72,14 @@ public: uint16_t Flags = 0; }; - static std::vector extractAnimationSpecs(const Word* code, size_t size) { - std::vector specs; - if(!code || size == 0) { - return specs; - } - - struct Range { - size_t Start = 0; - size_t End = 0; - }; - - std::vector visited; - - auto read8 = [&](size_t& ip, uint8_t& out)->bool{ - if(ip >= size) return false; - out = code[ip++]; - return true; - }; - auto read16 = [&](size_t& ip, uint16_t& out)->bool{ - if(ip + 1 >= size) return false; - out = uint16_t(code[ip]) | (uint16_t(code[ip + 1]) << 8); - ip += 2; - return true; - }; - auto read24 = [&](size_t& ip, uint32_t& out)->bool{ - if(ip + 2 >= size) return false; - out = uint32_t(code[ip]) | - (uint32_t(code[ip + 1]) << 8) | - (uint32_t(code[ip + 2]) << 16); - ip += 3; - return true; - }; - auto read32 = [&](size_t& ip, uint32_t& out)->bool{ - if(ip + 3 >= size) return false; - out = uint32_t(code[ip]) | - (uint32_t(code[ip + 1]) << 8) | - (uint32_t(code[ip + 2]) << 16) | - (uint32_t(code[ip + 3]) << 24); - ip += 4; - return true; - }; - - struct SrcMeta { - SrcKind Kind = SrcKind::TexId; - uint32_t TexId = 0; - uint32_t Off = 0; - uint32_t Len = 0; - }; - - auto readSrc = [&](size_t& ip, SrcMeta& out)->bool{ - uint8_t kind = 0; - if(!read8(ip, kind)) return false; - out.Kind = static_cast(kind); - if(out.Kind == SrcKind::TexId) { - return read24(ip, out.TexId); - } - if(out.Kind == SrcKind::Sub) { - return read24(ip, out.Off) && read24(ip, out.Len); - } - return false; - }; - - auto scan = [&](auto&& self, size_t start, size_t end) -> void { - if(start >= end || end > size) { - return; - } - for(const auto& r : visited) { - if(r.Start == start && r.End == end) { - return; - } - } - visited.push_back(Range{start, end}); - - size_t ip = start; - while(ip < end) { - uint8_t opByte = 0; - if(!read8(ip, opByte)) return; - Op op = static_cast(opByte); - switch(op) { - case Op::End: - return; - - case Op::Base_Tex: { - SrcMeta src{}; - if(!readSrc(ip, src)) return; - if(src.Kind == SrcKind::Sub) { - size_t subStart = src.Off; - size_t subEnd = subStart + src.Len; - if(subStart < subEnd && subEnd <= size) { - self(self, subStart, subEnd); - } - } - } break; - - case Op::Base_Fill: { - uint16_t tmp16 = 0; - uint32_t tmp32 = 0; - if(!read16(ip, tmp16)) return; - if(!read16(ip, tmp16)) return; - if(!read32(ip, tmp32)) return; - } break; - - case Op::Base_Anim: { - SrcMeta src{}; - if(!readSrc(ip, src)) return; - uint16_t frameW = 0; - uint16_t frameH = 0; - uint16_t frameCount = 0; - uint16_t fpsQ = 0; - uint8_t flags = 0; - if(!read16(ip, frameW)) return; - if(!read16(ip, frameH)) return; - if(!read16(ip, frameCount)) return; - if(!read16(ip, fpsQ)) return; - if(!read8(ip, flags)) return; - - if(src.Kind == SrcKind::TexId) { - AnimSpec spec{}; - spec.TexId = src.TexId; - spec.HasTexId = true; - spec.FrameW = frameW; - spec.FrameH = frameH; - spec.FrameCount = frameCount; - spec.FpsQ = fpsQ; - spec.Flags = flags; - specs.push_back(spec); - } else if(src.Kind == SrcKind::Sub) { - size_t subStart = src.Off; - size_t subEnd = subStart + src.Len; - if(subStart < subEnd && subEnd <= size) { - self(self, subStart, subEnd); - } - } - } break; - - case Op::Resize: { - uint16_t tmp16 = 0; - if(!read16(ip, tmp16)) return; - if(!read16(ip, tmp16)) return; - } break; - - case Op::Transform: - case Op::Opacity: - case Op::Invert: - if(!read8(ip, opByte)) return; - break; - - case Op::NoAlpha: - case Op::Brighten: - break; - - case Op::MakeAlpha: - if(ip + 2 >= size) return; - ip += 3; - break; - - case Op::Contrast: - if(ip + 1 >= size) return; - ip += 2; - break; - - case Op::Multiply: - case Op::Screen: { - uint32_t tmp32 = 0; - if(!read32(ip, tmp32)) return; - } break; - - case Op::Colorize: { - uint32_t tmp32 = 0; - if(!read32(ip, tmp32)) return; - if(!read8(ip, opByte)) return; - } break; - - case Op::Anim: { - uint16_t frameW = 0; - uint16_t frameH = 0; - uint16_t frameCount = 0; - uint16_t fpsQ = 0; - uint8_t flags = 0; - if(!read16(ip, frameW)) return; - if(!read16(ip, frameH)) return; - if(!read16(ip, frameCount)) return; - if(!read16(ip, fpsQ)) return; - if(!read8(ip, flags)) return; - - AnimSpec spec{}; - spec.HasTexId = false; - spec.FrameW = frameW; - spec.FrameH = frameH; - spec.FrameCount = frameCount; - spec.FpsQ = fpsQ; - spec.Flags = flags; - specs.push_back(spec); - } break; - - case Op::Overlay: - case Op::Mask: { - SrcMeta src{}; - if(!readSrc(ip, src)) return; - if(src.Kind == SrcKind::Sub) { - size_t subStart = src.Off; - size_t subEnd = subStart + src.Len; - if(subStart < subEnd && subEnd <= size) { - self(self, subStart, subEnd); - } - } - } break; - - case Op::LowPart: { - if(!read8(ip, opByte)) return; - SrcMeta src{}; - if(!readSrc(ip, src)) return; - if(src.Kind == SrcKind::Sub) { - size_t subStart = src.Off; - size_t subEnd = subStart + src.Len; - if(subStart < subEnd && subEnd <= size) { - self(self, subStart, subEnd); - } - } - } break; - - case Op::Combine: { - uint16_t w = 0, h = 0, n = 0; - if(!read16(ip, w)) return; - if(!read16(ip, h)) return; - if(!read16(ip, n)) return; - for(uint16_t i = 0; i < n; ++i) { - uint16_t tmp16 = 0; - if(!read16(ip, tmp16)) return; - if(!read16(ip, tmp16)) return; - SrcMeta src{}; - if(!readSrc(ip, src)) return; - if(src.Kind == SrcKind::Sub) { - size_t subStart = src.Off; - size_t subEnd = subStart + src.Len; - if(subStart < subEnd && subEnd <= size) { - self(self, subStart, subEnd); - } - } - } - (void)w; (void)h; - } break; - - default: - return; - } - } - }; - - scan(scan, 0, size); - return specs; - } - - static bool remapTexIds(std::vector& code, const std::vector& remap, std::string* err = nullptr) { - struct Range { - size_t Start = 0; - size_t End = 0; - }; - - struct SrcMeta { - SrcKind Kind = SrcKind::TexId; - uint32_t TexId = 0; - uint32_t Off = 0; - uint32_t Len = 0; - size_t TexIdOffset = 0; - }; - - const size_t size = code.size(); - std::vector visited; - - auto read8 = [&](size_t& ip, uint8_t& out)->bool{ - if(ip >= size) return false; - out = code[ip++]; - return true; - }; - auto read16 = [&](size_t& ip, uint16_t& out)->bool{ - if(ip + 1 >= size) return false; - out = uint16_t(code[ip]) | (uint16_t(code[ip + 1]) << 8); - ip += 2; - return true; - }; - auto read24 = [&](size_t& ip, uint32_t& out)->bool{ - if(ip + 2 >= size) return false; - out = uint32_t(code[ip]) | - (uint32_t(code[ip + 1]) << 8) | - (uint32_t(code[ip + 2]) << 16); - ip += 3; - return true; - }; - auto read32 = [&](size_t& ip, uint32_t& out)->bool{ - if(ip + 3 >= size) return false; - out = uint32_t(code[ip]) | - (uint32_t(code[ip + 1]) << 8) | - (uint32_t(code[ip + 2]) << 16) | - (uint32_t(code[ip + 3]) << 24); - ip += 4; - return true; - }; - - auto readSrc = [&](size_t& ip, SrcMeta& out)->bool{ - uint8_t kind = 0; - if(!read8(ip, kind)) return false; - out.Kind = static_cast(kind); - if(out.Kind == SrcKind::TexId) { - out.TexIdOffset = ip; - return read24(ip, out.TexId); - } - if(out.Kind == SrcKind::Sub) { - return read24(ip, out.Off) && read24(ip, out.Len); - } - return false; - }; - - auto patchTexId = [&](const SrcMeta& src)->bool{ - if(src.Kind != SrcKind::TexId) - return true; - if(src.TexId >= remap.size()) { - if(err) *err = "Идентификатор текстуры вне диапазона переназначения"; - return false; - } - uint32_t newId = remap[src.TexId]; - if(newId >= (1u << 24)) { - if(err) *err = "TexId выходит за 24 бита (u24)"; - return false; - } - if(src.TexIdOffset + 2 >= code.size()) { - if(err) *err = "Применение идентификатора выходит за рамки кода"; - return false; - } - code[src.TexIdOffset + 0] = uint8_t(newId & 0xFFu); - code[src.TexIdOffset + 1] = uint8_t((newId >> 8) & 0xFFu); - code[src.TexIdOffset + 2] = uint8_t((newId >> 16) & 0xFFu); - return true; - }; - - std::move_only_function scan; - scan = [&](size_t start, size_t end) -> bool { - if(start >= end || end > size) { - return true; - } - for(const auto& r : visited) { - if(r.Start == start && r.End == end) { - return true; - } - } - visited.push_back(Range{start, end}); - - size_t ip = start; - while(ip < end) { - uint8_t opByte = 0; - if(!read8(ip, opByte)) return false; - Op op = static_cast(opByte); - switch(op) { - case Op::End: - return true; - - case Op::Base_Tex: { - SrcMeta src{}; - if(!readSrc(ip, src)) return false; - if(!patchTexId(src)) return false; - if(src.Kind == SrcKind::Sub) { - size_t subStart = src.Off; - size_t subEnd = subStart + src.Len; - if(!scan(subStart, subEnd)) return false; - } - } break; - - case Op::Base_Fill: { - uint16_t tmp16 = 0; - uint32_t tmp32 = 0; - if(!read16(ip, tmp16)) return false; - if(!read16(ip, tmp16)) return false; - if(!read32(ip, tmp32)) return false; - } break; - - case Op::Base_Anim: { - SrcMeta src{}; - if(!readSrc(ip, src)) return false; - if(!patchTexId(src)) return false; - uint16_t frameW = 0; - uint16_t frameH = 0; - uint16_t frameCount = 0; - uint16_t fpsQ = 0; - uint8_t flags = 0; - if(!read16(ip, frameW)) return false; - if(!read16(ip, frameH)) return false; - if(!read16(ip, frameCount)) return false; - if(!read16(ip, fpsQ)) return false; - if(!read8(ip, flags)) return false; - (void)frameW; (void)frameH; (void)frameCount; (void)fpsQ; (void)flags; - if(src.Kind == SrcKind::Sub) { - size_t subStart = src.Off; - size_t subEnd = subStart + src.Len; - if(!scan(subStart, subEnd)) return false; - } - } break; - - case Op::Resize: { - uint16_t tmp16 = 0; - if(!read16(ip, tmp16)) return false; - if(!read16(ip, tmp16)) return false; - } break; - - case Op::Transform: - case Op::Opacity: - case Op::Invert: - if(!read8(ip, opByte)) return false; - break; - - case Op::NoAlpha: - case Op::Brighten: - break; - - case Op::MakeAlpha: - if(ip + 2 >= size) return false; - ip += 3; - break; - - case Op::Contrast: - if(ip + 1 >= size) return false; - ip += 2; - break; - - case Op::Multiply: - case Op::Screen: { - uint32_t tmp32 = 0; - if(!read32(ip, tmp32)) return false; - } break; - - case Op::Colorize: { - uint32_t tmp32 = 0; - if(!read32(ip, tmp32)) return false; - if(!read8(ip, opByte)) return false; - } break; - - case Op::Anim: { - uint16_t frameW = 0; - uint16_t frameH = 0; - uint16_t frameCount = 0; - uint16_t fpsQ = 0; - uint8_t flags = 0; - if(!read16(ip, frameW)) return false; - if(!read16(ip, frameH)) return false; - if(!read16(ip, frameCount)) return false; - if(!read16(ip, fpsQ)) return false; - if(!read8(ip, flags)) return false; - (void)frameW; (void)frameH; (void)frameCount; (void)fpsQ; (void)flags; - } break; - - case Op::Overlay: - case Op::Mask: { - SrcMeta src{}; - if(!readSrc(ip, src)) return false; - if(!patchTexId(src)) return false; - if(src.Kind == SrcKind::Sub) { - size_t subStart = src.Off; - size_t subEnd = subStart + src.Len; - if(!scan(subStart, subEnd)) return false; - } - } break; - - case Op::LowPart: { - if(!read8(ip, opByte)) return false; - SrcMeta src{}; - if(!readSrc(ip, src)) return false; - if(!patchTexId(src)) return false; - if(src.Kind == SrcKind::Sub) { - size_t subStart = src.Off; - size_t subEnd = subStart + src.Len; - if(!scan(subStart, subEnd)) return false; - } - } break; - - case Op::Combine: { - uint16_t w = 0, h = 0, n = 0; - if(!read16(ip, w)) return false; - if(!read16(ip, h)) return false; - if(!read16(ip, n)) return false; - for(uint16_t i = 0; i < n; ++i) { - uint16_t tmp16 = 0; - if(!read16(ip, tmp16)) return false; - if(!read16(ip, tmp16)) return false; - SrcMeta src{}; - if(!readSrc(ip, src)) return false; - if(!patchTexId(src)) return false; - if(src.Kind == SrcKind::Sub) { - size_t subStart = src.Off; - size_t subEnd = subStart + src.Len; - if(!scan(subStart, subEnd)) return false; - } - } - (void)w; (void)h; - } break; - - default: - return false; - } - } - return true; - }; - - return scan(0, size); - } + static std::vector extractAnimationSpecs(const Word* code, size_t size); + static bool remapTexIds(std::vector& code, const std::vector& remap, std::string* err = nullptr); static std::vector extractAnimationSpecs(const std::vector& code) { return extractAnimationSpecs(code.data(), code.size()); } - void fromBytes(std::vector bytes) { - Code_ = std::move(bytes); - Patches_.clear(); - Source_.clear(); - PendingSub_.clear(); - } + void fromBytes(std::vector bytes); private: // ======================== @@ -731,628 +190,48 @@ private: public: using TextureProvider = TexturePipelineProgram::TextureProviderFunc; - explicit VM(TextureProvider provider) : Provider_(std::move(provider)) {} - - bool run(const std::vector& code, OwnedTexture& out, double timeSeconds, std::string* err) { - if(code.empty()) { if(err) *err="Пустой байткод"; return false; } - - Image cur; - std::unordered_map texCache; - std::unordered_map subCache; // key = (off<<24) | len - - size_t ip = 0; - - auto need = [&](size_t n)->bool{ - if(ip + n > code.size()) { if(err) *err="Байткод усечен"; return false; } - return true; - }; - - while(true) { - if(!need(1)) return false; - Op op = static_cast(code[ip++]); - if(op == Op::End) break; - - switch(op) { - case Op::Base_Tex: { - SrcRef src; - if(!_readSrc(code, ip, src, err)) return false; - if(src.Kind != SrcKind::TexId) return _bad(err, "Base_Tex must be TexId"); - cur = _loadTex(src.TexId24, texCache, err); - if(cur.W == 0) return false; - } break; - - case Op::Base_Fill: { - if(!need(2+2+4)) return false; - uint32_t w = _rd16(code, ip); - uint32_t h = _rd16(code, ip); - uint32_t color = _rd32(code, ip); - cur = _makeSolid(w, h, color); - } break; - - case Op::Base_Anim: { - SrcRef src; - if(!_readSrc(code, ip, src, err)) return false; - if(src.Kind != SrcKind::TexId) return _bad(err, "Base_Anim must be TexId"); - if(!need(2+2+2+2+1)) return false; - - uint32_t frameW = _rd16(code, ip); - uint32_t frameH = _rd16(code, ip); - uint32_t frameCount = _rd16(code, ip); - uint32_t fpsQ = _rd16(code, ip); - uint32_t flags = code[ip++]; - - Image sheet = _loadTex(src.TexId24, texCache, err); - if(sheet.W == 0) return false; - - uint32_t fw = frameW ? frameW : sheet.W; - uint32_t fh = frameH ? frameH : sheet.H; - if(fw == 0 || fh == 0) return _bad(err, "Base_Anim invalid frame size"); - - bool useGrid = (flags & AnimGrid) != 0; - bool horizontal = (flags & AnimHorizontal) != 0; - if(frameCount == 0) { - uint32_t avail = 0; - if(useGrid) { - uint32_t cols = sheet.W / fw; - uint32_t rows = sheet.H / fh; - avail = cols * rows; - } else { - avail = horizontal ? (sheet.W / fw) : (sheet.H / fh); - } - frameCount = std::max(1u, avail); - } - - uint32_t fpsQv = fpsQ ? fpsQ : DefaultAnimFpsQ; - double fps = double(fpsQv) / 256.0; - double frameTime = timeSeconds * fps; - if(frameTime < 0.0) frameTime = 0.0; - - uint32_t frameIndex = frameCount ? (uint32_t(frameTime) % frameCount) : 0u; - double frac = frameTime - std::floor(frameTime); - - cur = useGrid ? _cropFrameGrid(sheet, frameIndex, fw, fh) - : _cropFrame(sheet, frameIndex, fw, fh, horizontal); - - if(flags & AnimSmooth) { - uint32_t nextIndex = frameCount ? ((frameIndex + 1u) % frameCount) : 0u; - Image next = useGrid ? _cropFrameGrid(sheet, nextIndex, fw, fh) - : _cropFrame(sheet, nextIndex, fw, fh, horizontal); - _lerp(cur, next, frac); - } - } break; - - case Op::Anim: { - if(!cur.W || !cur.H) return _bad(err, "Anim requires base image"); - if(!need(2+2+2+2+1)) return false; - - uint32_t frameW = _rd16(code, ip); - uint32_t frameH = _rd16(code, ip); - uint32_t frameCount = _rd16(code, ip); - uint32_t fpsQ = _rd16(code, ip); - uint32_t flags = code[ip++]; - - const Image& sheet = cur; - uint32_t fw = frameW ? frameW : sheet.W; - uint32_t fh = frameH ? frameH : sheet.H; - if(fw == 0 || fh == 0) return _bad(err, "Anim invalid frame size"); - - bool useGrid = (flags & AnimGrid) != 0; - bool horizontal = (flags & AnimHorizontal) != 0; - if(frameCount == 0) { - uint32_t avail = 0; - if(useGrid) { - uint32_t cols = sheet.W / fw; - uint32_t rows = sheet.H / fh; - avail = cols * rows; - } else { - avail = horizontal ? (sheet.W / fw) : (sheet.H / fh); - } - frameCount = std::max(1u, avail); - } - - uint32_t fpsQv = fpsQ ? fpsQ : DefaultAnimFpsQ; - double fps = double(fpsQv) / 256.0; - double frameTime = timeSeconds * fps; - if(frameTime < 0.0) frameTime = 0.0; - - uint32_t frameIndex = frameCount ? (uint32_t(frameTime) % frameCount) : 0u; - double frac = frameTime - std::floor(frameTime); - - cur = useGrid ? _cropFrameGrid(sheet, frameIndex, fw, fh) - : _cropFrame(sheet, frameIndex, fw, fh, horizontal); - if(flags & AnimSmooth) { - uint32_t nextIndex = frameCount ? ((frameIndex + 1u) % frameCount) : 0u; - Image next = useGrid ? _cropFrameGrid(sheet, nextIndex, fw, fh) - : _cropFrame(sheet, nextIndex, fw, fh, horizontal); - _lerp(cur, next, frac); - } - } break; - - case Op::Overlay: { - SrcRef src; - if(!_readSrc(code, ip, src, err)) return false; - Image over = _loadSrc(code, src, texCache, subCache, timeSeconds, err); - if(over.W == 0) return false; - if(!cur.W) { cur = std::move(over); break; } - over = _resizeNN_ifNeeded(over, cur.W, cur.H); - _alphaOver(cur, over); - } break; - - case Op::Mask: { - SrcRef src; - if(!_readSrc(code, ip, src, err)) return false; - Image m = _loadSrc(code, src, texCache, subCache, timeSeconds, err); - if(m.W == 0) return false; - if(!cur.W) return _bad(err, "Mask requires base image"); - m = _resizeNN_ifNeeded(m, cur.W, cur.H); - _applyMask(cur, m); - } break; - - case Op::LowPart: { - if(!need(1)) return false; - uint32_t pct = std::min(100u, uint32_t(code[ip++])); - SrcRef src; - if(!_readSrc(code, ip, src, err)) return false; - Image over = _loadSrc(code, src, texCache, subCache, timeSeconds, err); - if(over.W == 0) return false; - if(!cur.W) return _bad(err, "LowPart requires base image"); - over = _resizeNN_ifNeeded(over, cur.W, cur.H); - _lowpart(cur, over, pct); - } break; - - case Op::Resize: { - if(!cur.W) return _bad(err, "Resize requires base image"); - if(!need(2+2)) return false; - uint32_t w = _rd16(code, ip); - uint32_t h = _rd16(code, ip); - cur = _resizeNN(cur, w, h); - } break; - - case Op::Transform: { - if(!cur.W) return _bad(err, "Transform requires base image"); - if(!need(1)) return false; - uint32_t t = code[ip++] & 7u; - cur = _transform(cur, t); - } break; - - case Op::Opacity: { - if(!cur.W) return _bad(err, "Opacity requires base image"); - if(!need(1)) return false; - uint32_t a = code[ip++] & 0xFFu; - _opacity(cur, uint8_t(a)); - } break; - - case Op::NoAlpha: { - if(!cur.W) return _bad(err, "NoAlpha requires base image"); - _noAlpha(cur); - } break; - - case Op::MakeAlpha: { - if(!cur.W) return _bad(err, "MakeAlpha requires base image"); - if(!need(3)) return false; - uint32_t rr = code[ip++], gg = code[ip++], bb = code[ip++]; - uint32_t rgb24 = (rr << 16) | (gg << 8) | bb; - _makeAlpha(cur, rgb24); - } break; - - case Op::Invert: { - if(!cur.W) return _bad(err, "Invert requires base image"); - if(!need(1)) return false; - uint32_t mask = code[ip++] & 0xFu; - _invert(cur, mask); - } break; - - case Op::Brighten: { - if(!cur.W) return _bad(err, "Brighten requires base image"); - _brighten(cur); - } break; - - case Op::Contrast: { - if(!cur.W) return _bad(err, "Contrast requires base image"); - if(!need(2)) return false; - int c = int(code[ip++]) - 127; - int b = int(code[ip++]) - 127; - _contrast(cur, c, b); - } break; - - case Op::Multiply: { - if(!cur.W) return _bad(err, "Multiply requires base image"); - if(!need(4)) return false; - uint32_t color = _rd32(code, ip); - _multiply(cur, color); - } break; - - case Op::Screen: { - if(!cur.W) return _bad(err, "Screen requires base image"); - if(!need(4)) return false; - uint32_t color = _rd32(code, ip); - _screen(cur, color); - } break; - - case Op::Colorize: { - if(!cur.W) return _bad(err, "Colorize requires base image"); - if(!need(4+1)) return false; - uint32_t color = _rd32(code, ip); - uint32_t ratio = code[ip++] & 0xFFu; - _colorize(cur, color, uint8_t(ratio)); - } break; - - default: - return _bad(err, "Unknown opcode (no skip table in this minimal VM)"); - } - } - - out.Width = cur.W; - out.Height = cur.H; - out.Pixels = std::move(cur.Px); - return true; - } + explicit VM(TextureProvider provider); + bool run(const std::vector& code, OwnedTexture& out, double timeSeconds, std::string* err); private: TextureProvider Provider_; - static bool _bad(std::string* err, const char* msg){ if(err) *err = msg; return false; } - - static bool _readSrc(const std::vector& code, size_t& ip, SrcRef& out, std::string* err) { - if(ip >= code.size()) return _bad(err, "Bytecode truncated (SrcRef.kind)"); - out.Kind = static_cast(code[ip++]); - if(out.Kind == SrcKind::TexId) { - if(ip + 3 > code.size()) return _bad(err, "Bytecode truncated (TexId24)"); - out.TexId24 = _rd24(code, ip); - out.Off24 = 0; out.Len24 = 0; - return true; - } - if(out.Kind == SrcKind::Sub) { - if(ip + 6 > code.size()) return _bad(err, "Bytecode truncated (Sub off/len)"); - out.Off24 = _rd24(code, ip); - out.Len24 = _rd24(code, ip); - out.TexId24 = 0; - return true; - } - return _bad(err, "Unknown SrcKind"); - } - - Image _loadTex(uint32_t id, std::unordered_map& cache, std::string* err) { - auto it = cache.find(id); - if(it != cache.end()) return it->second; - - auto t = Provider_(id); - if(!t || !t->Pixels || !t->Width || !t->Height) { - if(err) *err = "Идентификатор текстуры не найден: " + std::to_string(id); - return {}; - } - Image img; - img.W = t->Width; img.H = t->Height; - img.Px.assign(t->Pixels, t->Pixels + size_t(img.W)*size_t(img.H)); - cache.emplace(id, img); - return img; - } - + static bool _bad(std::string* err, const char* msg); + static bool _readSrc(const std::vector& code, size_t& ip, SrcRef& out, std::string* err); + Image _loadTex(uint32_t id, std::unordered_map& cache, std::string* err); Image _loadSub(const std::vector& code, uint32_t off, uint32_t len, - std::unordered_map& /*texCache*/, + std::unordered_map& texCache, std::unordered_map& subCache, double timeSeconds, - std::string* err) { - uint64_t key = (uint32_t(off) << 24) | uint32_t(len); - auto it = subCache.find(key); - if(it != subCache.end()) return it->second; - - size_t start = size_t(off); - size_t end = start + size_t(len); - if(end > code.size()) { if(err) *err="Подпрограмма выходит за пределы"; return {}; } - - std::vector slice(code.begin()+start, code.begin()+end); - OwnedTexture tmp; - VM nested(Provider_); - if(!nested.run(slice, tmp, timeSeconds, err)) return {}; - - Image img; - img.W = tmp.Width; img.H = tmp.Height; img.Px = std::move(tmp.Pixels); - subCache.emplace(key, img); - return img; - } - + std::string* err); Image _loadSrc(const std::vector& code, const SrcRef& src, std::unordered_map& texCache, std::unordered_map& subCache, double timeSeconds, - std::string* err) { - if(src.Kind == SrcKind::TexId) return _loadTex(src.TexId24, texCache, err); - if(src.Kind == SrcKind::Sub) return _loadSub(code, src.Off24, src.Len24, texCache, subCache, timeSeconds, err); - if(err) *err = "Неизвестный SrcKind"; - return {}; - } + std::string* err); // ---- image ops (как в исходнике) ---- - static Image _makeSolid(uint32_t w, uint32_t h, uint32_t color) { - Image img; img.W=w; img.H=h; - img.Px.assign(size_t(w)*size_t(h), color); - return img; - } - - static Image _resizeNN(const Image& src, uint32_t nw, uint32_t nh) { - Image dst; dst.W=nw; dst.H=nh; - dst.Px.resize(size_t(nw)*size_t(nh)); - for(uint32_t y=0;y= sheet.H) continue; - for(uint32_t x = 0; x < fw; ++x) { - uint32_t sx = baseX + x; - if(sx >= sheet.W) continue; - out.Px[size_t(y) * fw + x] = sheet.Px[size_t(sy) * sheet.W + sx]; - } - } - return out; - } - - static Image _cropFrameGrid(const Image& sheet, uint32_t index, uint32_t fw, uint32_t fh) { - Image out; - out.W = fw; - out.H = fh; - out.Px.assign(size_t(fw) * size_t(fh), 0u); - if(fw == 0 || fh == 0) return out; - - uint32_t cols = sheet.W / fw; - uint32_t rows = sheet.H / fh; - if(cols == 0 || rows == 0) return out; - - uint32_t col = index % cols; - uint32_t row = index / cols; - if(row >= rows) return out; - - uint32_t baseX = col * fw; - uint32_t baseY = row * fh; - - for(uint32_t y = 0; y < fh; ++y) { - uint32_t sy = baseY + y; - if(sy >= sheet.H) continue; - for(uint32_t x = 0; x < fw; ++x) { - uint32_t sx = baseX + x; - if(sx >= sheet.W) continue; - out.Px[size_t(y) * fw + x] = sheet.Px[size_t(sy) * sheet.W + sx]; - } - } - return out; - } - - static void _lerp(Image& base, const Image& over, double t) { - if(t <= 0.0) return; - if(t >= 1.0) { base = over; return; } - if(base.W != over.W || base.H != over.H) return; - - const size_t n = base.Px.size(); - for(size_t i = 0; i < n; ++i) { - uint32_t a = base.Px[i]; - uint32_t b = over.Px[i]; - int ar = _r(a), ag = _g(a), ab = _b(a), aa = _a(a); - int br = _r(b), bg = _g(b), bb = _b(b), ba = _a(b); - - uint8_t rr = _clampu8(int(ar + (br - ar) * t)); - uint8_t rg = _clampu8(int(ag + (bg - ag) * t)); - uint8_t rb = _clampu8(int(ab + (bb - ab) * t)); - uint8_t ra = _clampu8(int(aa + (ba - aa) * t)); - - base.Px[i] = _pack(ra, rr, rg, rb); - } - } - - static void _alphaOver(Image& base, const Image& over) { - const size_t n = base.Px.size(); - for(size_t i=0;i(255, (outRp * 255) / outA)); - outG = uint8_t(std::min(255, (outGp * 255) / outA)); - outB = uint8_t(std::min(255, (outBp * 255) / outA)); - } - base.Px[i] = _pack(uint8_t(outA), outR, outG, outB); - } - } - - static void _applyMask(Image& base, const Image& mask) { - const size_t n = base.Px.size(); - for(size_t i=0;i> 16) & 0xFF); - uint8_t gg = uint8_t((rgb24 >> 8) & 0xFF); - uint8_t bb = uint8_t((rgb24 >> 0) & 0xFF); - for(auto& p : img.Px) { - if(_r(p)==rr && _g(p)==gg && _b(p)==bb) p = _pack(0, _r(p), _g(p), _b(p)); - } - } - static void _invert(Image& img, uint32_t maskBits) { - for(auto& p : img.Px) { - uint8_t a=_a(p), r=_r(p), g=_g(p), b=_b(p); - if(maskBits & 1u) r = 255 - r; - if(maskBits & 2u) g = 255 - g; - if(maskBits & 4u) b = 255 - b; - if(maskBits & 8u) a = 255 - a; - p = _pack(a,r,g,b); - } - } - static void _brighten(Image& img) { - for(auto& p : img.Px) { - int r = _r(p), g = _g(p), b = _b(p); - r = r + (255 - r) / 3; - g = g + (255 - g) / 3; - b = b + (255 - b) / 3; - p = _pack(_a(p), _clampu8(r), _clampu8(g), _clampu8(b)); - } - } - static void _contrast(Image& img, int c, int br) { - double C = double(std::max(-127, std::min(127, c))); - double factor = (259.0 * (C + 255.0)) / (255.0 * (259.0 - C)); - for(auto& p : img.Px) { - int r = int(factor * (int(_r(p)) - 128) + 128) + br; - int g = int(factor * (int(_g(p)) - 128) + 128) + br; - int b = int(factor * (int(_b(p)) - 128) + 128) + br; - p = _pack(_a(p), _clampu8(r), _clampu8(g), _clampu8(b)); - } - } - static void _multiply(Image& img, uint32_t color) { - uint8_t cr=_r(color), cg=_g(color), cb=_b(color); - for(auto& p : img.Px) { - uint8_t r = uint8_t((uint32_t(_r(p)) * cr) / 255); - uint8_t g = uint8_t((uint32_t(_g(p)) * cg) / 255); - uint8_t b = uint8_t((uint32_t(_b(p)) * cb) / 255); - p = _pack(_a(p), r,g,b); - } - } - static void _screen(Image& img, uint32_t color) { - uint8_t cr=_r(color), cg=_g(color), cb=_b(color); - for(auto& p : img.Px) { - uint8_t r = uint8_t(255 - ((255 - _r(p)) * (255 - cr)) / 255); - uint8_t g = uint8_t(255 - ((255 - _g(p)) * (255 - cg)) / 255); - uint8_t b = uint8_t(255 - ((255 - _b(p)) * (255 - cb)) / 255); - p = _pack(_a(p), r,g,b); - } - } - static void _colorize(Image& img, uint32_t color, uint8_t ratio) { - uint8_t cr=_r(color), cg=_g(color), cb=_b(color); - for(auto& p : img.Px) { - int r = (int(_r(p)) * (255 - ratio) + int(cr) * ratio) / 255; - int g = (int(_g(p)) * (255 - ratio) + int(cg) * ratio) / 255; - int b = (int(_b(p)) * (255 - ratio) + int(cb) * ratio) / 255; - p = _pack(_a(p), uint8_t(r), uint8_t(g), uint8_t(b)); - } - } - static void _lowpart(Image& base, const Image& over, uint32_t percent) { - uint32_t startY = base.H - (base.H * percent) / 100; - for(uint32_t y=startY; y(255, (outRp * 255) / outA)); - outG = uint8_t(std::min(255, (outGp * 255) / outA)); - outB = uint8_t(std::min(255, (outBp * 255) / outA)); - } - base.Px[i] = _pack(uint8_t(outA), outR, outG, outB); - } - } - } - - static Image _transform(const Image& src, uint32_t t) { - Image dst; - auto at = [&](uint32_t x, uint32_t y)->uint32_t { return src.Px[size_t(y)*src.W + x]; }; - auto make = [&](uint32_t w, uint32_t h){ - Image d; d.W=w; d.H=h; d.Px.resize(size_t(w)*size_t(h)); - return d; - }; - auto set = [&](Image& im, uint32_t x, uint32_t y, uint32_t v){ - im.Px[size_t(y)*im.W + x] = v; - }; - - switch (t & 7u) { - case 0: return src; - case 1: { dst = make(src.H, src.W); - for(uint32_t y=0;y='0'&&c<='9'); } static bool isAlnum(char c){ return isAlpha(c)||isNum(c); } - void unread(const Tok& t) { - // allow only 1-level unread - HasBuf = true; - Buf = t; - } - - Tok peek() { - Tok t = next(); - unread(t); - return t; - } - - void skipWs() { - while (I < S.size()) { - char c = S[I]; - if(c==' '||c=='\t'||c=='\r'||c=='\n'){ I++; continue; } - if(c=='#'){ while (I= S.size()) return {TokKind::End, {}, 0}; - - if(S[I]=='|' && I+1') { I+=2; return {TokKind::Pipe, "|>",0}; } - char c = S[I]; - if(c==','){ I++; return {TokKind::Comma,",",0}; } - if(c=='('){ I++; return {TokKind::LParen,"(",0}; } - if(c==')'){ I++; return {TokKind::RParen,")",0}; } - if(c=='='){ I++; return {TokKind::Eq,"=",0}; } - if(c=='x' || c=='X'){ I++; return {TokKind::X,"x",0}; } - - if(c=='"') { - I++; - std::string out; - while (I < S.size()) { - char ch = S[I++]; - if(ch=='"') break; - if(ch=='\\' && I 0xAARRGGBB // ======================== - static bool _parseHexColor(std::string_view s, uint32_t& outARGB) { - if(s.size()!=7 && s.size()!=9) return false; - if(s[0] != '#') return false; - auto hex = [](char c)->int{ - if(c>='0'&&c<='9') return c-'0'; - if(c>='a'&&c<='f') return 10+(c-'a'); - if(c>='A'&&c<='F') return 10+(c-'A'); - return -1; - }; - auto byteAt = [&](size_t idx)->std::optional{ - int hi=hex(s[idx]), lo=hex(s[idx+1]); - if(hi<0||lo<0) return std::nullopt; - return uint8_t((hi<<4)|lo); - }; - auto r = byteAt(1), g = byteAt(3), b = byteAt(5); - if(!r||!g||!b) return false; - uint8_t a = 255; - if(s.size()==9) { - auto aa = byteAt(7); - if(!aa) return false; - a = *aa; - } - outARGB = (uint32_t(a)<<24) | (uint32_t(*r)<<16) | (uint32_t(*g)<<8) | (uint32_t(*b)); - return true; - } + static bool _parseHexColor(std::string_view s, uint32_t& outARGB); // ======================== // Parsing entry: full program // ======================== - bool _parseProgram(std::string* err) { - Lexer lx{Source_}; - Tok t = lx.next(); - bool hasTexPrefix = (t.Kind == TokKind::Ident && t.Text == "tex"); - - // compile base into main Code_ - if(hasTexPrefix) { - if(!_compileBaseAfterTex(lx, Code_, /*abs*/&Patches_, /*rel*/nullptr, err)) return false; - } else { - if(!_compileBaseFromToken(lx, t, Code_, /*abs*/&Patches_, /*rel*/nullptr, err)) return false; - } - - // pipeline: |> op ... - Tok nt = lx.next(); - while (nt.Kind == TokKind::Pipe) { - Tok opName = lx.next(); - if(opName.Kind != TokKind::Ident) { if(err) *err="Ожидалось имя операции после |>"; return false; } - ParsedOp op; op.Name = opName.Text; - - Tok peek = lx.next(); - if(peek.Kind == TokKind::LParen) { - if(!_parseArgListOrTextureExpr(lx, op, err)) return false; - nt = lx.next(); - } else { - nt = peek; // no-arg op - } - - if(!_compileOpInto(lx, op, Code_, /*abs*/&Patches_, /*rel*/nullptr, err)) return false; - } - - _emitOp(Code_, Op::End); - if (Code_.size() > MaxCodeBytes) { - if (err) - *err = "Байткод пайплайна слишком большой: " + std::to_string(Code_.size()) + - " > MaxCodeBytes(" + std::to_string(MaxCodeBytes) + ")"; - return false; - } - - return true; - } + bool _parseProgram(std::string* err); // ======================== // Base compilation (optionally after 'tex') @@ -1605,214 +348,25 @@ private: std::vector& out, std::vector* absPatches, std::vector* relPatches, - std::string* err) { - Tok a = lx.next(); - return _compileBaseFromToken(lx, a, out, absPatches, relPatches, err); - } + std::string* err); bool _compileBaseFromToken(Lexer& lx, const Tok& a, std::vector& out, std::vector* absPatches, std::vector* relPatches, - std::string* err) { - if(a.Kind == TokKind::End) { - if(err) *err="Ожидалось текстурное выражение"; - return false; - } - - if(a.Kind == TokKind::Ident && a.Text == "anim") { - Tok lp = lx.next(); - if(lp.Kind != TokKind::LParen) { if(err) *err="Ожидалась '(' после anim"; return false; } - - ParsedOp op; op.Name="anim"; - if(!_parseArgList(lx, op, err)) return false; - - auto posU = [&](size_t i)->std::optional{ - if(i >= op.Pos.size()) return std::nullopt; - if(op.Pos[i].Kind != ArgVal::ValueKind::U32) return std::nullopt; - return op.Pos[i].U32; - }; - auto posS = [&](size_t i)->std::optional{ - if(i >= op.Pos.size()) return std::nullopt; - return op.Pos[i].S; - }; - auto namedU = [&](std::string_view k)->std::optional{ - auto it = op.Named.find(std::string(k)); - if(it==op.Named.end() || it->second.Kind!=ArgVal::ValueKind::U32) return std::nullopt; - return it->second.U32; - }; - auto namedS = [&](std::string_view k)->std::optional{ - auto it = op.Named.find(std::string(k)); - if(it==op.Named.end()) return std::nullopt; - return it->second.S; - }; - - std::string tex = namedS("tex").value_or(posS(0).value_or("")); - if(tex.empty()) { if(err) *err="anim требует имя текстуры"; return false; } - - uint32_t frameW = namedU("frame_w").value_or(namedU("w").value_or(posU(1).value_or(0))); - uint32_t frameH = namedU("frame_h").value_or(namedU("h").value_or(posU(2).value_or(0))); - uint32_t frames = namedU("frames").value_or(namedU("count").value_or(posU(3).value_or(0))); - uint32_t fps = namedU("fps").value_or(posU(4).value_or(0)); - uint32_t smooth = namedU("smooth").value_or(posU(5).value_or(0)); - - std::string axis = namedS("axis").value_or(""); - bool axisHorizontal = (!axis.empty() && (axis[0] == 'x' || axis[0] == 'h')); - bool axisVertical = (!axis.empty() && (axis[0] == 'y' || axis[0] == 'v')); - bool useGrid = axis.empty() || axis[0] == 'g'; - - if(frameW > 65535u || frameH > 65535u || frames > 65535u) { - if(err) *err="параметры anim должны помещаться в uint16"; - return false; - } - - uint32_t fpsQ = fps ? std::min(0xFFFFu, fps * 256u) : DefaultAnimFpsQ; - uint32_t flags = (smooth ? AnimSmooth : 0); - if(useGrid) flags |= AnimGrid; - else if(axisHorizontal) flags |= AnimHorizontal; - else if(!axisVertical) flags |= AnimGrid; - - _emitOp(out, Op::Base_Anim); - _emitSrcTexName(out, absPatches, relPatches, tex); - _emitU16(out, frameW); - _emitU16(out, frameH); - _emitU16(out, frames); - _emitU16(out, fpsQ); - _emitU8(out, flags); - return true; - } - - if(a.Kind == TokKind::Ident || a.Kind == TokKind::String) { - // name (or "name.png" => normalized) - _emitOp(out, Op::Base_Tex); - _emitSrcTexName(out, absPatches, relPatches, a.Text); - return true; - } - - if(a.Kind == TokKind::Number) { - // 32x32 "#RRGGBBAA" - Tok xTok = lx.next(); - Tok b = lx.next(); - Tok colTok = lx.next(); - if(xTok.Kind != TokKind::X || b.Kind != TokKind::Number || (colTok.Kind!=TokKind::Ident && colTok.Kind!=TokKind::String)) { - if(err) *err="Ожидалось: x <#color>"; - return false; - } - uint32_t w = a.U32, h = b.U32; - uint32_t color = 0; - if(!_parseHexColor(colTok.Text, color)) { - if(err) *err="Неверный литерал цвета (используйте #RRGGBB или #RRGGBBAA)"; - return false; - } - if(w>65535u || h>65535u) { if(err) *err="w/h должны помещаться в uint16"; return false; } - _emitOp(out, Op::Base_Fill); - _emitU16(out, w); - _emitU16(out, h); - _emitU32(out, color); - return true; - } - - if(err) *err="Некорректное базовое текстурное выражение"; - return false; - } + 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) { - Tok first = lx.next(); + bool _parseArgListOrTextureExpr(Lexer& lx, ParsedOp& op, std::string* err); - if(first.Kind==TokKind::Ident && first.Text=="tex") { - // marker - ArgVal av; av.Kind = ArgVal::ValueKind::Ident; av.S = "__SUBTEX__"; - op.Pos.push_back(av); + bool _parseArgList(Lexer& lx, ParsedOp& op, std::string* err); - PendingSubData sub; - if(!_compileSubProgramFromAlreadySawTex(lx, sub, err)) return false; - - Tok end = lx.next(); - if(end.Kind != TokKind::RParen) { if(err) *err="Ожидалась ')' после подвыражения текстуры"; return false; } - - PendingSub_[&op] = std::move(sub); - return true; - } - - // otherwise parse as normal arg list, where `first` is first token inside '(' - Tok t = first; - if(t.Kind == TokKind::RParen) return true; - - while (true) { - if(t.Kind == TokKind::Ident) { - Tok maybeEq = lx.next(); - if(maybeEq.Kind == TokKind::Eq) { - Tok v = lx.next(); - ArgVal av; - if(!_tokToVal(v, av, err)) return false; - op.Named[t.Text] = std::move(av); - t = lx.next(); - } else { - ArgVal av; av.Kind = ArgVal::ValueKind::Ident; av.S = t.Text; - op.Pos.push_back(std::move(av)); - t = maybeEq; - } - } else { - ArgVal av; - if(!_tokToVal(t, av, err)) return false; - op.Pos.push_back(std::move(av)); - t = lx.next(); - } - - if(t.Kind == TokKind::Comma) { t = lx.next(); continue; } - if(t.Kind == TokKind::RParen) return true; - - if(err) *err = "Ожидалась ',' или ')' в списке аргументов"; - return false; - } - } - - bool _parseArgList(Lexer& lx, ParsedOp& op, std::string* err) { - Tok t = lx.next(); - if(t.Kind == TokKind::RParen) return true; - - while (true) { - if(t.Kind == TokKind::Ident) { - Tok maybeEq = lx.next(); - if(maybeEq.Kind == TokKind::Eq) { - Tok v = lx.next(); - ArgVal av; - if(!_tokToVal(v, av, err)) return false; - op.Named[t.Text] = std::move(av); - t = lx.next(); - } else { - ArgVal av; av.Kind = ArgVal::ValueKind::Ident; av.S = t.Text; - op.Pos.push_back(std::move(av)); - t = maybeEq; - } - } else { - ArgVal av; - if(!_tokToVal(t, av, err)) return false; - op.Pos.push_back(std::move(av)); - t = lx.next(); - } - - if(t.Kind == TokKind::Comma) { t = lx.next(); continue; } - if(t.Kind == TokKind::RParen) return true; - - if(err) *err = "Ожидалась ',' или ')' в списке аргументов"; - return false; - } - } - - bool _tokToVal(const Tok& t, ArgVal& out, std::string* err) { - if(t.Kind == TokKind::Number) { out.Kind=ArgVal::ValueKind::U32; out.U32=t.U32; return true; } - if(t.Kind == TokKind::String) { out.Kind=ArgVal::ValueKind::Str; out.S=t.Text; return true; } - if(t.Kind == TokKind::Ident) { out.Kind=ArgVal::ValueKind::Ident; out.S=t.Text; return true; } - if(err) *err = "Ожидалось значение"; - return false; - } + bool _tokToVal(const Tok& t, ArgVal& out, std::string* err); // ======================== // Subprogram compilation: @@ -1824,77 +378,7 @@ private: std::vector RelPatches; }; - bool _compileSubProgramFromAlreadySawTex(Lexer& lx, PendingSubData& outSub, std::string* err) { - outSub.Bytes.clear(); - outSub.RelPatches.clear(); - - // base - if(!_compileBaseAfterTex(lx, outSub.Bytes, /*abs*/nullptr, /*rel*/&outSub.RelPatches, err)) - return false; - - // pipeline until ')' - while(true) { - // peek - Tok nt = lx.peek(); - if(nt.Kind == TokKind::RParen) break; - if(nt.Kind != TokKind::Pipe) { if(err) *err="Подтекстура: ожидалось '|>' или ')'"; return false; } - - // consume pipe - lx.next(); - Tok opName = lx.next(); - if(opName.Kind != TokKind::Ident) { if(err) *err="Подтекстура: ожидалось имя операции"; return false; } - - ParsedOp op; op.Name = opName.Text; - - Tok lp = lx.next(); - if(lp.Kind == TokKind::LParen) { - if(!_parseArgListOrTextureExpr(lx, op, err)) return false; - } else { - // no-arg op, lp already is next token (pipe or ')'), so we need to "unread" — can't. - // simplest: treat it as next token for outer loop by rewinding lexer state. - // We'll do it by storing the token back via a small hack: rebuild peek? Too heavy. - // Instead: enforce parentheses for ops in subprogram except no-arg ops (brighten/noalpha) which can be without. - // To keep behavior identical to main, we handle no-arg by rewinding I one token is not possible, - // so we accept that in subprogram, no-arg ops must be written as brighten() etc. - if(err) *err="Подтекстура: операции без аргументов должны использовать скобки, напр. brighten()"; - return false; - } - - if(!_compileOpInto(lx, op, outSub.Bytes, /*abs*/nullptr, /*rel*/&outSub.RelPatches, err)) - return false; - } - - // Pipeline until we see ')' - while (true) { - Tok nt = lx.peek(); - if(nt.Kind == TokKind::RParen) break; - if(nt.Kind != TokKind::Pipe) { if(err) *err="Подтекстура: ожидалось '|>' или ')'"; return false; } - - // consume pipe - lx.next(); - - Tok opName = lx.next(); - if(opName.Kind != TokKind::Ident) { if(err) *err="Подтекстура: ожидалось имя операции"; return false; } - - ParsedOp op; op.Name = opName.Text; - - // allow both op and op(...) - Tok maybe = lx.peek(); - if(maybe.Kind == TokKind::LParen) { - lx.next(); // consume '(' - if(!_parseArgListOrTextureExpr(lx, op, err)) return false; - } else { - // no-arg op; nothing to parse - } - - if(!_compileOpInto(lx, op, outSub.Bytes, /*abs*/nullptr, /*rel*/&outSub.RelPatches, err)) - return false; - } - - - _emitOp(outSub.Bytes, Op::End); - return true; - } + bool _compileSubProgramFromAlreadySawTex(Lexer& lx, PendingSubData& outSub, std::string* err); // pending subprogram associated with ParsedOp pointer (created during parsing) mutable std::unordered_map PendingSub_; @@ -1906,240 +390,17 @@ private: std::vector* relPatches, uint32_t& outOff, uint32_t& outLen, - std::string* err) { - const size_t offset = out.size(); - const size_t len = sub.Bytes.size(); - - if(offset > 0xFFFFFFu || len > 0xFFFFFFu || (offset + len) > 0xFFFFFFu) { - if(err) *err = "Подпрограмма слишком большая (off/len должны влезать в u24 байт)"; - return false; - } - - if(offset + len > MaxCodeBytes) { - if(err) *err = "Байткод пайплайна слишком большой после вставки подпрограммы: " + - std::to_string(offset + len) + " > MaxCodeBytes(" + std::to_string(MaxCodeBytes) + ")"; - return false; - } - - // migrate patches - if(absPatches) { - for(const auto& rp : sub.RelPatches) { - absPatches->push_back(Patch{offset + rp.Rel0, rp.Name}); - } - } - if(relPatches) { - for(const auto& rp : sub.RelPatches) { - relPatches->push_back(RelPatch{offset + rp.Rel0, rp.Name}); - } - } - - out.insert(out.end(), sub.Bytes.begin(), sub.Bytes.end()); - - outOff = uint32_t(offset); - outLen = uint32_t(len); - return true; - } + 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*/, + bool _compileOpInto(Lexer& lx, const ParsedOp& op, std::vector& out, std::vector* absPatches, std::vector* relPatches, - std::string* err) { - auto posU = [&](size_t i)->std::optional{ - if(i >= op.Pos.size()) return std::nullopt; - if(op.Pos[i].Kind != ArgVal::ValueKind::U32) return std::nullopt; - return op.Pos[i].U32; - }; - auto posS = [&](size_t i)->std::optional{ - if(i >= op.Pos.size()) return std::nullopt; - return op.Pos[i].S; - }; - auto namedU = [&](std::string_view k)->std::optional{ - auto it = op.Named.find(std::string(k)); - if(it==op.Named.end() || it->second.Kind!=ArgVal::ValueKind::U32) return std::nullopt; - return it->second.U32; - }; - auto namedS = [&](std::string_view k)->std::optional{ - auto it = op.Named.find(std::string(k)); - if(it==op.Named.end()) return std::nullopt; - return it->second.S; - }; - - auto emitSrcFromName = [&](const std::string& n){ - _emitSrcTexName(out, absPatches, relPatches, n); - }; - - auto emitSrcFromPendingSub = [&]()->bool{ - auto it = PendingSub_.find(&op); - if(it == PendingSub_.end()) { if(err) *err="Внутренняя ошибка: отсутствует подпрограмма"; return false; } - uint32_t off=0, len=0; - if(!_appendSubprogram(out, std::move(it->second), absPatches, relPatches, off, len, err)) return false; - PendingSub_.erase(it); - _emitSrcSub(out, off, len); - return true; - }; - - // --- Ops that accept a "texture" argument: overlay/mask/lowpart --- - if(op.Name == "overlay") { - _emitOp(out, Op::Overlay); - if(!op.Pos.empty() && op.Pos[0].S == "__SUBTEX__") return emitSrcFromPendingSub(); - - // allow overlay(name) or overlay(tex=name) - std::string tex = namedS("tex").value_or(posS(0).value_or("")); - if(tex.empty()) { if(err) *err="overlay требует аргумент-текстуру"; return false; } - emitSrcFromName(tex); - return true; - } - - if(op.Name == "mask") { - _emitOp(out, Op::Mask); - if(!op.Pos.empty() && op.Pos[0].S == "__SUBTEX__") return emitSrcFromPendingSub(); - - std::string tex = namedS("tex").value_or(posS(0).value_or("")); - if(tex.empty()) { if(err) *err="mask требует аргумент-текстуру"; return false; } - emitSrcFromName(tex); - return true; - } - - if(op.Name == "lowpart") { - uint32_t pct = namedU("percent").value_or(posU(0).value_or(0)); - if(!pct) { if(err) *err="lowpart требует процент"; return false; } - - _emitOp(out, Op::LowPart); - _emitU8(out, std::min(100u, pct)); - - // 2nd arg can be nested subtex or name - if(op.Pos.size() >= 2 && op.Pos[1].S == "__SUBTEX__") return emitSrcFromPendingSub(); - - std::string tex = namedS("tex").value_or(posS(1).value_or("")); - if(tex.empty()) { if(err) *err="lowpart требует аргумент-текстуру"; return false; } - emitSrcFromName(tex); - return true; - } - - // --- Unary ops --- - if(op.Name == "resize") { - uint32_t w = namedU("w").value_or(posU(0).value_or(0)); - uint32_t h = namedU("h").value_or(posU(1).value_or(0)); - if(!w || !h || w>65535u || h>65535u) { if(err) *err="resize(w,h) должны помещаться в uint16"; return false; } - _emitOp(out, Op::Resize); _emitU16(out, w); _emitU16(out, h); - return true; - } - - if(op.Name == "transform") { - uint32_t t = namedU("t").value_or(posU(0).value_or(0)); - _emitOp(out, Op::Transform); _emitU8(out, t & 7u); - return true; - } - - if(op.Name == "opacity") { - uint32_t a = namedU("a").value_or(posU(0).value_or(255)); - _emitOp(out, Op::Opacity); _emitU8(out, a & 0xFFu); - return true; - } - - if(op.Name == "remove_alpha" || op.Name == "noalpha") { - _emitOp(out, Op::NoAlpha); - return true; - } - - if(op.Name == "make_alpha") { - std::string col = namedS("color").value_or(posS(0).value_or("")); - uint32_t argb=0; - if(!_parseHexColor(col, argb)) { if(err) *err="make_alpha требует цвет #RRGGBB"; return false; } - uint32_t rgb24 = argb & 0x00FFFFFFu; - _emitOp(out, Op::MakeAlpha); - _emitU8(out, (rgb24 >> 16) & 0xFFu); - _emitU8(out, (rgb24 >> 8) & 0xFFu); - _emitU8(out, (rgb24 >> 0) & 0xFFu); - return true; - } - - if(op.Name == "invert") { - std::string ch = namedS("channels").value_or(posS(0).value_or("rgb")); - uint32_t mask=0; - for(char c : ch) { - if(c=='r') mask |= 1; - if(c=='g') mask |= 2; - if(c=='b') mask |= 4; - if(c=='a') mask |= 8; - } - _emitOp(out, Op::Invert); _emitU8(out, mask & 0xFu); - return true; - } - - if(op.Name == "brighten") { - _emitOp(out, Op::Brighten); - return true; - } - - if(op.Name == "contrast") { - int c = int(namedU("value").value_or(posU(0).value_or(0))); - int b = int(namedU("brightness").value_or(posU(1).value_or(0))); - c = std::max(-127, std::min(127, c)); - b = std::max(-127, std::min(127, b)); - _emitOp(out, Op::Contrast); - _emitU8(out, uint32_t(c + 127)); - _emitU8(out, uint32_t(b + 127)); - return true; - } - - auto compileColorOp = [&](Op opcode, bool needsRatio)->bool{ - std::string col = namedS("color").value_or(posS(0).value_or("")); - uint32_t argb=0; - if(!_parseHexColor(col, argb)) { if(err) *err="Неверный литерал цвета"; return false; } - _emitOp(out, opcode); - _emitU32(out, argb); - if(needsRatio) { - uint32_t ratio = namedU("ratio").value_or(posU(1).value_or(255)); - _emitU8(out, ratio & 0xFFu); - } - return true; - }; - - if(op.Name == "multiply") return compileColorOp(Op::Multiply, false); - if(op.Name == "screen") return compileColorOp(Op::Screen, false); - if(op.Name == "colorize") return compileColorOp(Op::Colorize, true); - - if(op.Name == "anim") { - uint32_t frameW = namedU("frame_w").value_or(namedU("w").value_or(posU(0).value_or(0))); - uint32_t frameH = namedU("frame_h").value_or(namedU("h").value_or(posU(1).value_or(0))); - uint32_t frames = namedU("frames").value_or(namedU("count").value_or(posU(2).value_or(0))); - uint32_t fps = namedU("fps").value_or(posU(3).value_or(0)); - uint32_t smooth = namedU("smooth").value_or(posU(4).value_or(0)); - - std::string axis = namedS("axis").value_or(""); - bool axisHorizontal = (!axis.empty() && (axis[0] == 'x' || axis[0] == 'h')); - bool axisVertical = (!axis.empty() && (axis[0] == 'y' || axis[0] == 'v')); - bool useGrid = axis.empty() || axis[0] == 'g'; - - if(frameW > 65535u || frameH > 65535u || frames > 65535u) { - if(err) *err="параметры anim должны помещаться в uint16"; - return false; - } - - uint32_t fpsQ = fps ? std::min(0xFFFFu, fps * 256u) : DefaultAnimFpsQ; - uint32_t flags = (smooth ? AnimSmooth : 0); - if(useGrid) flags |= AnimGrid; - else if(axisHorizontal) flags |= AnimHorizontal; - else if(!axisVertical) flags |= AnimGrid; - - _emitOp(out, Op::Anim); - _emitU16(out, frameW); - _emitU16(out, frameH); - _emitU16(out, frames); - _emitU16(out, fpsQ); - _emitU8(out, flags); - return true; - } - - if(err) *err = "Неизвестная операция: " + op.Name; - return false; - } + std::string* err); };