#include #include #include #include #include #include "Object.hh" #include "ProgramProvider.hh" #include "Renderer.hh" #include "ObjDecoder.hh" #include "PaletteDecoder.hh" #include "renderutil.hh" using namespace gl; namespace render { namespace { struct VertexAttribs { int32_t vertex[3]; uint16_t texCoords[2]; uint8_t useTexAnim; 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]); } }; struct TexAtlasInfo { unsigned x, y; unsigned alignWidth, alignHeight; float xofs, yofs, xscale, yscale; ObjDecoder::Texture const& tex; }; /* Copy from paletted texture 'src' into BGRA texture 'dst' using palette 'palt' Palette entry 0xff is transparent, all others are opaque. For opaque pixels the color components are copied from a neighbouring opaque pixel if available. */ void copyPaletteTexture(uint8_t const* src, unsigned srcWidth, unsigned srcHeight, uint8_t *dst, unsigned dstStride, PaletteDecoder::Palette const& palt) { const uint8_t TRANS = 0xff; auto getPixel = [src, srcWidth, srcHeight](int x, int y) -> uint8_t { if (x < 0) x = 0; if (y < 0) y = 0; if (static_cast(x) > srcWidth-1) x = srcWidth-1; if (static_cast(y) > srcHeight-1) y = srcHeight-1; return src[y*srcWidth+x]; }; for (unsigned y = 0;y < srcHeight;++y) { for (unsigned x = 0;x < srcWidth;++x) { auto col = getPixel(x, y); if (col == TRANS) { auto ocol = TRANS; // // Copy color from neighbouring opaque pixel if available if (getPixel(x-1, y) != TRANS) ocol = getPixel(x-1, y); else if (getPixel(x+1, y) != TRANS) ocol = getPixel(x+1, y); else if (getPixel(x, y-1) != TRANS) ocol = getPixel(x, y-1); else if (getPixel(x, y+1) != TRANS) ocol = getPixel(x, y+1); else if (getPixel(x-1, y-1) != TRANS) ocol = getPixel(x-1, y-1); else if (getPixel(x-1, y+1) != TRANS) ocol = getPixel(x-1, y+1); else if (getPixel(x+1, y-1) != TRANS) ocol = getPixel(x+1, y-1); else if (getPixel(x+1, y+1) != TRANS) ocol = getPixel(x+1, y+1); if (ocol != TRANS) { *dst++ = palt[ocol*3u+2u]; // b *dst++ = palt[ocol*3u+1u]; // g *dst++ = palt[ocol*3u]; // r } else { *dst++ = 0u; // b *dst++ = 0u; // g *dst++ = 0u; // r } *dst++ = 0u; // a } else { *dst++ = palt[col*3u+2u]; // b *dst++ = palt[col*3u+1u]; // g *dst++ = palt[col*3u]; // r *dst++ = 255u; // a } } dst += (dstStride - srcWidth*4); } } /* Generate Mipmaps for BGRA texture 'src'. The mipmapping algorithm computes the alpha-weighted average of the color values, and the average of the alpha values, of a 2x2 block of pixels. */ std::vector genMipmap(uint8_t const* src, unsigned srcWidth, unsigned srcHeight, unsigned levels, bool colorize = false) { if (!levels) return std::vector(); unsigned origWidth = srcWidth, origHeight = srcHeight; unsigned size = 0; for(unsigned i = 1;i <= levels;++i) { unsigned lHeight = std::max(srcHeight>>i, 1u); unsigned lWidth = std::max(srcWidth>>i, 1u); size += lHeight*lWidth; } std::vector ret; ret.reserve(size*4); for (unsigned lvl = 0;lvl < levels;++lvl) { auto height = std::max(srcHeight>>1u, 1u), width = std::max(srcWidth>>1u, 1u); uint8_t const* nextSrc = &ret.back()+1; for (unsigned y = 0;y < height;++y) for (unsigned x = 0;x < width;++x) { std::array, 4> pixels; for (unsigned sy = 0;sy < 2;++sy) for (unsigned sx = 0;sx < 2;++sx) for (unsigned i = 0;i < 4;++i) { if (y*2+sy > srcHeight-1) { if (x*2+sx > srcWidth-1) { pixels[sy*2+sx][i] = src[((y*2)*srcWidth+x*2)*4+i]; } else pixels[sy*2+sx][i] = src[((y*2)*srcWidth+x*2+sx)*4+i]; } else if (x*2+sx > srcWidth-1) { pixels[sy*2+sx][i] = src[((y*2+sy)*srcWidth+x*2)*4+i]; } else pixels[sy*2+sx][i] = src[((y*2+sy)*srcWidth+x*2+sx)*4+i]; } unsigned asum = 0, rsum = 0, gsum = 0, bsum = 0; for (unsigned i = 0;i < 4;++i) { auto alpha = pixels[i][3]; asum += alpha; rsum += (pixels[i][2]*alpha)/255u; gsum += (pixels[i][1]*alpha)/255u; bsum += (pixels[i][0]*alpha)/255u; } if (asum == 0) { ret.push_back(0u); ret.push_back(0u); ret.push_back(0u); } else { ret.push_back((255u*bsum)/asum); ret.push_back((255u*gsum)/asum); ret.push_back((255u*rsum)/asum); } ret.push_back(asum/4u); } src = nextSrc; srcHeight = std::max(srcHeight>>1u, 1u); srcWidth = std::max(srcWidth>>1u, 1u); } if (colorize) { size_t pos = 0; for (unsigned lvl = 1;lvl <= levels;++lvl) { for (unsigned y = 0;y < origHeight>>lvl;++y) for (unsigned x = 0;x < origWidth>>lvl;++x) ret[pos+(y*(origWidth>>lvl)+x)*4+(lvl%3)] = 255u; pos += (origWidth>>lvl)*(origHeight>>lvl)*4; } } return ret; } std::tuple, TextureResource> genTexAtlas(ObjDecoder::Textures const& texs, std::set const& usedTexs, 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); // If the atlas gets too large we may have problems with texture coordinate precision if (maxTex > 8192) maxTex = 8192; unsigned minSize = std::numeric_limits::max(); unsigned accumWidth = 0, maxHeight = 0, maxWidth = 0; unsigned i = 0; for (auto& tex : texs) { if (usedTexs.find(i++) == usedTexs.end()) continue; minSize = std::min(minSize, std::min(tex.width, tex.height)); accumWidth += tex.width; maxHeight = std::max(maxHeight, tex.height); maxWidth = std::max(maxWidth, tex.width); } // Set lower bound of minSize to ensure a minimum number of mipmap levels minSize = std::max(minSize, 16u); unsigned minLg = ilog2(minSize), minPow2 = (1< ret; unsigned xpos = 0, ypos = 0, lineMaxHeight = 0; maxWidth = 0; i = 0; for (auto& tex : texs) { if (usedTexs.find(i++) == usedTexs.end()) { TexAtlasInfo info = {0, 0, 0, 0, 0.0f, 0.0f, 0.0f, 0.0f, tex}; ret.push_back(info); continue; } 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 > accumWidth2) { if (alignWidth > maxTex) throw Exception{"Cannot fit obj textures into GL texture"}; ypos += lineMaxHeight; maxWidth = std::max(maxWidth, xpos); 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 = std::max(xpos, maxWidth), atlasHeight = ypos+lineMaxHeight; printf("Texture atlas size: %ux%u\n", atlasWidth, atlasHeight); TextureResource tex = create2DTexture(atlasWidth, atlasHeight, true, minLg+1); std::vector pixels(atlasWidth*atlasHeight*4); // Copy textures into atlas for (auto& info : ret) { if (info.alignWidth == 0) continue; 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); copyPaletteTexture(info.tex.pixels.data(), info.tex.width, info.tex.height, pixels.data()+info.y*atlasWidth*4+info.x*4, atlasWidth*4, palt); } glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, atlasWidth, atlasHeight, GL_BGRA, GL_UNSIGNED_BYTE, pixels.data()); auto mipdata = genMipmap(pixels.data(), atlasWidth, atlasHeight, minLg); size_t pos = 0; for (unsigned lvl = 1;lvl <= minLg;++lvl) { unsigned lWidth = std::max(atlasWidth>>lvl, 1u), lHeight = std::max(atlasHeight>>lvl, 1u); glTexSubImage2D(GL_TEXTURE_2D, lvl, 0, 0, lWidth, lHeight, GL_BGRA, GL_UNSIGNED_BYTE, mipdata.data()+pos); pos += lWidth*lHeight*4; } return std::make_tuple(std::move(ret), std::move(tex)); } struct AnimTexInfo { float xofs, yofs, xscale, yscale; }; std::tuple genTexAnim(ObjDecoder::TextureAnimation const& texAnim, PaletteDecoder::Palette const& palt) { unsigned width = (1< pixels(texAnim.frames*width*height*4); for (unsigned f = 0;f < texAnim.frames;++f) { copyPaletteTexture(texAnim.pixels[f].data(), texAnim.width, texAnim.height, pixels.data()+f*width*height*4, width*4, palt); } glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, texAnim.frames, GL_BGRA, GL_UNSIGNED_BYTE, pixels.data()); unsigned logWidth = ilog2(width), logHeight = ilog2(height); unsigned levels = std::max(logWidth, logHeight); for (unsigned f = 0;f < texAnim.frames;++f) { auto mipdata = genMipmap(pixels.data()+f*width*height*4, width, height, levels); size_t pos = 0; for (unsigned lvl = 1;lvl <= levels;++lvl) { unsigned lWidth = std::max(width>>lvl, 1u), lHeight = std::max(height>>lvl, 1u); glTexSubImage3D(GL_TEXTURE_2D_ARRAY, lvl, 0, 0, f, lWidth, lHeight, 1, GL_BGRA, GL_UNSIGNED_BYTE, mipdata.data()+pos); pos += lWidth*lHeight*4; } } return std::make_tuple(animInfo, std::move(tex)); } std::tuple, std::vector > genVertexAttribs(ObjDecoder& obj, ObjDecoder::Triangles const& tris, ObjDecoder::Quads const& quads, std::vector& atlasInfo, AnimTexInfo *animTex = nullptr) { // 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, unsigned lod) : TransformDrawable(renderer), vbo_(), rot_(0.0f), animFrame_(0) { // Acquire shader program_ = ProgramProvider::getInstance().getProgram("object", "object"); // Determine used textures auto tris = obj.getTriangles(lod); auto quads = obj.getQuads(lod); std::set usedTexs; for (auto& tri: tris) usedTexs.insert(tri.tex); for (auto& quad: quads) usedTexs.insert(quad.tex); // Generate texture atlas auto& texs = obj.getTextures(); std::vector atlasInfo; if (texs.size() > 0) { std::tie(atlasInfo, tex_) = genTexAtlas(texs, usedTexs, palt.getPalette()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, static_cast(GL_LINEAR_MIPMAP_LINEAR)); } auto& texAnims = obj.getTextureAnimations(); AnimTexInfo animInfo; if ((usedTexs.find(atlasInfo.size()) != usedTexs.end()) && (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, tris, quads, atlasInfo, &animInfo); else std::tie(vertexAttribs, indices) = genVertexAttribs(obj, tris, quads, 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)*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()); vp_ = glm::perspectiveFov(75.0f, static_cast(renderer.getWidth()), static_cast(renderer.getHeight()), 7500.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); useProgram(program_); glUniformMatrix4fv(0, 1, GL_FALSE, glm::value_ptr(vp_)); glUniformMatrix4fv(1, 1, GL_FALSE, glm::value_ptr(transformMatrix_)); glUniform1i(2, 0); glUniform1i(3, 1); glBindTexture(GL_TEXTURE_2D, tex_); if (texAnim_) { glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D_ARRAY, texAnim_); glActiveTexture(GL_TEXTURE0); glUniform1ui(4, animFrame_); } glBindVertexArray(vertexArray_); glDrawRangeElements(GL_TRIANGLES, 0, maxIndex_, numIndices_, GL_UNSIGNED_SHORT, vbo_.getOfs(indexOfs_)); } }