diff --git a/CMakeLists.txt b/CMakeLists.txt index 3dde9fc7..56223f51 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,7 +76,6 @@ if (ANDROID) "src/font.cpp" "src/glyph.cpp" "src/shader.cpp" - "src/spriteRenderer.cpp" "src/warning.cpp" "src/jGL/common.cpp" "src/id.cpp" @@ -161,7 +160,6 @@ IF (TEST_SUITE) "src/jGL/OpenGL/*.cpp" "src/jGL/shader.cpp" "src/jGL/warning.cpp" - "src/jGL/spriteRenderer.cpp" "src/id.cpp" "src/jGL/Display/*.cpp" "src/jGL/common.cpp" @@ -219,7 +217,6 @@ IF (TEST_SUITE) ${TEST_SRC} "src/jGL/shader.cpp" "src/jGL/warning.cpp" - "src/jGL/spriteRenderer.cpp" "src/id.cpp" "src/jGL/font.cpp" "src/jGL/glyph.cpp" diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index ca0edcf0..54803fdf 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(Particles) add_subdirectory(Sprite) -add_subdirectory(Shape) \ No newline at end of file +add_subdirectory(Shape) +add_subdirectory(SpriteBenchmark) \ No newline at end of file diff --git a/examples/Shape/main.cpp b/examples/Shape/main.cpp index c7b9f910..3f249084 100644 --- a/examples/Shape/main.cpp +++ b/examples/Shape/main.cpp @@ -33,8 +33,9 @@ int main(int argv, char ** argc) jGLInstance->setTextProjection(glm::ortho(0.0,double(resX),0.0,double(resY))); jGLInstance->setMSAA(1); - std::vector> shapes; + std::vector shapes; std::vector trans; + std::vector cols; RNG rng; uint64_t n = 1000000; @@ -46,16 +47,17 @@ int main(int argv, char ** argc) shapes.reserve(n); trans.reserve(n); + cols.reserve(n); for (unsigned i = 0; i < n; i++) { trans.push_back(jGL::Transform(rng.nextFloat(), rng.nextFloat(), 0.0, 0.001f)); + cols.push_back(glm::vec4(rng.nextFloat(), rng.nextFloat(), rng.nextFloat(), 1.0)); shapes.push_back ( - std::make_shared - ( - trans[i], - glm::vec4(rng.nextFloat(), rng.nextFloat(), rng.nextFloat(), 1.0) - ) + { + &trans[i], + &cols[i] + } ); circles->add(shapes[i], std::to_string(i)); diff --git a/examples/Sprite/main.cpp b/examples/Sprite/main.cpp index b6d2bf11..d21de652 100644 --- a/examples/Sprite/main.cpp +++ b/examples/Sprite/main.cpp @@ -9,17 +9,17 @@ int main(int argv, char ** argc) #ifdef MACOS conf.COCOA_RETINA = true; #endif - + jGL::DesktopDisplay display(glm::ivec2(resX, resY), "Sprite", conf); glewInit(); - + jGLInstance = std::move(std::make_unique(display.getRes())); jGL::OrthoCam camera(resX, resY, glm::vec2(0.0,0.0)); camera.setPosition(0.0f, 0.0f); - + jLog::Log log; high_resolution_clock::time_point tic, tock; @@ -85,14 +85,42 @@ int main(int argv, char ** argc) {"highest", jGL::Transform(0.6f, 0.2f, 0.0f, 0.1f)} }; + std::vector animationFrames + { + jGL::TextureRegion(0, 0, 16, 16), + jGL::TextureRegion(16, 0, 16, 16), + jGL::TextureRegion(0, 16, 16, 16), + jGL::TextureRegion(16, 16, 16, 16) + }; + + std::map reg = + { + {"sAtlas1", jGL::TextureRegion(0, 0, 16, 16)}, + {"sAtlas2", jGL::TextureRegion(16, 0, 16, 16)}, + {"sAtlas3", jGL::TextureRegion(0, 16, 16, 16)}, + {"sAtlas4", jGL::TextureRegion(16, 16, 16, 16)}, + {"sAtlasFull", animationFrames[0]}, + {"sAtlasAnimated", jGL::TextureRegion(0, 0, 16, 16)}, + {"sJerboa", jGL::TextureRegion()}, + {"sPi", jGL::TextureRegion()}, + {"sHeart", jGL::TextureRegion()}, + {"sRandom", jGL::TextureRegion()}, + {"lowest", jGL::TextureRegion()}, + {"middle", jGL::TextureRegion()}, + {"highest", jGL::TextureRegion()} + }; + + glm::vec4 colour(1.0); + sprites->setProjection(camera.getVP()); sprites->add ( { - trans["sJerboa"], - jGL::TextureRegion(), - jerboa + &trans["sJerboa"], + ®["sJerboa"], + jerboa.get(), + &colour }, "sJerboa" ); @@ -100,9 +128,10 @@ int main(int argv, char ** argc) sprites->add ( { - trans["sAtlas1"], - jGL::TextureRegion(0, 0, 16, 16), - atlas + &trans["sAtlas1"], + ®["sAtlas1"], + atlas.get(), + &colour }, "sAtlas1" ); @@ -110,9 +139,10 @@ int main(int argv, char ** argc) sprites->add ( { - trans["sAtlas2"], - jGL::TextureRegion(16, 0, 16, 16), - atlas + &trans["sAtlas2"], + ®["sAtlas2"], + atlas.get(), + &colour }, "sAtlas2" ); @@ -120,9 +150,10 @@ int main(int argv, char ** argc) sprites->add ( { - trans["sAtlas3"], - jGL::TextureRegion(0, 16, 16, 16), - atlas + &trans["sAtlas3"], + ®["sAtlas3"], + atlas.get(), + &colour }, "sAtlas3" ); @@ -130,9 +161,10 @@ int main(int argv, char ** argc) sprites->add ( { - trans["sAtlas4"], - jGL::TextureRegion(16, 16, 16, 16), - atlas + &trans["sAtlas4"], + ®["sAtlas4"], + atlas.get(), + &colour }, "sAtlas4" ); @@ -140,28 +172,23 @@ int main(int argv, char ** argc) sprites->add ( { - trans["sAtlasFull"], - jGL::TextureRegion(), - atlas + &trans["sAtlasFull"], + ®["sAtlasFull"], + atlas.get(), + &colour }, "sAtlasFull" ); - std::vector animationFrames - { - jGL::TextureRegion(0, 0, 16, 16), - jGL::TextureRegion(16, 0, 16, 16), - jGL::TextureRegion(0, 16, 16, 16), - jGL::TextureRegion(16, 16, 16, 16) - }; uint8_t animationFrame = 0; sprites->add ( { - trans["sAtlasAnimated"], - animationFrames[animationFrame], - atlas + &trans["sAtlasAnimated"], + ®["sAtlasAnimated"], + atlas.get(), + &colour }, "sAtlasAnimated" ); @@ -170,9 +197,10 @@ int main(int argv, char ** argc) sprites->add ( { - trans["sPi"], - jGL::TextureRegion(), - Pi + &trans["sPi"], + ®["sPi"], + Pi.get(), + &colour }, "sPi" ); @@ -180,9 +208,10 @@ int main(int argv, char ** argc) sprites->add ( { - trans["sHeart"], - jGL::TextureRegion(), - heart + &trans["sHeart"], + ®["sHeart"], + heart.get(), + &colour }, "sHeart" ); @@ -190,9 +219,10 @@ int main(int argv, char ** argc) sprites->add ( { - trans["sRandom"], - jGL::TextureRegion(), - random + &trans["sRandom"], + ®["sRandom"], + random.get(), + &colour }, "sRandom" ); @@ -200,9 +230,10 @@ int main(int argv, char ** argc) sprites->add ( { - trans["lowest"], - jGL::TextureRegion(), - Pi + &trans["lowest"], + ®["lowest"], + Pi.get(), + &colour }, "lowest" ); @@ -210,10 +241,10 @@ int main(int argv, char ** argc) sprites->add ( { - trans["middle"], - jGL::TextureRegion(), - heart, - 0.5f + &trans["middle"], + ®["middle"], + heart.get(), + &colour }, "middle", 1000 @@ -222,9 +253,10 @@ int main(int argv, char ** argc) sprites->add ( { - trans["highest"], - jGL::TextureRegion(), - jerboa + &trans["highest"], + ®["highest"], + jerboa.get(), + &colour }, "highest", 100000 @@ -235,21 +267,27 @@ int main(int argv, char ** argc) 2 ); - std::vector shapeTrans = + std::vector shapeTrans = { jGL::Transform(0.0, 0.0, 0.0, 1.0), jGL::Transform(0.0, 0.0, 0.0, 1.0) }; + std::vector shapeCols = + { + glm::vec4(1.0, 0.0, 0.0, 0.3), + glm::vec4(1.0, 0.0, 0.0, 0.3) + }; - auto bbPi = std::make_shared + auto bbPi = jGL::Shape ( - shapeTrans[0], - glm::vec4(1.0, 0.0, 0.0, 0.3) + &shapeTrans[0], + &shapeCols[0] ); - auto bbHeart = std::make_shared + + auto bbHeart = jGL::Shape ( - shapeTrans[1], - glm::vec4(1.0, 0.0, 0.0, 0.3) + &shapeTrans[1], + &shapeCols[1] ); shapes->add(bbPi, "pi"); @@ -275,7 +313,7 @@ int main(int argv, char ** argc) trans["sHeart"] = jGL::Transform(0.5f, 0.5f, theta, 0.1f); trans["sPi"] = jGL::Transform(0.2f, 0.2f, theta, scale); - sprites->getSprite("sAtlasAnimated").setTextureRegion(animationFrames[animationFrame]); + reg["sAtlasAnimated"] = animationFrames[animationFrame]; if (frameId % 15 == 0) { animationFrame = (animationFrame+1)%animationFrames.size(); @@ -287,26 +325,46 @@ int main(int argv, char ** argc) sprites->draw(); shapes->draw(); + if (frameId == 1) + { + sprites->remove("sJerboa"); + shapes->remove("heart"); + } + else if (frameId == 30) + { + sprites->add + ( + { + &trans["sJerboa"], + ®["sJerboa"], + jerboa.get(), + &colour + }, + "sJerboa" + ); + shapes->add(bbHeart, "heart"); + } + delta = 0.0; for (int n = 0; n < 60; n++) { delta += deltas[n]; } delta /= 60.0; - + std::stringstream debugText; double mouseX, mouseY; display.mousePosition(mouseX,mouseY); debugText << "Delta: " << fixedLengthNumber(delta,6) - << " ( FPS: " << fixedLengthNumber(1.0/delta,4) + << " ( FPS: " << fixedLengthNumber(1.0/delta,4) << ")\n" - << "Render draw time: \n" + << "Render draw time: \n" << " " << fixedLengthNumber(rdt, 6) << "\n" - << "Mouse (" << fixedLengthNumber(mouseX,4) - << "," - << fixedLengthNumber(mouseY,4) + << "Mouse (" << fixedLengthNumber(mouseX,4) + << "," + << fixedLengthNumber(mouseY,4) << ")\n" << "Mouse in Pi's BB? " << sprites->getSprite("sPi").getScreenBoundingBox(camera).in(mouseX, mouseY); @@ -334,7 +392,7 @@ int main(int argv, char ** argc) deltas[frameId] = duration_cast>(tock-tic).count(); frameId = (frameId+1) % 60; - + } jGLInstance->finish(); diff --git a/examples/SpriteBenchmark/CMakeLists.txt b/examples/SpriteBenchmark/CMakeLists.txt new file mode 100644 index 00000000..95827384 --- /dev/null +++ b/examples/SpriteBenchmark/CMakeLists.txt @@ -0,0 +1,25 @@ +set(OUTPUT_NAME SpriteBenchmark) + +file(GLOB_RECURSE SRC + "*.cpp" +) + +if (WINDOWS) + if (RELEASE) + # launch as windows, not console app - so cmd does not open as well + add_link_options(-mwindows) + endif () +else() + # so nautilus etc recognise target as executable rather than .so + add_link_options(-no-pie) +endif() + +include_directories(.) + +add_executable(${OUTPUT_NAME} ${SRC}) + +target_link_libraries(${OUTPUT_NAME} jGL) + +set_target_properties(${OUTPUT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/Examples/${OUTPUT_NAME}") + +file(COPY resource DESTINATION "${CMAKE_BINARY_DIR}/Examples/${OUTPUT_NAME}/") \ No newline at end of file diff --git a/examples/SpriteBenchmark/main.cpp b/examples/SpriteBenchmark/main.cpp new file mode 100644 index 00000000..35725768 --- /dev/null +++ b/examples/SpriteBenchmark/main.cpp @@ -0,0 +1,149 @@ +#include + +int main(int argv, char ** argc) +{ + + jGL::DesktopDisplay::Config conf; + + conf.VULKAN = false; + #ifdef MACOS + conf.COCOA_RETINA = true; + #endif + + jGL::DesktopDisplay display(glm::ivec2(resX, resY), "Sprite", conf); + + glewInit(); + + jGLInstance = std::move(std::make_unique(display.getRes())); + + jGL::OrthoCam camera(resX, resY, glm::vec2(0.0,0.0)); + + camera.setPosition(0.0f, 0.0f); + + jLog::Log log; + + high_resolution_clock::time_point tic, tock; + double rdt = 0.0; + + jGLInstance->setTextProjection(glm::ortho(0.0,double(resX),0.0,double(resY))); + jGLInstance->setMSAA(1); + + std::shared_ptr jerboa = jGLInstance->createTexture + ( + std::vector(LOGO32.begin(), LOGO32.end()), + jGL::Texture::Type::RGBA + ); + + int n = 500000; + + std::shared_ptr sprites = jGLInstance->createSpriteRenderer + ( + n + ); + + float scale = camera.screenToWorld(8.0f, 0.0f).x; + + std::vector animationFrames + { + jGL::TextureRegion(0, 0, 16, 16), + jGL::TextureRegion(16, 0, 16, 16), + jGL::TextureRegion(0, 16, 16, 16), + jGL::TextureRegion(16, 16, 16, 16) + }; + + glm::vec4 colour(1.0); + + std::vector trans; + trans.reserve(n); + RNG rng; + for (int i = 0; i < n; i++) + { + trans.push_back(jGL::Transform(rng.nextFloat(), rng.nextFloat(), 0.0, scale)); + } + + for (int i = 0; i < n; i++) + { + sprites->add + ( + {&trans[i], &animationFrames[i % animationFrames.size()], jerboa.get(), &colour}, + std::to_string(i) + ); + } + + sprites->setProjection(camera.getVP()); + + double delta = 0.0; + + float dt = 1.0/600.0; + + while (display.isOpen()) + { + tic = high_resolution_clock::now(); + + jGLInstance->beginFrame(); + + jGLInstance->clear(); + + for (auto & tr : trans) + { + tr.x = tr.x+dt*(rng.nextFloat()-0.5); + tr.y = tr.y+dt*(rng.nextFloat()-0.5); + tr.theta = tr.theta; + tr.scaleX = tr.scaleX; + } + + sprites->draw(); + + delta = 0.0; + for (int n = 0; n < 60; n++) + { + delta += deltas[n]; + } + delta /= 60.0; + + std::stringstream debugText; + + double mouseX, mouseY; + display.mousePosition(mouseX,mouseY); + + debugText << "Delta: " << fixedLengthNumber(delta,6) + << " ( FPS: " << fixedLengthNumber(1.0/delta,4) + << ")\n" + << "Render draw time: \n" + << " " << fixedLengthNumber(rdt, 6) << "\n" + << "Mouse (" << fixedLengthNumber(mouseX,4) + << "," + << fixedLengthNumber(mouseY,4) + << ")\n"; + + jGLInstance->text( + debugText.str(), + glm::vec2(resX*0.5f, resY-64.0f), + 0.5f, + glm::vec4(0.0f,0.0f,0.0f,1.0f), + glm::bvec2(true,true) + ); + + if (frameId == 30) + { + if (log.size() > 0) + { + std::cout << log << "\n"; + } + } + + jGLInstance->endFrame(); + + display.loop(); + + tock = high_resolution_clock::now(); + + deltas[frameId] = duration_cast>(tock-tic).count(); + frameId = (frameId+1) % 60; + + } + + jGLInstance->finish(); + + return 0; +} \ No newline at end of file diff --git a/examples/SpriteBenchmark/main.h b/examples/SpriteBenchmark/main.h new file mode 100644 index 00000000..2d5ad74c --- /dev/null +++ b/examples/SpriteBenchmark/main.h @@ -0,0 +1,45 @@ +#ifndef MAIN_H +#define MAIN_H + +#include +#include + +#include +#include +#include + +#include + +#include + +#include +using namespace std::chrono; + +const int resX = 800; +const int resY = 1000; + +uint8_t frameId = 0; +double deltas[60]; + +std::unique_ptr jGLInstance; + +std::string fixedLengthNumber(double x, unsigned length) +{ + std::string d = std::to_string(x); + std::string dtrunc(length,' '); + for (unsigned c = 0; c < dtrunc.length(); c++/*ayy lmao*/) + { + + if (c >= d.length()) + { + dtrunc[c] = '0'; + } + else + { + dtrunc[c] = d[c]; + } + } + return dtrunc; +} + +#endif /* MAIN_H */ diff --git a/examples/SpriteBenchmark/rand.cpp b/examples/SpriteBenchmark/rand.cpp new file mode 100644 index 00000000..8bd4a7e1 --- /dev/null +++ b/examples/SpriteBenchmark/rand.cpp @@ -0,0 +1,5 @@ +#include + +std::uniform_real_distribution RNG::floatU; +std::random_device RNG::device; +std::mt19937 RNG::engine(device()); diff --git a/examples/SpriteBenchmark/rand.h b/examples/SpriteBenchmark/rand.h new file mode 100644 index 00000000..d703110c --- /dev/null +++ b/examples/SpriteBenchmark/rand.h @@ -0,0 +1,22 @@ +#ifndef RAND_H +#define RAND_H + +#include + +class RNG +{ + +public: + + float nextFloat(){ return floatU(engine); } + +private: + + static std::uniform_real_distribution floatU; + static std::random_device device; + static std::mt19937 engine; + +}; + +#endif /* RAND_H */ + diff --git a/examples/SpriteBenchmark/resource/texture/HEART.png b/examples/SpriteBenchmark/resource/texture/HEART.png new file mode 100644 index 00000000..9d44ab97 Binary files /dev/null and b/examples/SpriteBenchmark/resource/texture/HEART.png differ diff --git a/examples/SpriteBenchmark/resource/texture/Pi.png b/examples/SpriteBenchmark/resource/texture/Pi.png new file mode 100644 index 00000000..c811289d Binary files /dev/null and b/examples/SpriteBenchmark/resource/texture/Pi.png differ diff --git a/examples/SpriteBenchmark/resource/texture/atlas.png b/examples/SpriteBenchmark/resource/texture/atlas.png new file mode 100644 index 00000000..12865eff Binary files /dev/null and b/examples/SpriteBenchmark/resource/texture/atlas.png differ diff --git a/examples/SpriteBenchmark/resource/texture/random.png b/examples/SpriteBenchmark/resource/texture/random.png new file mode 100644 index 00000000..8d5dc5b3 Binary files /dev/null and b/examples/SpriteBenchmark/resource/texture/random.png differ diff --git a/include/jGL/OpenGL/glShapeRenderer.h b/include/jGL/OpenGL/glShapeRenderer.h index 0c36c67c..02d80991 100644 --- a/include/jGL/OpenGL/glShapeRenderer.h +++ b/include/jGL/OpenGL/glShapeRenderer.h @@ -60,7 +60,7 @@ namespace jGL::GL void draw ( std::shared_ptr shader, - std::vector>> & shapes, + std::vector> & shapes, UpdateInfo info = UpdateInfo() ); diff --git a/include/jGL/OpenGL/glSpriteRenderer.h b/include/jGL/OpenGL/glSpriteRenderer.h index 8d4c7add..05da6184 100644 --- a/include/jGL/OpenGL/glSpriteRenderer.h +++ b/include/jGL/OpenGL/glSpriteRenderer.h @@ -6,10 +6,10 @@ #include namespace jGL::GL -{ +{ /** * @brief Opengl implementation of SpriteRenderer - * + * */ class glSpriteRenderer : public SpriteRenderer { @@ -23,8 +23,8 @@ namespace jGL::GL textureRegion = std::vector(sizeHint*textureRegionDim+padSprites*textureRegionDim,0.0f); textureOptions = std::vector(sizeHint*textureOptionsDim+padSprites*textureOptionsDim,0.0f); initGL(); - defaultShader = std::make_shared(vertexShader, fragmentShader); - defaultShader->use(); + shader = std::make_shared(vertexShader, fragmentShader); + shader->use(); } ~glSpriteRenderer() @@ -32,25 +32,28 @@ namespace jGL::GL freeGL(); } - void draw(std::shared_ptr shader, std::multimap ids); - void draw(std::multimap ids) { draw(defaultShader, ids); } - private: + void draw + ( + std::shared_ptr shader, + std::vector> & sprites + ); + GLuint vao, a_position, a_xytheta, a_scale, a_textureRegion, a_textureOption; - float quad[6*4] = + float quad[6*4] = { // positions / texture coords 0.5f, 0.5f, 1.0f, 1.0f, // top right 0.5f, -0.5f, 1.0f, 0.0f, // bottom right -0.5f, -0.5f, 0.0f, 0.0f, // bottom left - -0.5f, 0.5f, 0.0f, 1.0f, // top left + -0.5f, 0.5f, 0.0f, 1.0f, // top left -0.5f, -0.5f, 0.0f, 0.0f, // bottom left 0.5f, 0.5f, 1.0f, 1.0f // top right }; - std::vector xytheta; + std::vector xytheta; size_t xythetaDim = 3; size_t xythetaAttribute = 1; @@ -71,9 +74,7 @@ namespace jGL::GL void initGL(); void freeGL(); - std::shared_ptr defaultShader; - - const char * vertexShader = + const char * vertexShader = "#version " GLSL_VERSION "\n" "precision lowp float; precision lowp int;\n" "layout(location=0) in vec4 a_position;\n" @@ -97,7 +98,7 @@ namespace jGL::GL "alpha = a_textureOptions.y;\n" "}"; - const char * fragmentShader = + const char * fragmentShader = "#version " GLSL_VERSION "\n" "precision lowp float; precision lowp int;\n" "uniform sampler2D sampler0;\n" @@ -108,7 +109,7 @@ namespace jGL::GL "flat in float alpha;\n" "flat in int tex;\n" "layout(location=0) out vec4 colour;\n" - "void main(){\n" + "void main(){\n" // is this mental? "if (tex == 0) {colour = texture(sampler0, texCoord);}\n" "else if (tex == 1) {colour = texture(sampler1, texCoord);}\n" diff --git a/include/jGL/Vulkan/vkShapeRenderer.h b/include/jGL/Vulkan/vkShapeRenderer.h index 1064a0d2..0e3eb16f 100644 --- a/include/jGL/Vulkan/vkShapeRenderer.h +++ b/include/jGL/Vulkan/vkShapeRenderer.h @@ -17,7 +17,7 @@ namespace jGL::Vulkan void draw ( std::shared_ptr shader, - std::vector>> & shapes, + std::vector> & shapes, UpdateInfo info = UpdateInfo() ){} }; diff --git a/include/jGL/Vulkan/vkSpriteRenderer.h b/include/jGL/Vulkan/vkSpriteRenderer.h index b40b0587..e6df180a 100644 --- a/include/jGL/Vulkan/vkSpriteRenderer.h +++ b/include/jGL/Vulkan/vkSpriteRenderer.h @@ -13,14 +13,12 @@ namespace jGL::Vulkan vkSpriteRenderer(size_t sizeHint) : SpriteRenderer(sizeHint) {} - - void draw(std::shared_ptr shader, std::multimap ids) {TODO("jGL::Vulkan::vkSprite::draw");} - void draw(std::multimap ids) {TODO("jGL::Vulkan::vkSprite::draw");} - - - void draw(std::shared_ptr shader, std::vector ids) {TODO("jGL::Vulkan::vkSprite::draw");} - void draw(std::vector ids) {TODO("jGL::Vulkan::vkSprite::draw");} - + private: + void draw + ( + std::shared_ptr shader, + std::vector> & sprites + ){} }; } diff --git a/include/jGL/priorityStore.h b/include/jGL/priorityStore.h index d19931eb..02db87d9 100644 --- a/include/jGL/priorityStore.h +++ b/include/jGL/priorityStore.h @@ -61,7 +61,7 @@ class PriorityStore cache.reserve(sizeHint); } - void clear() + virtual void clear() { idToElement.clear(); cache.clear(); @@ -75,7 +75,7 @@ class PriorityStore * @param priority its priority. * @remark Throws std::runtime_error if the id already exists. */ - void add(std::shared_ptr s, ElementId id, Priority priority = 0) + virtual void add(T s, ElementId id, Priority priority = 0) { if (idToElement.find(id) != idToElement.end()) { @@ -87,20 +87,20 @@ class PriorityStore ( cache.begin(), cache.end(), - std::pair(Info(id, priority), nullptr), + priority, [] ( - std::pair> l, - std::pair> r + Priority p, + std::pair r ) { - return l.first.priority < r.first.priority; + return p < r.first.priority; } ); cache.insert(pos, std::pair(Info(id, priority), s)); } - void remove(ElementId id) + virtual void remove(ElementId id) { if (idToElement.find(id) == idToElement.end()) { return; } @@ -110,7 +110,7 @@ class PriorityStore ( cache.begin(), cache.end(), - [id](std::pair> v) + [id](std::pair v) { return v.first.id == id; } @@ -134,16 +134,16 @@ class PriorityStore * @brief Return a vector from overriding priorities. * * @param oids overriding priorities. - * @return std::vector>> cached values. + * @return std::vector> cached values. * @remark Overriding is expensive, but useful for drawing partially with * modest element counts. */ - std::vector>> vectorise + std::vector> vectorise ( std::multimap & oids ) { - std::vector>> s; + std::vector> s; s.reserve(oids.size()); for (const auto & id : oids) { @@ -152,19 +152,21 @@ class PriorityStore return s; } - std::shared_ptr & operator [](ElementId id) { return idToElement[id].first; } + T & operator [](ElementId id) { return idToElement[id].first; } - typename std::vector>>::const_iterator begin() const { return cache.cbegin(); } - typename std::vector>>::const_iterator end() const { return cache.cend(); } + typename std::vector>::const_iterator begin() const { return cache.cbegin(); } + typename std::vector>::const_iterator end() const { return cache.cend(); } uint64_t size() const { return cache.size(); } + bool hasId(const ElementId id) const { return idToElement.find(id) != idToElement.end(); } + protected: // fast lookup of ids - std::unordered_map, Priority>> idToElement; + std::unordered_map> idToElement; // contiguous for efficient drawing/iteration - std::vector>> cache; + std::vector> cache; }; #endif /* PRIORITYSTORE_H */ diff --git a/include/jGL/shape.h b/include/jGL/shape.h index 53ab74d3..fe657779 100644 --- a/include/jGL/shape.h +++ b/include/jGL/shape.h @@ -8,28 +8,32 @@ namespace jGL { /** * @brief A drawable shape - * @remark Detailed geometry is assumed defined in the shader. Circle and Rectangle are provided in ShapeRenderer. + * @remark Detailed geometry is assumed defined in the shader. Circle and Rectangle are provided in ShapeRenderer. */ class Shape { - + public: - Shape(const Transform & tra, glm::vec4 c) + Shape() + : transform(nullptr), colour(nullptr) + {} + + Shape(const Transform * tra, const glm::vec4 * c) : transform(tra), colour(c) {} - const Transform & transform; - glm::vec4 colour; + const Transform * transform; + const glm::vec4 * colour; /** * @brief Get the WorldBoundingBox of the Shape. - * - * @return WorldBoundingBox + * + * @return WorldBoundingBox */ WorldBoundingBox getWorldBoundingBox() const { - WorldBoundingBox wbb = + WorldBoundingBox wbb = { { glm::vec2(-0.5, -0.5), @@ -39,11 +43,11 @@ namespace jGL } }; - float ct = std::cos(transform.theta); float st = std::sin(transform.theta); + float ct = std::cos(transform->theta); float st = std::sin(transform->theta); glm::mat2 rot(ct, -st, st, ct); - glm::vec2 pos(transform.x, transform.y); - glm::vec2 scale(transform.scaleX, transform.scaleY); - + glm::vec2 pos(transform->x, transform->y); + glm::vec2 scale(transform->scaleX, transform->scaleY); + for (uint8_t i = 0; i < wbb.vertices.size(); i++) { @@ -55,9 +59,9 @@ namespace jGL /** * @brief Get the ScreenBoundingBox of the Shape. - * + * * @param camera for projection to the screen. - * @return ScreenBoundingBox + * @return ScreenBoundingBox */ ScreenBoundingBox getScreenBoundingBox(const OrthoCam & camera) { diff --git a/include/jGL/shapeRenderer.h b/include/jGL/shapeRenderer.h index 7ce7e62f..5319f454 100644 --- a/include/jGL/shapeRenderer.h +++ b/include/jGL/shapeRenderer.h @@ -64,10 +64,10 @@ namespace jGL : PriorityStore(sizeHint), shader(shader) {} - std::shared_ptr getShape(ShapeId id) { return this->operator[](id); } + Shape & getShape(ShapeId id) { return this->operator[](id); } - const Transform & getTransform(ShapeId id) { return getShape(id)->transform; } - const glm::vec4 & getColour(ShapeId id) { return getShape(id)->colour; } + const Transform * getTransform(ShapeId id) { return getShape(id).transform; } + const glm::vec4 * getColour(ShapeId id) { return getShape(id).colour; } /** * @brief Draw with overriding render priority and shader. @@ -86,7 +86,7 @@ namespace jGL UpdateInfo info = UpdateInfo() ) { - std::vector>> shapes = vectorise(ids); + std::vector> shapes = vectorise(ids); draw(shader, shapes, info); } @@ -105,7 +105,7 @@ namespace jGL UpdateInfo info = UpdateInfo() ) { - std::vector>> shapes = vectorise(ids); + std::vector> shapes = vectorise(ids); draw(shader, shapes, info); } @@ -144,7 +144,7 @@ namespace jGL virtual void draw ( std::shared_ptr shader, - std::vector>> & shapes, + std::vector> & shapes, UpdateInfo info = UpdateInfo() ) = 0; diff --git a/include/jGL/sprite.h b/include/jGL/sprite.h index eebdfa98..2288300d 100644 --- a/include/jGL/sprite.h +++ b/include/jGL/sprite.h @@ -10,11 +10,11 @@ /** * @brief A drawable graphic. - * + * * Observes a Transform (position, orientation, scale), and a Texture. - * + * * Rendered using the TextureRegion (pixel units) region of the Texture, at the alpha value. - * + * */ namespace jGL @@ -24,6 +24,8 @@ namespace jGL public: + Sprite() = default; + /** * @brief Construct a Sprite * @@ -33,69 +35,31 @@ namespace jGL * @param alpha Transparency modifier (alpha * texture alpha) */ Sprite - ( - const Transform & tra, - TextureRegion to, - const std::shared_ptr tex, - float alpha = 1.0f + ( + Transform * tra, + TextureRegion * to, + Texture * tex, + glm::vec4 * colour ) - : transform(tra), texture(tex), alpha(std::clamp(alpha, 0.0f, 1.0f)), texOffset(toNormalised(to)) + : transform(tra), + texOffset(to), + texture(tex), + colour(colour) {} - /** - * @brief Sets the sprite alpha modifier. - * - * @param a alpha value, clamping is applied: std::clamp(a, 0.0f, 1.0f). - */ - inline virtual void setAlpha(float a) { alpha = std::clamp(a, 0.0f, 1.0f); } - - /** - * @brief Get the alpha modifer. - * - * @return const float, which is in [0.0, 1.0]. - */ - const float getAlpha() const { return alpha; } - - /** - * @brief Get the TextureRegion in pixel units. - * - * @return TextureRegion - */ - TextureRegion getTextureRegion() const - { - return fromNormalised(texOffset); - } - - /** - * @brief Get the NormalisedTextureRegion in normalised units. - * - * @return TextureRegion - */ - NormalisedTextureRegion getNormalisedTextureRegion() const - { - return texOffset; - } - - /** - * @brief Set the Texture Offset from a pixel region. - * - * @remark Values outside of the textures pixel range will be clamped. - * @remark Lengths lx or ly < 0 will be set to maximum. - * - */ - void setTextureRegion(TextureRegion to) { texOffset = toNormalised(to); } - - const Transform & transform; - const std::shared_ptr texture; + Transform * transform; + TextureRegion * texOffset; + Texture * texture; + glm::vec4 * colour; /** * @brief Get the WorldBoundingBox of the Sprite. - * - * @return WorldBoundingBox + * + * @return WorldBoundingBox */ WorldBoundingBox getWorldBoundingBox() const { - WorldBoundingBox wbb = + WorldBoundingBox wbb = { { glm::vec2(-0.5, -0.5), @@ -105,11 +69,11 @@ namespace jGL } }; - float ct = std::cos(transform.theta); float st = std::sin(transform.theta); + float ct = std::cos(transform->theta); float st = std::sin(transform->theta); glm::mat2 rot(ct, -st, st, ct); - glm::vec2 pos(transform.x, transform.y); - glm::vec2 scale(transform.scaleX, transform.scaleY); - + glm::vec2 pos(transform->x, transform->y); + glm::vec2 scale(transform->scaleX, transform->scaleY); + for (uint8_t i = 0; i < wbb.vertices.size(); i++) { @@ -121,9 +85,9 @@ namespace jGL /** * @brief Get the ScreenBoundingBox of the Sprite. - * + * * @param camera for projection to the screen. - * @return ScreenBoundingBox + * @return ScreenBoundingBox */ ScreenBoundingBox getScreenBoundingBox(const OrthoCam & camera) { @@ -139,11 +103,16 @@ namespace jGL return sbb; } + NormalisedTextureRegion getNormalisedTextureRegion() const + { + return toNormalised(*texOffset); + } + protected: - NormalisedTextureRegion toNormalised(TextureRegion to) + NormalisedTextureRegion toNormalised(TextureRegion to) const { - glm::vec3 whc = texture->size(); + glm::vec3 whc = texture->size(); if (to.lx == 0){ to.lx = whc.x; } if (to.ly == 0){ to.ly = whc.y; } return NormalisedTextureRegion( @@ -164,10 +133,6 @@ namespace jGL uint16_t(to.ly * whc.y) ); } - - float alpha; - NormalisedTextureRegion texOffset; - }; } diff --git a/include/jGL/spriteRenderer.h b/include/jGL/spriteRenderer.h index 380bd1a4..333f63db 100644 --- a/include/jGL/spriteRenderer.h +++ b/include/jGL/spriteRenderer.h @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -12,132 +13,100 @@ namespace jGL { - /** + /** * @brief User name for a Sprite. - * @typedef SpriteId + * @typedef SpriteId * */ typedef std::string SpriteId; /** * @brief Renders sprites in batches, with optional render priority. - * + * * @remark Currently there are 4 texture slots loaded at once. * @remark Keeping to 4 textures is most efficient (1 draw call), atlas textures are useful for this. - * @remark RenderPriority of Sprites may lead to inefficient batching across textures. + * @remark RenderPriority of Sprites may lead to inefficient batching across textures. * Try to keep similar RenderPriority within the same texture/ group of 4 textures. */ - class SpriteRenderer + class SpriteRenderer : public PriorityStore { public: /** * @brief Largest number of concurrent textures bound for one batch. - * + * */ static const uint8_t MAX_BATCH_BOUND_TEXTURES = MAX_SPRITE_BATCH_BOUND_TEXTURES; - + /** * @brief Construct a new SpriteRenderer. - * + * * @param sizeHint Reserve some memory for this many Sprites. */ SpriteRenderer(size_t sizeHint = 8) - { - sprites.reserve(sizeHint); - textures.reserve(MAX_BATCH_BOUND_TEXTURES); - } + : PriorityStore(sizeHint) + {} - Sprite & getSprite(SpriteId id); - - const Transform & getTransform(SpriteId id) { return getSprite(id).transform; } - - /** - * @brief Get a Sprites TextureRegion - * @remark In pixel units, see TextureRegion::getTextureRegion() - * - * @param id - * @return const TextureRegion - */ - const TextureRegion getTextureRegion(SpriteId id) { return getSprite(id).getTextureRegion(); } + Sprite & getSprite(SpriteId id){ return this->operator[](id); } /** * @brief Draw with overriding render priority and shader. - * + * * @param shader An OpenGL Shader to draw all the Sprites with. * @param ids Render priorities for the Sprites. + * @remark Overriding priorities with many sprites performs poorly on cpu. + * Consider SpriteRenderer::updatePriority to cache priority. */ - virtual void draw(std::shared_ptr shader, std::multimap ids) = 0; + virtual void draw + ( + std::shared_ptr shader, + std::multimap ids + ) + { + std::vector> sprites = vectorise(ids); + draw(shader, sprites); + } /** * @brief Draw with overriding render priority. - * + * * @param ids Render priorities for the Sprites. + * @remark Overriding priorities with many sprites performs poorly on cpu. + * Consider SpritRenderer::updatePriority to cache priority. */ - virtual void draw(std::multimap ids) = 0; - + virtual void draw(std::multimap ids) + { + std::vector> sprites = vectorise(ids); + draw(shader, sprites); + } + /** * @brief Draw with overriding shader. - * - * @param shader An Shader to draw all the Sprites with. + * + * @param shader A Shader to draw all the Sprites with. */ - virtual void draw(std::shared_ptr shader) { draw(shader, ids); } + virtual void draw(std::shared_ptr shader) + { + draw(shader, cache); + } /** * @brief Draw with default shader and priority. - * + * */ - virtual void draw() { draw(ids); } - - virtual void add(Sprite s, SpriteId id, RenderPriority priority = 0); - - virtual void remove(SpriteId id) - { - if (sprites.find(id) != sprites.end()) - { - sprites.erase(id); - } - - for (auto & e : ids) - { - if (e.second == id) - { - ids.erase(e.first); - break; - } - } - } - - bool hasId(const SpriteId id) const { return sprites.find(id) != sprites.end(); } - - virtual void clear() { ids.clear(); sprites.clear(); } + virtual void draw() { draw(shader, cache); } virtual void setProjection(glm::mat4 p) {projection = p;} - virtual void updatePriority(SpriteId id, RenderPriority newPriority) - { - if (sprites.find(id) == sprites.end()){ return; } - - std::multimap::iterator iter; - for (iter = ids.begin(); iter != ids.end(); iter++) - { - if (iter->second == id) - { - ids.erase(iter); - ids.insert(std::pair(newPriority, id)); - break; - } - } - } - protected: - std::vector> textures; - - std::unordered_map sprites; - std::unordered_map spriteToTexture; + virtual void draw + ( + std::shared_ptr shader, + std::vector> & sprites + ) = 0; - std::multimap ids; + std::shared_ptr shader; glm::mat4 projection = glm::mat4(0.0f); diff --git a/src/jGL/OpenGL/glShapeRenderer.cpp b/src/jGL/OpenGL/glShapeRenderer.cpp index 896a3e7a..3cc654f3 100644 --- a/src/jGL/OpenGL/glShapeRenderer.cpp +++ b/src/jGL/OpenGL/glShapeRenderer.cpp @@ -48,7 +48,7 @@ namespace jGL::GL void glShapeRenderer::draw ( std::shared_ptr shader, - std::vector>> & shapes, + std::vector> & shapes, UpdateInfo info ) { @@ -70,23 +70,23 @@ namespace jGL::GL { if (info.xytheta) { - xytheta[i*xythetaDim] = shape.second->transform.x; - xytheta[i*xythetaDim+1] = shape.second->transform.y; - xytheta[i*xythetaDim+2] = shape.second->transform.theta; + xytheta[i*xythetaDim] = shape.second.transform->x; + xytheta[i*xythetaDim+1] = shape.second.transform->y; + xytheta[i*xythetaDim+2] = shape.second.transform->theta; } if (info.scale) { - scale[i*scaleDim] = shape.second->transform.scaleX; - scale[i*scaleDim+1] = shape.second->transform.scaleY; + scale[i*scaleDim] = shape.second.transform->scaleX; + scale[i*scaleDim+1] = shape.second.transform->scaleY; } if (info.colour) { - colours[i*coloursDim] = shape.second->colour.r; - colours[i*coloursDim+1] = shape.second->colour.g; - colours[i*coloursDim+2] = shape.second->colour.b; - colours[i*coloursDim+3] = shape.second->colour.a; + colours[i*coloursDim] = shape.second.colour->r; + colours[i*coloursDim+1] = shape.second.colour->g; + colours[i*coloursDim+2] = shape.second.colour->b; + colours[i*coloursDim+3] = shape.second.colour->a; } i += 1; diff --git a/src/jGL/OpenGL/glSpriteRenderer.cpp b/src/jGL/OpenGL/glSpriteRenderer.cpp index 8d4a767a..b28b8dcc 100644 --- a/src/jGL/OpenGL/glSpriteRenderer.cpp +++ b/src/jGL/OpenGL/glSpriteRenderer.cpp @@ -2,10 +2,14 @@ namespace jGL::GL { - void glSpriteRenderer::draw(std::shared_ptr shader, std::multimap ids) + void glSpriteRenderer::draw + ( + std::shared_ptr shader, + std::vector> & sprites + ) { - uint32_t n = ids.size(); + uint32_t n = idToElement.size(); if (xytheta.size() < xythetaDim*n) { @@ -17,46 +21,49 @@ namespace jGL::GL initGL(); } - std::vector textureIndices(ids.size(), 0); - std::vector>> batches; + std::vector>> batches; batches.reserve(n); uint64_t i = 0; - std::vector batch; + std::vector batch; - for (auto & sid : ids) + for (auto & sprite : sprites) { - size_t textureIndex = spriteToTexture[sid.second]; - bool newTexture = std::find(batch.begin(), batch.end(), textureIndex) == batch.end(); - if (newTexture) + size_t textureIndex; + Id textureId = sprite.second.texture->getId(); + auto is_equal = [textureId](Texture * t) { return t->getId() == textureId; }; + auto match = std::find_if(batch.begin(), batch.end(), is_equal); + if (match == batch.end()) { if (batch.size() == MAX_BATCH_BOUND_TEXTURES) { batches.push_back(std::pair(i, batch)); batch.clear(); } - batch.push_back(textureIndex); + batch.push_back(sprite.second.texture); + textureIndex = batch.size()-1; + } + else + { + textureIndex = std::distance(batch.begin(), match); } - const Sprite & sprite = sprites.at(sid.second); - const Transform & trans = sprite.transform; - const NormalisedTextureRegion toff = sprite.getNormalisedTextureRegion(); - const float alpha = sprite.getAlpha(); + const NormalisedTextureRegion toff = sprite.second.getNormalisedTextureRegion(); - xytheta[i*xythetaDim] = trans.x; - xytheta[i*xythetaDim+1] = trans.y; - xytheta[i*xythetaDim+2] = trans.theta; + xytheta[i*xythetaDim] = sprite.second.transform->x; + xytheta[i*xythetaDim+1] = sprite.second.transform->y; + xytheta[i*xythetaDim+2] = sprite.second.transform->theta; - scale[i*scaleDim] = trans.scaleX; - scale[i*scaleDim+1] = trans.scaleY; + scale[i*scaleDim] = sprite.second.transform->scaleX; + scale[i*scaleDim+1] = sprite.second.transform->scaleY; textureRegion[i*textureRegionDim] = toff.tx; textureRegion[i*textureRegionDim+1] = toff.ty; textureRegion[i*textureRegionDim+2] = toff.lx; textureRegion[i*textureRegionDim+3] = toff.ly; - textureOptions[i*textureOptionsDim] = float(std::distance(batch.begin(), std::find(batch.begin(), batch.end(), textureIndex))); - textureOptions[i*textureOptionsDim+1] = alpha; + textureOptions[i*textureOptionsDim] = float(textureIndex); + textureOptions[i*textureOptionsDim+1] = sprite.second.colour->a; i++; } @@ -65,7 +72,7 @@ namespace jGL::GL uint64_t b = 0; uint64_t current = 0; uint64_t next = batches[b].first-1; - std::vector::const_iterator biter; + std::vector::const_iterator biter; while (b < batches.size()) { @@ -73,7 +80,7 @@ namespace jGL::GL for (unsigned slot = 0; slot < MAX_BATCH_BOUND_TEXTURES; slot++) { if (biter == batches[b].second.cend()) { break; } - textures[*biter]->bind(slot); + (*biter)->bind(slot); biter++; } @@ -98,7 +105,7 @@ namespace jGL::GL &xytheta[current*xythetaDim] ); glBindBuffer(GL_ARRAY_BUFFER, 0); - + glBindBuffer(GL_ARRAY_BUFFER, a_scale); glBufferSubData @@ -199,7 +206,7 @@ namespace jGL::GL 0 ); glVertexAttribDivisor(0,0); - + glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ARRAY_BUFFER, a_xytheta); @@ -223,7 +230,7 @@ namespace jGL::GL 0 ); glVertexAttribDivisor(xythetaAttribute, 1); - + glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ARRAY_BUFFER, a_scale); @@ -247,7 +254,7 @@ namespace jGL::GL 0 ); glVertexAttribDivisor(scaleAttribute, 1); - + glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ARRAY_BUFFER, a_textureRegion); diff --git a/src/jGL/spriteRenderer.cpp b/src/jGL/spriteRenderer.cpp deleted file mode 100644 index 39139d62..00000000 --- a/src/jGL/spriteRenderer.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include - -namespace jGL -{ - Sprite & SpriteRenderer::getSprite(SpriteId id) - { - return sprites.at(id); - } - - void SpriteRenderer::add(Sprite s, SpriteId id, RenderPriority priority) - { - Id textureId = s.texture->getId(); - auto is_equal = [textureId](std::shared_ptr t) { return t->getId() == textureId; }; - auto match = std::find_if(textures.begin(), textures.end(), is_equal); - - if (match == textures.end()) - { - textures.push_back(s.texture); - spriteToTexture[id] = textures.size()-1; - } - else - { - spriteToTexture[id] = std::distance(textures.begin(), match); - } - - sprites.insert(std::pair(id, s)); - ids.insert(std::pair(priority, id)); - } -} \ No newline at end of file