renderer: use ChunkMeshData from rendering thread
This system decouples the Secondary threads from the Render thread. Once a chunk is meshed, only the mesh data is sent to the rendering thread, which does not use any direct reference to the chunk itselfpull/13/head
parent
1822911845
commit
7786d4f04d
|
@ -10,9 +10,38 @@
|
||||||
#include "shader.hpp"
|
#include "shader.hpp"
|
||||||
|
|
||||||
namespace renderer{
|
namespace renderer{
|
||||||
typedef oneapi::tbb::concurrent_unordered_set<Chunk::Chunk*> 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 init(GLFWwindow* window);
|
||||||
|
void send_chunk_to_gpu(ChunkMeshData* mesh_data, RenderInfo* render_info);
|
||||||
void render();
|
void render();
|
||||||
void resize_framebuffer(int width, int height);
|
void resize_framebuffer(int width, int height);
|
||||||
void framebuffer_size_callback(GLFWwindow *window, 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);
|
void saveScreenshot(bool forceFullHD=false);
|
||||||
|
|
||||||
Shader* getRenderShader();
|
Shader* getRenderShader();
|
||||||
RenderSet& getChunksToRender();
|
|
||||||
ChunkMeshDataQueue& getMeshDataQueue();
|
ChunkMeshDataQueue& getMeshDataQueue();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -81,7 +81,6 @@ namespace chunkmanager
|
||||||
if(chunks_to_mesh_queue.try_pop(entry)){
|
if(chunks_to_mesh_queue.try_pop(entry)){
|
||||||
Chunk::Chunk* chunk = entry.first;
|
Chunk::Chunk* chunk = entry.first;
|
||||||
chunkmesher::mesh(chunk);
|
chunkmesher::mesh(chunk);
|
||||||
renderer::getChunksToRender().insert(chunk);
|
|
||||||
chunk->setState(Chunk::CHUNK_STATE_IN_MESHING_QUEUE, false);
|
chunk->setState(Chunk::CHUNK_STATE_IN_MESHING_QUEUE, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,6 @@ void mesh(Chunk::Chunk* chunk)
|
||||||
mesh_data->index = chunk->getIndex();
|
mesh_data->index = chunk->getIndex();
|
||||||
mesh_data->position = chunk->getPosition();
|
mesh_data->position = chunk->getPosition();
|
||||||
|
|
||||||
|
|
||||||
// convert tree to array since it is easier to work with it
|
// convert tree to array since it is easier to work with it
|
||||||
int length{0};
|
int length{0};
|
||||||
std::unique_ptr<Block[]> blocks;
|
std::unique_ptr<Block[]> blocks;
|
||||||
|
|
|
@ -95,18 +95,15 @@ namespace debug{
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ImGui::CollapsingHeader("Mesh")){
|
if(ImGui::CollapsingHeader("Mesh")){
|
||||||
ImGui::Text("Total chunks updated: %d",
|
ImGui::Text("Total chunk meshed: %d",
|
||||||
std::any_cast<int>(parameters.at("render_chunks_total")));
|
std::any_cast<int>(parameters.at("render_chunks_total")));
|
||||||
|
ImGui::Text("Of which renderable (not empty): %d",
|
||||||
|
std::any_cast<int>(parameters.at("render_chunks_renderable")));
|
||||||
ImGui::Text("Chunks rendered: %d",
|
ImGui::Text("Chunks rendered: %d",
|
||||||
std::any_cast<int>(parameters.at("render_chunks_rendered")));
|
std::any_cast<int>(parameters.at("render_chunks_rendered")));
|
||||||
ImGui::Text("Frustum culled: %d",
|
ImGui::Text("Frustum culled: %d",
|
||||||
std::any_cast<int>(parameters.at("render_chunks_culled")));
|
std::any_cast<int>(parameters.at("render_chunks_culled")));
|
||||||
ImGui::Text("Chunks out of view: %d",
|
ImGui::Text("Total vertices in the scene: %d",
|
||||||
std::any_cast<int>(parameters.at("render_chunks_oof")));
|
|
||||||
if(parameters.find("render_chunks_deleted") != parameters.end())
|
|
||||||
ImGui::Text("Chunks deleted: %d",
|
|
||||||
std::any_cast<int>(parameters.at("render_chunks_deleted")));
|
|
||||||
ImGui::Text("Vertices in the scene: %d",
|
|
||||||
std::any_cast<int>(parameters.at("render_chunks_vertices")));
|
std::any_cast<int>(parameters.at("render_chunks_vertices")));
|
||||||
ImGui::Checkbox("Wireframe",
|
ImGui::Checkbox("Wireframe",
|
||||||
std::any_cast<bool*>(parameters.at("wireframe_return")));
|
std::any_cast<bool*>(parameters.at("wireframe_return")));
|
||||||
|
|
172
src/renderer.cpp
172
src/renderer.cpp
|
@ -1,7 +1,8 @@
|
||||||
#include "renderer.hpp"
|
#include "renderer.hpp"
|
||||||
|
|
||||||
#include <oneapi/tbb/concurrent_vector.h>
|
#include <glm/ext.hpp>
|
||||||
#include <oneapi/tbb/concurrent_queue.h>
|
#include <glm/gtx/string_cast.hpp>
|
||||||
|
#include <oneapi/tbb/concurrent_hash_map.h>
|
||||||
|
|
||||||
#include "chunkmanager.hpp"
|
#include "chunkmanager.hpp"
|
||||||
#include "chunkmesher.hpp"
|
#include "chunkmesher.hpp"
|
||||||
|
@ -12,15 +13,15 @@
|
||||||
#include "stb_image_write.h"
|
#include "stb_image_write.h"
|
||||||
|
|
||||||
namespace renderer{
|
namespace renderer{
|
||||||
RenderSet chunks_torender;
|
typedef oneapi::tbb::concurrent_hash_map<chunk_index_t, RenderInfo*> RenderTable;
|
||||||
oneapi::tbb::concurrent_vector<Chunk::Chunk*> render_todelete;
|
|
||||||
|
RenderTable ChunksToRender;
|
||||||
ChunkMeshDataQueue MeshDataQueue;
|
ChunkMeshDataQueue MeshDataQueue;
|
||||||
|
|
||||||
Shader* theShader, *quadShader;
|
Shader* theShader, *quadShader;
|
||||||
GLuint chunkTexture;
|
GLuint chunkTexture;
|
||||||
|
|
||||||
Shader* getRenderShader() { return theShader; }
|
Shader* getRenderShader() { return theShader; }
|
||||||
RenderSet& getChunksToRender(){ return chunks_torender; }
|
|
||||||
ChunkMeshDataQueue& getMeshDataQueue(){ return MeshDataQueue; }
|
ChunkMeshDataQueue& getMeshDataQueue(){ return MeshDataQueue; }
|
||||||
|
|
||||||
GLuint renderTexFrameBuffer, renderTex, renderTexDepthBuffer, quadVAO, quadVBO;
|
GLuint renderTexFrameBuffer, renderTex, renderTexDepthBuffer, quadVAO, quadVBO;
|
||||||
|
@ -140,97 +141,82 @@ namespace renderer{
|
||||||
|
|
||||||
ChunkMeshData* m;
|
ChunkMeshData* m;
|
||||||
while(MeshDataQueue.try_pop(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);
|
chunkmesher::getMeshDataQueue().push(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// TODO: implement removal of chunks from rendering
|
||||||
for(auto& c : chunks_torender){
|
|
||||||
float dist = glm::distance(c->getPosition(), cameraChunkPos);
|
|
||||||
if(dist <= static_cast<float>(RENDER_DISTANCE)){
|
|
||||||
if(!c->getState(Chunk::CHUNK_STATE_MESH_LOADED)) continue;
|
|
||||||
|
|
||||||
if(c->numVertices > 0){
|
/* Render the chunks */
|
||||||
// Increase total vertex count
|
// parallel_for cannot be used since all the rendering needs to happen in a single thread
|
||||||
vertices += c->numVertices;
|
for(RenderTable::iterator i = ChunksToRender.begin(); i != ChunksToRender.end(); i++){
|
||||||
|
RenderInfo* render_info = i->second;
|
||||||
|
|
||||||
// reset out-of-vision and unload flags
|
if(render_info->num_vertices > 0)
|
||||||
c->setState(Chunk::CHUNK_STATE_OUTOFVISION, false);
|
{
|
||||||
c->setState(Chunk::CHUNK_STATE_UNLOADED, false);
|
total++;
|
||||||
|
|
||||||
// Perform frustum culling and eventually render
|
// Increase total vertex count
|
||||||
glm::vec3 chunk = c->getPosition();
|
vertices += render_info->num_vertices;
|
||||||
glm::vec4 chunkW = glm::vec4(chunk.x*static_cast<float>(CHUNK_SIZE), chunk.y*static_cast<float>(CHUNK_SIZE), chunk.z*static_cast<float>(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
|
// Perform frustum culling and eventually render
|
||||||
// TODO (?) implement frustum culling as per (Inigo Quilez)[https://iquilezles.org/articles/frustumcorrect/], and check each
|
glm::vec3 chunk = render_info->position;
|
||||||
// plane against each corner of the chunk
|
glm::vec4 chunkW = glm::vec4(chunk.x*static_cast<float>(CHUNK_SIZE), chunk.y*static_cast<float>(CHUNK_SIZE), chunk.z*static_cast<float>(CHUNK_SIZE),1.0);
|
||||||
bool out=false;
|
glm::mat4 model = glm::translate(glm::mat4(1.0), ((float)CHUNK_SIZE) * chunk);
|
||||||
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){
|
// Check if all the corners of the chunk are outside any of the planes
|
||||||
out=true;
|
// TODO (?) implement frustum culling as per (Inigo Quilez)[https://iquilezles.org/articles/frustumcorrect/], and check each
|
||||||
break;
|
// 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)
|
if(a==8){
|
||||||
{
|
out=true;
|
||||||
theShader->setMat4("model", model);
|
break;
|
||||||
theShader->setMat4("view", theCamera.getView());
|
|
||||||
theShader->setMat4("projection", theCamera.getProjection());
|
|
||||||
|
|
||||||
glBindVertexArray(c->VAO);
|
|
||||||
glDrawArrays(GL_POINTS, 0, c->numVertices);
|
|
||||||
glBindVertexArray(0);
|
|
||||||
|
|
||||||
toGpu++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else{
|
|
||||||
// When the chunk is outside render distance
|
|
||||||
|
|
||||||
if(c->getState(Chunk::CHUNK_STATE_OUTOFVISION)){
|
if (!out)
|
||||||
oof++;
|
{
|
||||||
if(glfwGetTime() - c->unload_timer > UNLOAD_TIMEOUT){
|
theShader->setMat4("model", model);
|
||||||
// If chunk was already out and enough time has passed
|
theShader->setMat4("view", theCamera.getView());
|
||||||
// Mark the chunk to be unloaded
|
theShader->setMat4("projection", theCamera.getProjection());
|
||||||
// And mark is to be removed from the render set
|
|
||||||
render_todelete.push_back(c);
|
glBindVertexArray(render_info->VAO);
|
||||||
}
|
glDrawArrays(GL_POINTS, 0, render_info->num_vertices);
|
||||||
} else{
|
glBindVertexArray(0);
|
||||||
// Mark has out of vision and annotate when it started
|
|
||||||
c->setState(Chunk::CHUNK_STATE_OUTOFVISION, true);
|
toGpu++;
|
||||||
c->setState(Chunk::CHUNK_STATE_UNLOADED, false);
|
|
||||||
c->unload_timer = glfwGetTime();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
total = chunks_torender.size();
|
debug::window::set_parameter("render_chunks_total", (int)(ChunksToRender.size()));
|
||||||
debug::window::set_parameter("render_chunks_total", total);
|
|
||||||
debug::window::set_parameter("render_chunks_rendered", toGpu);
|
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_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);
|
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 */
|
/* DISPLAY TEXTURE ON A QUAD THAT FILLS THE SCREEN */
|
||||||
// Now to render the quad, with the texture on top
|
// Now to render the quad, with the texture on top
|
||||||
// Switch to the default frame buffer
|
// Switch to the default frame buffer
|
||||||
|
@ -252,6 +238,40 @@ namespace renderer{
|
||||||
debug::window::render();
|
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){
|
void framebuffer_size_callback(GLFWwindow *window, int width, int height){
|
||||||
resize_framebuffer(width, height);
|
resize_framebuffer(width, height);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue