VoxelEngine/src/graphics/render/ChunksRenderer.cpp
2024-11-22 17:13:15 +02:00

304 lines
9.7 KiB
C++

#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"
static debug::Logger logger("chunks-render");
size_t ChunksRenderer::visibleChunks = 0;
class RendererWorker : public util::Worker<std::shared_ptr<Chunk>, 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>& 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)};
}
};
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<RendererWorker>(*level, cache, settings);},
[&](RendererResult& result){
if (!result.cancelled) {
auto meshData = std::move(result.meshData);
meshes[result.key] = ChunkMesh {
std::make_unique<Mesh>(meshData.mesh),
std::move(meshData.sortingMesh)
};
}
inwork.erase(result.key);
}, settings.graphics.chunkMaxRenderers.get())
{
threadPool.setStopOnFail(false);
renderer = std::make_unique<BlocksRenderer>(
settings.graphics.chunkMaxVertices.get(),
*level->content, cache, settings
);
logger.info() << "created " << threadPool.getWorkersCount() << " workers";
}
ChunksRenderer::~ChunksRenderer() {
}
const Mesh* ChunksRenderer::render(
const std::shared_ptr<Chunk>& 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>& 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();
}
const Mesh* ChunksRenderer::retrieveChunk(
size_t index, const Camera& camera, Shader& shader, bool culling
) {
auto chunk = level.chunks->getChunks()[index];
if (chunk == nullptr || !chunk->flags.lighted) {
return nullptr;
}
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 nullptr;
}
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 nullptr;
}
return mesh;
}
void ChunksRenderer::drawChunks(
const Camera& camera, Shader& shader
) {
const auto& chunks = *level.chunks;
const auto& atlas = assets.require<Atlas>("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<float>(CHUNK_W) - 0.5f;
float pz = camera.position.z / static_cast<float>(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;
shader.uniform1i("u_alphaClip", true);
// TODO: minimize draw calls number
for (size_t i = 0; i < indices.size(); i++) {
auto chunk = chunks.getChunks()[indices[i].index];
auto mesh = retrieveChunk(indices[i].index, camera, shader, culling);
if (mesh) {
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();
visibleChunks++;
}
}
}
static inline void write_sorting_mesh_entries(
float* buffer, const std::vector<SortingMeshEntry>& chunkEntries
) {
for (const auto& entry : chunkEntries) {
const auto& vertexData = entry.vertexData;
std::memcpy(
buffer,
vertexData.data(),
vertexData.size() * sizeof(float)
);
buffer += vertexData.size();
}
}
void ChunksRenderer::drawSortedMeshes(const Camera& camera, Shader& shader) {
const int sortInterval = TRANSLUCENT_BLOCKS_SORT_INTERVAL;
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<Atlas>("blocks");
shader.use();
atlas.getTexture()->bind();
shader.uniformMatrix("u_model", glm::mat4(1.0f));
shader.uniform1i("u_alphaClip", false);
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() || found->second.sortingMeshData.entries.empty()) {
continue;
}
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)) continue;
}
auto& chunkEntries = found->second.sortingMeshData.entries;
if (chunkEntries.size() == 1) {
auto& entry = chunkEntries.at(0);
if (found->second.sortedMesh == nullptr) {
found->second.sortedMesh = std::make_unique<Mesh>(
entry.vertexData.data(),
entry.vertexData.size() / CHUNK_VERTEX_SIZE,
CHUNK_VATTRS
);
}
found->second.sortedMesh->draw();
continue;
}
for (auto& entry : chunkEntries) {
entry.distance = static_cast<long long>(
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<float> buffer;
if (buffer.size() < size) {
buffer = util::Buffer<float>(size);
}
write_sorting_mesh_entries(buffer.data(), chunkEntries);
found->second.sortedMesh = std::make_unique<Mesh>(
buffer.data(), size / CHUNK_VERTEX_SIZE, CHUNK_VATTRS
);
}
found->second.sortedMesh->draw();
}
}