experimental multithreaded generation and meshing

vertex-deduplication
EmaMaker 2023-03-03 21:33:11 +01:00
parent 2ec728897e
commit 716056b8c5
6 changed files with 291 additions and 108 deletions

View File

@ -7,6 +7,8 @@
#include <array>
#include <bitset>
#include <mutex>
#include <vector>
#include "block.hpp"
#include "spacefilling.hpp"
@ -22,8 +24,9 @@ namespace Chunk
constexpr uint8_t CHUNK_STATE_GENERATED = 1;
constexpr uint8_t CHUNK_STATE_MESHED = 2;
constexpr uint8_t CHUNK_STATE_LOADED = 3;
constexpr uint8_t CHUNK_STATE_EMPTY = 4;
constexpr uint8_t CHUNK_STATE_MESH_LOADED = 3;
constexpr uint8_t CHUNK_STATE_LOADED = 4;
constexpr uint8_t CHUNK_STATE_EMPTY = 7;
int coord3DTo1D(int x, int y, int z);
@ -48,11 +51,17 @@ namespace Chunk
public:
GLuint VAO{0}, VBO{0}, EBO{0}, colorBuffer{0}, vIndex{0};
std::mutex mutex_state;
std::vector<GLfloat> vertices;
std::vector<GLfloat> colors;
std::vector<GLuint> indices;
private:
glm::vec3 position{};
IntervalMap<Block> blocks{};
std::bitset<8> state{0};
};
};

View File

@ -1,12 +1,18 @@
#ifndef CHUNKMANAGER_H
#define CHUNKMANAGER_H
#include <thread>
namespace chunkmanager
{
void init();
std::thread initGenThread();
std::thread initMeshThread();
void update(float deltaTime);
void updateChunk(uint32_t, uint16_t, uint16_t, uint16_t);
void destroy();
void mesh();
void generate();
}
#endif

View File

@ -10,11 +10,14 @@
#include "globals.hpp"
#include "shader.hpp"
#include <string>
namespace chunkmesher{
void mesh(Chunk::Chunk* chunk);
void draw(Chunk::Chunk* chunk, glm::mat4 model);
void quad(glm::vec3 bottomLeft, glm::vec3 topLeft, glm::vec3 topRight, glm::vec3 bottomRight, Block block, bool backFace);
void quad(Chunk::Chunk* chunk, glm::vec3 bottomLeft, glm::vec3 topLeft, glm::vec3 topRight, glm::vec3 bottomRight, Block block, bool backFace);
}
#endif

View File

