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 198536b..ca35f42 100644 --- a/include/camera.hpp +++ b/include/camera.hpp @@ -6,7 +6,7 @@ #include #include -#include +#include class Camera { @@ -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) @@ -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)); @@ -49,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) @@ -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) @@ -105,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); @@ -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 2fdc6e7..569a006 100644 --- a/include/chunk.hpp +++ b/include/chunk.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -15,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) @@ -24,9 +25,11 @@ 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_OUTOFVISION = 16; + constexpr uint8_t CHUNK_STATE_UNLOADED = 32; + constexpr uint8_t CHUNK_STATE_EMPTY = 64; int coord3DTo1D(int x, int y, int z); @@ -38,9 +41,12 @@ namespace Chunk ~Chunk(); public: + void createBuffers(); + void deleteBuffers(); + 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); @@ -50,19 +56,14 @@ 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}; - - std::mutex mutex_state; - - std::vector vertices; - std::vector colors; - std::vector indices; + GLuint VAO{0}, VBO{0}, EBO{0}, colorBuffer{0}, numVertices{0}; + std::atomic unload_timer{0}; private: glm::vec3 position{}; IntervalMap blocks{}; - std::bitset<8> state{0}; + std::atomic_uint8_t state{0}; }; }; diff --git a/include/chunkmanager.hpp b/include/chunkmanager.hpp index 5661dd3..5c296fa 100644 --- a/include/chunkmanager.hpp +++ b/include/chunkmanager.hpp @@ -1,33 +1,27 @@ #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 + #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(); + oneapi::tbb::concurrent_queue& getDeleteVector(); 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/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 a59e783..f8a7f69 100644 --- a/include/renderer.hpp +++ b/include/renderer.hpp @@ -1,13 +1,22 @@ #ifndef RENDERER_H #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(); + RenderSet& getChunksToRender(); + oneapi::tbb::concurrent_queue& getMeshDataQueue(); }; 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}) diff --git a/src/chunk.cpp b/src/chunk.cpp index c96f7ef..4f98a8d 100644 --- a/src/chunk.cpp +++ b/src/chunk.cpp @@ -19,7 +19,13 @@ namespace Chunk { this->position = pos; this->setState(CHUNK_STATE_EMPTY, true); + } + Chunk ::~Chunk() + { + } + + void Chunk::createBuffers(){ glGenVertexArrays(1, &(this->VAO)); glGenBuffers(1, &(this->colorBuffer)); glGenBuffers(1, &(this->VBO)); @@ -27,18 +33,12 @@ namespace Chunk } - 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) @@ -60,8 +60,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); } } diff --git a/src/chunkgenerator.cpp b/src/chunkgenerator.cpp index c80a0c6..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]; @@ -84,6 +86,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..90644f0 100644 --- a/src/chunkmanager.cpp +++ b/src/chunkmanager.cpp @@ -1,105 +1,40 @@ +#include "chunkmanager.hpp" + +#include +#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; + std::atomic_bool 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(){ + int chunks_volume_real; + 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 +66,57 @@ 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; } - void update(float deltaTime) - { - // Try to lock resources - f = 0; - f |= mutex_queue_generate.try_lock(); - f |= mutex_queue_mesh.try_lock() << 1; + oneapi::tbb::concurrent_queue chunks_todelete; + int nUnloaded{0}; + void update(){ + while(should_run) { + 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_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); - // Use time in float to be consistent with glfw - float currentTime = glfwGetTime(); + if(x > 1023 || y > 1023 || z > 1023) continue; - // 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++; + ChunkTable::accessor a; + if(!chunks.find(a, index)) chunks.emplace(a, std::make_pair(index, new Chunk::Chunk(glm::vec3(x,y,z)))); - // Delete afterwards to avoid exception due to invalid iterators - to_delete_delete.insert(n.first); + if(! (a->second->getState(Chunk::CHUNK_STATE_GENERATED))) generateChunk(a->second); + if(! (a->second->getState(Chunk::CHUNK_STATE_MESHED))) chunkmesher::mesh(a->second); + + renderer::getChunksToRender().insert(a->second); + + a.release(); } - } - for(uint32_t i : to_delete_delete) to_delete.erase(i); - to_delete_delete.clear(); - if(nUnloaded) std::cout << "Unloaded " << nUnloaded << " chunks\n"; - 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); + 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); - // 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; + delete n; + chunks.erase(index); + nUnloaded++; } } } @@ -302,21 +126,14 @@ namespace chunkmanager return i | (j << 10) | (k << 20); } - std::unordered_map& getChunks(){ return chunks; } + oneapi::tbb::concurrent_queue& getDeleteVector(){ return chunks_todelete; } 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..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,18 +36,25 @@ 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)) return; + if(chunk->getState(Chunk::CHUNK_STATE_EMPTY)){ + chunk->setState(Chunk::CHUNK_STATE_MESHED, true); + renderer::getMeshDataQueue().push(mesh_data); + 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); + renderer::getMeshDataQueue().push(mesh_data); return; } @@ -146,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]), @@ -180,18 +194,23 @@ 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) { + if(mesh_data->chunk->VAO == 0) mesh_data->chunk->createBuffers(); // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). - glBindVertexArray(chunk->VAO); + glBindVertexArray(mesh_data->chunk->VAO); - glBindBuffer(GL_ARRAY_BUFFER, chunk->VBO); - glBufferData(GL_ARRAY_BUFFER, chunk->vertices.size() * sizeof(GLfloat), &(chunk->vertices[0]), GL_STATIC_DRAW); + 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); @@ -202,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); @@ -215,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/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..84fb7a2 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -1,16 +1,25 @@ #include "renderer.hpp" +#include +#include + #include "chunkmanager.hpp" #include "chunkmesher.hpp" #include "globals.hpp" - #include "stb_image.h" namespace renderer{ + RenderSet chunks_torender; + oneapi::tbb::concurrent_vector render_todelete; + oneapi::tbb::concurrent_queue MeshDataQueue; + Shader* theShader; GLuint chunkTexture; Shader* getRenderShader() { return theShader; } + RenderSet& getChunksToRender(){ return chunks_torender; } + oneapi::tbb::concurrent_queue& getMeshDataQueue(){ return MeshDataQueue; } + void init(){ // Create Shader @@ -40,61 +49,92 @@ 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); + chunkmesher::MeshData* m; + while(MeshDataQueue.try_pop(m)){ + chunkmesher::sendtogpu(m); + chunkmesher::getMeshDataQueue().push(m); + } - // 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; + 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)){ + if(!c->getState(Chunk::CHUNK_STATE_MESH_LOADED)) continue; - if(a==8){ - out=true; - break; + // reset out-of-vision and unload flags + c->setState(Chunk::CHUNK_STATE_OUTOFVISION, false); + c->setState(Chunk::CHUNK_STATE_UNLOADED, false); + + // 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); + + // 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) - { - toGpu++; - - if(c->getState(Chunk::CHUNK_STATE_MESH_LOADED) && c->vIndex > 0) + if (!out) { - // glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // wireframe mode - theShader->setMat4("model", model); - theShader->setMat4("view", theCamera.getView()); - theShader->setMat4("projection", theCamera.getProjection()); + if(c->numVertices > 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); + glBindVertexArray(c->VAO); + 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)){ + 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(); + } + } } - //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(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(){