#include "ChunksRenderer.hpp" #include "BlocksRenderer.hpp" #include "debug/Logger.hpp" #include "assets/Assets.hpp" #include "graphics/core/Mesh.hpp" #include "graphics/core/Shader.hpp" #include "graphics/core/Texture.hpp" #include "graphics/core/Atlas.hpp" #include "voxels/Chunk.hpp" #include "voxels/Chunks.hpp" #include "world/Level.hpp" #include "window/Camera.hpp" #include "maths/FrustumCulling.hpp" #include "util/listutil.hpp" #include "settings.hpp" #include #include #include #include "util/timeutil.hpp" static debug::Logger logger("chunks-render"); size_t ChunksRenderer::visibleChunks = 0; class RendererWorker : public util::Worker, RendererResult> { const Level& level; BlocksRenderer renderer; public: RendererWorker( const Level& level, const ContentGfxCache& cache, const EngineSettings& settings ) : level(level), renderer(settings.graphics.chunkMaxVertices.get(), *level.content, cache, settings) {} RendererResult operator()(const std::shared_ptr& chunk) override { renderer.build(chunk.get(), level.chunks.get()); if (renderer.isCancelled()) { return RendererResult { glm::ivec2(chunk->x, chunk->z), true, MeshData()}; } auto meshData = renderer.createMesh(); return RendererResult { glm::ivec2(chunk->x, chunk->z), false, std::move(meshData)}; } }; const vattr ATTRS[]{ {3}, {2}, {1}, {0} }; inline constexpr int VERTEX_SIZE = 6; ChunksRenderer::ChunksRenderer( const Level* level, const Assets& assets, const Frustum& frustum, const ContentGfxCache& cache, const EngineSettings& settings ) : level(*level), assets(assets), frustum(frustum), settings(settings), threadPool( "chunks-render-pool", [&](){return std::make_shared(*level, cache, settings);}, [&](RendererResult& result){ if (!result.cancelled) { auto meshData = std::move(result.meshData); meshes[result.key] = ChunkMesh { std::make_unique(meshData.mesh), std::move(meshData.sortingMesh) }; } inwork.erase(result.key); }, settings.graphics.chunkMaxRenderers.get()) { threadPool.setStopOnFail(false); renderer = std::make_unique( settings.graphics.chunkMaxVertices.get(), *level->content, cache, settings ); logger.info() << "created " << threadPool.getWorkersCount() << " workers"; float buf[1]{}; sortedMesh = std::make_unique(buf, 0, ATTRS); } ChunksRenderer::~ChunksRenderer() { } const Mesh* ChunksRenderer::render( const std::shared_ptr& chunk, bool important ) { chunk->flags.modified = false; if (important) { auto mesh = renderer->render(chunk.get(), level.chunks.get()); meshes[glm::ivec2(chunk->x, chunk->z)] = ChunkMesh { std::move(mesh.mesh), std::move(mesh.sortingMeshData) }; return meshes[glm::ivec2(chunk->x, chunk->z)].mesh.get(); } glm::ivec2 key(chunk->x, chunk->z); if (inwork.find(key) != inwork.end()) { return nullptr; } inwork[key] = true; threadPool.enqueueJob(chunk); return nullptr; } void ChunksRenderer::unload(const Chunk* chunk) { auto found = meshes.find(glm::ivec2(chunk->x, chunk->z)); if (found != meshes.end()) { meshes.erase(found); } } void ChunksRenderer::clear() { meshes.clear(); inwork.clear(); threadPool.clearQueue(); } const Mesh* ChunksRenderer::getOrRender( const std::shared_ptr& chunk, bool important ) { auto found = meshes.find(glm::ivec2(chunk->x, chunk->z)); if (found == meshes.end()) { return render(chunk, important); } if (chunk->flags.modified) { render(chunk, important); } return found->second.mesh.get(); } void ChunksRenderer::update() { threadPool.update(); } bool ChunksRenderer::drawChunk( size_t index, const Camera& camera, Shader& shader, bool culling ) { auto chunk = level.chunks->getChunks()[index]; if (chunk == nullptr || !chunk->flags.lighted) { return false; } float distance = glm::distance( camera.position, glm::vec3( (chunk->x + 0.5f) * CHUNK_W, camera.position.y, (chunk->z + 0.5f) * CHUNK_D ) ); auto mesh = getOrRender(chunk, distance < CHUNK_W * 1.5f); if (mesh == nullptr) { return false; } if (culling) { glm::vec3 min(chunk->x * CHUNK_W, chunk->bottom, chunk->z * CHUNK_D); glm::vec3 max( chunk->x * CHUNK_W + CHUNK_W, chunk->top, chunk->z * CHUNK_D + CHUNK_D ); if (!frustum.isBoxVisible(min, max)) return false; } glm::vec3 coord(chunk->x * CHUNK_W + 0.5f, 0.5f, chunk->z * CHUNK_D + 0.5f); glm::mat4 model = glm::translate(glm::mat4(1.0f), coord); shader.uniformMatrix("u_model", model); mesh->draw(); return true; } void ChunksRenderer::drawChunks( const Camera& camera, Shader& shader ) { const auto& chunks = *level.chunks; const auto& atlas = assets.require("blocks"); atlas.getTexture()->bind(); update(); // [warning] this whole method is not thread-safe for chunks int chunksWidth = chunks.getWidth(); int chunksOffsetX = chunks.getOffsetX(); int chunksOffsetY = chunks.getOffsetY(); if (indices.size() != chunks.getVolume()) { indices.clear(); for (int i = 0; i < chunks.getVolume(); i++) { indices.push_back(ChunksSortEntry {i, 0}); } } float px = camera.position.x / static_cast(CHUNK_W) - 0.5f; float pz = camera.position.z / static_cast(CHUNK_D) - 0.5f; for (auto& index : indices) { float x = index.index % chunksWidth + chunksOffsetX - px; float z = index.index / chunksWidth + chunksOffsetY - pz; index.d = (x * x + z * z) * 1024; } util::insertion_sort(indices.begin(), indices.end()); bool culling = settings.graphics.frustumCulling.get(); visibleChunks = 0; //if (GLEW_ARB_multi_draw_indirect && false) { // TODO: implement Multi Draw Indirect chunks draw //} else { for (size_t i = 0; i < indices.size(); i++) { visibleChunks += drawChunk(indices[i].index, camera, shader, culling); } //} } void ChunksRenderer::drawSortedMeshes(const Camera& camera, Shader& shader) { timeutil::ScopeLogTimer log(444); const int sortInterval = 6; static int frameid = 0; frameid++; bool culling = settings.graphics.frustumCulling.get(); const auto& chunks = level.chunks->getChunks(); const auto& cameraPos = camera.position; const auto& atlas = assets.require("blocks"); atlas.getTexture()->bind(); shader.uniformMatrix("u_model", glm::mat4(1.0f)); for (const auto& index : indices) { const auto& chunk = chunks[index.index]; if (chunk == nullptr || !chunk->flags.lighted) { continue; } const auto& found = meshes.find(glm::ivec2(chunk->x, chunk->z)); if (found == meshes.end()) { continue; } glm::vec3 min( chunk->x * CHUNK_W - CHUNK_W, 0, chunk->z * CHUNK_D - CHUNK_D ); glm::vec3 max( chunk->x * CHUNK_W + CHUNK_W*2, CHUNK_H, chunk->z * CHUNK_D + CHUNK_D*2 ); if (!frustum.isBoxVisible(min, max)) continue; auto& chunkEntries = found->second.sortingMeshData.entries; if (chunkEntries.empty()) { continue; } else if (chunkEntries.size() == 1) { auto& entry = chunkEntries.at(0); if (found->second.sortedMesh == nullptr) { found->second.sortedMesh = std::make_unique( entry.vertexData.data(), entry.vertexData.size() / VERTEX_SIZE, ATTRS ); } found->second.sortedMesh->draw(); continue; } for (auto& entry : chunkEntries) { entry.distance = static_cast(glm::distance2(entry.position, cameraPos)); } if (found->second.sortedMesh == nullptr || (frameid + chunk->x) % sortInterval == 0) { std::sort(chunkEntries.begin(), chunkEntries.end()); size_t size = 0; for (const auto& entry : chunkEntries) { size += entry.vertexData.size(); } static util::Buffer buffer; if (buffer.size() < size) { buffer = util::Buffer(size); } size_t offset = 0; for (const auto& entry : chunkEntries) { std::memcpy( (buffer.data() + offset), entry.vertexData.data(), entry.vertexData.size() * sizeof(float) ); offset += entry.vertexData.size(); } found->second.sortedMesh = std::make_unique( buffer.data(), size / VERTEX_SIZE, ATTRS ); } found->second.sortedMesh->draw(); } }