@ -9,31 +9,129 @@
#include "globals.hpp"
#include <iostream>
#include <unordered_map>
#include <mutex>
#include <set>
#include <string>
#include <unordered_map>
#include <thread>
std::unordered_map<std::uint32_t, Chunk::Chunk*> chunks;
std::unordered_map<std::uint32_t, Chunk::Chunk *> chunks;
namespace chunkmanager
{
void init()
std::mutex mutex_queue_generate;
std::set<Chunk::Chunk *> to_generate;
std::set<Chunk::Chunk *> to_generate_shared;
std::mutex mutex_queue_mesh;
std::set<Chunk::Chunk *> to_mesh;
std::set<Chunk::Chunk *> to_mesh_shared;
void mesh()
{
while (true)
{
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();
}
// while (!to_mesh.empty())
// {
// Chunk::Chunk *c = to_mesh.front();
// if (c->mutex_state.try_lock())
// {
// chunkmesher::mesh(c);
// // std::cout << "Meshing chunk at " << c->getPosition().x << ", " << c->getPosition().y << ", " << c->getPosition().z << "\n";
// c->setState(Chunk::CHUNK_STATE_MESHED, true);
// c->mutex_state.unlock();
// to_mesh.pop();
// }
// }
// if(mutex_queue_mesh.try_lock()){
// to_mesh = to_mesh_shared;
// mutex_queue_mesh.unlock();
// }
// std::cout << "To mesh empty\n";
}
}
void generate()
{
while (true)
{
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()
{
std::thread mesh_thread(mesh);
return mesh_thread;
}
std::thread initGenThread()
{
std::thread gen_thread(generate);
return gen_thread;
}
int total{0}, toGpu{0};
int rr{RENDER_DISTANCE * RENDER_DISTANCE};
uint8_t f = 0;
void update(float deltaTime)
{
f = 0;
f |= mutex_queue_generate.try_lock();
f |= mutex_queue_mesh.try_lock() << 1;
// Iterate over all chunks, in concentric spheres starting fron the player and going outwards
// Eq. of the sphere (x - a)² + (y - b)² + (z - c)² = r²
glm::vec3 cameraPos = theCamera.getPos();
int chunkX{(static_cast<int>(cameraPos.x)) / CHUNK_SIZE}, chunkY{(static_cast<int>(cameraPos.y)) / CHUNK_SIZE}, chunkZ{(static_cast<int>(cameraPos.z)) / CHUNK_SIZE};
// Possible change: coordinates everything at the origin, then translate later?
// Step 1. Eq. of a circle. Fix the x coordinate, get the 2 possible y's
for (int x = chunkX - RENDER_DISTANCE; x <= chunkX + RENDER_DISTANCE; x++)
int xp{0}, x{0};
bool b = true;
while (xp <= RENDER_DISTANCE)
{
if (b)
{
x = chunkX + xp;
}
else
{
x = chunkX - xp;
}
// for (int x = chunkX - RENDER_DISTANCE; x < chunkX + RENDER_DISTANCE; x++)
// {
// Possible optimization: use sqrt lookup
int y1 = sqrt((rr) - (x - chunkX) * (x - chunkX)) + chunkY;
@ -49,7 +147,6 @@ namespace chunkmanager
for (int z = z2; z <= z1; z++)
{
uint16_t i{}, j{}, k{};
if (x < 0)
@ -77,42 +174,138 @@ namespace chunkmanager
chunkmanager::updateChunk(in, i, j, k);
}
}
if (!b)
{
xp++;
b = true;
}
else
{
b = false;
}
}
std::cout << "Total chunks to draw: " << total << ". Sent to GPU: " << toGpu << "\n";
total = 0;
toGpu = 0;
// std::cout << "Total chunks to draw: " << total << ". Sent to GPU: " << toGpu << "\n";
// total = 0;
// toGpu = 0;
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 queues+mutexes to pass the chunks 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
// For now using frustum culling decreases performance (somehow)
void updateChunk(uint32_t index, uint16_t i, uint16_t j, uint16_t k)
{
// std::cout << "Checking" << i << ", " << j << ", " << k <<std::endl;
if (chunks.find(index) == chunks.end())
{
chunks.insert(std::make_pair(index, new Chunk::Chunk(glm::vec3(i, j, k))));
generateChunk(chunks.at(index));
mesh(chunks.at(index));
// std::cout << "Creating new chunk" << i << ", " << j << ", " << k <<std::endl;
Chunk::Chunk *c = new Chunk::Chunk(glm::vec3(i, j, k));
chunks.insert(std::make_pair(index, c));
}
else
{
glm::vec3 chunk = chunks.at(index)->getPosition() /*+ glm::vec3(static_cast<float>(CHUNK_SIZE))*/;
glm::mat4 model = glm::translate(glm::mat4(1.0), ((float)CHUNK_SIZE)*chunk);
Chunk::Chunk *c = chunks.at(index);
total++;
if (!(c->mutex_state.try_lock()))
return;
int a{0};
for (int i = 0; i < 8; i++)
if (!c->getState(Chunk::CHUNK_STATE_GENERATED))
{
glm::vec4 vertex = glm::vec4(chunk.x + (float)(i & 1), chunk.y + (float)((i & 2) >> 1), chunk.z + (float)((i & 4) >> 2), 500.0f) * (theCamera.getProjection() * theCamera.getView() * model);
vertex = glm::normalize(vertex);
a += (-vertex.w <= vertex.x && vertex.x <= vertex.w && -vertex.w <= vertex.y && vertex.y <= vertex.w /*&& -vertex.w < vertex.z && vertex.z < vertex.w*/);
if (f & 1)
to_generate.insert(c);
}
if (a)
else
{
toGpu++;
draw(chunks.at(index), model);
if (!c->getState(Chunk::CHUNK_STATE_MESHED))
{
if (f & 2)
to_mesh.insert(c);
}
else
{
if (!c->getState(Chunk::CHUNK_STATE_MESH_LOADED))
{
if (c->vIndex > 0)
{
// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
glBindVertexArray(c->VAO);
glBindBuffer(GL_ARRAY_BUFFER, c->VBO);
glBufferData(GL_ARRAY_BUFFER, c->vertices.size() * sizeof(GLfloat), &(c->vertices[0]), GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, c->EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, c->indices.size() * sizeof(GLuint), &(c->indices[0]), GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, c->colorBuffer);
glBufferData(GL_ARRAY_BUFFER, c->colors.size() * sizeof(GLfloat), &(c->colors[0]), GL_STATIC_DRAW);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0);
// glDisableVertexAttribArray(0);
// glDisableVertexAttribArray(1);
glBindVertexArray(0);
c->vIndex = (GLuint)(c->indices.size());
c->vertices.clear();
c->indices.clear();
c->colors.clear();
}
c->setState(Chunk::CHUNK_STATE_MESH_LOADED, true);
}
glm::vec3 chunk = c->getPosition();
glm::mat4 model = glm::translate(glm::mat4(1.0), ((float)CHUNK_SIZE) * chunk);
// chunkmesher::draw(c, model);
total++;
int a{0};
for (int i = 0; i < 8; i++)
{
glm::vec4 vertex = glm::vec4(chunk.x + (float)(i & 1), chunk.y + (float)((i & 2) >> 1), chunk.z + (float)((i & 4) >> 2), 500.0f) * (theCamera.getProjection() * theCamera.getView() * model);
vertex = glm::normalize(vertex);
a += (-vertex.w <= vertex.x && vertex.x <= vertex.w && -vertex.w <= vertex.y && vertex.y <= vertex.w /*&& -vertex.w < vertex.z && vertex.z < vertex.w*/);
}
if (a)
{
toGpu++;
chunkmesher::draw(c, model);
}
}
}
// if ((f & 4) == 4)
// {
// glm::vec3 chunk = c->getPosition();
// glm::mat4 model = glm::translate(glm::mat4(1.0), ((float)CHUNK_SIZE) * chunk);
// // chunkmesher::draw(c, model);
// // total++;
// // int a{0};
// // for (int i = 0; i < 8; i++)
// // {
// // glm::vec4 vertex = glm::vec4(chunk.x + (float)(i & 1), chunk.y + (float)((i & 2) >> 1), chunk.z + (float)((i & 4) >> 2), 500.0f) * (theCamera.getProjection() * theCamera.getView() * model);
// // vertex = glm::normalize(vertex);
// // a += (-vertex.w <= vertex.x && vertex.x <= vertex.w && -vertex.w <= vertex.y && vertex.y <= vertex.w /*&& -vertex.w < vertex.z && vertex.z < vertex.w*/);
// // }
// // if (a)
// // {
// // toGpu++;
// chunkmesher::draw(c, model);
// // }
// }
c->mutex_state.unlock();
}
}
@ -121,4 +314,4 @@ namespace chunkmanager
for (auto &n : chunks)
delete n.second;
}
};
};

