diff --git a/CMakeLists.txt b/CMakeLists.txt index b04b1f2..200fe0d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,4 +58,13 @@ if (USE_EXTRAS) set_target_properties(FinalProject PROPERTIES COMPILE_FLAGS "-pthread") else() target_link_libraries(FinalProject PRIVATE glfw) +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() \ No newline at end of file diff --git a/include/render/gl.h b/include/render/gl.h index d3a6e27..73b2bea 100644 --- a/include/render/gl.h +++ b/include/render/gl.h @@ -36,7 +36,7 @@ namespace fp { vbo_type type = ARRAY_BUFFER; 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); bind(); glBufferData(type, size, data, mem_type); diff --git a/include/render/textures.h b/include/render/textures.h index b352654..841d8f5 100644 --- a/include/render/textures.h +++ b/include/render/textures.h @@ -112,8 +112,8 @@ namespace fp::texture { int width, int height, GLint bind_type = GL_TEXTURE_2D, GLint color_mode = GL_RGBA ): - m_width(width), m_height(height), textureBindType(bind_type), - textureColorMode(color_mode) { + textureBindType(bind_type), textureColorMode(color_mode), m_width(width), + m_height(height) { glGenTextures(1, &textureID); } diff --git a/include/util/threadpool.h b/include/util/threadpool.h index 39b25f5..5d67d20 100644 --- a/include/util/threadpool.h +++ b/include/util/threadpool.h @@ -29,7 +29,7 @@ namespace blt { private: volatile bool halted; int MAX_THREADS; - std::queue runQueue {}; + std::queue*> runQueue {}; std::vector threads {}; std::mutex queueMutex {}; public: @@ -40,7 +40,7 @@ namespace blt { */ void start(); - void run(std::function& func); + void run(std::function* func); void run(runnable* func); diff --git a/include/world/world.h b/include/world/world.h index 04e376a..5bf3371 100644 --- a/include/world/world.h +++ b/include/world/world.h @@ -11,6 +11,7 @@ #include #include #include "blt/profiling/profiler.h" +#include "util/threadpool.h" #include namespace fp { @@ -151,7 +152,14 @@ namespace fp { class world { private: - phmap::flat_hash_map 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_storage; + std::mutex chunkInsertMutex; + std::thread* chunkGenerationThread; + const unsigned int THREAD_COUNT = 16; + volatile bool running = true; protected: void generateChunkMesh(chunk* chunk); @@ -166,9 +174,16 @@ namespace fp { 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) - return; + return false; + // we must lock for the entire insertion including neighbour updates. + // otherwise chunk updates might not be properly handled + std::scoped_lock 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_neighbours chunkNeighbours{}; @@ -178,6 +193,18 @@ namespace fp { if (p) 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) { @@ -192,7 +219,7 @@ namespace fp { } public: - world() = default; + world(); void update(); diff --git a/src/main.cpp b/src/main.cpp index 9e0dc02..68b58fa 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -52,14 +52,20 @@ int main() { fp::window::init(); + BLT_TRACE("Creating renderer"); renderer = new fp::renderer(); + BLT_TRACE("Init graphics"); fp::graphics::init(*renderer); + BLT_TRACE("Creating textures"); // textures must come first as blocks will require the IDs fp::registry::registerDefaultTextures(); fp::registry::registerDefaultBlocks(); + BLT_TRACE("Registered textures!"); + BLT_TRACE("Creating chunk shader!"); chunk_shader = renderer->createShader(fp::shader(shader_chunk_vert, shader_chunk_frag)); world = new fp::world(); + BLT_TRACE("World created!"); glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); diff --git a/src/render/renderer.cpp b/src/render/renderer.cpp index b30a1ab..0a1b5f4 100644 --- a/src/render/renderer.cpp +++ b/src/render/renderer.cpp @@ -8,8 +8,11 @@ namespace fp { shader* renderer::createShader(shader&& shader) { + BLT_TRACE("Creating shader"); auto s = new class shader(std::move(shader)); + BLT_TRACE("Shader created"); shaders.push_back(s); + BLT_TRACE("returning"); return s; } } \ No newline at end of file diff --git a/src/render/ui/graphics.cpp b/src/render/ui/graphics.cpp index 9a623f1..ae5dc4c 100644 --- a/src/render/ui/graphics.cpp +++ b/src/render/ui/graphics.cpp @@ -122,10 +122,12 @@ namespace fp::graphics { } void init(renderer& renderer) { + BLT_TRACE("Init font"); if (FT_Init_FreeType(&ft)) { BLT_FATAL("Unable to init freetype library!"); std::abort(); } + BLT_TRACE("Init face"); if (FT_New_Face(ft, "assets/fonts/JetBrains Mono.ttf", 0, &monospaced_face)) { BLT_ERROR("Unable to load default monospaced (JetBrains Mono) font!"); std::abort(); @@ -134,6 +136,7 @@ namespace fp::graphics { // 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. glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + BLT_TRACE("Alignment set, generating characters"); generateCharacters(FONT_11); generateCharacters(FONT_12); @@ -145,15 +148,20 @@ namespace fp::graphics { generateCharacters(FONT_72); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + BLT_TRACE("Character generation complete!"); FT_Done_Face(monospaced_face); FT_Done_FreeType(ft); + BLT_TRACE("Creating font shaders and GL objects"); // create the GL objects required to render texts 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)); + BLT_TRACE("plane shader created!"); quad_vao = new VAO(); plane_vao = new VAO(); + BLT_TRACE("VAOs created!"); float vertices[6 * 4] = { // vertices uvs @@ -167,11 +175,12 @@ namespace fp::graphics { 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); // 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->bindElementVBO(new VBO(ELEMENT_BUFFER, nullptr, 0, DYNAMIC)); + BLT_TRACE("Init complete!"); } void cleanup() { diff --git a/src/render/window.cpp b/src/render/window.cpp index d555f94..914c705 100644 --- a/src/render/window.cpp +++ b/src/render/window.cpp @@ -148,7 +148,7 @@ void fp::window::init(int width, int height) { //glfwSwapInterval(1); updateWindowViewport(width, height); - + BLT_TRACE("Updated window view port!"); } void fp::window::update() { diff --git a/src/util/threadpool.cpp b/src/util/threadpool.cpp index 3a7e55a..41b4887 100644 --- a/src/util/threadpool.cpp +++ b/src/util/threadpool.cpp @@ -5,17 +5,6 @@ */ #include -class runnable_function : public blt::runnable { - private: - std::function& func; - public: - explicit runnable_function(std::function& func): func(func) {} - - void run() final { - func(); - } -}; - blt::thread_pool::thread_pool(int maxThreads): MAX_THREADS(maxThreads) { halted = false; } @@ -31,13 +20,16 @@ void blt::thread_pool::start() { [this]() -> void { while (!halted){ // acquire a resource from the runnable queue + while (runQueue.empty()) + std::this_thread::yield(); std::unique_lock lock(queueMutex); - runnable* run = runQueue.front(); + auto* run = runQueue.front(); runQueue.pop(); lock.unlock(); // attempt to run the function - run->run(); + if (run) + (*run)(); delete(run); } @@ -65,14 +57,15 @@ void blt::thread_pool::stop() { } } -void blt::thread_pool::run(std::function& func) { +void blt::thread_pool::run(std::function* func) { std::scoped_lock lock(queueMutex); - runQueue.push(new runnable_function(func)); + runQueue.push(func); } void blt::thread_pool::run(blt::runnable* func) { std::scoped_lock lock(queueMutex); - runQueue.push(func) + runQueue.push(new std::function([&]() -> void { + func->run(); + })); } - diff --git a/src/world/world.cpp b/src/world/world.cpp index 89eaa61..a2ce481 100644 --- a/src/world/world.cpp +++ b/src/world/world.cpp @@ -120,20 +120,18 @@ void fp::world::generateChunkMesh(chunk* chunk) { } -std::queue chunks_to_generate{}; - void fp::world::update() { - auto target_delta = 1000000000 / std::stoi(fp::settings::get("FPS")); - - while (fp::window::getCurrentDelta() < target_delta/2) { - if (chunks_to_generate.empty()) - break; - const auto& front = chunks_to_generate.front(); - - spawnChunk(generateChunk(front)); - - chunks_to_generate.pop(); - } +// auto target_delta = 1000000000 / std::stoi(fp::settings::get("FPS")); +// +// while (fp::window::getCurrentDelta() < target_delta) { +// if (chunks_to_generate.empty()) +// break; +// const auto& front = chunks_to_generate.front(); +// +// spawnChunk(generateChunk(front)); +// +// chunks_to_generate.pop(); +// } } 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 k = -view_distance; k <= view_distance; k++) { // get the chunks around the player's camera - 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}); - 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. + chunk_pos adjusted_chunk_pos = offsetCameraByChunk(i, j, k); + // don't try to render null chunks! A separate thread should handle the generation. auto* chunk = this->getChunk(adjusted_chunk_pos); if (!chunk) { - chunks_to_generate.push(adjusted_chunk_pos); +// chunks_to_generate.push(adjusted_chunk_pos); continue; } @@ -175,12 +166,13 @@ void fp::world::render(fp::shader& shader) { 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& m = camera::getPVM(); - if (frustum::isInsideFrustum(m, p_min)) + //if (frustum::isInsideFrustum(m, p_min)) chunk->render(shader); } } @@ -209,7 +201,9 @@ fp::chunk* fp::world::generateChunk(const fp::chunk_pos& pos) { float noise_total = 1; 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; @@ -218,7 +212,9 @@ fp::chunk* fp::world::generateChunk(const fp::chunk_pos& pos) { for (int j = 0; j < 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) 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"); for (auto& chunk : chunk_storage) 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 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) {