diff --git a/include/renderer.hpp b/include/renderer.hpp index 58907f7..2b560c9 100644 --- a/include/renderer.hpp +++ b/include/renderer.hpp @@ -10,9 +10,38 @@ #include "shader.hpp" namespace renderer{ - typedef oneapi::tbb::concurrent_unordered_set RenderSet; + typedef struct RenderInfo { + chunk_index_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; + } + + void deallocateBuffers(){ + // Allocate buffers + glDeleteBuffers(1, &VBO); + glDeleteBuffers(1, &extentsBuffer); + glDeleteBuffers(1, &texinfoBuffer); + glDeleteVertexArrays(1, &VAO); + + buffers_allocated=false; + } + } 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,7 +50,6 @@ namespace renderer{ void saveScreenshot(bool forceFullHD=false); Shader* getRenderShader(); - RenderSet& getChunksToRender(); ChunkMeshDataQueue& getMeshDataQueue(); }; diff --git a/src/chunkmanager.cpp b/src/chunkmanager.cpp index 1eb021d..fd76077 100644 --- a/src/chunkmanager.cpp +++ b/src/chunkmanager.cpp @@ -85,7 +85,6 @@ namespace chunkmanager if(chunks_to_mesh_queue.try_pop(entry)){ Chunk::Chunk* chunk = entry.first; chunkmesher::mesh(chunk); - renderer::getChunksToRender().insert(chunk); chunk->setState(Chunk::CHUNK_STATE_IN_MESHING_QUEUE, false); } } diff --git a/src/chunkmesher.cpp b/src/chunkmesher.cpp index 5a2497f..6b2a3fa 100755 --- a/src/chunkmesher.cpp +++ b/src/chunkmesher.cpp @@ -49,7 +49,6 @@ void mesh(Chunk::Chunk* chunk) mesh_data->index = chunk->getIndex(); mesh_data->position = chunk->getPosition(); - // convert tree to array since it is easier to work with it int length{0}; std::unique_ptr blocks; diff --git a/src/debugwindow.cpp b/src/debugwindow.cpp index f6e7977..256dd2e 100644 --- a/src/debugwindow.cpp +++ b/src/debugwindow.cpp @@ -95,18 +95,15 @@ namespace debug{ } if(ImGui::CollapsingHeader("Mesh")){ - ImGui::Text("Total chunks updated: %d", + ImGui::Text("Total chunk meshed: %d", std::any_cast(parameters.at("render_chunks_total"))); + ImGui::Text("Of which renderable (not empty): %d", + std::any_cast(parameters.at("render_chunks_renderable"))); ImGui::Text("Chunks rendered: %d", std::any_cast(parameters.at("render_chunks_rendered"))); ImGui::Text("Frustum culled: %d", std::any_cast(parameters.at("render_chunks_culled"))); - ImGui::Text("Chunks out of view: %d", - std::any_cast(parameters.at("render_chunks_oof"))); - if(parameters.find("render_chunks_deleted") != parameters.end()) - ImGui::Text("Chunks deleted: %d", - std::any_cast(parameters.at("render_chunks_deleted"))); - ImGui::Text("Vertices in the scene: %d", + ImGui::Text("Total vertices in the scene: %d", std::any_cast(parameters.at("render_chunks_vertices"))); ImGui::Checkbox("Wireframe", std::any_cast(parameters.at("wireframe_return"))); diff --git a/src/renderer.cpp b/src/renderer.cpp index ddd2b45..ed20533 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -1,7 +1,8 @@ #include "renderer.hpp" -#include -#include +#include +#include +#include #include "chunkmanager.hpp" #include "chunkmesher.hpp" @@ -12,15 +13,15 @@ #include "stb_image_write.h" namespace renderer{ - RenderSet chunks_torender; - oneapi::tbb::concurrent_vector render_todelete; + typedef oneapi::tbb::concurrent_hash_map RenderTable; + + RenderTable ChunksToRender; ChunkMeshDataQueue MeshDataQueue; Shader* theShader, *quadShader; GLuint chunkTexture; Shader* getRenderShader() { return theShader; } - RenderSet& getChunksToRender(){ return chunks_torender; } ChunkMeshDataQueue& getMeshDataQueue(){ return MeshDataQueue; } GLuint renderTexFrameBuffer, renderTex, renderTexDepthBuffer, quadVAO, quadVBO; @@ -140,97 +141,82 @@ namespace renderer{ 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; + }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); } - /* - for(auto& c : chunks_torender){ - float dist = glm::distance(c->getPosition(), cameraChunkPos); - if(dist <= static_cast(RENDER_DISTANCE)){ - if(!c->getState(Chunk::CHUNK_STATE_MESH_LOADED)) continue; + // TODO: implement removal of chunks from rendering - if(c->numVertices > 0){ - // Increase total vertex count - vertices += c->numVertices; + /* 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; - // reset out-of-vision and unload flags - c->setState(Chunk::CHUNK_STATE_OUTOFVISION, false); - c->setState(Chunk::CHUNK_STATE_UNLOADED, false); + if(render_info->num_vertices > 0) + { + total++; - // 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); + // Increase total vertex count + vertices += render_info->num_vertices; - // 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; + // Perform frustum culling and eventually render + 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); - if(a==8){ - out=true; - break; - } - } + // 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 (!out) - { - theShader->setMat4("model", model); - theShader->setMat4("view", theCamera.getView()); - theShader->setMat4("projection", theCamera.getProjection()); - - glBindVertexArray(c->VAO); - glDrawArrays(GL_POINTS, 0, c->numVertices); - glBindVertexArray(0); - - toGpu++; + if(a==8){ + out=true; + break; } } - }else{ - // When the chunk is outside render distance - if(c->getState(Chunk::CHUNK_STATE_OUTOFVISION)){ - oof++; - 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(); + if (!out) + { + theShader->setMat4("model", model); + theShader->setMat4("view", theCamera.getView()); + theShader->setMat4("projection", theCamera.getProjection()); + + glBindVertexArray(render_info->VAO); + glDrawArrays(GL_POINTS, 0, render_info->num_vertices); + glBindVertexArray(0); + + toGpu++; } - } } - */ - total = chunks_torender.size(); - debug::window::set_parameter("render_chunks_total", total); + debug::window::set_parameter("render_chunks_total", (int)(ChunksToRender.size())); debug::window::set_parameter("render_chunks_rendered", toGpu); + debug::window::set_parameter("render_chunks_renderable", total); debug::window::set_parameter("render_chunks_culled", total-toGpu); - debug::window::set_parameter("render_chunks_oof", oof); - debug::window::set_parameter("render_chunks_deleted", (int) (render_todelete.size())); debug::window::set_parameter("render_chunks_vertices", vertices); - /*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();*/ - /* DISPLAY TEXTURE ON A QUAD THAT FILLS THE SCREEN */ // Now to render the quad, with the texture on top // Switch to the default frame buffer @@ -252,6 +238,40 @@ namespace renderer{ 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); }