View File

@ -8,11 +8,8 @@
#include "spacefilling.hpp"
#include "utils.hpp"
std::vector<GLfloat> vertices;
std::vector<GLfloat> colors;
std::vector<GLuint> indices;
GLuint vIndex{0};
namespace chunkmesher{
void mesh(Chunk::Chunk* chunk)
{
@ -37,11 +34,11 @@ void mesh(Chunk::Chunk* chunk)
* containing all the quads. In the future, maybe translucent blocks and liquids
* will need a separate mesh, but still on a per-chunk basis
*/
vertices.clear();
indices.clear();
vIndex = 0;
chunk->vertices.clear();
chunk->indices.clear();
chunk->vIndex = 0;
// if(chunk->getState(Chunk::CHUNK_STATE_EMPTY)) return;
if(chunk->getState(Chunk::CHUNK_STATE_EMPTY)) return;
// convert tree to array since it is easier to work with it
int length{0};
@ -149,7 +146,7 @@ void mesh(Chunk::Chunk* chunk)
dv[2] = 0;
dv[v] = h;
quad(glm::vec3(x[0], x[1], x[2]),
quad(chunk, 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]),
@ -183,46 +180,15 @@ void mesh(Chunk::Chunk* chunk)
}
}
if (vIndex > 0)
{
// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
glBindVertexArray(chunk->VAO);
glBindBuffer(GL_ARRAY_BUFFER, chunk->VBO);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(GLfloat), &(vertices[0]), GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, chunk->EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(GLuint), &(indices[0]), GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, chunk->colorBuffer);
glBufferData(GL_ARRAY_BUFFER, colors.size() * sizeof(GLfloat), &(colors[0]), GL_STATIC_DRAW);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0);
// glDisableVertexAttribArray(0);
// glDisableVertexAttribArray(1);
glBindVertexArray(0);
chunk->vIndex = (GLuint)(indices.size());
vertices.clear();
indices.clear();
colors.clear();
}
delete[] blocks;
}
void draw(Chunk::Chunk* chunk, glm::mat4 model)
{
// glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // wireframe mode
if(!chunk->getState(Chunk::CHUNK_STATE_EMPTY))
if(chunk->getState(Chunk::CHUNK_STATE_MESH_LOADED))
{
theShader->use();
theShader->setMat4("model", model);
@ -235,44 +201,44 @@ void draw(Chunk::Chunk* chunk, glm::mat4 model)
}
}
void quad(glm::vec3 bottomLeft, glm::vec3 topLeft, glm::vec3 topRight, glm::vec3 bottomRight, Block block, bool backFace)
void quad(Chunk::Chunk* chunk, glm::vec3 bottomLeft, glm::vec3 topLeft, glm::vec3 topRight, glm::vec3 bottomRight, Block block, bool backFace)
{
vertices.push_back(bottomLeft.x);
vertices.push_back(bottomLeft.y);
vertices.push_back(bottomLeft.z);
chunk->vertices.push_back(bottomLeft.x);
chunk->vertices.push_back(bottomLeft.y);
chunk->vertices.push_back(bottomLeft.z);
vertices.push_back(bottomRight.x);
vertices.push_back(bottomRight.y);
vertices.push_back(bottomRight.z);
chunk->vertices.push_back(bottomRight.x);
chunk->vertices.push_back(bottomRight.y);
chunk->vertices.push_back(bottomRight.z);
vertices.push_back(topLeft.x);
vertices.push_back(topLeft.y);
vertices.push_back(topLeft.z);
chunk->vertices.push_back(topLeft.x);
chunk->vertices.push_back(topLeft.y);
chunk->vertices.push_back(topLeft.z);
vertices.push_back(topRight.x);
vertices.push_back(topRight.y);
vertices.push_back(topRight.z);
chunk->vertices.push_back(topRight.x);
chunk->vertices.push_back(topRight.y);
chunk->vertices.push_back(topRight.z);
if (backFace)
{
indices.push_back(vIndex + 2);
indices.push_back(vIndex);
indices.push_back(vIndex + 1);
indices.push_back(vIndex + 1);
indices.push_back(vIndex + 3);
indices.push_back(vIndex + 2);
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);
}
else
{
indices.push_back(vIndex + 2);
indices.push_back(vIndex + 3);
indices.push_back(vIndex + 1);
indices.push_back(vIndex + 1);
indices.push_back(vIndex);
indices.push_back(vIndex + 2);
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);
}
vIndex += 4;
chunk->vIndex += 4;
// ugly switch case
GLfloat r, g, b;
@ -317,8 +283,9 @@ void quad(glm::vec3 bottomLeft, glm::vec3 topLeft, glm::vec3 topRight, glm::vec3
for (int i = 0; i < 4; i++)
{
colors.push_back(r);
colors.push_back(g);
colors.push_back(b);
chunk->colors.push_back(r);
chunk->colors.push_back(g);
chunk->colors.push_back(b);
}
}
}
};

View File

@ -3,6 +3,7 @@
#include <GLFW/glfw3.h>
#include <iostream>
#include <thread>
#include "chunkmanager.hpp"
#include "main.hpp"
@ -52,11 +53,12 @@ int main()
glfwSetCursorPosCallback(window, mouse_callback);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE); //GL_BACK GL_CCW by default
std::cout << "Using GPU: " << glGetString(GL_VENDOR) << " " << glGetString(GL_RENDERER) << "\n";
SpaceFilling::initLUT();
chunkmanager::init();
std::thread genThread = chunkmanager::initGenThread();
std::thread meshThread = chunkmanager::initMeshThread();
theShader = new Shader{"shaders/shader.vs", "shaders/shader.fs"};
@ -93,6 +95,9 @@ int main()
}
delete theShader;
genThread.join();
meshThread.join();
chunkmanager::destroy();
glfwTerminate();