From b780ff7f458d7a2dfe530877ee156602154e8c47 Mon Sep 17 00:00:00 2001 From: Matthias Blankertz Date: Sun, 3 May 2015 01:36:35 +0200 Subject: [PATCH] Mostly working REAL 3D parser and renderer --- Makefile | 2 +- MveDecoder.cc | 8 +- ObjDecoder.cc | 82 +++++++-- ObjDecoder.hh | 24 +++ PaletteDecoder.cc | 25 +++ PaletteDecoder.hh | 24 +++ game/GSMvePlay.cc | 7 +- game/GSShowObject.cc | 8 +- game/GSShowObject.hh | 4 +- objdecode.cc | 6 +- render/GlResource.hh | 2 + render/Object.cc | 420 ++++++++++++++++++++++++++++++++++++------- render/Object.hh | 17 +- render/Overlay.cc | 2 +- render/renderutil.cc | 67 ++++++- render/renderutil.hh | 10 ++ shaders/object.fs | 17 +- shaders/object.vs | 3 + 18 files changed, 630 insertions(+), 98 deletions(-) create mode 100644 PaletteDecoder.cc create mode 100644 PaletteDecoder.hh diff --git a/Makefile b/Makefile index b35560b..300be66 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ font2png_LIBS ::= -lpng mvedecode_CXXSRCS ::= mvedecode.cc TreFile.cc IffFile.cc util.cc MveDecoder.cc exceptions.cc decompress.cc $(render_CXXSRCS) $(game_CXXSRCS) mvedecode_LIBS ::= -lSDL2 -lglbinding -objdecode_CXXSRCS ::= objdecode.cc TreFile.cc IffFile.cc util.cc ObjDecoder.cc exceptions.cc decompress.cc $(render_CXXSRCS) $(game_CXXSRCS) +objdecode_CXXSRCS ::= objdecode.cc TreFile.cc IffFile.cc util.cc ObjDecoder.cc exceptions.cc decompress.cc PaletteDecoder.cc $(render_CXXSRCS) $(game_CXXSRCS) objdecode_LIBS ::= -lSDL2 -lglbinding progs ::= iffexplore treexplore mvedecode objdecode diff --git a/MveDecoder.cc b/MveDecoder.cc index b6785e7..db0be9f 100644 --- a/MveDecoder.cc +++ b/MveDecoder.cc @@ -245,7 +245,7 @@ std::tuple MveDecoder::Mov } } - return std::make_tuple(frame_, palette); + return std::make_tuple(std::ref(frame_), std::ref(palette)); } bool MveDecoder::Movie::hasAudio() const @@ -392,7 +392,7 @@ MveDecoder::Frame MveDecoder::parseVGA(IffFile::Object const& vga, Frame const* throw FormatException{"Reference to non-existing prev. frame"}; if (ret.size()+size > prev->size()) throw FormatException{"Copy from prev exceeds frame"}; - + std::copy(prev->begin()+ret.size(), prev->begin()+ret.size()+size, std::back_inserter(ret)); @@ -434,8 +434,8 @@ MveDecoder::Frame MveDecoder::parseVGA(IffFile::Object const& vga, Frame const* throw FormatException{"Copy from prev exceeds frame"}; std::copy(prev->begin()+ref, - prev->begin()+ref+size, - std::back_inserter(ret)); + prev->begin()+ref+size, + std::back_inserter(ret)); flag = false; } } diff --git a/ObjDecoder.cc b/ObjDecoder.cc index 6d008c7..a443f86 100644 --- a/ObjDecoder.cc +++ b/ObjDecoder.cc @@ -18,7 +18,7 @@ ObjDecoder::ObjDecoder(uint8_t const* base, size_t length) for (auto realIt = rootForm.childrenBegin();realIt != rootForm.childrenEnd();++realIt) { if (realIt->getType() == "INFO") { name_ = *realIt; - printf("Name: %s\n", name_.c_str()); + printf("Name: \"%s\"\n", name_.c_str()); } else if (realIt->isForm()) { auto& itForm = dynamic_cast(*realIt); if (itForm.getSubtype() == "OBJT") @@ -63,7 +63,7 @@ void ObjDecoder::parsePOLY_(IffFile::Form const& form) std::array vertex; for (size_t j = 0;j < 3;++j) vertex[j] = readU32LE(it->begin()+i+j*4); - printf("V: %d %d %d\n", vertex[0], vertex[1], vertex[2]); + // printf("V: %d %d %d\n", vertex[0], vertex[1], vertex[2]); vertices_.push_back(vertex); } } else if (it->isForm()) { @@ -129,10 +129,10 @@ void ObjDecoder::parseTRIS_(IffFile::Form const& form) tri.texCoords[j][0] = readU16LE(maps->begin()+i*16+4+j*4); tri.texCoords[j][1] = readU16LE(maps->begin()+i*16+4+j*4+2); } - printf("T%u: (%hu (%hu %hu)), (%hu (%hu %hu)), (%hu (%hu %hu))\n", tri.id, - tri.vertIdx[0], tri.texCoords[0][0], tri.texCoords[0][1], - tri.vertIdx[1], tri.texCoords[1][0], tri.texCoords[1][1], - tri.vertIdx[2], tri.texCoords[2][0], tri.texCoords[2][1]); + // printf("T%u: (%hu (%hu %hu)), (%hu (%hu %hu)), (%hu (%hu %hu))\n", tri.id, + // tri.vertIdx[0], tri.texCoords[0][0], tri.texCoords[0][1], + // tri.vertIdx[1], tri.texCoords[1][0], tri.texCoords[1][1], + // tri.vertIdx[2], tri.texCoords[2][0], tri.texCoords[2][1]); triangles_.push_back(tri); } } @@ -174,22 +174,25 @@ void ObjDecoder::parseQUAD_(IffFile::Form const& form) quad.texCoords[j][0] = readU16LE(maps->begin()+i*20+4+j*4); quad.texCoords[j][1] = readU16LE(maps->begin()+i*20+4+j*4+2); } - printf("Q%u: (%hu (%hu %hu)), (%hu (%hu %hu)), (%hu (%hu %hu)), (%hu (%hu %hu))\n", quad.id, - quad.vertIdx[0], quad.texCoords[0][0], quad.texCoords[0][1], - quad.vertIdx[1], quad.texCoords[1][0], quad.texCoords[1][1], - quad.vertIdx[2], quad.texCoords[2][0], quad.texCoords[2][1], - quad.vertIdx[3], quad.texCoords[3][0], quad.texCoords[3][1]); + // printf("Q%u: (%hu (%hu %hu)), (%hu (%hu %hu)), (%hu (%hu %hu)), (%hu (%hu %hu))\n", quad.id, + // quad.vertIdx[0], quad.texCoords[0][0], quad.texCoords[0][1], + // quad.vertIdx[1], quad.texCoords[1][0], quad.texCoords[1][1], + // quad.vertIdx[2], quad.texCoords[2][0], quad.texCoords[2][1], + // quad.vertIdx[3], quad.texCoords[3][0], quad.texCoords[3][1]); quads_.push_back(quad); } } void ObjDecoder::parseTXMS_(IffFile::Form const& form) { + if ((textures_.size() != 0) || texAnims_.size() != 0) + throw FormatException{"Multiple TXMS"}; + if (form.getChildCount() < 1) throw FormatException{"Unexpected child count in TXMS"}; auto it = form.childrenBegin(); - auto& info = *it; + auto& info = *it++; if (info.getType() != "INFO") throw FormatException{"Missing INFO in TXMS"}; if (info.getSize() != 4) @@ -198,6 +201,59 @@ void ObjDecoder::parseTXMS_(IffFile::Form const& form) uint16_t numTXMP, numTXMA; numTXMP = readU16LE(info.begin()); numTXMA = readU16LE(info.begin()+2); - if (form.getChildCount() != 1+numTXMP+numTXMA) + if (form.getChildCount() != 1u+numTXMP+numTXMA) throw FormatException{"Unexpected child count in TXMS"}; + + textures_.reserve(numTXMP); + texAnims_.reserve(numTXMA); + for (;it != form.childrenEnd();++it) { + if (it->getType() == "TXMP") { + if (it->getSize() < 12u) + throw FormatException{"TXMP smaller than header"}; + + Texture tex; + if (it->begin()[7] == '\0') + tex.name = std::string(reinterpret_cast(it->begin())); + else + tex.name = std::string(reinterpret_cast(it->begin()), 8); + printf("TXMP name \"%s\"\n", tex.name.c_str()); + + tex.width = readU16LE(it->begin()+8); + tex.height = readU16LE(it->begin()+10); + printf("TXMP size %hux%hu\n", tex.width, tex.height); + + if (it->getSize() < 12u+tex.width*tex.height) + throw FormatException{"TXMP too small for pixel data"}; + tex.pixels = std::vector(it->begin()+12, it->begin()+12+tex.width*tex.height); + textures_.emplace_back(std::move(tex)); + } else if (it->getType() == "TXMA") { + if (it->getSize() < 14u) + throw FormatException{"TXMA smaller than header"}; + + TextureAnimation tex; + if (it->begin()[7] == '\0') + tex.name = std::string(reinterpret_cast(it->begin())); + else + tex.name = std::string(reinterpret_cast(it->begin()), 8); + printf("TXMA name \"%s\"\n", tex.name.c_str()); + + tex.width = readU16LE(it->begin()+8); + tex.height = readU16LE(it->begin()+10); + tex.frames = readU16LE(it->begin()+12); + printf("TXMA size %hux%hu, %hu frames\n", tex.width, tex.height, tex.frames); + + if (it->getSize() < 14u+tex.width*tex.height*tex.frames) + throw FormatException{"TXMA too small for pixel data"}; + + tex.pixels.reserve(tex.frames); + uint8_t const* pos = it->begin()+14; + for (unsigned i = 0;i < tex.frames;++i) { + tex.pixels.emplace_back(pos, pos+tex.width*tex.height); + pos += tex.width*tex.height; + } + + texAnims_.emplace_back(std::move(tex)); + } else + throw FormatException{"Unkown TXMS child " + it->getType()}; + } } diff --git a/ObjDecoder.hh b/ObjDecoder.hh index 3be57a8..f2149f3 100644 --- a/ObjDecoder.hh +++ b/ObjDecoder.hh @@ -32,6 +32,20 @@ public: }; using Quads = std::vector; + struct Texture { + std::string name; + uint16_t width, height; + std::vector pixels; + }; + using Textures = std::vector; + + struct TextureAnimation { + std::string name; + uint16_t width, height, frames; + std::vector > pixels; + }; + using TextureAnimations = std::vector; + Vertices const& getVertices() const { return vertices_; } @@ -43,6 +57,14 @@ public: Quads const& getQuads() const { return quads_; } + + Textures const& getTextures() const { + return textures_; + } + + TextureAnimations const& getTextureAnimations() const { + return texAnims_; + } private: IffFile iff_; std::string name_; @@ -57,6 +79,8 @@ private: Vertices vertices_; Triangles triangles_; Quads quads_; + Textures textures_; + TextureAnimations texAnims_; }; #endif diff --git a/PaletteDecoder.cc b/PaletteDecoder.cc new file mode 100644 index 0000000..2d91a57 --- /dev/null +++ b/PaletteDecoder.cc @@ -0,0 +1,25 @@ +#include + +#include "PaletteDecoder.hh" +#include "exceptions.hh" + +PaletteDecoder::PaletteDecoder(uint8_t const* base, size_t length) + : iff_(base, length) +{ + auto& root = iff_.getRoot(); + if (!root.isForm()) + throw FormatException{"Root node not FORM"}; + + auto& rootForm = dynamic_cast(root); + if (rootForm.getSubtype() != "PALT") + throw FormatException{"Root form not PALT"}; + + for (auto paltIt = rootForm.childrenBegin();paltIt != rootForm.childrenEnd();++paltIt) { + if (paltIt->getType() == "INFO") { + } else if (paltIt->getType() == "PALT") { + std::transform(paltIt->begin()+4, paltIt->begin()+772, + palette_.begin(), + [](const char& in) -> uint8_t {return (in << 2) | ((in >> 6)&0x3);}); + } + } +} diff --git a/PaletteDecoder.hh b/PaletteDecoder.hh new file mode 100644 index 0000000..88a0b71 --- /dev/null +++ b/PaletteDecoder.hh @@ -0,0 +1,24 @@ +#ifndef WC3RE_PALETTEDECODER_HH__ +#define WC3RE_PALETTEDECODER_HH__ + +#include +#include + +#include "IffFile.hh" + +class PaletteDecoder { +public: + PaletteDecoder(uint8_t const* base, size_t length); + + using Palette = std::array; + + Palette const& getPalette() const { + return palette_; + } + +private: + IffFile iff_; + Palette palette_; +}; + +#endif diff --git a/game/GSMvePlay.cc b/game/GSMvePlay.cc index beddac9..f6015dd 100644 --- a/game/GSMvePlay.cc +++ b/game/GSMvePlay.cc @@ -51,9 +51,10 @@ namespace game { auto& palette = std::get<1>(dec); size_t i = 0; - for (unsigned y = 0;y < movie_.getHeight();++y) { - for (unsigned x = 0;x < movie_.getWidth();++x) { - auto idx = frame[y*movie_.getWidth()+x]; + auto height = movie_.getHeight(), width = movie_.getWidth(); + for (unsigned y = 0;y < height;++y) { + for (unsigned x = 0;x < width;++x) { + auto idx = frame[y*width+x]; std::copy(&palette[idx*3], &palette[idx*3+3], &frameRGB_[i]); i += 3; diff --git a/game/GSShowObject.cc b/game/GSShowObject.cc index a5066de..0b7fd8c 100644 --- a/game/GSShowObject.cc +++ b/game/GSShowObject.cc @@ -4,11 +4,12 @@ #include "render/Object.hh" namespace game { - GSShowObject::GSShowObject(render::Renderer& renderer, ObjDecoder& obj) + GSShowObject::GSShowObject(render::Renderer& renderer, ObjDecoder& obj, + PaletteDecoder& palt) : GameState(renderer), obj_(obj), object_(nullptr), delta_(0) { - object_ = std::make_unique(renderer, obj); + object_ = std::make_unique(renderer, obj, palt); } GSShowObject::~GSShowObject() @@ -17,6 +18,9 @@ namespace game { void GSShowObject::draw(unsigned delta_ms) { + delta_ += delta_ms; + object_->setRot(delta_*0.0005f); + object_->setAnimFrame((delta_/125)%8); object_->draw(); } } diff --git a/game/GSShowObject.hh b/game/GSShowObject.hh index 194444f..e0a2443 100644 --- a/game/GSShowObject.hh +++ b/game/GSShowObject.hh @@ -4,6 +4,7 @@ #include "GameState.hh" class ObjDecoder; +class PaletteDecoder; namespace render { class Object; } @@ -11,7 +12,8 @@ namespace render { namespace game { class GSShowObject : public GameState { public: - GSShowObject(render::Renderer& renderer, ObjDecoder& obj); + GSShowObject(render::Renderer& renderer, ObjDecoder& obj, + PaletteDecoder& palt); ~GSShowObject() override; diff --git a/objdecode.cc b/objdecode.cc index ed5f0f4..df8946d 100644 --- a/objdecode.cc +++ b/objdecode.cc @@ -12,6 +12,7 @@ #include "TreFile.hh" #include "IffFile.hh" #include "ObjDecoder.hh" +#include "PaletteDecoder.hh" #include "render/Renderer.hh" #include "game/GSShowObject.hh" @@ -51,6 +52,9 @@ int main(int argc, char *argv[]) { } try { + MmapFile paltMmap{"tmp/1DC61F50"}; + PaletteDecoder palt{paltMmap.data(), paltMmap.size()}; + MmapFile mmap{inFile}; std::unique_ptr tre; @@ -78,7 +82,7 @@ int main(int argc, char *argv[]) { obj = std::make_unique(mmap.data(), mmap.size()); render::Renderer renderer; - renderer.pushGS(std::make_unique(renderer, *obj)); + renderer.pushGS(std::make_unique(renderer, *obj, palt)); renderer.run(); } catch (POSIXException &ex) { diff --git a/render/GlResource.hh b/render/GlResource.hh index 9c7aa54..330749a 100644 --- a/render/GlResource.hh +++ b/render/GlResource.hh @@ -53,6 +53,8 @@ namespace render { Deleter()(handle_); handle_ = move.handle_; move.handle_ = Handle(); + + return *this; } ~GlResource() { diff --git a/render/Object.cc b/render/Object.cc index dbd5fd8..0050c67 100644 --- a/render/Object.cc +++ b/render/Object.cc @@ -2,11 +2,14 @@ #include #include +#include #include "Object.hh" #include "ProgramProvider.hh" #include "Renderer.hh" #include "ObjDecoder.hh" +#include "PaletteDecoder.hh" +#include "renderutil.hh" using namespace gl; @@ -15,93 +18,390 @@ namespace render { struct VertexAttribs { int32_t vertex[3]; uint16_t texCoords[2]; - } __attribute__((__packed__)); - } - - Object::Object(Renderer& renderer, ObjDecoder& obj) - : Drawable(renderer), - vbo_() - { - program_ = ProgramProvider::getInstance().getProgram("object", "object"); - auto& tris = obj.getTriangles(); - auto& quads = obj.getQuads(); - numVertices_ = (tris.size()*3+quads.size()*6); - vbo_ = VBOManager::getInstance().alloc(sizeof(VertexAttribs)*numVertices_); - - glGenVertexArrays(1, &vertexArray_.get()); - std::vector vertexAttribs; - vertexAttribs.reserve(numVertices_); + uint8_t useTexAnim; - auto& vertices = obj.getVertices(); - for (auto& tri : tris) { - for (unsigned i = 0;i < 3;++i) { - VertexAttribs attrs; - std::copy(vertices.at(tri.vertIdx[i]).begin(), vertices.at(tri.vertIdx[i]).end(), - attrs.vertex); - std::copy(tri.texCoords[i].begin(), tri.texCoords[i].end(), - attrs.texCoords); - - vertexAttribs.push_back(attrs); + bool operator<(VertexAttribs const& other) const { + if (vertex[0] == other.vertex[0]) { + if (vertex[1] == other.vertex[1]) { + if (vertex[2] == other.vertex[2]) { + if (texCoords[0] == other.texCoords[0]) { + if (texCoords[1] == other.texCoords[1]) + return (useTexAnim < other.useTexAnim); + return (texCoords[1] < other.texCoords[1]); + } + return (texCoords[0] < other.texCoords[0]); + } + return (vertex[2] < other.vertex[2]); + } + return (vertex[1] < other.vertex[1]); + } + return (vertex[0] < other.vertex[0]); } + }; + + const std::array vgaPalette = { + 0x000000, 0x0000a8, 0x00a800, 0x00a8a8, 0xa80000, 0xa800a8, 0xa85400, 0xa8a8a8, + 0x545454, 0x5454fc, 0x54fc54, 0x54fcfc, 0xfc5454, 0xfc54fc, 0xfcfc54, 0xfcfcfc, + 0x000000, 0x141414, 0x202020, 0x2c2c2c, 0x383838, 0x444444, 0x505050, 0x606060, + 0x707070, 0x808080, 0x909090, 0xa0a0a0, 0xb4b4b4, 0xc8c8c8, 0xe0e0e0, 0xfcfcfc, + 0x0000fc, 0x4000fc, 0x7c00fc, 0xbc00fc, 0xfc00fc, 0xfc00bc, 0xfc007c, 0xfc0040, + 0xfc0000, 0xfc4000, 0xfc7c00, 0xfcbc00, 0xfcfc00, 0xbcfc00, 0x7cfc00, 0x40fc00, + 0x00fc00, 0x00fc40, 0x00fc7c, 0x00fcbc, 0x00fcfc, 0x00bcfc, 0x007cfc, 0x0040fc, + 0x7c7cfc, 0x9c7cfc, 0xbc7cfc, 0xdc7cfc, 0xfc7cfc, 0xfc7cdc, 0xfc7cbc, 0xfc7c9c, + 0xfc7c7c, 0xfc9c7c, 0xfcbc7c, 0xfcdc7c, 0xfcfc7c, 0xdcfc7c, 0xbcfc7c, 0x9cfc7c, + 0x7cfc7c, 0x7cfc9c, 0x7cfcbc, 0x7cfcdc, 0x7cfcfc, 0x7cdcfc, 0x7cbcfc, 0x7c9cfc, + 0xb4b4fc, 0xc4b4fc, 0xd8b4fc, 0xe8b4fc, 0xfcb4fc, 0xfcb4e8, 0xfcb4d8, 0xfcb4c4, + 0xfcb4b4, 0xfcc4b4, 0xfcd8b4, 0xfce8b4, 0xfcfcb4, 0xe8fcb4, 0xd8fcb4, 0xc4fcb4, + 0xb4fcb4, 0xb4fcc4, 0xb4fcd8, 0xb4fce8, 0xb4fcfc, 0xb4e8fc, 0xb4d8fc, 0xb4c4fc, + 0x000070, 0x1c0070, 0x380070, 0x540070, 0x700070, 0x700054, 0x700038, 0x70001c, + 0x700000, 0x701c00, 0x703800, 0x705400, 0x707000, 0x547000, 0x387000, 0x1c7000, + 0x007000, 0x00701c, 0x007038, 0x007054, 0x007070, 0x005470, 0x003870, 0x001c70, + 0x383870, 0x443870, 0x543870, 0x603870, 0x703870, 0x703860, 0x703854, 0x703844, + 0x703838, 0x704438, 0x705438, 0x706038, 0x707038, 0x607038, 0x547038, 0x447038, + 0x387038, 0x387044, 0x387054, 0x387060, 0x387070, 0x386070, 0x385470, 0x384470, + 0x505070, 0x585070, 0x605070, 0x685070, 0x705070, 0x705068, 0x705060, 0x705058, + 0x705050, 0x705850, 0x706050, 0x706850, 0x707050, 0x687050, 0x607050, 0x587050, + 0x507050, 0x507058, 0x507060, 0x507068, 0x507070, 0x506870, 0x506070, 0x505870, + 0x000040, 0x100040, 0x200040, 0x300040, 0x400040, 0x400030, 0x400020, 0x400010, + 0x400000, 0x401000, 0x402000, 0x403000, 0x404000, 0x304000, 0x204000, 0x104000, + 0x004000, 0x004010, 0x004020, 0x004030, 0x004040, 0x003040, 0x002040, 0x001040, + 0x202040, 0x282040, 0x302040, 0x382040, 0x402040, 0x402038, 0x402030, 0x402028, + 0x402020, 0x402820, 0x403020, 0x403820, 0x404020, 0x384020, 0x304020, 0x284020, + 0x204020, 0x204028, 0x204030, 0x204038, 0x204040, 0x203840, 0x203040, 0x202840, + 0x2c2c40, 0x302c40, 0x342c40, 0x3c2c40, 0x402c40, 0x402c3c, 0x402c34, 0x402c30, + 0x402c2c, 0x40302c, 0x40342c, 0x403c2c, 0x40402c, 0x3c402c, 0x34402c, 0x30402c, + 0x2c402c, 0x2c4030, 0x2c4034, 0x2c403c, 0x2c4040, 0x2c3c40, 0x2c3440, 0x2c3040, + 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000}; + + struct TexAtlasInfo { + unsigned x, y; + unsigned alignWidth, alignHeight; + float xofs, yofs, xscale, yscale; + ObjDecoder::Texture const& tex; + }; + + std::tuple, TextureResource> + genTexAtlas(ObjDecoder::Textures const& texs, + PaletteDecoder::Palette const& palt) + { + // Build texture atlas for object + unsigned maxTex, maxLayers; + std::tie(maxTex, maxLayers) = getGLTextureMaximums(); + printf("Maximum texture size: %d, maximum array texture layers: %d\n", maxTex, maxLayers); + + + unsigned minSize = std::numeric_limits::max(); + for (auto& tex : texs) { + minSize = std::min(minSize, std::min(tex.width, tex.height)); + } + unsigned minLg = ilog2(minSize), minPow2 = (1< ret; + unsigned xpos = 0, ypos = 0, lineMaxHeight = 0; + for (auto& tex : texs) { + unsigned alignWidth = (tex.width%minPow2 == 0)?tex.width:tex.width+(minPow2-tex.width%minPow2); + unsigned alignHeight = (tex.height%minPow2 == 0)?tex.height:tex.height+(minPow2-tex.height%minPow2); + + if (xpos + alignWidth > static_cast(maxTex)) { + ypos += lineMaxHeight; + lineMaxHeight = 0; + xpos = 0; + } + if (ypos + alignHeight > static_cast(maxTex)) + throw Exception{"Cannot fit obj textures into GL texture"}; + + TexAtlasInfo info = {xpos, ypos, alignWidth, alignHeight, 0.0f, 0.0f, 0.0f, 0.0f, tex}; + ret.push_back(info); + + lineMaxHeight = std::max(lineMaxHeight, alignHeight); + xpos += alignWidth; + } + unsigned atlasWidth = xpos, atlasHeight = ypos+lineMaxHeight; + printf("Texture atlas size: %ux%u\n", atlasWidth, atlasHeight); + + TextureResource tex = create2DTexture(atlasWidth, atlasHeight, false, minLg+1); + + std::vector pixels(atlasWidth*atlasHeight*3); + // Copy textures into atlas + for (auto& info : ret) { + info.xofs = (info.x+0.5f)/atlasWidth; + info.yofs = (info.y+0.5f)/atlasHeight; + info.xscale = (info.tex.width-1.0f)/(atlasWidth*info.tex.width); + info.yscale = (info.tex.height-1.0f)/(atlasHeight*info.tex.height); + printf("Texture coordinate factors: %f, %f; %f, %f\n", + info.xofs, info.yofs, info.xscale, info.yscale); + + for (unsigned y = 0;y < info.alignHeight;++y) { + for (unsigned x = 0;x < info.alignWidth;++x) { + unsigned col; + if (y < info.tex.height) { + if (x < info.tex.width) { + col = info.tex.pixels[y*info.tex.width + x]; + } else { + col = info.tex.pixels[y*info.tex.width + info.tex.width-1]; + } + } else { + if (x < info.tex.width) { + col = info.tex.pixels[(info.tex.height-1)*info.tex.width + x]; + } else { + col = info.tex.pixels[info.tex.height*info.tex.width - 1]; + } + } + pixels[(y+info.y)*atlasWidth*3+(info.x+x)*3] = palt[col*3]; //vgaPalette[col]>>16; + pixels[(y+info.y)*atlasWidth*3+(info.x+x)*3+1] = palt[col*3+1]; //vgaPalette[col]>>8; + pixels[(y+info.y)*atlasWidth*3+(info.x+x)*3+2] = palt[col*3+2]; //vgaPalette[col]; + } + } + } + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, atlasWidth, atlasHeight, GL_RGB, GL_UNSIGNED_BYTE, pixels.data()); + glGenerateMipmap(GL_TEXTURE_2D); + + return std::make_tuple(std::move(ret), std::move(tex)); } - for (auto& quad : quads) { - for (unsigned i = 0;i < 3;++i) { - VertexAttribs attrs; - std::copy(vertices.at(quad.vertIdx[i]).begin(), vertices.at(quad.vertIdx[i]).end(), - attrs.vertex); - std::copy(quad.texCoords[i].begin(), quad.texCoords[i].end(), - attrs.texCoords); - - vertexAttribs.push_back(attrs); - } - for (unsigned i = 0;i < 3;++i) { - VertexAttribs attrs; - std::copy(vertices.at(quad.vertIdx[i+1]).begin(), vertices.at(quad.vertIdx[i+1]).end(), - attrs.vertex); - std::copy(quad.texCoords[i+1].begin(), quad.texCoords[i+1].end(), - attrs.texCoords); - - vertexAttribs.push_back(attrs); - } - } + struct AnimTexInfo { + float xofs, yofs, xscale, yscale; + }; - // std::vector vertexAttribs = { - // {{-1500, -1500, 0}, {0, 0}}, - // {{-1500, 1500, 0}, {0, 0}}, - // {{1500, -1500, 0}, {0, 0}}, - // {{1500, 1500, 0}, {0, 0}}}; + std::tuple + genTexAnim(ObjDecoder::TextureAnimation const& texAnim, + PaletteDecoder::Palette const& palt) + { + unsigned width = texAnim.width; + if (width%4 != 0) + width += 4 - (width%4); + unsigned height = texAnim.height; + TextureResource tex = create2DArrayTexture(width, height, texAnim.frames, false); + + AnimTexInfo animInfo; + animInfo.xofs = 0.5f/width; + animInfo.yofs = 0.5f/height; + animInfo.xscale = (texAnim.width-1.0f)/(width*texAnim.width); + animInfo.yscale = (texAnim.height-1.0f)/(height*texAnim.height); + std::vector pixels(texAnim.frames*width*height*3); + + for (unsigned f = 0;f < texAnim.frames;++f) { + for (unsigned y = 0;y < texAnim.height;++y) { + for (unsigned x = 0;x < texAnim.width;++x) { + unsigned col = texAnim.pixels[f][y*texAnim.width+x]; + + pixels[f*width*height*3+y*width*3+x*3] = palt[col*3]; + pixels[f*width*height*3+y*width*3+x*3+1] = palt[col*3+1]; + pixels[f*width*height*3+y*width*3+x*3+2] = palt[col*3+2]; + } + } + } + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, texAnim.frames, + GL_RGB, GL_UNSIGNED_BYTE, pixels.data()); + glGenerateMipmap(GL_TEXTURE_2D_ARRAY); + + return std::make_tuple(animInfo, std::move(tex)); + } + + std::tuple, std::vector > + genVertexAttribs(ObjDecoder& obj, std::vector& atlasInfo, + AnimTexInfo *animTex = nullptr) + { + auto& tris = obj.getTriangles(); + auto& quads = obj.getQuads(); + + // Deduplicate vertex attributes, track indices + std::map vertexAttribsMap; + std::vector vertexAttribs; + std::vector indices; + + auto& vertices = obj.getVertices(); + for (auto& tri : tris) { + for (unsigned i = 0;i < 3;++i) { + VertexAttribs attrs; + std::copy(vertices.at(tri.vertIdx[i]).begin(), vertices.at(tri.vertIdx[i]).end(), + attrs.vertex); + if (tri.tex < atlasInfo.size()) { + auto& tex = atlasInfo[tri.tex]; + attrs.texCoords[0] = glm::packUnorm1x16(tex.xofs + tri.texCoords[i][0]*tex.xscale); + attrs.texCoords[1] = glm::packUnorm1x16(tex.yofs + tri.texCoords[i][1]*tex.yscale); + attrs.useTexAnim = 0u; + } else if ((tri.tex == atlasInfo.size()) && animTex) { + attrs.texCoords[0] = glm::packUnorm1x16(animTex->xofs + tri.texCoords[i][0]*animTex->xscale); + attrs.texCoords[1] = glm::packUnorm1x16(animTex->yofs + tri.texCoords[i][1]*animTex->yscale); + attrs.useTexAnim = 1u; + } else + throw Exception{"Texture index out of range"}; + + auto ins = vertexAttribsMap.insert(std::make_pair(attrs, vertexAttribs.size())); + if (ins.second) { + indices.push_back(vertexAttribs.size()); + vertexAttribs.push_back(attrs); + } else + indices.push_back(ins.first->second); + } + } + + for (auto& quad : quads) { + for (unsigned i = 0;i < 3;++i) { + VertexAttribs attrs; + std::copy(vertices.at(quad.vertIdx[i]).begin(), vertices.at(quad.vertIdx[i]).end(), + attrs.vertex); + if (quad.tex < atlasInfo.size()) { + auto& tex = atlasInfo[quad.tex]; + attrs.texCoords[0] = glm::packUnorm1x16(tex.xofs + quad.texCoords[i][0]*tex.xscale); + attrs.texCoords[1] = glm::packUnorm1x16(tex.yofs + quad.texCoords[i][1]*tex.yscale); + attrs.useTexAnim = 0u; + } else if ((quad.tex == atlasInfo.size()) && animTex) { + attrs.texCoords[0] = glm::packUnorm1x16(animTex->xofs + quad.texCoords[i][0]*animTex->xscale); + attrs.texCoords[1] = glm::packUnorm1x16(animTex->yofs + quad.texCoords[i][1]*animTex->yscale); + attrs.useTexAnim = 1u; + } else + throw Exception{"Texture index out of range"}; + + auto ins = vertexAttribsMap.insert(std::make_pair(attrs, vertexAttribs.size())); + if (ins.second) { + indices.push_back(vertexAttribs.size()); + vertexAttribs.push_back(attrs); + } else + indices.push_back(ins.first->second); + } + for (unsigned i = 0;i < 3;++i) { + VertexAttribs attrs; + std::copy(vertices.at(quad.vertIdx[i?(i+1):i]).begin(), vertices.at(quad.vertIdx[i?(i+1):i]).end(), + attrs.vertex); + if (quad.tex < atlasInfo.size()) { + auto& tex = atlasInfo[quad.tex]; + attrs.texCoords[0] = glm::packUnorm1x16(tex.xofs + quad.texCoords[i?(i+1):i][0]*tex.xscale); + attrs.texCoords[1] = glm::packUnorm1x16(tex.yofs + quad.texCoords[i?(i+1):i][1]*tex.yscale); + attrs.useTexAnim = 0u; + } else if ((quad.tex == atlasInfo.size()) && animTex) { + attrs.texCoords[0] = glm::packUnorm1x16(animTex->xofs + quad.texCoords[i?(i+1):i][0]*animTex->xscale); + attrs.texCoords[1] = glm::packUnorm1x16(animTex->yofs + quad.texCoords[i?(i+1):i][1]*animTex->yscale); + attrs.useTexAnim = 1u; + } else + throw Exception{"Texture index out of range"}; + + auto ins = vertexAttribsMap.insert(std::make_pair(attrs, vertexAttribs.size())); + if (ins.second) { + indices.push_back(vertexAttribs.size()); + vertexAttribs.push_back(attrs); + } else + indices.push_back(ins.first->second); + } + } + + printf("%lu indices, %lu attributes\n", + indices.size(), vertexAttribs.size()); + + return std::make_tuple(std::move(vertexAttribs), std::move(indices)); + } + + } + + Object::Object(Renderer& renderer, ObjDecoder& obj, PaletteDecoder& palt) + : Drawable(renderer), + vbo_(), rot_(0.0f), animFrame_(0) + { + // Acquire shader + program_ = ProgramProvider::getInstance().getProgram("object", "object"); + + // Generate texture atlas + auto& texs = obj.getTextures(); + std::vector atlasInfo; + if (texs.size() > 0) { + std::tie(atlasInfo, tex_) = genTexAtlas(texs, palt.getPalette()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, static_cast(GL_LINEAR_MIPMAP_LINEAR)); + } + + auto& texAnims = obj.getTextureAnimations(); + AnimTexInfo animInfo; + if (texAnims.size() > 0) { + std::tie(animInfo, texAnim_) = genTexAnim(texAnims[0], palt.getPalette()); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, static_cast(GL_LINEAR_MIPMAP_LINEAR)); + } + + // Generate vertex attribute array + std::vector vertexAttribs; + std::vector indices; + if (texAnims.size() > 0) + std::tie(vertexAttribs, indices) = genVertexAttribs(obj, atlasInfo, &animInfo); + else + std::tie(vertexAttribs, indices) = genVertexAttribs(obj, atlasInfo); + + // Setup GL vertex buffer and vertex array + glGenVertexArrays(1, &vertexArray_.get()); + vbo_ = VBOManager::getInstance().alloc(sizeof(VertexAttribs)*vertexAttribs.size()+ + 2*indices.size()); + glBindBuffer(GL_ARRAY_BUFFER, vbo_.getVBOId()); glBindVertexArray(vertexArray_); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo_.getVBOId()); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_INT, GL_FALSE, sizeof(VertexAttribs), vbo_.getOfs(offsetof(VertexAttribs, vertex))); - + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_UNSIGNED_SHORT, GL_TRUE, sizeof(VertexAttribs), + vbo_.getOfs(offsetof(VertexAttribs, texCoords))); + glEnableVertexAttribArray(2); + glVertexAttribIPointer(2, 1, GL_UNSIGNED_BYTE, sizeof(VertexAttribs), + vbo_.getOfs(offsetof(VertexAttribs, useTexAnim))); + glBufferSubData(GL_ARRAY_BUFFER, vbo_.getBase(), - sizeof(VertexAttribs)*numVertices_, + sizeof(VertexAttribs)*vertexAttribs.size(), vertexAttribs.data()); + maxIndex_ = vertexAttribs.size()-1; + indexOfs_ = sizeof(VertexAttribs)*vertexAttribs.size(); + numIndices_ = indices.size(); + + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, vbo_.getBase()+indexOfs_, + 2*numIndices_, + indices.data()); + mvp_ = glm::perspectiveFov(75.0f, static_cast(renderer.getWidth()), static_cast(renderer.getHeight()), - 100.f, 20000.0f) * - glm::lookAt(glm::vec3(0.0f, 0.0f, 10000), + 1000.f, 800000.0f) * + glm::lookAt(glm::vec3(0.0f, 0.0f, 200000), glm::vec3(0.0f, 0.0f, 0), glm::vec3(0, 1, 0)); + int maxVertices, maxIndices; + glGetIntegerv(GL_MAX_ELEMENTS_INDICES, &maxIndices); + glGetIntegerv(GL_MAX_ELEMENTS_VERTICES, &maxVertices); + + printf("Max recommened vertices: %d, indices: %d\n", + maxVertices, maxIndices); } void Object::draw() { glDisable(GL_CULL_FACE); + //glFrontFace(GL_CW); + glEnable(GL_DEPTH_TEST); - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - glUseProgram(program_); - glUniformMatrix4fv(0, 1, GL_FALSE, glm::value_ptr(mvp_)); - glBindVertexArray(vertexArray_); + //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + useProgram(program_); + glUniformMatrix4fv(0, 1, GL_FALSE, + glm::value_ptr(mvp_*glm::rotate(rot_, glm::vec3(1.0f, 0.0f, 0.0f)))); + glUniform1i(1, 0); + glUniform1i(2, 1); - glDrawArrays(GL_TRIANGLES, 0, numVertices_); + glBindTexture(GL_TEXTURE_2D, tex_); + if (texAnim_) { + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D_ARRAY, texAnim_); + glActiveTexture(GL_TEXTURE0); + glUniform1ui(3, animFrame_); + } + + glBindVertexArray(vertexArray_); + + glDrawRangeElements(GL_TRIANGLES, 0, maxIndex_, + numIndices_, GL_UNSIGNED_SHORT, reinterpret_cast(indexOfs_)); } } diff --git a/render/Object.hh b/render/Object.hh index 3ff13da..40a76dc 100644 --- a/render/Object.hh +++ b/render/Object.hh @@ -9,20 +9,33 @@ #include "VBOManager.hh" class ObjDecoder; +class PaletteDecoder; namespace render { class Object : public Drawable { public: - Object(Renderer& renderer, ObjDecoder& obj); + Object(Renderer& renderer, ObjDecoder& obj, PaletteDecoder& palt); + void setRot(float rot) { + rot_ = rot; + } + + void setAnimFrame(unsigned frame) { + animFrame_ = frame; + } + void draw() override; private: VertexArrayResource vertexArray_; gl::GLuint program_; VBOManager::VBOAlloc vbo_; + TextureResource tex_, texAnim_; glm::mat4 mvp_; - unsigned numVertices_; + uintptr_t indexOfs_; + size_t numIndices_, maxIndex_; + float rot_; + unsigned animFrame_; }; } diff --git a/render/Overlay.cc b/render/Overlay.cc index 8cb508e..9010508 100644 --- a/render/Overlay.cc +++ b/render/Overlay.cc @@ -61,7 +61,7 @@ namespace render { void Overlay::draw() { glDisable(GL_DEPTH_TEST); - glUseProgram(program_); + useProgram(program_); glUniformMatrix4fv(0, 1, GL_FALSE, glm::value_ptr(ovlProj_)); glUniform1i(1, 0); diff --git a/render/renderutil.cc b/render/renderutil.cc index f07627a..8bd43a0 100644 --- a/render/renderutil.cc +++ b/render/renderutil.cc @@ -2,19 +2,43 @@ #include #include "renderutil.hh" +#include "exceptions.hh" using namespace gl; namespace render { - namespace { - unsigned ilog2(unsigned in) - { - unsigned ret = 0u; - while (in >>= 1) ++ret; - return ret; - } + unsigned ilog2(unsigned in) + { + unsigned ret = 0u; + while (in >>= 1) ++ret; + return ret; } + std::tuple getGLTextureMaximums() + { + static int maxTexSize = -1, maxArrTexLayers = -1; + + if (maxTexSize < 0) + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize); + if (maxArrTexLayers < 0) + glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &maxArrTexLayers); + + if ((maxTexSize < 0) || (maxArrTexLayers < 0)) + throw Exception{"GL returned negative values for max tex. dimensions"}; + + return std::make_tuple(maxTexSize, maxArrTexLayers); + } + + void useProgram(GLuint program) + { + static GLuint curProgram = 0; + + if (curProgram != program) { + glUseProgram(program); + curProgram = program; + } + } + TextureResource create2DTexture(unsigned width, unsigned height, bool alpha, unsigned levels) { TextureResource tex; @@ -29,14 +53,41 @@ namespace render { else { std::printf("Warning: extension GL_ARB_texture_storage not supported!\n"); for (unsigned i = 0u; i < levels; ++i) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, levels-1); glTexImage2D(GL_TEXTURE_2D, i, static_cast(alpha?GL_RGBA8:GL_RGB8), width, height, 0, alpha?GL_RGBA8:GL_RGB8, GL_UNSIGNED_BYTE, NULL); width = std::max(1u, (width / 2u)); height = std::max(1u, (height / 2u)); } } + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, levels-1); + + return tex; + } + TextureResource create2DArrayTexture(unsigned width, unsigned height, unsigned count, + bool alpha, unsigned levels) + { + TextureResource tex; + glGenTextures(1, &tex.get()); + + glBindTexture(GL_TEXTURE_2D_ARRAY, tex); + unsigned logWidth = ilog2(width), logHeight = ilog2(height); + if (levels == 0) + levels = std::max(logWidth,logHeight)+1u; + if(SDL_GL_ExtensionSupported("GL_ARB_texture_storage")) + glTexStorage3D(GL_TEXTURE_2D_ARRAY, levels, alpha?GL_RGBA8:GL_RGB8, width, height, count); + else { + std::printf("Warning: extension GL_ARB_texture_storage not supported!\n"); + for (unsigned i = 0u; i < levels; ++i) { + glTexImage3D(GL_TEXTURE_2D_ARRAY, i, static_cast(alpha?GL_RGBA8:GL_RGB8), + width, height, count, + 0, alpha?GL_RGBA8:GL_RGB8, GL_UNSIGNED_BYTE, NULL); + width = std::max(1u, (width / 2u)); + height = std::max(1u, (height / 2u)); + } + } + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, levels-1); + return tex; } } diff --git a/render/renderutil.hh b/render/renderutil.hh index 35bb68b..f6b0358 100644 --- a/render/renderutil.hh +++ b/render/renderutil.hh @@ -4,7 +4,17 @@ #include "GlResource.hh" namespace render { + unsigned ilog2(unsigned in); + + // Return (GL_MAX_TEXTURE_SIZE, GL_MAX_ARRAY_TEXTURE_LAYERS) + std::tuple getGLTextureMaximums(); + + // call glUseProgram if program != current program + void useProgram(gl::GLuint program); + TextureResource create2DTexture(unsigned width, unsigned height, bool alpha, unsigned levels = 0); + TextureResource create2DArrayTexture(unsigned width, unsigned height, unsigned count, + bool alpha, unsigned levels = 0); } #endif diff --git a/shaders/object.fs b/shaders/object.fs index 4c75f78..471a77c 100644 --- a/shaders/object.fs +++ b/shaders/object.fs @@ -1,8 +1,21 @@ #version 330 core #extension GL_ARB_explicit_uniform_location : enable +layout(location = 1) uniform sampler2D texBase; +layout(location = 2) uniform sampler2DArray texAnim; +layout(location = 3) uniform uint animFrame; + +in vec2 fragTC; +flat in uint fragUseAnimTex; + out vec4 color; void main(void) { - color = vec4(1.0, 1.0, 1.0, 1.0); -} \ No newline at end of file + vec2 texDx = dFdx(fragTC); + vec2 texDy = dFdy(fragTC); + if (fragUseAnimTex > 0u) + color = textureGrad(texAnim, vec3(fragTC, animFrame), texDx, texDy); + //color = vec4(1.0, 1.0, 0.0, 1.0); + else + color = textureGrad(texBase, fragTC, texDx, texDy); +} diff --git a/shaders/object.vs b/shaders/object.vs index 0cc157f..055eeb0 100644 --- a/shaders/object.vs +++ b/shaders/object.vs @@ -6,11 +6,14 @@ layout(location = 0) uniform mat4 mvp_matrix; layout(location = 0) in vec3 vertex; layout(location = 1) in vec2 vertexTC; +layout(location = 2) in uint useAnimTex; out vec2 fragTC; +flat out uint fragUseAnimTex; void main(void) { gl_Position = mvp_matrix * vec4(vertex, 1.0); fragTC = vertexTC; + fragUseAnimTex = useAnimTex; }