From 52537715ef0905627edd2e5769dd6f26b43ee9ee Mon Sep 17 00:00:00 2001 From: EmaMaker Date: Sun, 23 Apr 2023 16:28:36 +0200 Subject: [PATCH 1/6] chunks: make chunk state atomic --- include/chunk.hpp | 13 +++++++------ src/chunk.cpp | 5 +++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/include/chunk.hpp b/include/chunk.hpp index 2fdc6e7..180b903 100644 --- a/include/chunk.hpp +++ b/include/chunk.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -24,9 +25,9 @@ namespace Chunk constexpr uint8_t CHUNK_STATE_GENERATED = 1; constexpr uint8_t CHUNK_STATE_MESHED = 2; - constexpr uint8_t CHUNK_STATE_MESH_LOADED = 3; - constexpr uint8_t CHUNK_STATE_LOADED = 4; - constexpr uint8_t CHUNK_STATE_EMPTY = 7; + constexpr uint8_t CHUNK_STATE_MESH_LOADED = 4; + constexpr uint8_t CHUNK_STATE_LOADED = 8; + constexpr uint8_t CHUNK_STATE_EMPTY = 64; int coord3DTo1D(int x, int y, int z); @@ -39,8 +40,8 @@ namespace Chunk public: glm::vec3 getPosition() { return this->position; } - std::bitset<8> getTotalState() { return this->state; } - bool getState(uint8_t n) { return this->state.test(n); } + uint8_t getTotalState() { return this->state; } + bool getState(uint8_t n) { return (this->state & n) == n; } void setState(uint8_t nstate, bool value); void setBlock(Block b, int x, int y, int z); @@ -62,7 +63,7 @@ namespace Chunk glm::vec3 position{}; IntervalMap blocks{}; - std::bitset<8> state{0}; + std::atomic_uint8_t state{0}; }; }; diff --git a/src/chunk.cpp b/src/chunk.cpp index c96f7ef..d621a44 100644 --- a/src/chunk.cpp +++ b/src/chunk.cpp @@ -25,6 +25,7 @@ namespace Chunk glGenBuffers(1, &(this->VBO)); glGenBuffers(1, &(this->EBO)); + mutex_state.unlock(); } Chunk ::~Chunk() @@ -60,8 +61,8 @@ namespace Chunk void Chunk::setState(uint8_t nstate, bool value) { if (value) - this->state.set((size_t)nstate); + this->state.fetch_or(nstate); else - this->state.reset((size_t)nstate); + this->state.fetch_and(~nstate); } } From 1d3132cf3ccacb72d804ad44be8d3535f7fbea28 Mon Sep 17 00:00:00 2001 From: EmaMaker Date: Sat, 29 Apr 2023 14:54:09 +0200 Subject: [PATCH 2/6] use oneTBB library --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c9d04f5..f05a4ea 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,5 +5,5 @@ set(SOURCE_FILES main.cpp chunk.cpp chunkmanager.cpp chunkmesher.cpp chunkgenera add_executable(OpenGLTest ${SOURCE_FILES}) -target_link_libraries(OpenGLTest glfw glad glm) +target_link_libraries(OpenGLTest glfw tbb glad glm) install(TARGETS OpenGLTest DESTINATION ${DIVISIBLE_INSTALL_BIN_DIR}) From 78e3bc11e6bb4093944b951a67102a0e62176ce6 Mon Sep 17 00:00:00 2001 From: EmaMaker Date: Sat, 29 Apr 2023 14:54:43 +0200 Subject: [PATCH 3/6] initial update and render with concurrent DS --- include/camera.hpp | 11 +- include/chunk.hpp | 8 +- include/chunkmanager.hpp | 19 +-- include/renderer.hpp | 4 + src/chunk.cpp | 19 +-- src/chunkgenerator.cpp | 1 + src/chunkmanager.cpp | 297 ++++++--------------------------------- src/chunkmesher.cpp | 9 +- src/main.cpp | 17 +-- src/renderer.cpp | 120 ++++++++++------ 10 files changed, 168 insertions(+), 337 deletions(-) diff --git a/include/camera.hpp b/include/camera.hpp index 198536b..ce5c591 100644 --- a/include/camera.hpp +++ b/include/camera.hpp @@ -6,7 +6,7 @@ #include #include -#include +#include class Camera { @@ -38,6 +38,9 @@ public: if (glfwGetKey(window, GLFW_KEY_Z) == GLFW_PRESS) this->cameraPos -= cameraSpeed * cameraUp; + posX = cameraPos.x; + posY = cameraPos.y; + posZ = cameraPos.z; direction.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch)); direction.y = sin(glm::radians(pitch)); @@ -78,6 +81,10 @@ public: glm::mat4 getView() { return view; } glm::mat4 getProjection() { return projection; } + float getAtomicPosX() { return posX; } + float getAtomicPosY() { return posY; } + float getAtomicPosZ() { return posZ; } + // Plane extraction as per Gribb&Hartmann // 6 planes, each with 4 components (a,b,c,d) void getFrustumPlanes(glm::vec4 planes[6], bool normalize) @@ -114,6 +121,8 @@ private: float lastX = 400, lastY = 300; float yaw, pitch; + + std::atomic posX, posY, posZ; }; #endif diff --git a/include/chunk.hpp b/include/chunk.hpp index 180b903..bde004e 100644 --- a/include/chunk.hpp +++ b/include/chunk.hpp @@ -27,6 +27,8 @@ namespace Chunk constexpr uint8_t CHUNK_STATE_MESHED = 2; constexpr uint8_t CHUNK_STATE_MESH_LOADED = 4; constexpr uint8_t CHUNK_STATE_LOADED = 8; + constexpr uint8_t CHUNK_STATE_OUTOFVISION = 16; + constexpr uint8_t CHUNK_STATE_UNLOADED = 32; constexpr uint8_t CHUNK_STATE_EMPTY = 64; int coord3DTo1D(int x, int y, int z); @@ -39,6 +41,9 @@ namespace Chunk ~Chunk(); public: + void createBuffers(); + void deleteBuffers(); + glm::vec3 getPosition() { return this->position; } uint8_t getTotalState() { return this->state; } bool getState(uint8_t n) { return (this->state & n) == n; } @@ -52,9 +57,8 @@ namespace Chunk public: GLuint VAO{0}, VBO{0}, EBO{0}, colorBuffer{0}, vIndex{0}; + std::atomic unload_timer{0}; - std::mutex mutex_state; - std::vector vertices; std::vector colors; std::vector indices; diff --git a/include/chunkmanager.hpp b/include/chunkmanager.hpp index 5661dd3..9216d05 100644 --- a/include/chunkmanager.hpp +++ b/include/chunkmanager.hpp @@ -1,33 +1,24 @@ #ifndef CHUNKMANAGER_H #define CHUNKMANAGER_H -// Second to be passed outside of render distance for a chunk to be destroyed +// Seconds to be passed outside of render distance for a chunk to be destroyed #define UNLOAD_TIMEOUT 10 -#include #include + #include "chunk.hpp" #include "globals.hpp" namespace chunkmanager { - std::thread initGenThread(); - std::thread initMeshThread(); - void stopGenThread(); - void stopMeshThread(); - - void mesh(); - void generate(); - - void init(); + std::thread init(); void blockpick(bool place); uint32_t calculateIndex(uint16_t i, uint16_t j, uint16_t k); + void stop(); void destroy(); - std::unordered_map& getChunks(); std::array, chunks_volume>& getChunksIndices(); - void update(float deltaTime); - void updateChunk(uint32_t, uint16_t, uint16_t, uint16_t); + void update(); } #endif diff --git a/include/renderer.hpp b/include/renderer.hpp index a59e783..546ea50 100644 --- a/include/renderer.hpp +++ b/include/renderer.hpp @@ -1,6 +1,9 @@ #ifndef RENDERER_H #define RENDERER_H +#include + +#include "chunk.hpp" #include "shader.hpp" namespace renderer{ @@ -8,6 +11,7 @@ namespace renderer{ void render(); void destroy(); Shader* getRenderShader(); + oneapi::tbb::concurrent_unordered_set& getChunksToRender(); }; diff --git a/src/chunk.cpp b/src/chunk.cpp index d621a44..821b9db 100644 --- a/src/chunk.cpp +++ b/src/chunk.cpp @@ -19,27 +19,30 @@ namespace Chunk { this->position = pos; this->setState(CHUNK_STATE_EMPTY, true); + } + Chunk ::~Chunk() + { + vertices.clear(); + indices.clear(); + colors.clear(); + this->deleteBuffers(); + } + + void Chunk::createBuffers(){ glGenVertexArrays(1, &(this->VAO)); glGenBuffers(1, &(this->colorBuffer)); glGenBuffers(1, &(this->VBO)); glGenBuffers(1, &(this->EBO)); - mutex_state.unlock(); } - Chunk ::~Chunk() - { + void Chunk::deleteBuffers(){ glDeleteBuffers(1, &(this->colorBuffer)); glDeleteBuffers(1, &(this->VBO)); glDeleteBuffers(1, &(this->EBO)); glDeleteVertexArrays(1, &(this->VAO)); - vertices.clear(); - indices.clear(); - colors.clear(); - - mutex_state.unlock(); } Block Chunk::getBlock(int x, int y, int z) diff --git a/src/chunkgenerator.cpp b/src/chunkgenerator.cpp index c80a0c6..1ebec6b 100644 --- a/src/chunkgenerator.cpp +++ b/src/chunkgenerator.cpp @@ -84,6 +84,7 @@ void generateNoise(Chunk::Chunk *chunk) } chunk->setBlocks(block_prev_start, CHUNK_VOLUME, block_prev); + chunk->setState(Chunk::CHUNK_STATE_GENERATED, true); } void generateNoise3D(Chunk::Chunk *chunk) { diff --git a/src/chunkmanager.cpp b/src/chunkmanager.cpp index 2245326..aa63d6e 100644 --- a/src/chunkmanager.cpp +++ b/src/chunkmanager.cpp @@ -1,105 +1,36 @@ +#include "chunkmanager.hpp" + +#include +#include +#include + #include #include -#include +#include #include "chunk.hpp" #include "chunkgenerator.hpp" -#include "chunkmanager.hpp" #include "chunkmesher.hpp" #include "globals.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include +#include "renderer.hpp" namespace chunkmanager { - std::unordered_map chunks; + typedef oneapi::tbb::concurrent_hash_map ChunkTable; + ChunkTable chunks; + + //std::unordered_map chunks; std::array, chunks_volume> chunks_indices; - // thread management - std::mutex mutex_queue_generate; - std::mutex mutex_queue_mesh; - std::set to_generate; - std::set to_mesh; - std::atomic_bool generate_should_run; - std::atomic_bool mesh_should_run; - - // update variables - uint8_t f = 0; - int rr{RENDER_DISTANCE * RENDER_DISTANCE}; - glm::vec3 cameraPos; - int chunkX, chunkY, chunkZ; - - // disposal - std::unordered_map to_delete; - std::set to_delete_delete; - - void mesh() - { - while (mesh_should_run) - if (mutex_queue_mesh.try_lock()) - { - for (const auto &c : to_mesh) - { - if (c->mutex_state.try_lock()) - { - chunkmesher::mesh(c); - c->setState(Chunk::CHUNK_STATE_MESHED, true); - c->mutex_state.unlock(); - } - } - to_mesh.clear(); - mutex_queue_mesh.unlock(); - } - } - - void generate() - { - while (generate_should_run) - if (mutex_queue_generate.try_lock()) - { - for (const auto &c : to_generate) - { - if (c->mutex_state.try_lock()) - { - generateChunk(c); - c->setState(Chunk::CHUNK_STATE_GENERATED, true); - c->mutex_state.unlock(); - } - } - to_generate.clear(); - mutex_queue_generate.unlock(); - } - } - - std::thread initMeshThread() - { - mesh_should_run = true; - std::thread mesh_thread(mesh); - return mesh_thread; - } - - std::thread initGenThread() - { - generate_should_run = true; - std::thread gen_thread(generate); - return gen_thread; - } - - void init(){ + std::atomic_bool should_run; + std::thread init(){ int index{0}; + int rr{RENDER_DISTANCE * RENDER_DISTANCE}; int xp{0}, x{0}; bool b = true; - // Iterate over all chunks, in concentric spheres starting fron the player and going - // outwards. Alternate left and right + // Iterate over all chunks, in concentric spheres starting fron the player and going outwards. Alternate left and right // Eq. of the sphere (x - a)² + (y - b)² + (z - c)² = r² while (xp <= RENDER_DISTANCE) { @@ -131,168 +62,36 @@ namespace chunkmanager } else b = false; } + + should_run = true; + std::thread update_thread (update); + return update_thread; } - void update(float deltaTime) - { - // Try to lock resources - f = 0; - f |= mutex_queue_generate.try_lock(); - f |= mutex_queue_mesh.try_lock() << 1; + std::vector chunks_todelete; + void update(){ + while(should_run) { + //cameraPos = theCamera.getPos(); + int chunkX=static_cast(theCamera.getAtomicPosX() / CHUNK_SIZE); + int chunkY=static_cast(theCamera.getAtomicPosY() / CHUNK_SIZE); + int chunkZ=static_cast(theCamera.getAtomicPosZ() / CHUNK_SIZE); - cameraPos = theCamera.getPos(); - chunkX=static_cast(cameraPos.x) / CHUNK_SIZE; - chunkY=static_cast(cameraPos.y) / CHUNK_SIZE; - chunkZ=static_cast(cameraPos.z) / CHUNK_SIZE; + // Update other chunks + for(int i = 0; i < chunks_volume; i++) { + const uint16_t x = chunks_indices[i][0] + chunkX; + const uint16_t y = chunks_indices[i][1] + chunkY; + const uint16_t z = chunks_indices[i][2] + chunkZ; + const uint32_t index = calculateIndex(x, y, z); - // Use time in float to be consistent with glfw - float currentTime = glfwGetTime(); + ChunkTable::accessor a; + if(!chunks.find(a, index)) chunks.emplace(a, std::make_pair(index, new Chunk::Chunk(glm::vec3(x,y,z)))); - // Check for far chunks that need to be cleaned up from memory - int nUnloaded{0}; - for(const auto& n : chunks){ - Chunk::Chunk* c = n.second; - int x{(int)(c->getPosition().x)}; - int y{(int)(c->getPosition().y)}; - int z{(int)(c->getPosition().z)}; - if( (chunkX-x)*(chunkX-x) + (chunkY-y)*(chunkY-y) + (chunkZ-z)*(chunkZ-z) >= - (int)(RENDER_DISTANCE*1.5)*(int)(RENDER_DISTANCE*1.5)) - if(to_delete.find(n.first) == to_delete.end()) - to_delete.insert(std::make_pair(n.first, currentTime)); - } - for(const auto& n :to_delete){ - if( currentTime>=n.second + UNLOAD_TIMEOUT) { - delete chunks.at(n.first); - chunks.erase(n.first); - nUnloaded++; + if(! (a->second->getState(Chunk::CHUNK_STATE_GENERATED))) generateChunk(a->second); + if(! (a->second->getState(Chunk::CHUNK_STATE_MESHED))) chunkmesher::mesh(a->second); - // Delete afterwards to avoid exception due to invalid iterators - to_delete_delete.insert(n.first); - } - } - for(uint32_t i : to_delete_delete) to_delete.erase(i); - to_delete_delete.clear(); - if(nUnloaded) std::cout << "Unloaded " << nUnloaded << " chunks\n"; + renderer::getChunksToRender().insert(a->second); - for(int i = 0; i < chunks_volume; i++) - updateChunk(calculateIndex(chunks_indices[i][0] + chunkX, - chunks_indices[i][1] + chunkY, - chunks_indices[i][2] + chunkZ), - chunks_indices[i][0] + chunkX, - chunks_indices[i][1] + chunkY, - chunks_indices[i][2] + chunkZ); - - // Unlock mutexes if previously locked. Unlocking a mutex not locked by the current thread - // or already locked is undefined behaviour, so checking has to be done - if ((f & 1)) - mutex_queue_generate.unlock(); - if ((f & 2)) - mutex_queue_mesh.unlock(); - } - - // Generation and meshing happen in two separate threads from the main one - // Chunk states are used to decide which actions need to be done on the chunk and sets+mutexes - // to pass the chunks to be operated on between the threads. - // Uploading data to GPU still needs to be done in the main thread, or another OpenGL context - // needs to be opened, which further complicates stuff. - void updateChunk(uint32_t index, uint16_t i, uint16_t j, uint16_t k) - { - if (chunks.find(index) == chunks.end()) - { - Chunk::Chunk *c = new Chunk::Chunk(glm::vec3(i, j, k)); - chunks.insert(std::make_pair(index, c)); - } - else - { - Chunk::Chunk *c = chunks.at(index); - - if (!(c->mutex_state.try_lock())) - return; - - if (!c->getState(Chunk::CHUNK_STATE_GENERATED)) - { - if (f & 1) - to_generate.insert(c); - } - else - { - if (!c->getState(Chunk::CHUNK_STATE_MESHED)) - { - if (f & 2) - to_mesh.insert(c); - } - else - { - if (!c->getState(Chunk::CHUNK_STATE_MESH_LOADED)) chunkmesher::sendtogpu(c); - } - } - c->mutex_state.unlock(); - } - } - - void blockpick(bool place){ - // cast a ray from the camera in the direction pointed by the camera itself - glm::vec3 pos = cameraPos; - for(float t = 0.0; t <= 10.0; t += 0.5){ - // traverse the ray a block at the time - pos = theCamera.getPos() + t * theCamera.getFront(); - - // get which chunk and block the ray is at - int px = ((int)(pos.x))/CHUNK_SIZE; - int py = ((int)(pos.y))/CHUNK_SIZE; - int pz = ((int)(pos.z))/CHUNK_SIZE; - int bx = pos.x - px*CHUNK_SIZE; - int by = pos.y - py*CHUNK_SIZE; - int bz = pos.z - pz*CHUNK_SIZE; - - // exit early if the position is invalid or the chunk does not exist - if(px < 0 || py < 0 || pz < 0) return; - if(chunks.find(calculateIndex(px, py, pz)) == chunks.end()) return; - - Chunk::Chunk* c = chunks.at(calculateIndex(px, py, pz)); - Block b = c->getBlock(bx, by, bz); - - // if the block is non empty - if(b != Block::AIR){ - - // if placing a new block - if(place){ - // Go half a block backwards on the ray, to check the block where the ray was - // coming from - // Doing this and not using normal adds the unexpected (and unwanted) ability to - // place blocks diagonally, without faces colliding with the block that has - // been clicked - pos -= theCamera.getFront()*0.5f; - - int px1 = ((int)(pos.x))/CHUNK_SIZE; - int py1 = ((int)(pos.y))/CHUNK_SIZE; - int pz1 = ((int)(pos.z))/CHUNK_SIZE; - int bx1 = pos.x - px1*CHUNK_SIZE; - int by1 = pos.y - py1*CHUNK_SIZE; - int bz1 = pos.z - pz1*CHUNK_SIZE; - - // exit early if the position is invalid or the chunk does not exist - if(px1 < 0 || py1 < 0 || pz1 < 0) return; - if(chunks.find(calculateIndex(px1, py1, pz1)) == chunks.end()) return; - - Chunk::Chunk* c1 = chunks.at(calculateIndex(px1, py1, pz1)); - // place the new block (only stone for now) - c1->setBlock( Block::STONE, bx1, by1, bz1); - - // update the mesh of the chunk - chunkmesher::mesh(c1); - // mark the mesh of the chunk the be updated on the gpu - c1->setState(Chunk::CHUNK_STATE_MESH_LOADED, false); - }else{ - // replace the current block with air to remove it - c->setBlock( Block::AIR, bx, by, bz); - - // update the mesh of the chunk - chunkmesher::mesh(c); - // mark the mesh of the chunk the be updated on the gpu - c->setState(Chunk::CHUNK_STATE_MESH_LOADED, false); - } - break; + a.release(); } } } @@ -302,21 +101,13 @@ namespace chunkmanager return i | (j << 10) | (k << 20); } - std::unordered_map& getChunks(){ return chunks; } std::array, chunks_volume>& getChunksIndices(){ return chunks_indices; } - void destroy() - { - for (auto &n : chunks) - delete n.second; - } - - void stopGenThread(){ - generate_should_run = false; - } - - void stopMeshThread(){ - mesh_should_run = false; + void stop() { should_run=false; } + void destroy(){ + /*for(const auto& n : chunks){ + delete n.second; + }*/ } }; diff --git a/src/chunkmesher.cpp b/src/chunkmesher.cpp index b3d4e56..061365a 100755 --- a/src/chunkmesher.cpp +++ b/src/chunkmesher.cpp @@ -35,12 +35,16 @@ void mesh(Chunk::Chunk* chunk) chunk->vIndex = 0; // Abort if chunk is empty - if(chunk->getState(Chunk::CHUNK_STATE_EMPTY)) return; + if(chunk->getState(Chunk::CHUNK_STATE_EMPTY)){ + chunk->setState(Chunk::CHUNK_STATE_MESHED, true); + return; + } // convert tree to array since it is easier to work with it int length{0}; std::unique_ptr blocks = chunk->getBlocksArray(&length); if(length == 0) { + chunk->setState(Chunk::CHUNK_STATE_MESHED, true); return; } @@ -180,13 +184,14 @@ void mesh(Chunk::Chunk* chunk) } } } + + chunk->setState(Chunk::CHUNK_STATE_MESHED, true); } void sendtogpu(Chunk::Chunk* chunk) { if (chunk->vIndex > 0) { - // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). glBindVertexArray(chunk->VAO); diff --git a/src/main.cpp b/src/main.cpp index 51e61f1..a8df144 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -63,9 +63,7 @@ int main() SpaceFilling::initLUT(); renderer::init(); - chunkmanager::init(); - std::thread genThread = chunkmanager::initGenThread(); - std::thread meshThread = chunkmanager::initMeshThread(); + std::thread chunkmanager_thread = chunkmanager::init(); while (!glfwWindowShouldClose(window)) { @@ -94,9 +92,6 @@ int main() // Reset blockping timeout if 200ms have passed if(glfwGetTime() - lastBlockPick > 0.1) blockpick = false; - // ChunkManager - chunkmanager::update(deltaTime); - // Render pass renderer::render(); @@ -106,10 +101,8 @@ int main() } // Stop threads and wait for them to finish - chunkmanager::stopGenThread(); - chunkmanager::stopMeshThread(); - genThread.join(); - meshThread.join(); + chunkmanager::stop(); + chunkmanager_thread.join(); // Cleanup allocated memory chunkmanager::destroy(); @@ -136,13 +129,13 @@ void processInput(GLFWwindow *window) glfwSetWindowShouldClose(window, true); if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2) == GLFW_PRESS && !blockpick){ - chunkmanager::blockpick(false); + //chunkmanager::blockpick(false); blockpick=true; lastBlockPick=glfwGetTime(); } if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS && !blockpick){ - chunkmanager::blockpick(true); + //chunkmanager::blockpick(true); blockpick=true; lastBlockPick=glfwGetTime(); } diff --git a/src/renderer.cpp b/src/renderer.cpp index b2450a8..8214eb0 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -3,14 +3,18 @@ #include "chunkmanager.hpp" #include "chunkmesher.hpp" #include "globals.hpp" - #include "stb_image.h" namespace renderer{ + oneapi::tbb::concurrent_unordered_set chunks_torender; + oneapi::tbb::concurrent_unordered_set render_todelete; + Shader* theShader; GLuint chunkTexture; Shader* getRenderShader() { return theShader; } + oneapi::tbb::concurrent_unordered_set& getChunksToRender(){ return chunks_torender; } + void init(){ // Create Shader @@ -40,63 +44,89 @@ namespace renderer{ int total{0}, toGpu{0}; glm::vec4 frustumPlanes[6]; theCamera.getFrustumPlanes(frustumPlanes, true); - - glm::vec3 cameraPos = theCamera.getPos(); - int chunkX=static_cast(cameraPos.x) / CHUNK_SIZE; - int chunkY=static_cast(cameraPos.y) / CHUNK_SIZE; - int chunkZ=static_cast(cameraPos.z) / CHUNK_SIZE; + glm::vec3 cameraPos = theCamera.getPos(); + glm::vec3 cameraChunkPos = cameraPos / static_cast(CHUNK_SIZE); theShader->use(); theShader->setVec3("viewPos", cameraPos); - for(int i = 0; i < chunks_volume; i++) { - Chunk::Chunk* c = chunkmanager::getChunks().at(chunkmanager::calculateIndex(chunkmanager::getChunksIndices()[i][0] + - chunkX, chunkmanager::getChunksIndices()[i][1] + chunkY, chunkmanager::getChunksIndices()[i][2] + chunkZ)); - // Frustum Culling of chunk - total++; - glm::vec3 chunk = c->getPosition(); - glm::vec4 chunkW = glm::vec4(chunk.x*static_cast(CHUNK_SIZE), chunk.y*static_cast(CHUNK_SIZE), chunk.z*static_cast(CHUNK_SIZE),1.0); - glm::mat4 model = glm::translate(glm::mat4(1.0), ((float)CHUNK_SIZE) * chunk); + for(Chunk::Chunk* c : chunks_torender){ + if(! (c->getState(Chunk::CHUNK_STATE_MESHED))) continue; - // Check if all the corners of the chunk are outside any of the planes - // TODO (?) implement frustum culling as per (Inigo Quilez)[https://iquilezles.org/articles/frustumcorrect/], and check each - // plane against each corner of the chunk - bool out=false; - int a{0}; - for(int p = 0; p < 6; p++){ - a = 0; - for(int i = 0; i < 8; i++) a += glm::dot(frustumPlanes[p], glm::vec4(chunkW.x + ((float)(i & 1))*CHUNK_SIZE, chunkW.y - + ((float)((i & 2) >> 1))*CHUNK_SIZE, chunkW.z + ((float)((i & 4) >> 2))*CHUNK_SIZE, 1.0)) < 0.0; - - if(a==8){ - out=true; - break; - } + // If the mesh is ready send it to the gpu + if(! (c->getState(Chunk::CHUNK_STATE_MESH_LOADED))){ + if(c->VAO == 0) c->createBuffers(); + chunkmesher::sendtogpu(c); + c->setState(Chunk::CHUNK_STATE_MESH_LOADED, true); } - if (!out) - { - toGpu++; + if(glm::distance(c->getPosition(), cameraChunkPos) < static_cast(RENDER_DISTANCE)){ + // if chunk can be seen + // reset out-of-vision and unload flags + c->setState(Chunk::CHUNK_STATE_OUTOFVISION, false); + c->setState(Chunk::CHUNK_STATE_UNLOADED, false); - if(c->getState(Chunk::CHUNK_STATE_MESH_LOADED) && c->vIndex > 0) - { - // glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // wireframe mode - theShader->setMat4("model", model); - theShader->setMat4("view", theCamera.getView()); - theShader->setMat4("projection", theCamera.getProjection()); + // Perform frustum culling and eventually render + glm::vec3 chunk = c->getPosition(); + glm::vec4 chunkW = glm::vec4(chunk.x*static_cast(CHUNK_SIZE), chunk.y*static_cast(CHUNK_SIZE), chunk.z*static_cast(CHUNK_SIZE),1.0); + glm::mat4 model = glm::translate(glm::mat4(1.0), ((float)CHUNK_SIZE) * chunk); - glBindVertexArray(c->VAO); - glDrawElements(GL_TRIANGLES, c->vIndex , GL_UNSIGNED_INT, 0); - glBindVertexArray(0); + // Check if all the corners of the chunk are outside any of the planes + // TODO (?) implement frustum culling as per (Inigo Quilez)[https://iquilezles.org/articles/frustumcorrect/], and check each + // plane against each corner of the chunk + bool out=false; + int a{0}; + for(int p = 0; p < 6; p++){ + a = 0; + for(int i = 0; i < 8; i++) a += glm::dot(frustumPlanes[p], glm::vec4(chunkW.x + ((float)(i & 1))*CHUNK_SIZE, chunkW.y + + ((float)((i & 2) >> 1))*CHUNK_SIZE, chunkW.z + ((float)((i & 4) >> 2))*CHUNK_SIZE, 1.0)) < 0.0; + + if(a==8){ + out=true; + break; + } } + + if (!out) + { + if(c->vIndex > 0) + { + // glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // wireframe mode + theShader->setMat4("model", model); + theShader->setMat4("view", theCamera.getView()); + theShader->setMat4("projection", theCamera.getProjection()); + + glBindVertexArray(c->VAO); + glDrawElements(GL_TRIANGLES, c->vIndex , GL_UNSIGNED_INT, 0); + glBindVertexArray(0); + } + } + + }else{ + // When the chunk is outside render distance + + if(c->getState(Chunk::CHUNK_STATE_OUTOFVISION) && glfwGetTime() - + c->unload_timer > UNLOAD_TIMEOUT){ + // If chunk was already out and enough time has passed + // Mark the chunk to be unloaded + c->setState(Chunk::CHUNK_STATE_UNLOADED, true); + // And delete it from the render set + render_todelete.insert(c); + } else{ + // Mark has out of vision and annotate when it started + c->setState(Chunk::CHUNK_STATE_OUTOFVISION, true); + c->setState(Chunk::CHUNK_STATE_UNLOADED, false); + c->unload_timer = glfwGetTime(); + } + } } - - //std::cout << "Chunks to mesh: " << to_mesh.size() << "\n"; - //std::cout << "Chunks to generate: " << to_generate.size() << "\n"; - //std::cout << "Total chunks to draw: " << total << ". Sent to GPU: " << toGpu << "\n"; + + for(Chunk::Chunk* i : render_todelete){ + chunks_torender.unsafe_erase(i); + } + render_todelete.clear(); } - void destroy(){ delete theShader; } From e225babb0c14eb9a16b495baa3f0f8610a7f8bfb Mon Sep 17 00:00:00 2001 From: EmaMaker Date: Sat, 20 May 2023 21:19:44 +0200 Subject: [PATCH 4/6] chunkmesher: external queue of chunk mesh data instead of every chunk having its own queue This decreases usage of ram --- CMakeLists.txt | 1 + include/camera.hpp | 2 +- include/chunk.hpp | 6 +- include/chunkmanager.hpp | 3 + include/chunkmesher.hpp | 21 ++-- include/renderer.hpp | 7 +- src/chunk.cpp | 4 - src/chunkgenerator.cpp | 10 +- src/chunkmanager.cpp | 18 +++- src/chunkmesher.cpp | 203 +++++++++++++++++++++------------------ src/renderer.cpp | 84 +++++++++------- 11 files changed, 202 insertions(+), 157 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 17b0a22..b4183c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,7 @@ project(cmake-project-template) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -O3") +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g") set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR}) diff --git a/include/camera.hpp b/include/camera.hpp index ce5c591..ab73bfb 100644 --- a/include/camera.hpp +++ b/include/camera.hpp @@ -112,7 +112,7 @@ public: private: - glm::vec3 cameraPos = glm::vec3(0.0, 80.0f, 0.0f); + glm::vec3 cameraPos = glm::vec3(256.0, 80.0f, 256.0f); glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f); glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f); glm::vec3 direction = glm::vec3(0.0f); diff --git a/include/chunk.hpp b/include/chunk.hpp index bde004e..303fb31 100644 --- a/include/chunk.hpp +++ b/include/chunk.hpp @@ -56,12 +56,8 @@ namespace Chunk std::unique_ptr getBlocksArray(int* len) { return (this->blocks.toArray(len)); } public: - GLuint VAO{0}, VBO{0}, EBO{0}, colorBuffer{0}, vIndex{0}; + GLuint VAO{0}, VBO{0}, EBO{0}, colorBuffer{0}, numVertices{0}; std::atomic unload_timer{0}; - - std::vector vertices; - std::vector colors; - std::vector indices; private: glm::vec3 position{}; diff --git a/include/chunkmanager.hpp b/include/chunkmanager.hpp index 9216d05..5c296fa 100644 --- a/include/chunkmanager.hpp +++ b/include/chunkmanager.hpp @@ -6,6 +6,8 @@ #include +#include + #include "chunk.hpp" #include "globals.hpp" @@ -17,6 +19,7 @@ namespace chunkmanager void stop(); void destroy(); + oneapi::tbb::concurrent_queue& getDeleteVector(); std::array, chunks_volume>& getChunksIndices(); void update(); } diff --git a/include/chunkmesher.hpp b/include/chunkmesher.hpp index 5533568..ff26f4d 100644 --- a/include/chunkmesher.hpp +++ b/include/chunkmesher.hpp @@ -1,22 +1,29 @@ #ifndef CHUNKMESH_H #define CHUNKMESH_H -#include -#include - #include #include +#include +#include +#include + #include "chunk.hpp" #include "globals.hpp" #include "shader.hpp" namespace chunkmesher{ - void mesh(Chunk::Chunk* chunk); - void sendtogpu(Chunk::Chunk* chunk); - void draw(Chunk::Chunk* chunk, glm::mat4 model); + struct MeshData{ + Chunk::Chunk* chunk; + std::vector vertices; + std::vector colors; + std::vector indices; + }; + oneapi::tbb::concurrent_queue& getMeshDataQueue(); - void quad(Chunk::Chunk* chunk, glm::vec3 bottomLeft, glm::vec3 topLeft, glm::vec3 topRight, + void mesh(Chunk::Chunk* chunk); + void sendtogpu(MeshData* mesh_data); + void quad(MeshData* mesh_data, glm::vec3 bottomLeft, glm::vec3 topLeft, glm::vec3 topRight, glm::vec3 bottomRight, glm::vec3 normal, Block block, int dim, bool backFace); } diff --git a/include/renderer.hpp b/include/renderer.hpp index 546ea50..f8a7f69 100644 --- a/include/renderer.hpp +++ b/include/renderer.hpp @@ -2,16 +2,21 @@ #define RENDERER_H #include +#include #include "chunk.hpp" +#include "chunkmesher.hpp" #include "shader.hpp" namespace renderer{ + typedef oneapi::tbb::concurrent_unordered_set RenderSet; + void init(); void render(); void destroy(); Shader* getRenderShader(); - oneapi::tbb::concurrent_unordered_set& getChunksToRender(); + RenderSet& getChunksToRender(); + oneapi::tbb::concurrent_queue& getMeshDataQueue(); }; diff --git a/src/chunk.cpp b/src/chunk.cpp index 821b9db..4f98a8d 100644 --- a/src/chunk.cpp +++ b/src/chunk.cpp @@ -23,10 +23,6 @@ namespace Chunk Chunk ::~Chunk() { - vertices.clear(); - indices.clear(); - colors.clear(); - this->deleteBuffers(); } void Chunk::createBuffers(){ diff --git a/src/chunkgenerator.cpp b/src/chunkgenerator.cpp index 1ebec6b..16d5c4e 100644 --- a/src/chunkgenerator.cpp +++ b/src/chunkgenerator.cpp @@ -14,8 +14,8 @@ #define NOISE_DIRT_MIN 2 #define NOISE_DIRT_X_MULT 0.001f #define NOISE_DIRT_Z_MULT 0.001f -#define NOISE_GRASS_X_MULT 0.035f -#define NOISE_GRASS_Z_MULT 0.035f +#define NOISE_GRASS_X_MULT 0.018f +#define NOISE_GRASS_Z_MULT 0.018f void generatePyramid(Chunk::Chunk *chunk); void generateNoise(Chunk::Chunk *chunk); @@ -56,10 +56,12 @@ void generateNoise(Chunk::Chunk *chunk) int z = HILBERT_XYZ_DECODE[s][2] + CHUNK_SIZE * chunk->getPosition().z; int d2 = HILBERT_XYZ_DECODE[s][0] * CHUNK_SIZE + HILBERT_XYZ_DECODE[s][2]; - if (grassNoiseLUT[d2] == -1) + if (grassNoiseLUT[d2] == -1){ grassNoiseLUT[d2] = GRASS_OFFSET + (int)((0.5 + noiseGen1.eval(x * NOISE_GRASS_X_MULT, z * NOISE_GRASS_Z_MULT) * NOISE_GRASS_MULT)); - if (dirtNoiseLUT[d2] == -1) + } + if (dirtNoiseLUT[d2] == -1){ dirtNoiseLUT[d2] = NOISE_DIRT_MIN + (int)((0.5 + noiseGen2.eval(x * NOISE_DIRT_X_MULT, z * NOISE_DIRT_Z_MULT) * NOISE_DIRT_MULT)); + } int grassNoise = grassNoiseLUT[d2]; int dirtNoise = dirtNoiseLUT[d2]; diff --git a/src/chunkmanager.cpp b/src/chunkmanager.cpp index aa63d6e..01c69d4 100644 --- a/src/chunkmanager.cpp +++ b/src/chunkmanager.cpp @@ -2,10 +2,12 @@ #include #include +#include #include #include #include + #include #include "chunk.hpp" @@ -23,6 +25,8 @@ namespace chunkmanager std::array, chunks_volume> chunks_indices; std::atomic_bool should_run; + + int chunks_volume_real; std::thread init(){ int index{0}; int rr{RENDER_DISTANCE * RENDER_DISTANCE}; @@ -62,27 +66,34 @@ namespace chunkmanager } else b = false; } + chunks_volume_real = index; + + // Also init mesh data queue + for(int i = 0; i < 10; i++) + chunkmesher::getMeshDataQueue().push(new chunkmesher::MeshData()); should_run = true; std::thread update_thread (update); return update_thread; } - std::vector chunks_todelete; + oneapi::tbb::concurrent_queue chunks_todelete; + int nUnloaded{0}; void update(){ while(should_run) { - //cameraPos = theCamera.getPos(); int chunkX=static_cast(theCamera.getAtomicPosX() / CHUNK_SIZE); int chunkY=static_cast(theCamera.getAtomicPosY() / CHUNK_SIZE); int chunkZ=static_cast(theCamera.getAtomicPosZ() / CHUNK_SIZE); // Update other chunks - for(int i = 0; i < chunks_volume; i++) { + for(int i = 0; i < chunks_volume_real; i++) { const uint16_t x = chunks_indices[i][0] + chunkX; const uint16_t y = chunks_indices[i][1] + chunkY; const uint16_t z = chunks_indices[i][2] + chunkZ; const uint32_t index = calculateIndex(x, y, z); + if(x > 1023 || y > 1023 || z > 1023) continue; + ChunkTable::accessor a; if(!chunks.find(a, index)) chunks.emplace(a, std::make_pair(index, new Chunk::Chunk(glm::vec3(x,y,z)))); @@ -101,6 +112,7 @@ namespace chunkmanager return i | (j << 10) | (k << 20); } + oneapi::tbb::concurrent_queue& getDeleteVector(){ return chunks_todelete; } std::array, chunks_volume>& getChunksIndices(){ return chunks_indices; } void stop() { should_run=false; } diff --git a/src/chunkmesher.cpp b/src/chunkmesher.cpp index 061365a..b787d73 100755 --- a/src/chunkmesher.cpp +++ b/src/chunkmesher.cpp @@ -5,13 +5,20 @@ #include "chunk.hpp" #include "chunkmesher.hpp" #include "globals.hpp" +#include "renderer.hpp" #include "spacefilling.hpp" #include "utils.hpp" namespace chunkmesher{ + +oneapi::tbb::concurrent_queue MeshDataQueue; + +oneapi::tbb::concurrent_queue& getMeshDataQueue(){ return MeshDataQueue; } void mesh(Chunk::Chunk* chunk) { + MeshData* mesh_data; + if(!MeshDataQueue.try_pop(mesh_data)) return; /* * Taking inspiration from 0fps and the jme3 porting at @@ -29,14 +36,16 @@ void mesh(Chunk::Chunk* chunk) */ // Cleanup previous data - chunk->vertices.clear(); - chunk->indices.clear(); - chunk->colors.clear(); - chunk->vIndex = 0; + chunk->numVertices = 0; + mesh_data->chunk = chunk; + mesh_data->vertices.clear(); + mesh_data->indices.clear(); + mesh_data->colors.clear(); // Abort if chunk is empty if(chunk->getState(Chunk::CHUNK_STATE_EMPTY)){ chunk->setState(Chunk::CHUNK_STATE_MESHED, true); + renderer::getMeshDataQueue().push(mesh_data); return; } @@ -45,6 +54,7 @@ void mesh(Chunk::Chunk* chunk) std::unique_ptr blocks = chunk->getBlocksArray(&length); if(length == 0) { chunk->setState(Chunk::CHUNK_STATE_MESHED, true); + renderer::getMeshDataQueue().push(mesh_data); return; } @@ -150,7 +160,7 @@ void mesh(Chunk::Chunk* chunk) dv[2] = 0; dv[v] = h; - quad(chunk, glm::vec3(x[0], x[1], x[2]), + quad(mesh_data, glm::vec3(x[0], x[1], x[2]), glm::vec3(x[0] + du[0], x[1] + du[1], x[2] + du[2]), glm::vec3(x[0] + du[0] + dv[0], x[1] + du[1] + dv[1], x[2] + du[2] + dv[2]), @@ -186,17 +196,21 @@ void mesh(Chunk::Chunk* chunk) } chunk->setState(Chunk::CHUNK_STATE_MESHED, true); + renderer::getMeshDataQueue().push(mesh_data); + return; } -void sendtogpu(Chunk::Chunk* chunk) +void sendtogpu(MeshData* mesh_data) { - if (chunk->vIndex > 0) + if (mesh_data->chunk->numVertices > 0) { - // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). - glBindVertexArray(chunk->VAO); + if(mesh_data->chunk->VAO == 0) mesh_data->chunk->createBuffers(); - glBindBuffer(GL_ARRAY_BUFFER, chunk->VBO); - glBufferData(GL_ARRAY_BUFFER, chunk->vertices.size() * sizeof(GLfloat), &(chunk->vertices[0]), GL_STATIC_DRAW); + // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). + glBindVertexArray(mesh_data->chunk->VAO); + + glBindBuffer(GL_ARRAY_BUFFER, mesh_data->chunk->VBO); + glBufferData(GL_ARRAY_BUFFER, mesh_data->vertices.size() * sizeof(GLfloat), &(mesh_data->vertices[0]), GL_STATIC_DRAW); // position attribute glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void *)0); @@ -207,12 +221,12 @@ void sendtogpu(Chunk::Chunk* chunk) sizeof(float))); glEnableVertexAttribArray(1); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, chunk->EBO); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, chunk->indices.size() * sizeof(GLuint), &(chunk->indices[0]), GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh_data->chunk->EBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, mesh_data->indices.size() * sizeof(GLuint), &(mesh_data->indices[0]), GL_STATIC_DRAW); // texcoords attribute - glBindBuffer(GL_ARRAY_BUFFER, chunk->colorBuffer); - glBufferData(GL_ARRAY_BUFFER, chunk->colors.size() * sizeof(GLfloat), &(chunk->colors[0]), GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, mesh_data->chunk->colorBuffer); + glBufferData(GL_ARRAY_BUFFER, mesh_data->colors.size() * sizeof(GLfloat), &(mesh_data->colors[0]), GL_STATIC_DRAW); glEnableVertexAttribArray(2); glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0); @@ -220,119 +234,118 @@ void sendtogpu(Chunk::Chunk* chunk) glBindVertexArray(0); // save the number of indices of the mesh, it is needed later for drawing - chunk->vIndex = (GLuint)(chunk->indices.size()); + mesh_data->chunk->numVertices = (GLuint)(mesh_data->indices.size()); // once data has been sent to the GPU, it can be cleared from system RAM - chunk->vertices.clear(); - chunk->indices.clear(); - chunk->colors.clear(); + mesh_data->vertices.clear(); + mesh_data->indices.clear(); + mesh_data->colors.clear(); } // mark the chunk mesh has loaded on GPU - chunk->setState(Chunk::CHUNK_STATE_MESH_LOADED, true); + mesh_data->chunk->setState(Chunk::CHUNK_STATE_MESH_LOADED, true); } -void quad(Chunk::Chunk* chunk, glm::vec3 bottomLeft, glm::vec3 topLeft, glm::vec3 topRight, +void quad(MeshData* mesh_data, glm::vec3 bottomLeft, glm::vec3 topLeft, glm::vec3 topRight, glm::vec3 bottomRight, glm::vec3 normal, Block block, int dim, bool backFace) { + mesh_data->vertices.push_back(bottomLeft.x); + mesh_data->vertices.push_back(bottomLeft.y); + mesh_data->vertices.push_back(bottomLeft.z); + mesh_data->vertices.push_back(normal.x); + mesh_data->vertices.push_back(normal.y); + mesh_data->vertices.push_back(normal.z); - chunk->vertices.push_back(bottomLeft.x); - chunk->vertices.push_back(bottomLeft.y); - chunk->vertices.push_back(bottomLeft.z); - chunk->vertices.push_back(normal.x); - chunk->vertices.push_back(normal.y); - chunk->vertices.push_back(normal.z); + mesh_data->vertices.push_back(bottomRight.x); + mesh_data->vertices.push_back(bottomRight.y); + mesh_data->vertices.push_back(bottomRight.z); + mesh_data->vertices.push_back(normal.x); + mesh_data->vertices.push_back(normal.y); + mesh_data->vertices.push_back(normal.z); - chunk->vertices.push_back(bottomRight.x); - chunk->vertices.push_back(bottomRight.y); - chunk->vertices.push_back(bottomRight.z); - chunk->vertices.push_back(normal.x); - chunk->vertices.push_back(normal.y); - chunk->vertices.push_back(normal.z); + mesh_data->vertices.push_back(topLeft.x); + mesh_data->vertices.push_back(topLeft.y); + mesh_data->vertices.push_back(topLeft.z); + mesh_data->vertices.push_back(normal.x); + mesh_data->vertices.push_back(normal.y); + mesh_data->vertices.push_back(normal.z); - chunk->vertices.push_back(topLeft.x); - chunk->vertices.push_back(topLeft.y); - chunk->vertices.push_back(topLeft.z); - chunk->vertices.push_back(normal.x); - chunk->vertices.push_back(normal.y); - chunk->vertices.push_back(normal.z); - - chunk->vertices.push_back(topRight.x); - chunk->vertices.push_back(topRight.y); - chunk->vertices.push_back(topRight.z); - chunk->vertices.push_back(normal.x); - chunk->vertices.push_back(normal.y); - chunk->vertices.push_back(normal.z); + mesh_data->vertices.push_back(topRight.x); + mesh_data->vertices.push_back(topRight.y); + mesh_data->vertices.push_back(topRight.z); + mesh_data->vertices.push_back(normal.x); + mesh_data->vertices.push_back(normal.y); + mesh_data->vertices.push_back(normal.z); // texcoords if(dim == 0){ - chunk->colors.push_back(0); - chunk->colors.push_back(0); - chunk->colors.push_back(((int)block) - 2); + mesh_data->colors.push_back(0); + mesh_data->colors.push_back(0); + mesh_data->colors.push_back(((int)block) - 2); - chunk->colors.push_back(abs(bottomRight.z - bottomLeft.z)); - chunk->colors.push_back(abs(bottomRight.y - bottomLeft.y)); - chunk->colors.push_back(((int)block) - 2); + mesh_data->colors.push_back(abs(bottomRight.z - bottomLeft.z)); + mesh_data->colors.push_back(abs(bottomRight.y - bottomLeft.y)); + mesh_data->colors.push_back(((int)block) - 2); - chunk->colors.push_back(abs(topLeft.z - bottomLeft.z)); - chunk->colors.push_back(abs(topLeft.y - bottomLeft.y)); - chunk->colors.push_back(((int)block) - 2); + mesh_data->colors.push_back(abs(topLeft.z - bottomLeft.z)); + mesh_data->colors.push_back(abs(topLeft.y - bottomLeft.y)); + mesh_data->colors.push_back(((int)block) - 2); - chunk->colors.push_back(abs(topRight.z - bottomLeft.z)); - chunk->colors.push_back(abs(topRight.y - bottomLeft.y)); - chunk->colors.push_back(((int)block) - 2); + mesh_data->colors.push_back(abs(topRight.z - bottomLeft.z)); + mesh_data->colors.push_back(abs(topRight.y - bottomLeft.y)); + mesh_data->colors.push_back(((int)block) - 2); }else if(dim == 1){ - chunk->colors.push_back(0); - chunk->colors.push_back(0); - chunk->colors.push_back(((int)block) - 2); + mesh_data->colors.push_back(0); + mesh_data->colors.push_back(0); + mesh_data->colors.push_back(((int)block) - 2); - chunk->colors.push_back(abs(bottomRight.z - bottomLeft.z)); - chunk->colors.push_back(abs(bottomRight.x - bottomLeft.x)); - chunk->colors.push_back(((int)block) - 2); + mesh_data->colors.push_back(abs(bottomRight.z - bottomLeft.z)); + mesh_data->colors.push_back(abs(bottomRight.x - bottomLeft.x)); + mesh_data->colors.push_back(((int)block) - 2); - chunk->colors.push_back(abs(topLeft.z - bottomLeft.z)); - chunk->colors.push_back(abs(topLeft.x - bottomLeft.x)); - chunk->colors.push_back(((int)block) - 2); + mesh_data->colors.push_back(abs(topLeft.z - bottomLeft.z)); + mesh_data->colors.push_back(abs(topLeft.x - bottomLeft.x)); + mesh_data->colors.push_back(((int)block) - 2); - chunk->colors.push_back(abs(topRight.z - bottomLeft.z)); - chunk->colors.push_back(abs(topRight.x - bottomLeft.x)); - chunk->colors.push_back(((int)block) - 2); + mesh_data->colors.push_back(abs(topRight.z - bottomLeft.z)); + mesh_data->colors.push_back(abs(topRight.x - bottomLeft.x)); + mesh_data->colors.push_back(((int)block) - 2); }else{ - chunk->colors.push_back(0); - chunk->colors.push_back(0); - chunk->colors.push_back(((int)block) - 2); + mesh_data->colors.push_back(0); + mesh_data->colors.push_back(0); + mesh_data->colors.push_back(((int)block) - 2); - chunk->colors.push_back(abs(bottomRight.x - bottomLeft.x)); - chunk->colors.push_back(abs(bottomRight.y - bottomLeft.y)); - chunk->colors.push_back(((int)block) - 2); + mesh_data->colors.push_back(abs(bottomRight.x - bottomLeft.x)); + mesh_data->colors.push_back(abs(bottomRight.y - bottomLeft.y)); + mesh_data->colors.push_back(((int)block) - 2); - chunk->colors.push_back(abs(topLeft.x - bottomLeft.x)); - chunk->colors.push_back(abs(topLeft.y - bottomLeft.y)); - chunk->colors.push_back(((int)block) - 2); + mesh_data->colors.push_back(abs(topLeft.x - bottomLeft.x)); + mesh_data->colors.push_back(abs(topLeft.y - bottomLeft.y)); + mesh_data->colors.push_back(((int)block) - 2); - chunk->colors.push_back(abs(topRight.x - bottomLeft.x)); - chunk->colors.push_back(abs(topRight.y - bottomLeft.y)); - chunk->colors.push_back(((int)block) - 2); + mesh_data->colors.push_back(abs(topRight.x - bottomLeft.x)); + mesh_data->colors.push_back(abs(topRight.y - bottomLeft.y)); + mesh_data->colors.push_back(((int)block) - 2); } if (backFace) { - chunk->indices.push_back(chunk->vIndex + 2); - chunk->indices.push_back(chunk->vIndex); - chunk->indices.push_back(chunk->vIndex + 1); - chunk->indices.push_back(chunk->vIndex + 1); - chunk->indices.push_back(chunk->vIndex + 3); - chunk->indices.push_back(chunk->vIndex + 2); + mesh_data->indices.push_back(mesh_data->chunk->numVertices + 2); + mesh_data->indices.push_back(mesh_data->chunk->numVertices); + mesh_data->indices.push_back(mesh_data->chunk->numVertices + 1); + mesh_data->indices.push_back(mesh_data->chunk->numVertices + 1); + mesh_data->indices.push_back(mesh_data->chunk->numVertices + 3); + mesh_data->indices.push_back(mesh_data->chunk->numVertices + 2); } else { - chunk->indices.push_back(chunk->vIndex + 2); - chunk->indices.push_back(chunk->vIndex + 3); - chunk->indices.push_back(chunk->vIndex + 1); - chunk->indices.push_back(chunk->vIndex + 1); - chunk->indices.push_back(chunk->vIndex); - chunk->indices.push_back(chunk->vIndex + 2); + mesh_data->indices.push_back(mesh_data->chunk->numVertices + 2); + mesh_data->indices.push_back(mesh_data->chunk->numVertices + 3); + mesh_data->indices.push_back(mesh_data->chunk->numVertices + 1); + mesh_data->indices.push_back(mesh_data->chunk->numVertices + 1); + mesh_data->indices.push_back(mesh_data->chunk->numVertices); + mesh_data->indices.push_back(mesh_data->chunk->numVertices + 2); } - chunk->vIndex += 4; + mesh_data->chunk->numVertices += 4; } }; diff --git a/src/renderer.cpp b/src/renderer.cpp index 8214eb0..a02899a 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -1,19 +1,24 @@ #include "renderer.hpp" +#include +#include + #include "chunkmanager.hpp" #include "chunkmesher.hpp" #include "globals.hpp" #include "stb_image.h" namespace renderer{ - oneapi::tbb::concurrent_unordered_set chunks_torender; - oneapi::tbb::concurrent_unordered_set render_todelete; + RenderSet chunks_torender; + oneapi::tbb::concurrent_vector render_todelete; + oneapi::tbb::concurrent_queue MeshDataQueue; Shader* theShader; GLuint chunkTexture; Shader* getRenderShader() { return theShader; } - oneapi::tbb::concurrent_unordered_set& getChunksToRender(){ return chunks_torender; } + RenderSet& getChunksToRender(){ return chunks_torender; } + oneapi::tbb::concurrent_queue& getMeshDataQueue(){ return MeshDataQueue; } void init(){ @@ -50,18 +55,45 @@ namespace renderer{ theShader->use(); theShader->setVec3("viewPos", cameraPos); - for(Chunk::Chunk* c : chunks_torender){ + for(auto& n : render_todelete){ + // we can get away with unsafe erase as access to the container is only done by this + // thread + n->deleteBuffers(); + chunks_torender.unsafe_erase(n); + } + render_todelete.clear(); + + chunkmesher::MeshData* m; + while(MeshDataQueue.try_pop(m)){ + chunkmesher::sendtogpu(m); + chunkmesher::getMeshDataQueue().push(m); + } + + for(auto& c : chunks_torender){ if(! (c->getState(Chunk::CHUNK_STATE_MESHED))) continue; + + float dist = glm::distance(c->getPosition(), cameraChunkPos); + if(dist >= static_cast(RENDER_DISTANCE)*1.75f){ + // When the chunk is outside render distance - // If the mesh is ready send it to the gpu - if(! (c->getState(Chunk::CHUNK_STATE_MESH_LOADED))){ - if(c->VAO == 0) c->createBuffers(); - chunkmesher::sendtogpu(c); - c->setState(Chunk::CHUNK_STATE_MESH_LOADED, true); - } + if(c->getState(Chunk::CHUNK_STATE_OUTOFVISION)){ + if(glfwGetTime() - c->unload_timer > UNLOAD_TIMEOUT){ + // If chunk was already out and enough time has passed + // Mark the chunk to be unloaded + // And mark is to be removed from the render set + render_todelete.push_back(c); + chunkmanager::getDeleteVector().push(c); + } + } else{ + // Mark has out of vision and annotate when it started + c->setState(Chunk::CHUNK_STATE_OUTOFVISION, true); + c->setState(Chunk::CHUNK_STATE_UNLOADED, false); + c->unload_timer = glfwGetTime(); + } + + }else if(dist <= static_cast(RENDER_DISTANCE)){ + if(!c->getState(Chunk::CHUNK_STATE_MESH_LOADED)) continue; - if(glm::distance(c->getPosition(), cameraChunkPos) < static_cast(RENDER_DISTANCE)){ - // if chunk can be seen // reset out-of-vision and unload flags c->setState(Chunk::CHUNK_STATE_OUTOFVISION, false); c->setState(Chunk::CHUNK_STATE_UNLOADED, false); @@ -89,7 +121,7 @@ namespace renderer{ if (!out) { - if(c->vIndex > 0) + if(c->numVertices > 0) { // glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // wireframe mode theShader->setMat4("model", model); @@ -97,36 +129,14 @@ namespace renderer{ theShader->setMat4("projection", theCamera.getProjection()); glBindVertexArray(c->VAO); - glDrawElements(GL_TRIANGLES, c->vIndex , GL_UNSIGNED_INT, 0); + glDrawElements(GL_TRIANGLES, c->numVertices , GL_UNSIGNED_INT, 0); glBindVertexArray(0); } } - - }else{ - // When the chunk is outside render distance - - if(c->getState(Chunk::CHUNK_STATE_OUTOFVISION) && glfwGetTime() - - c->unload_timer > UNLOAD_TIMEOUT){ - // If chunk was already out and enough time has passed - // Mark the chunk to be unloaded - c->setState(Chunk::CHUNK_STATE_UNLOADED, true); - // And delete it from the render set - render_todelete.insert(c); - } else{ - // Mark has out of vision and annotate when it started - c->setState(Chunk::CHUNK_STATE_OUTOFVISION, true); - c->setState(Chunk::CHUNK_STATE_UNLOADED, false); - c->unload_timer = glfwGetTime(); - } - } } - - for(Chunk::Chunk* i : render_todelete){ - chunks_torender.unsafe_erase(i); - } - render_todelete.clear(); } + void destroy(){ delete theShader; } From 0acb8c7c1e03534a97fb1285b1cf0a7a8c1f19ef Mon Sep 17 00:00:00 2001 From: EmaMaker Date: Sat, 20 May 2023 21:51:48 +0200 Subject: [PATCH 5/6] free chunks from memory --- src/chunkmanager.cpp | 14 ++++++++++++ src/renderer.cpp | 54 ++++++++++++++++++++++---------------------- 2 files changed, 41 insertions(+), 27 deletions(-) diff --git a/src/chunkmanager.cpp b/src/chunkmanager.cpp index 01c69d4..90644f0 100644 --- a/src/chunkmanager.cpp +++ b/src/chunkmanager.cpp @@ -104,6 +104,20 @@ namespace chunkmanager a.release(); } + + Chunk::Chunk* n; + nUnloaded = 0; + while(chunks_todelete.try_pop(n)){ + int x = static_cast(n->getPosition().x); + int y = static_cast(n->getPosition().y); + int z = static_cast(n->getPosition().z); + if(x > 1023 || y > 1023 || z > 1023) continue; + const uint32_t index = calculateIndex(x, y, z); + + delete n; + chunks.erase(index); + nUnloaded++; + } } } diff --git a/src/renderer.cpp b/src/renderer.cpp index a02899a..84fb7a2 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -55,14 +55,6 @@ namespace renderer{ theShader->use(); theShader->setVec3("viewPos", cameraPos); - for(auto& n : render_todelete){ - // we can get away with unsafe erase as access to the container is only done by this - // thread - n->deleteBuffers(); - chunks_torender.unsafe_erase(n); - } - render_todelete.clear(); - chunkmesher::MeshData* m; while(MeshDataQueue.try_pop(m)){ chunkmesher::sendtogpu(m); @@ -73,25 +65,7 @@ namespace renderer{ if(! (c->getState(Chunk::CHUNK_STATE_MESHED))) continue; float dist = glm::distance(c->getPosition(), cameraChunkPos); - if(dist >= static_cast(RENDER_DISTANCE)*1.75f){ - // When the chunk is outside render distance - - if(c->getState(Chunk::CHUNK_STATE_OUTOFVISION)){ - if(glfwGetTime() - c->unload_timer > UNLOAD_TIMEOUT){ - // If chunk was already out and enough time has passed - // Mark the chunk to be unloaded - // And mark is to be removed from the render set - render_todelete.push_back(c); - chunkmanager::getDeleteVector().push(c); - } - } else{ - // Mark has out of vision and annotate when it started - c->setState(Chunk::CHUNK_STATE_OUTOFVISION, true); - c->setState(Chunk::CHUNK_STATE_UNLOADED, false); - c->unload_timer = glfwGetTime(); - } - - }else if(dist <= static_cast(RENDER_DISTANCE)){ + if(dist <= static_cast(RENDER_DISTANCE)){ if(!c->getState(Chunk::CHUNK_STATE_MESH_LOADED)) continue; // reset out-of-vision and unload flags @@ -133,8 +107,34 @@ namespace renderer{ glBindVertexArray(0); } } + }else{ + // When the chunk is outside render distance + + if(c->getState(Chunk::CHUNK_STATE_OUTOFVISION)){ + if(glfwGetTime() - c->unload_timer > UNLOAD_TIMEOUT){ + // If chunk was already out and enough time has passed + // Mark the chunk to be unloaded + // And mark is to be removed from the render set + render_todelete.push_back(c); + } + } else{ + // Mark has out of vision and annotate when it started + c->setState(Chunk::CHUNK_STATE_OUTOFVISION, true); + c->setState(Chunk::CHUNK_STATE_UNLOADED, false); + c->unload_timer = glfwGetTime(); + } + } } + + for(auto& c : render_todelete){ + // we can get away with unsafe erase as access to the container is only done by this + // thread + c->deleteBuffers(); + chunks_torender.unsafe_erase(c); + chunkmanager::getDeleteVector().push(c); + } + render_todelete.clear(); } void destroy(){ From 2f9b7cebcdb5960fc0c6ba0788ba50c057e72470 Mon Sep 17 00:00:00 2001 From: EmaMaker Date: Sat, 20 May 2023 21:52:33 +0200 Subject: [PATCH 6/6] chunk: make chunk size 32 slight increase in fps with no great increase in ram usage, with greater render distance --- include/camera.hpp | 4 ++-- include/chunk.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/camera.hpp b/include/camera.hpp index ab73bfb..ca35f42 100644 --- a/include/camera.hpp +++ b/include/camera.hpp @@ -17,7 +17,7 @@ public: view = glm::mat4(1.0f); // This matrix needs to be also updated in viewPortCallback whenever it is changed - projection = glm::perspective(glm::radians(90.0f), 800.0f / 600.0f, 0.1f, 200.0f); + projection = glm::perspective(glm::radians(90.0f), 800.0f / 600.0f, 0.1f, 1200.0f); } void update(GLFWwindow *window, float deltaTime) @@ -52,7 +52,7 @@ public: void viewPortCallBack(GLFWwindow *window, int width, int height) { - projection = glm::perspective(glm::radians(80.0f), (float)width / (float)height, 0.1f, 350.0f); + projection = glm::perspective(glm::radians(80.0f), (float)width / (float)height, 0.1f, 1200.0f); } void mouseCallback(GLFWwindow *window, double xpos, double ypos) diff --git a/include/chunk.hpp b/include/chunk.hpp index 303fb31..569a006 100644 --- a/include/chunk.hpp +++ b/include/chunk.hpp @@ -16,7 +16,7 @@ #include "intervalmap.hpp" #include "shader.hpp" -#define CHUNK_SIZE 16 +#define CHUNK_SIZE 32 #define CHUNK_VOLUME (CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE) #define CHUNK_MAX_INDEX (CHUNK_VOLUME - 1)