diff --git a/CMakeLists.txt b/CMakeLists.txt index fe168775..488c0d2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,3 +9,5 @@ add_subdirectory(viewer) add_subdirectory(framework2) add_subdirectory(analyzer) + +add_subdirectory(tests) diff --git a/framework2/GTARenderer.cpp b/framework2/GTARenderer.cpp index ecd087e6..7b226bf9 100644 --- a/framework2/GTARenderer.cpp +++ b/framework2/GTARenderer.cpp @@ -422,9 +422,6 @@ void GTARenderer::renderNamedFrame(Model* model, const glm::mat4 &matrix, const culled++; continue; } - else { - rendered++; - } renderGeometry(model, g, matrix); break; @@ -452,8 +449,8 @@ void GTARenderer::renderGeometry(Model* model, size_t g, const glm::mat4& modelM glEnableVertexAttribArray(normalAttrib); glEnableVertexAttribArray(colourAttrib); - for(size_t sg = 0; sg < model->geometries[g].subgeom.size(); ++sg) - { + for(size_t sg = 0; sg <1 && sg < model->geometries[g].subgeom.size(); ++sg) + { if (model->geometries[g].materials.size() > model->geometries[g].subgeom[sg].material) { Model::Material& mat = model->geometries[g].materials[model->geometries[g].subgeom[sg].material]; @@ -487,25 +484,24 @@ void GTARenderer::renderGeometry(Model* model, size_t g, const glm::mat4& modelM glUniform1f(uniMatAmbient, mat.ambientIntensity); } - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, model->geometries[g].subgeom[sg].EBO); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, model->geometries[g].EBO); - glDrawElements((model->geometries[g].facetype == Model::Triangles ? GL_TRIANGLES : GL_TRIANGLE_STRIP), model->geometries[g].subgeom[sg].indices.size(), GL_UNSIGNED_INT, NULL); + rendered++; + + glDrawElements((model->geometries[g].facetype == Model::Triangles ? GL_TRIANGLES : GL_TRIANGLE_STRIP), model->geometries[g].indicesCount, GL_UNSIGNED_INT, NULL); } } void GTARenderer::renderModel(Model* model, const glm::mat4& modelMatrix, GTAObject* object, Animator *animator) { - for (size_t a = 0; a < model->atomics.size(); a++) + for (size_t a = 0; a < model->atomics.size(); a++) { size_t g = model->atomics[a].geometry; RW::BSGeometryBounds& bounds = model->geometries[g].geometryBounds; if(! camera.frustum.intersects(bounds.center + glm::vec3(modelMatrix[3]), bounds.radius)) { culled++; continue; - } - else { - rendered++; - } + } int32_t fi = model->atomics[a].frame; if( object && object->type() == GTAObject::Vehicle ) { diff --git a/framework2/LoaderDFF.cpp b/framework2/LoaderDFF.cpp index f3c64b38..4718a164 100644 --- a/framework2/LoaderDFF.cpp +++ b/framework2/LoaderDFF.cpp @@ -183,7 +183,7 @@ Model* LoaderDFF::loadFromMemory(char *data) { auto meshplg = extsec.readSubStructure(0); geometryStruct.subgeom.resize(meshplg.numsplits); - geometryStruct.facetype = static_cast(meshplg.facetype); + geometryStruct.facetype = static_cast(meshplg.facetype); size_t meshplgI = sizeof(RW::BSBinMeshPLG); for(size_t i = 0; i < meshplg.numsplits; ++i) { @@ -230,10 +230,13 @@ Model* LoaderDFF::loadFromMemory(char *data) // OpenGL buffer stuff glGenBuffers(1, &geometryStruct.VBO); glGenBuffers(1, &geometryStruct.EBO); + size_t Ecount = 0; for(size_t i = 0; i < geometryStruct.subgeom.size(); ++i) { - glGenBuffers(1, &(geometryStruct.subgeom[i].EBO)); + Ecount += geometryStruct.subgeom[i].indices.size(); + //glGenBuffers(1, &(geometryStruct.subgeom[i].EBO)); } + geometryStruct.indicesCount = Ecount; size_t buffsize = (geometryStruct.vertices.size() * sizeof(float) * 3) + (geometryStruct.texcoords.size() * sizeof(float) * 2) @@ -293,23 +296,33 @@ Model* LoaderDFF::loadFromMemory(char *data) indicies[i + 2] = tri.third; i += 3; } - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, geometryStruct.EBO); + + /*glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, geometryStruct.EBO); glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof(indicies), indicies, GL_STATIC_DRAW - ); - - for(size_t i = 0; i < geometryStruct.subgeom.size(); ++i) + );*/ + + // Allocate complete EBO buffer. + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, geometryStruct.EBO); + glBufferData( + GL_ELEMENT_ARRAY_BUFFER, + sizeof(uint32_t) * geometryStruct.indicesCount, + nullptr, + GL_STATIC_DRAW); + + // Upload each subgeometry chunk. + size_t subOffset = 0; + for(size_t i = 0; i < geometryStruct.subgeom.size(); ++i) { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, geometryStruct.subgeom[i].EBO); - glBufferData( - GL_ELEMENT_ARRAY_BUFFER, - sizeof(uint32_t) * geometryStruct.subgeom[i].indices.size(), - &(geometryStruct.subgeom[i].indices[0]), - GL_STATIC_DRAW - ); + glBufferSubData( + GL_ELEMENT_ARRAY_BUFFER, + subOffset, + sizeof(uint32_t) * geometryStruct.subgeom[i].indices.size(), + &(geometryStruct.subgeom[i].indices[0])); + subOffset += sizeof(uint32_t) * geometryStruct.subgeom[i].indices.size(); } geometryStruct.clumpNum = clumpID; diff --git a/framework2/TextureAtlas.cpp b/framework2/TextureAtlas.cpp new file mode 100644 index 00000000..8498b735 --- /dev/null +++ b/framework2/TextureAtlas.cpp @@ -0,0 +1,58 @@ +#include +#include + +TextureAtlas::TextureAtlas(size_t w, size_t h) + : width(w), height(h), textureName(0), X(0), Y(0), rowHeight(0) +{ + glGenTextures(1, &textureName); + glBindTexture(GL_TEXTURE_2D, textureName); + glTexImage2D( + GL_TEXTURE_2D, 0, GL_RGBA, + width, height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, nullptr + ); +} + +TextureAtlas::~TextureAtlas() +{ + glDeleteTextures(1, &textureName); +} + +void TextureAtlas::packTexture(uint8_t *data, size_t w, size_t h, float &s, float &t, float &sX, float &sY) +{ + // Ignore null pointer data for testing purposes. + if( data != nullptr ) + { + glBindTexture(GL_TEXTURE_2D, textureName); + glTexSubImage2D(GL_TEXTURE_2D, 0, X, Y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, data); + } + s = X/(float)width; + t = Y/(float)height; + sX = w/(float)width; + sY = h/(float)height; + + rowHeight = std::max(rowHeight, h); + X += w; + if( X >= width ) { + Y += rowHeight; + X = rowHeight = 0; + } +} + +bool TextureAtlas::canPack(size_t *w, size_t *h, size_t n) const +{ + size_t rwHeight = rowHeight; + size_t lX = X, lY = Y; + for(size_t i = 0; i < n; ++i) { + lX += (w[i]); + rwHeight = std::max(rwHeight, h[i]); + if(lX >= width) { + lY += rwHeight; + lX = rwHeight = 0; + } + } + if(lX <= width && lY <= height) { + return true; + } + return false; +} diff --git a/framework2/TextureLoader.cpp b/framework2/TextureLoader.cpp index a8b5e316..add225ac 100644 --- a/framework2/TextureLoader.cpp +++ b/framework2/TextureLoader.cpp @@ -37,6 +37,8 @@ bool TextureLoader::loadFromMemory(char *data) auto texNative = rootSection.readStructure(); GLuint texture = 0; + + std::cout << texNative.width << "x" << texNative.height << std::endl; if(texNative.platform != 8) { diff --git a/framework2/include/renderwure/loaders/LoaderDFF.hpp b/framework2/include/renderwure/loaders/LoaderDFF.hpp index 26e37867..c5d090f5 100644 --- a/framework2/include/renderwure/loaders/LoaderDFF.hpp +++ b/framework2/include/renderwure/loaders/LoaderDFF.hpp @@ -35,7 +35,7 @@ public: }; struct SubGeometry { - GLuint EBO; + //GLuint EBO; size_t material; std::vector indices; }; @@ -56,6 +56,7 @@ public: std::vector colours; std::vector vertices; std::vector normals; + uint32_t indicesCount; std::vector materials; std::vector subgeom; diff --git a/framework2/include/renderwure/render/TextureAtlas.hpp b/framework2/include/renderwure/render/TextureAtlas.hpp new file mode 100644 index 00000000..56df25e2 --- /dev/null +++ b/framework2/include/renderwure/render/TextureAtlas.hpp @@ -0,0 +1,43 @@ +#pragma once +#ifndef _TEXTURE_ATLAS_HPP_ +#define _TEXTURE_ATLAS_HPP_ +#include + +class TextureAtlas +{ + /** + * @brief width Width of the backing texture. + */ + size_t width; + /** + * @brief height Height of the backing texture. + */ + size_t height; + + GLuint textureName; + + size_t X; // X edge of latest texture. + size_t Y; // Y of current row. + size_t rowHeight; // Maximum texture height for the current row. + +public: + + TextureAtlas(size_t w, size_t h); + + ~TextureAtlas(); + + void packTexture(uint8_t* data, size_t w, size_t h, float& s, float& t, float& sX, float& sY); + + /** + * @brief canPack Returns true if enough space remains in the atlas + * for the given texture sizes to be packed. + * @param w + * @param h + * @param n + * @return True on success, false on failure. + */ + bool canPack(size_t* w, size_t* h, size_t n) const; + +}; + +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 00000000..586e2436 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,16 @@ + +FILE(GLOB TEST_SOURCES "*.cpp") +ADD_DEFINITIONS(-DBOOST_TEST_DYN_LINK) + +add_executable(run_tests ${TEST_SOURCES}) + +include_directories(include) +include_directories(${PROJECT_SOURCE_DIR}/engine/include) + +find_package(Boost COMPONENTS unit_test_framework REQUIRED) + +include_directories(../framework2/include) + +target_link_libraries(run_tests renderware sfml-window sfml-system GL ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}) + +add_test(UnitTests run_tests) diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 00000000..1ba9240f --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,21 @@ +#define BOOST_TEST_MODULE gtfw +#include +#include + +// Many tests require OpenGL be functional, seems like a reasonable solution. + +class GlobalFixture +{ +public: + sf::Window wnd; + + GlobalFixture() { + wnd.create(sf::VideoMode(640, 360), "Testing"); + } + + ~GlobalFixture() { + wnd.close(); + } +}; + +BOOST_GLOBAL_FIXTURE(GlobalFixture) diff --git a/tests/test_atlas.cpp b/tests/test_atlas.cpp new file mode 100644 index 00000000..111b4777 --- /dev/null +++ b/tests/test_atlas.cpp @@ -0,0 +1,56 @@ +#include +#include + +BOOST_AUTO_TEST_SUITE(TextureAtlasTests) + +BOOST_AUTO_TEST_CASE(atlas_fill_test) +{ + TextureAtlas atlas(16, 16); + + size_t dim = 16; + + BOOST_CHECK( atlas.canPack(&dim, &dim, 1) ); + + float s, t, w, h; + + atlas.packTexture(nullptr, dim, dim, s, t, w, h); + + BOOST_CHECK( s == 0.f ); + BOOST_CHECK( t == 0.f ); + BOOST_CHECK( w == 1.f ); + BOOST_CHECK( h == 1.f ); + + BOOST_CHECK( atlas.canPack(&dim, &dim, 1) == false ); +} + +BOOST_AUTO_TEST_CASE(atlas_pack_test) +{ + TextureAtlas atlas(4, 4); + size_t dim = 1; + + uint8_t pixels[] = { 0xFF, 0x00, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0xFF }; + + float s, t, w, h; + + atlas.packTexture(pixels+0, dim, dim, s, t, w, h); + BOOST_CHECK( s == 0.f && t == 0.f && w == 0.25f && h == 0.25f ); + atlas.packTexture(pixels+4, dim, dim, s, t, w, h); + BOOST_CHECK( s == 0.25f && t == 0.f && w == 0.25f && h == 0.25f ); + atlas.packTexture(pixels+8, dim, dim, s, t, w, h); + BOOST_CHECK( s == 0.5f && t == 0.f && w == 0.25f && h == 0.25f ); + atlas.packTexture(pixels+12, dim, dim, s, t, w, h); + BOOST_CHECK( s == 0.75f && t == 0.f && w == 0.25f && h == 0.25f ); + + BOOST_CHECK( atlas.canPack(&dim, &dim, 1) == true ); + + uint8_t outPixels[4*4*4]; + glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, outPixels); + for(size_t p = 0; p < 16; ++p) { + BOOST_CHECK(outPixels[p] == pixels[p]); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/viewer/main.cpp b/viewer/main.cpp index 7f749e44..adc7361f 100644 --- a/viewer/main.cpp +++ b/viewer/main.cpp @@ -121,6 +121,10 @@ void init(std::string gtapath) debugDrawer->setShaderProgram(gta->renderer.worldProgram); debugDrawer->setDebugMode(btIDebugDraw::DBG_DrawWireframe); gta->dynamicsWorld->setDebugDrawer(debugDrawer); + + std::cout << "Loaded " + << gta->gameData.models.size() << " models, " + << gta->gameData.textureLoader.textures.size() << " textures" << std::endl; } void update(float dt)