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
fix-multithread
EmaMaker 2023-10-03 12:25:56 +02:00
parent a05d019c69
commit ca44c0f284
10 changed files with 178 additions and 138 deletions

View File

@ -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<float> unload_timer{0};
private:
glm::vec3 position{};
IntervalMap<Block> blocks{};
int32_t index;
std::atomic_uint16_t state{0};
};
};

View File

@ -21,7 +21,7 @@
namespace chunkmanager
{
typedef oneapi::tbb::concurrent_hash_map<uint32_t, Chunk::Chunk*> ChunkTable;
typedef oneapi::tbb::concurrent_hash_map<int32_t, Chunk::Chunk*> ChunkTable;
typedef oneapi::tbb::concurrent_queue<int> IntQueue;
//typedef std::unordered_map<int32_t, Chunk::Chunk*> 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();

32
include/chunkmeshdata.hpp Normal file
View File

@ -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<GLfloat> vertices;
std::vector<GLfloat> extents;
std::vector<GLfloat> 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<ChunkMeshData*> ChunkMeshDataQueue;
#endif

View File

@ -5,28 +5,19 @@
#include <vector>
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <GLFW/glfw3.h>
#include <oneapi/tbb/concurrent_queue.h>
#include "chunk.hpp"
#include "chunkmeshdata.hpp"
#include "globals.hpp"
#include "shader.hpp"
namespace chunkmesher{
struct MeshData{
Chunk::Chunk* chunk;
GLuint numVertices{0};
std::vector<GLfloat> vertices;
std::vector<GLfloat> extents;
std::vector<GLfloat> texinfo;
};
oneapi::tbb::concurrent_queue<MeshData*>& 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);
}

View File

@ -1,18 +1,35 @@
#ifndef RENDERER_H
#define RENDERER_H
#include <oneapi/tbb/concurrent_unordered_set.h>
#include <oneapi/tbb/concurrent_queue.h>
#include <glm/glm.hpp>
#include "chunk.hpp"
#include "chunkmesher.hpp"
#include "chunkmeshdata.hpp"
#include "shader.hpp"
namespace renderer{
//typedef oneapi::tbb::concurrent_unordered_set<Chunk::Chunk*> RenderSet;
typedef oneapi::tbb::concurrent_queue<Chunk::Chunk*> 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<chunkmesher::MeshData*>& getMeshDataQueue();
ChunkMeshDataQueue& getMeshDataQueue();
};

View File

@ -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<int16_t>(pos.x);
int16_t j = static_cast<int16_t>(pos.y);
int16_t k = static_cast<int16_t>(pos.z);
index = i | (j << 10) | (k << 20);
}
Block Chunk::getBlock(int x, int y, int z)

View File

@ -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<int16_t>(position.x), static_cast<int16_t>(position.y),
static_cast<int16_t>(position.z));
}
int32_t calculateIndex(int16_t i, int16_t j, int16_t k){
return i | (j << 10) | (k << 20);
}

View File

@ -13,13 +13,17 @@
namespace chunkmesher{
oneapi::tbb::concurrent_queue<MeshData*> MeshDataQueue;
ChunkMeshDataQueue MeshDataQueue;
ChunkMeshDataQueue& getMeshDataQueue(){ return MeshDataQueue; }
oneapi::tbb::concurrent_queue<MeshData*>& 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<Block[]> blocks;
int length{0};
std::unique_ptr<Block[]> 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<Block, CHUNK_SIZE * CHUNK_SIZE> 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);
}
};

View File

@ -70,8 +70,9 @@ int main()
}
SpaceFilling::initLUT();
chunkmanager::init();
debug::window::init(window);
chunkmanager::init();
chunkmesher::init();
renderer::init(window);
while (!glfwWindowShouldClose(window))

View File

@ -3,8 +3,7 @@
#include <glm/ext.hpp>
#include <glm/gtx/string_cast.hpp>
#include <oneapi/tbb/concurrent_vector.h>
#include <oneapi/tbb/concurrent_queue.h>
#include <oneapi/tbb/concurrent_hash_map.h>
#include "chunkmanager.hpp"
#include "chunkmesher.hpp"
@ -15,22 +14,23 @@
#include "stb_image_write.h"
namespace renderer{
RenderQueue chunks_torender;
oneapi::tbb::concurrent_queue<chunkmesher::MeshData*> MeshDataQueue;
typedef oneapi::tbb::concurrent_hash_map<int32_t, RenderInfo*> RenderTable;
RenderTable ChunksToRender;
ChunkMeshDataQueue MeshDataQueue;
Shader* theShader, *quadShader;
GLuint chunkTexture;
Shader* getRenderShader() { return theShader; }
RenderQueue& getChunksToRender(){ return chunks_torender; }
oneapi::tbb::concurrent_queue<chunkmesher::MeshData*>& 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<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);
@ -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);
}