working on fixing threading

main
Brett 2023-04-24 16:14:37 -04:00
parent fddef65704
commit 22f8fedce2
11 changed files with 146 additions and 55 deletions

View File

@ -59,3 +59,12 @@ if (USE_EXTRAS)
else() else()
target_link_libraries(FinalProject PRIVATE glfw) target_link_libraries(FinalProject PRIVATE glfw)
endif() endif()
if(MSVC)
#target_compile_options(BLT_TESTS PRIVATE /W4)
if(${CMAKE_BUILD_TYPE} MATCHES Debug)
target_link_options(FinalProject PRIVATE /DEBUG)
endif()
else()
target_compile_options(FinalProject PRIVATE -Wall -Wextra -Wpedantic)
endif()

View File

@ -36,7 +36,7 @@ namespace fp {
vbo_type type = ARRAY_BUFFER; vbo_type type = ARRAY_BUFFER;
vbo_mem_type mem_type = STATIC; vbo_mem_type mem_type = STATIC;
VBO(vbo_type type, void* data, int size, vbo_mem_type mem_type = STATIC): type(type), size(size), mem_type(mem_type) { VBO(vbo_type type, void* data, int size, vbo_mem_type mem_type = STATIC): size(size), type(type), mem_type(mem_type) {
glGenBuffers(1, &vboID); glGenBuffers(1, &vboID);
bind(); bind();
glBufferData(type, size, data, mem_type); glBufferData(type, size, data, mem_type);

View File

@ -112,8 +112,8 @@ namespace fp::texture {
int width, int height, GLint bind_type = GL_TEXTURE_2D, int width, int height, GLint bind_type = GL_TEXTURE_2D,
GLint color_mode = GL_RGBA GLint color_mode = GL_RGBA
): ):
m_width(width), m_height(height), textureBindType(bind_type), textureBindType(bind_type), textureColorMode(color_mode), m_width(width),
textureColorMode(color_mode) { m_height(height) {
glGenTextures(1, &textureID); glGenTextures(1, &textureID);
} }

View File

@ -29,7 +29,7 @@ namespace blt {
private: private:
volatile bool halted; volatile bool halted;
int MAX_THREADS; int MAX_THREADS;
std::queue<runnable*> runQueue {}; std::queue<std::function<void()>*> runQueue {};
std::vector<std::thread*> threads {}; std::vector<std::thread*> threads {};
std::mutex queueMutex {}; std::mutex queueMutex {};
public: public:
@ -40,7 +40,7 @@ namespace blt {
*/ */
void start(); void start();
void run(std::function<void()>& func); void run(std::function<void()>* func);
void run(runnable* func); void run(runnable* func);

View File

@ -11,6 +11,7 @@
#include <render/gl.h> #include <render/gl.h>
#include <phmap.h> #include <phmap.h>
#include "blt/profiling/profiler.h" #include "blt/profiling/profiler.h"
#include "util/threadpool.h"
#include <render/frustum.h> #include <render/frustum.h>
namespace fp { namespace fp {
@ -151,7 +152,14 @@ namespace fp {
class world { class world {
private: private:
phmap::flat_hash_map<chunk_pos, chunk*, _static::chunk_pos_hash, _static::chunk_pos_equality> chunk_storage; // not using the parallel functions of the map, we will do manual concurrency control
// "The parallel hash maps are preferred when you have a few hash maps that will store a very large number of values.
// The non-parallel hash maps are preferred if you have a large number of hash maps, each storing a relatively small number of values."
phmap::parallel_flat_hash_map<chunk_pos, chunk*, _static::chunk_pos_hash, _static::chunk_pos_equality> chunk_storage;
std::mutex chunkInsertMutex;
std::thread* chunkGenerationThread;
const unsigned int THREAD_COUNT = 16;
volatile bool running = true;
protected: protected:
void generateChunkMesh(chunk* chunk); void generateChunkMesh(chunk* chunk);
@ -166,9 +174,16 @@ namespace fp {
neighbours[Z_NEG] = getChunk(chunk_pos{pos.x, pos.y, pos.z - 1}); neighbours[Z_NEG] = getChunk(chunk_pos{pos.x, pos.y, pos.z - 1});
} }
inline void spawnChunk(chunk* chunk) { inline bool spawnChunk(chunk* chunk) {
// make sure not to insert null chunks
if (chunk == nullptr) if (chunk == nullptr)
return; return false;
// we must lock for the entire insertion including neighbour updates.
// otherwise chunk updates might not be properly handled
std::scoped_lock<std::mutex> lock(chunkInsertMutex);
// or overwrite existing chunks (*memory leak*)
if (chunk_storage.find(chunk->getPos()) != chunk_storage.end())
return false;
chunk_storage.insert({chunk->getPos(), chunk}); chunk_storage.insert({chunk->getPos(), chunk});
chunk_neighbours chunkNeighbours{}; chunk_neighbours chunkNeighbours{};
@ -178,6 +193,18 @@ namespace fp {
if (p) if (p)
p->setStatus(NEIGHBOUR_CREATE); p->setStatus(NEIGHBOUR_CREATE);
} }
return true;
}
inline static chunk_pos offsetCameraByChunk(int i, int j, int k) {
const auto& pos = fp::camera::getPosition();
int x = (int) pos.x();
int y = (int) pos.y();
int z = (int) pos.z();
auto camera_chunk_pos = fp::_static::world_to_chunk({x, y, z});
return chunk_pos{camera_chunk_pos.x + i, // chunk x
camera_chunk_pos.y + j, // chunk y
camera_chunk_pos.z + k}; // chunk z
} }
inline chunk* getChunk(const chunk_pos& pos) { inline chunk* getChunk(const chunk_pos& pos) {
@ -192,7 +219,7 @@ namespace fp {
} }
public: public:
world() = default; world();
void update(); void update();

View File

@ -52,14 +52,20 @@ int main() {
fp::window::init(); fp::window::init();
BLT_TRACE("Creating renderer");
renderer = new fp::renderer(); renderer = new fp::renderer();
BLT_TRACE("Init graphics");
fp::graphics::init(*renderer); fp::graphics::init(*renderer);
BLT_TRACE("Creating textures");
// textures must come first as blocks will require the IDs // textures must come first as blocks will require the IDs
fp::registry::registerDefaultTextures(); fp::registry::registerDefaultTextures();
fp::registry::registerDefaultBlocks(); fp::registry::registerDefaultBlocks();
BLT_TRACE("Registered textures!");
BLT_TRACE("Creating chunk shader!");
chunk_shader = renderer->createShader(fp::shader(shader_chunk_vert, shader_chunk_frag)); chunk_shader = renderer->createShader(fp::shader(shader_chunk_vert, shader_chunk_frag));
world = new fp::world(); world = new fp::world();
BLT_TRACE("World created!");
glEnable(GL_CULL_FACE); glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);

View File

@ -8,8 +8,11 @@
namespace fp { namespace fp {
shader* renderer::createShader(shader&& shader) { shader* renderer::createShader(shader&& shader) {
BLT_TRACE("Creating shader");
auto s = new class shader(std::move(shader)); auto s = new class shader(std::move(shader));
BLT_TRACE("Shader created");
shaders.push_back(s); shaders.push_back(s);
BLT_TRACE("returning");
return s; return s;
} }
} }

View File

@ -122,10 +122,12 @@ namespace fp::graphics {
} }
void init(renderer& renderer) { void init(renderer& renderer) {
BLT_TRACE("Init font");
if (FT_Init_FreeType(&ft)) { if (FT_Init_FreeType(&ft)) {
BLT_FATAL("Unable to init freetype library!"); BLT_FATAL("Unable to init freetype library!");
std::abort(); std::abort();
} }
BLT_TRACE("Init face");
if (FT_New_Face(ft, "assets/fonts/JetBrains Mono.ttf", 0, &monospaced_face)) { if (FT_New_Face(ft, "assets/fonts/JetBrains Mono.ttf", 0, &monospaced_face)) {
BLT_ERROR("Unable to load default monospaced (JetBrains Mono) font!"); BLT_ERROR("Unable to load default monospaced (JetBrains Mono) font!");
std::abort(); std::abort();
@ -134,6 +136,7 @@ namespace fp::graphics {
// disable alignment restrictions. This might cause issues with WebGL! FIXME: if it does // disable alignment restrictions. This might cause issues with WebGL! FIXME: if it does
// gl requires an alignment of 4. Since we are going to only use a single character of any width/height the alignment must be changed. // gl requires an alignment of 4. Since we are going to only use a single character of any width/height the alignment must be changed.
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
BLT_TRACE("Alignment set, generating characters");
generateCharacters(FONT_11); generateCharacters(FONT_11);
generateCharacters(FONT_12); generateCharacters(FONT_12);
@ -145,15 +148,20 @@ namespace fp::graphics {
generateCharacters(FONT_72); generateCharacters(FONT_72);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4); glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
BLT_TRACE("Character generation complete!");
FT_Done_Face(monospaced_face); FT_Done_Face(monospaced_face);
FT_Done_FreeType(ft); FT_Done_FreeType(ft);
BLT_TRACE("Creating font shaders and GL objects");
// create the GL objects required to render texts // create the GL objects required to render texts
text_shader = renderer.createShader(shader(shader_text_vert, shader_text_frag)); text_shader = renderer.createShader(shader(shader_text_vert, shader_text_frag));
BLT_TRACE("text shader created!");
plane_shader = renderer.createShader(shader(shader_plane_vert, shader_plane_frag)); plane_shader = renderer.createShader(shader(shader_plane_vert, shader_plane_frag));
BLT_TRACE("plane shader created!");
quad_vao = new VAO(); quad_vao = new VAO();
plane_vao = new VAO(); plane_vao = new VAO();
BLT_TRACE("VAOs created!");
float vertices[6 * 4] = { float vertices[6 * 4] = {
// vertices uvs // vertices uvs
@ -167,11 +175,12 @@ namespace fp::graphics {
0, 1.0, 0.0f, 1.0f, 0, 1.0, 0.0f, 1.0f,
}; };
BLT_TRACE("Binding VBOs");
quad_vao->bindVBO(new VBO(ARRAY_BUFFER, vertices, sizeof(float) * 6 * 4), 0, 4); quad_vao->bindVBO(new VBO(ARRAY_BUFFER, vertices, sizeof(float) * 6 * 4), 0, 4);
// since we will be updating the plane VBO regularly, we should tell the driver of this fact // since we will be updating the plane VBO regularly, we should tell the driver of this fact
plane_vao->bindVBO(new VBO(ARRAY_BUFFER, nullptr, 0, DYNAMIC), 0, 3, GL_FLOAT, sizeof(float) * 3); plane_vao->bindVBO(new VBO(ARRAY_BUFFER, nullptr, 0, DYNAMIC), 0, 3, GL_FLOAT, sizeof(float) * 3);
plane_vao->bindElementVBO(new VBO(ELEMENT_BUFFER, nullptr, 0, DYNAMIC)); plane_vao->bindElementVBO(new VBO(ELEMENT_BUFFER, nullptr, 0, DYNAMIC));
BLT_TRACE("Init complete!");
} }
void cleanup() { void cleanup() {

View File

@ -148,7 +148,7 @@ void fp::window::init(int width, int height) {
//glfwSwapInterval(1); //glfwSwapInterval(1);
updateWindowViewport(width, height); updateWindowViewport(width, height);
BLT_TRACE("Updated window view port!");
} }
void fp::window::update() { void fp::window::update() {

View File

@ -5,17 +5,6 @@
*/ */
#include <util/threadpool.h> #include <util/threadpool.h>
class runnable_function : public blt::runnable {
private:
std::function<void()>& func;
public:
explicit runnable_function(std::function<void()>& func): func(func) {}
void run() final {
func();
}
};
blt::thread_pool::thread_pool(int maxThreads): MAX_THREADS(maxThreads) { blt::thread_pool::thread_pool(int maxThreads): MAX_THREADS(maxThreads) {
halted = false; halted = false;
} }
@ -31,13 +20,16 @@ void blt::thread_pool::start() {
[this]() -> void { [this]() -> void {
while (!halted){ while (!halted){
// acquire a resource from the runnable queue // acquire a resource from the runnable queue
while (runQueue.empty())
std::this_thread::yield();
std::unique_lock<std::mutex> lock(queueMutex); std::unique_lock<std::mutex> lock(queueMutex);
runnable* run = runQueue.front(); auto* run = runQueue.front();
runQueue.pop(); runQueue.pop();
lock.unlock(); lock.unlock();
// attempt to run the function // attempt to run the function
run->run(); if (run)
(*run)();
delete(run); delete(run);
} }
@ -65,14 +57,15 @@ void blt::thread_pool::stop() {
} }
} }
void blt::thread_pool::run(std::function<void()>& func) { void blt::thread_pool::run(std::function<void()>* func) {
std::scoped_lock<std::mutex> lock(queueMutex); std::scoped_lock<std::mutex> lock(queueMutex);
runQueue.push(new runnable_function(func)); runQueue.push(func);
} }
void blt::thread_pool::run(blt::runnable* func) { void blt::thread_pool::run(blt::runnable* func) {
std::scoped_lock<std::mutex> lock(queueMutex); std::scoped_lock<std::mutex> lock(queueMutex);
runQueue.push(func) runQueue.push(new std::function<void()>([&]() -> void {
func->run();
}));
} }

View File

@ -120,20 +120,18 @@ void fp::world::generateChunkMesh(chunk* chunk) {
} }
std::queue<fp::chunk_pos> chunks_to_generate{};
void fp::world::update() { void fp::world::update() {
auto target_delta = 1000000000 / std::stoi(fp::settings::get("FPS")); // auto target_delta = 1000000000 / std::stoi(fp::settings::get("FPS"));
//
while (fp::window::getCurrentDelta() < target_delta/2) { // while (fp::window::getCurrentDelta() < target_delta) {
if (chunks_to_generate.empty()) // if (chunks_to_generate.empty())
break; // break;
const auto& front = chunks_to_generate.front(); // const auto& front = chunks_to_generate.front();
//
spawnChunk(generateChunk(front)); // spawnChunk(generateChunk(front));
//
chunks_to_generate.pop(); // chunks_to_generate.pop();
} // }
} }
void fp::world::render(fp::shader& shader) { void fp::world::render(fp::shader& shader) {
@ -151,18 +149,11 @@ void fp::world::render(fp::shader& shader) {
for (int j = -view_distance; j <= view_distance; j++) { for (int j = -view_distance; j <= view_distance; j++) {
for (int k = -view_distance; k <= view_distance; k++) { for (int k = -view_distance; k <= view_distance; k++) {
// get the chunks around the player's camera // get the chunks around the player's camera
const auto& pos = fp::camera::getPosition(); chunk_pos adjusted_chunk_pos = offsetCameraByChunk(i, j, k);
int x = (int) pos.x(); // don't try to render null chunks! A separate thread should handle the generation.
int y = (int) pos.y();
int z = (int) pos.z();
auto camera_chunk_pos = fp::_static::world_to_chunk({x, y, z});
chunk_pos adjusted_chunk_pos {camera_chunk_pos.x + i, // chunk x
camera_chunk_pos.y + j, // chunk y
camera_chunk_pos.z + k}; // chunk z
// queue a chunk for generation if it doesn't exist. A separate thread should handle the generation.
auto* chunk = this->getChunk(adjusted_chunk_pos); auto* chunk = this->getChunk(adjusted_chunk_pos);
if (!chunk) { if (!chunk) {
chunks_to_generate.push(adjusted_chunk_pos); // chunks_to_generate.push(adjusted_chunk_pos);
continue; continue;
} }
@ -175,12 +166,13 @@ void fp::world::render(fp::shader& shader) {
chunk->updateChunkMesh(); chunk->updateChunkMesh();
} }
const auto p_min = blt::vec3{(float)i * CHUNK_SIZE, (float)j * CHUNK_SIZE, (float)k * CHUNK_SIZE}; const auto p_min = blt::vec3{(float) i * CHUNK_SIZE, (float) j * CHUNK_SIZE,
(float) k * CHUNK_SIZE};
const auto p_max = p_min + blt::vec3{CHUNK_SIZE, CHUNK_SIZE, CHUNK_SIZE}; const auto p_max = p_min + blt::vec3{CHUNK_SIZE, CHUNK_SIZE, CHUNK_SIZE};
const auto& m = camera::getPVM(); const auto& m = camera::getPVM();
if (frustum::isInsideFrustum(m, p_min)) //if (frustum::isInsideFrustum(m, p_min))
chunk->render(shader); chunk->render(shader);
} }
} }
@ -209,7 +201,9 @@ fp::chunk* fp::world::generateChunk(const fp::chunk_pos& pos) {
float noise_total = 1; float noise_total = 1;
for (int j = 1; j <= 8; j++) for (int j = 1; j <= 8; j++)
noise_total += stb_perlin_noise3(block_x / 256.0f, block_z / 256.0f, (float)j * 5.213953f, 0, 0, 0) * (float)(j); noise_total += stb_perlin_noise3(
block_x / 256.0f, block_z / 256.0f, (float) j * 5.213953f, 0, 0, 0
) * (float) (j);
noise_total /= 8; noise_total /= 8;
@ -218,7 +212,9 @@ fp::chunk* fp::world::generateChunk(const fp::chunk_pos& pos) {
for (int j = 0; j < CHUNK_SIZE; j++) { for (int j = 0; j < CHUNK_SIZE; j++) {
auto block_y = float(pos.y * CHUNK_SIZE + j); auto block_y = float(pos.y * CHUNK_SIZE + j);
float noise2 = stb_perlin_fbm_noise3(block_x / 32.0f, block_y / 32.0f, block_z / 32.0f, 2.0, 0.5, 5) + 0.75f; float noise2 = stb_perlin_fbm_noise3(
block_x / 32.0f, block_y / 32.0f, block_z / 32.0f, 2.0, 0.5, 5
) + 0.75f;
if (block_y < world_height && noise2 > 0) if (block_y < world_height && noise2 > 0)
storage->set({i, j, k}, noise2 > 1 ? fp::registry::GRASS : fp::registry::STONE); storage->set({i, j, k}, noise2 > 1 ? fp::registry::GRASS : fp::registry::STONE);
@ -239,6 +235,54 @@ fp::world::~world() {
BLT_WRITE_PROFILE(profile, "Chunk Mesh"); BLT_WRITE_PROFILE(profile, "Chunk Mesh");
for (auto& chunk : chunk_storage) for (auto& chunk : chunk_storage)
delete (chunk.second); delete (chunk.second);
running = false;
// for (int i = 0; i < THREAD_COUNT; i++)
// chunkGenerationThread[i].join();
chunkGenerationThread->join();
delete chunkGenerationThread;
}
fp::world::world() {
chunkGenerationThread = new std::thread(
[this]() -> void {
while (running) {
auto view_distance = std::stoi(fp::settings::get("VIEW_DISTANCE")) / 2;
// get the chunks around the player's camera
for (int i = -view_distance; i <= view_distance; i++) {
for (int j = -view_distance; j <= view_distance; j++) {
for (int k = -view_distance; k <= view_distance; k++) {
chunk_pos adjusted_chunk_pos = offsetCameraByChunk(i, j, k);
auto* chunk = this->getChunk(adjusted_chunk_pos);
// only generate non-existent chunks
if (chunk)
continue;
auto* generated_chunk = generateChunk(adjusted_chunk_pos);
spawnChunk(generated_chunk);
}
}
}
}
}
);
// for (int i = 0; i < THREAD_COUNT; i++){
// chunkGenerationThread[i] = std::thread{[this]() -> void {
// while (running) {
// std::unique_lock<std::mutex> clock(chunkQueueMutex);
// if (chunks_to_generate.empty()) {
// clock.unlock();
// continue;
// }
// auto chunk_pos = chunks_to_generate.front();
// chunks_to_generate.pop();
// clock.unlock();
//
// auto* generated_chunk = generateChunk(chunk_pos);
// spawnChunk(generated_chunk);
// BLT_TRACE("Generated Chunk! %d, %d, %d", chunk_pos.x, chunk_pos.y,
// chunk_pos.z);
// }
// }};
// }
} }
void fp::chunk::render(fp::shader& shader) { void fp::chunk::render(fp::shader& shader) {