From ca44c0f284bb5ccd6993f11e8aeb2a2e4148d52f Mon Sep 17 00:00:00 2001 From: EmaMaker Date: Tue, 3 Oct 2023 12:25:56 +0200 Subject: [PATCH] initial update/mesh/render thread communication refactor Quirks to iron out: - Some stuff is rendered wrong/not rendered (chunks missing) - Still no way to delete stuff from ChunksToRender --- include/chunk.hpp | 5 +- include/chunkmanager.hpp | 4 +- include/chunkmeshdata.hpp | 32 +++++++++++++ include/chunkmesher.hpp | 17 ++----- include/renderer.hpp | 28 ++++++++--- src/chunk.cpp | 18 ++------ src/chunkmanager.cpp | 21 ++++----- src/chunkmesher.cpp | 91 ++++++++++-------------------------- src/main.cpp | 3 +- src/renderer.cpp | 97 ++++++++++++++++++++++++++++++--------- 10 files changed, 178 insertions(+), 138 deletions(-) create mode 100644 include/chunkmeshdata.hpp diff --git a/include/chunk.hpp b/include/chunk.hpp index a6214c5..2977751 100644 --- a/include/chunk.hpp +++ b/include/chunk.hpp @@ -25,14 +25,11 @@ namespace Chunk constexpr uint16_t CHUNK_STATE_GENERATED = 1; constexpr uint16_t CHUNK_STATE_MESHED = 2; - constexpr uint16_t CHUNK_STATE_MESH_LOADED = 4; - constexpr uint16_t CHUNK_STATE_LOADED = 8; constexpr uint16_t CHUNK_STATE_OUTOFVISION = 16; constexpr uint16_t CHUNK_STATE_UNLOADED = 32; constexpr uint16_t CHUNK_STATE_EMPTY = 64; constexpr uint16_t CHUNK_STATE_IN_GENERATION_QUEUE = 128; constexpr uint16_t CHUNK_STATE_IN_MESHING_QUEUE = 256; - constexpr uint16_t CHUNK_STATE_IN_RENDERING_QUEUE = 512; int coord3DTo1D(int x, int y, int z); @@ -61,12 +58,14 @@ namespace Chunk public: GLuint VAO{0}, VBO{0}, extentsBuffer{0}, texinfoBuffer{0}, numVertices{0}; + int32_t getIndex(){ return index; } std::atomic unload_timer{0}; private: glm::vec3 position{}; IntervalMap blocks{}; + int32_t index; std::atomic_uint16_t state{0}; }; }; diff --git a/include/chunkmanager.hpp b/include/chunkmanager.hpp index ea87b02..c7b26f8 100644 --- a/include/chunkmanager.hpp +++ b/include/chunkmanager.hpp @@ -21,7 +21,7 @@ namespace chunkmanager { - typedef oneapi::tbb::concurrent_hash_map ChunkTable; + typedef oneapi::tbb::concurrent_hash_map ChunkTable; typedef oneapi::tbb::concurrent_queue IntQueue; //typedef std::unordered_map ChunkTable; @@ -37,6 +37,8 @@ namespace chunkmanager void init(); void blockpick(bool place); int32_t calculateIndex(int16_t i, int16_t j, int16_t k); + int32_t calculateIndex(Chunk::Chunk* c); + int32_t calculateIndex(glm::vec3 position); void stop(); void destroy(); diff --git a/include/chunkmeshdata.hpp b/include/chunkmeshdata.hpp new file mode 100644 index 0000000..a74c4af --- /dev/null +++ b/include/chunkmeshdata.hpp @@ -0,0 +1,32 @@ +#ifndef CHUNK_MESH_DATA_H +#define CHUNK_MESH_DATA_H + +enum class ChunkMeshDataType{ + MESH_UPDATE +}; + +typedef struct ChunkMeshData{ + int32_t index; + glm::vec3 position; + int num_vertices = 0; + + std::vector vertices; + std::vector extents; + std::vector texinfo; + + ChunkMeshDataType message_type; + + void clear(){ + vertices.clear(); + texinfo.clear(); + extents.clear(); + index = 0; + position = glm::vec3(0); + num_vertices = 0; + } + +}ChunkMeshData; +typedef oneapi::tbb::concurrent_queue ChunkMeshDataQueue; + + +#endif diff --git a/include/chunkmesher.hpp b/include/chunkmesher.hpp index ff8a79a..eee29b8 100644 --- a/include/chunkmesher.hpp +++ b/include/chunkmesher.hpp @@ -5,28 +5,19 @@ #include #include +#include #include #include #include "chunk.hpp" +#include "chunkmeshdata.hpp" #include "globals.hpp" #include "shader.hpp" namespace chunkmesher{ - struct MeshData{ - Chunk::Chunk* chunk; - GLuint numVertices{0}; - - std::vector vertices; - std::vector extents; - std::vector texinfo; - }; - oneapi::tbb::concurrent_queue& getMeshDataQueue(); - + ChunkMeshDataQueue& getMeshDataQueue(); + void init(); 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 a7575e6..33549d9 100644 --- a/include/renderer.hpp +++ b/include/renderer.hpp @@ -1,18 +1,35 @@ #ifndef RENDERER_H #define RENDERER_H -#include -#include +#include #include "chunk.hpp" #include "chunkmesher.hpp" +#include "chunkmeshdata.hpp" #include "shader.hpp" namespace renderer{ - //typedef oneapi::tbb::concurrent_unordered_set RenderSet; - typedef oneapi::tbb::concurrent_queue RenderQueue; + typedef struct RenderInfo { + int32_t index; + int num_vertices; + glm::vec3 position; + bool buffers_allocated=false; + + GLuint VAO, VBO, extentsBuffer, texinfoBuffer; + + void allocateBuffers(){ + // Allocate buffers + glGenVertexArrays(1, &VAO); + glGenBuffers(1, &VBO); + glGenBuffers(1, &extentsBuffer); + glGenBuffers(1, &texinfoBuffer); + + buffers_allocated=true; + } + } RenderInfo; void init(GLFWwindow* window); + void send_chunk_to_gpu(ChunkMeshData* mesh_data, RenderInfo* render_info); void render(); void resize_framebuffer(int width, int height); void framebuffer_size_callback(GLFWwindow *window, int width, int height); @@ -21,8 +38,7 @@ namespace renderer{ void saveScreenshot(bool forceFullHD=false); Shader* getRenderShader(); - RenderQueue& getChunksToRender(); - oneapi::tbb::concurrent_queue& getMeshDataQueue(); + ChunkMeshDataQueue& getMeshDataQueue(); }; diff --git a/src/chunk.cpp b/src/chunk.cpp index fad1fb7..bdf6940 100644 --- a/src/chunk.cpp +++ b/src/chunk.cpp @@ -20,21 +20,11 @@ namespace Chunk this->position = pos; this->setState(CHUNK_STATE_EMPTY, true); this->setBlocks(0, CHUNK_MAX_INDEX, Block::AIR); - } - void Chunk::createBuffers(){ - glGenVertexArrays(1, &(this->VAO)); - glGenBuffers(1, &(this->VBO)); - glGenBuffers(1, &(this->extentsBuffer)); - glGenBuffers(1, &(this->texinfoBuffer)); - - } - - void Chunk::deleteBuffers(){ - glDeleteBuffers(1, &(this->VBO)); - glDeleteBuffers(1, &(this->extentsBuffer)); - glDeleteBuffers(1, &(this->texinfoBuffer)); - glDeleteVertexArrays(1, &(this->VAO)); + int16_t i = static_cast(pos.x); + int16_t j = static_cast(pos.y); + int16_t k = static_cast(pos.z); + index = i | (j << 10) | (k << 20); } Block Chunk::getBlock(int x, int y, int z) diff --git a/src/chunkmanager.cpp b/src/chunkmanager.cpp index 5141d2c..cc303cc 100644 --- a/src/chunkmanager.cpp +++ b/src/chunkmanager.cpp @@ -59,10 +59,6 @@ namespace chunkmanager index++; } - // Also init mesh data queue - for(int i = 0; i < 10; i++) - chunkmesher::getMeshDataQueue().push(new chunkmesher::MeshData()); - should_run = true; update_thread = std::thread(update); gen_thread = std::thread(generate); @@ -80,7 +76,7 @@ namespace chunkmanager entry.first->setState(Chunk::CHUNK_STATE_IN_GENERATION_QUEUE, false); } } - chunks_to_generate_queue.clear(); + //chunks_to_generate_queue.clear(); } // Method for chunk meshing thread(s) @@ -93,7 +89,7 @@ namespace chunkmanager chunk->setState(Chunk::CHUNK_STATE_IN_MESHING_QUEUE, false); } } - chunks_to_mesh_queue.clear(); + //chunks_to_mesh_queue.clear(); } void update(){ @@ -177,10 +173,6 @@ namespace chunkmanager }else{ mesh++; // If generated & meshed, render - /*if(!c->getState(Chunk::CHUNK_STATE_IN_RENDERING_QUEUE)){ - renderer::getChunksToRender().push(c); - c->setState(Chunk::CHUNK_STATE_IN_RENDERING_QUEUE, true); - }*/ } } @@ -189,7 +181,7 @@ namespace chunkmanager if(c->getState(Chunk::CHUNK_STATE_OUTOFVISION)){ // If enough time has passed, set to be deleted if(c->isFree() && glfwGetTime() - c->unload_timer >= UNLOAD_TIMEOUT){ - chunks_todelete.push(calculateIndex(x,y,z)); + chunks_todelete.push(c->getIndex()); unload++; } }else{ @@ -230,6 +222,13 @@ namespace chunkmanager } // uint32_t is fine, since i'm limiting the coordinate to only use up to ten bits (1023). There's actually two spare bits + int32_t calculateIndex(Chunk::Chunk* c){ + return calculateIndex(c->getPosition()); + } + int32_t calculateIndex(glm::vec3 position){ + return calculateIndex(static_cast(position.x), static_cast(position.y), + static_cast(position.z)); + } int32_t calculateIndex(int16_t i, int16_t j, int16_t k){ return i | (j << 10) | (k << 20); } diff --git a/src/chunkmesher.cpp b/src/chunkmesher.cpp index f0d3898..a296eb0 100755 --- a/src/chunkmesher.cpp +++ b/src/chunkmesher.cpp @@ -13,13 +13,17 @@ namespace chunkmesher{ -oneapi::tbb::concurrent_queue MeshDataQueue; +ChunkMeshDataQueue MeshDataQueue; +ChunkMeshDataQueue& getMeshDataQueue(){ return MeshDataQueue; } -oneapi::tbb::concurrent_queue& getMeshDataQueue(){ return MeshDataQueue; } +void init(){ + for(int i = 0; i < 10; i++) + MeshDataQueue.push(new ChunkMeshData{}); +} void mesh(Chunk::Chunk* chunk) { - MeshData* mesh_data; + ChunkMeshData* mesh_data; if(!MeshDataQueue.try_pop(mesh_data)) return; /* @@ -38,28 +42,13 @@ void mesh(Chunk::Chunk* chunk) */ // Cleanup previous data - mesh_data->numVertices = 0; - mesh_data->chunk = chunk; - mesh_data->vertices.clear(); - mesh_data->extents.clear(); - mesh_data->texinfo.clear(); + mesh_data->clear(); + mesh_data->message_type = ChunkMeshDataType::MESH_UPDATE; + mesh_data->index = chunk->getIndex(); + mesh_data->position = chunk->getPosition(); - // 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; - } - - // convert tree to array since it is easier to work with it + std::unique_ptr blocks; 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; - } - int k, l, u, v, w, h, n, j, i; int x[]{0, 0, 0}; int q[]{0, 0, 0}; @@ -67,6 +56,14 @@ void mesh(Chunk::Chunk* chunk) int dv[]{0, 0, 0}; std::array mask; + + // Abort if chunk is empty + if(chunk->getState(Chunk::CHUNK_STATE_EMPTY)) goto end; + + // convert tree to array since it is easier to work with it + blocks = chunk->getBlocksArray(&length); + if(length == 0) goto end; + for (bool backFace = true, b = false; b != backFace; backFace = backFace && b, b = !b) { // iterate over 3 dimensions @@ -188,14 +185,15 @@ void mesh(Chunk::Chunk* chunk) mesh_data->vertices.push_back(x[1]); //bottomLeft.y mesh_data->vertices.push_back(x[2]); //bottomLeft.z - // extents, use normals for now + // extents mesh_data->extents.push_back(du[0] + dv[0]); mesh_data->extents.push_back(du[1] + dv[1]); mesh_data->extents.push_back(du[2] + dv[2]); + // texture info (block type, backFace) mesh_data->texinfo.push_back(backFace ? 0.0 : 1.0); mesh_data->texinfo.push_back((int)(mask[n]) - 2); - mesh_data->numVertices++; + mesh_data->num_vertices++; } for (l = 0; l < h; ++l) @@ -224,49 +222,8 @@ void mesh(Chunk::Chunk* chunk) } } +end: chunk->setState(Chunk::CHUNK_STATE_MESHED, true); renderer::getMeshDataQueue().push(mesh_data); } - -void sendtogpu(MeshData* mesh_data) -{ - if (mesh_data->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(mesh_data->chunk->VAO); - - // position attribute - 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); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0); - glEnableVertexAttribArray(0); - - // normal attribute - glBindBuffer(GL_ARRAY_BUFFER, mesh_data->chunk->extentsBuffer); - glBufferData(GL_ARRAY_BUFFER, mesh_data->extents.size() * sizeof(GLfloat), &(mesh_data->extents[0]), GL_STATIC_DRAW); - glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)(0)); - glEnableVertexAttribArray(1); - - // texcoords attribute - glBindBuffer(GL_ARRAY_BUFFER, mesh_data->chunk->texinfoBuffer); - glBufferData(GL_ARRAY_BUFFER, mesh_data->texinfo.size() * sizeof(GLfloat), &(mesh_data->texinfo[0]), GL_STATIC_DRAW); - glEnableVertexAttribArray(2); - glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)0); - - glBindVertexArray(0); - - // save the number of indices of the mesh, it is needed later for drawing - mesh_data->chunk->numVertices = (GLuint)(mesh_data->numVertices); - - // once data has been sent to the GPU, it can be cleared from system RAM - mesh_data->vertices.clear(); - mesh_data->extents.clear(); - mesh_data->texinfo.clear(); - } - - // mark the chunk mesh has loaded on GPU - mesh_data->chunk->setState(Chunk::CHUNK_STATE_MESH_LOADED, true); -} }; diff --git a/src/main.cpp b/src/main.cpp index 42c0f33..a6cbfed 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -70,8 +70,9 @@ int main() } SpaceFilling::initLUT(); - chunkmanager::init(); debug::window::init(window); + chunkmanager::init(); + chunkmesher::init(); renderer::init(window); while (!glfwWindowShouldClose(window)) diff --git a/src/renderer.cpp b/src/renderer.cpp index 9d59543..e15c197 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -3,8 +3,7 @@ #include #include -#include -#include +#include #include "chunkmanager.hpp" #include "chunkmesher.hpp" @@ -15,22 +14,23 @@ #include "stb_image_write.h" namespace renderer{ - RenderQueue chunks_torender; - oneapi::tbb::concurrent_queue MeshDataQueue; + typedef oneapi::tbb::concurrent_hash_map RenderTable; + + RenderTable ChunksToRender; + ChunkMeshDataQueue MeshDataQueue; Shader* theShader, *quadShader; GLuint chunkTexture; - Shader* getRenderShader() { return theShader; } - RenderQueue& getChunksToRender(){ return chunks_torender; } - oneapi::tbb::concurrent_queue& getMeshDataQueue(){ return MeshDataQueue; } - GLuint renderTexFrameBuffer, renderTex, renderTexDepthBuffer, quadVAO, quadVBO; int screenWidth, screenHeight; int crosshair_type{0}; bool wireframe{false}; + Shader* getRenderShader() { return theShader; } + ChunkMeshDataQueue& getMeshDataQueue(){ return MeshDataQueue; } + void init(GLFWwindow* window){ // Setup rendering // We will render the image to a texture, then display the texture on a quad that fills the @@ -140,26 +140,49 @@ namespace renderer{ theShader->use(); theShader->setVec3("viewPos", cameraPos); - chunkmesher::MeshData* m; + // TODO: works but some stuff is rendered wrong (trees floating or inside the terrain, + // missing or malformed chunks) + ChunkMeshData* m; while(MeshDataQueue.try_pop(m)){ - chunkmesher::sendtogpu(m); + RenderTable::accessor a; + RenderInfo* render_info; + + if(ChunksToRender.find(a, m->index)){ + render_info = a->second; + render_info->position = m->position; + render_info->num_vertices = m->num_vertices; + std::cout << "index collision on " << render_info->index << std::endl; + }else{ + render_info = new RenderInfo(); + render_info->index = m->index; + render_info->position = m->position; + render_info->num_vertices = m->num_vertices; + + ChunksToRender.emplace(a, std::make_pair(render_info->index, render_info)); + } + + send_chunk_to_gpu(m, render_info); chunkmesher::getMeshDataQueue().push(m); } - Chunk::Chunk* c; - while(chunks_torender.try_pop(c)){ - if(!c->getState(Chunk::CHUNK_STATE_MESH_LOADED)) goto end; + // TODO: implement removal of chunks from rendering + std::cout << "chunks to render: " << ChunksToRender.size(); + + + // Render the chunks + // parallel_for cannot be used since all the rendering needs to happen in a single thread + for(RenderTable::iterator i = ChunksToRender.begin(); i != ChunksToRender.end(); i++){ + RenderInfo* render_info = i->second; total++; - - if(c->numVertices > 0) + if(render_info->num_vertices > 0) { // Increase total vertex count - vertices += c->numVertices; + vertices += render_info->num_vertices; // Perform frustum culling and eventually render - glm::vec3 chunk = c->getPosition(); + glm::vec3 chunk = render_info->position; 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); @@ -185,16 +208,13 @@ namespace renderer{ theShader->setMat4("view", theCamera.getView()); theShader->setMat4("projection", theCamera.getProjection()); - glBindVertexArray(c->VAO); - glDrawArrays(GL_POINTS, 0, c->numVertices); + glBindVertexArray(render_info->VAO); + glDrawArrays(GL_POINTS, 0, render_info->num_vertices); glBindVertexArray(0); toGpu++; } } - -end: - c->setState(Chunk::CHUNK_STATE_IN_RENDERING_QUEUE, false); } debug::window::set_parameter("render_chunks_total", total); @@ -223,6 +243,39 @@ end: debug::window::render(); } + void send_chunk_to_gpu(ChunkMeshData* mesh_data, RenderInfo* render_info) + { + if (render_info->num_vertices > 0) + { + if(!render_info->buffers_allocated) render_info->allocateBuffers(); + + // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). + glBindVertexArray(render_info->VAO); + + // TODO: change GL_STATIC_DRAW to the one that means "few redraws and further in between" + + // position attribute + glBindBuffer(GL_ARRAY_BUFFER, render_info->VBO); + glBufferData(GL_ARRAY_BUFFER, mesh_data->vertices.size() * sizeof(GLfloat), &(mesh_data->vertices[0]), GL_STATIC_DRAW); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0); + glEnableVertexAttribArray(0); + + // normal attribute + glBindBuffer(GL_ARRAY_BUFFER, render_info->extentsBuffer); + glBufferData(GL_ARRAY_BUFFER, mesh_data->extents.size() * sizeof(GLfloat), &(mesh_data->extents[0]), GL_STATIC_DRAW); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)(0)); + glEnableVertexAttribArray(1); + + // texcoords attribute + glBindBuffer(GL_ARRAY_BUFFER, render_info->texinfoBuffer); + glBufferData(GL_ARRAY_BUFFER, mesh_data->texinfo.size() * sizeof(GLfloat), &(mesh_data->texinfo[0]), GL_STATIC_DRAW); + glEnableVertexAttribArray(2); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)0); + + glBindVertexArray(0); + } + } + void framebuffer_size_callback(GLFWwindow *window, int width, int height){ resize_framebuffer(width, height); }