Commit bcd34758 authored by Shu's avatar Shu 🍠
Browse files

LOD

parent 7a51061f
......@@ -44,6 +44,10 @@
- [ ] clang -fall
- [ ] Server
- [ ] ZeroMQ
- [~] Workers
- [x] Basic
- [ ] Use system infos
- [ ] Pool
- [x] Logger
- [ ] FastNoiseSIMD / HastyNoise double precision
- [x] Generational identifier
......@@ -75,9 +79,13 @@
## Contouring
- [x] Box contouring
- [x] Ignore sides
- [ ] LOD
- [~] LOD
- [x] Generate lod
- [x] Display lod
- [x] Select level count
- [ ] Group low lod buffers
- [ ] Octree
- [x] Dual MC
- [ ] Octree
- [~] Collision
- [ ] Dynamic index size
- [x] Chunk size performance
......
#include "FlatDualMC.hpp"
#include "../world/Chunk.hpp"
#include "../world/Area.hpp"
#include "../world/materials.hpp"
#include <Tracy.hpp> // NOLINT
#include <common/TracySystem.hpp> // NOLINT
#include <imgui.h> // NOLINT
#include <toml.h>
#include "dualmc.h"
#include <meshoptimizer.h>
#include "optimizer.hpp"
namespace contouring {
const std::vector<std::pair<float, float>> LEVELS = {{.001f, 9e-1f}, {.01f, 5e-1f}, {.1f, 1e-1f}, {.2, 1e-2f}, {.5, 5e-3f}};
FlatDualMC::FlatDualMC(const std::string &str) : AbstractFlat(str) {
auto opt = toml::parse(str);
iso = opt["iso"].value_or(iso);
manifold = opt["manifold"].value_or(manifold);
reordering = opt["reordering"].value_or(reordering);
lod_quality = opt["lod_quality"].value_or(lod_quality);
lod_strength = opt["lod_strength"].value_or(lod_strength);
if (const auto ptr = opt["lod_levels"].as_array(); ptr != nullptr && ptr->size() == LEVELS.size()) {
const auto& arr = *ptr;
for(const auto& v: arr) {
lod_levels.push_back(v.value_or(true));
}
} else {
lod_levels = {false, true, false, true, false};
}
for (size_t i = 0; i < LEVELS.size(); i++) {
if(lod_levels[i])
loadedLevels.push_back(LEVELS[i]);
}
for (size_t i = 1; i <= 2; i++) {
workers.emplace_back([&] {
......@@ -26,7 +43,7 @@ namespace contouring {
loadQueue.wait();
if (loadQueue.pop(ctx)) {
ZoneScopedN("ProcessContouring");
buffer::ShortIndexed::Data data;
buffer::LodShortIndexed::LodData data;
render(ctx.second, data);
loadedQueue.emplace(ctx.first, data);
}
......@@ -46,13 +63,19 @@ namespace contouring {
std::string FlatDualMC::getOptions() const {
std::ostringstream ss;
ss << toml::table({
toml::table tb({
{"load_distance", loadDistance},
{"keep_distance", keepDistance},
{"iso", iso},
{"manifold", manifold},
{"reordering", reordering}
{"reordering", reordering},
{"lod_quality", lod_quality},
{"lod_strength", lod_strength},
{"lod_levels", toml::array()}
});
for(auto& v: lod_levels)
tb["lod_levels"].as_array()->push_back(v);
ss << tb;
return ss.str();
}
......@@ -85,12 +108,12 @@ namespace contouring {
void FlatDualMC::update(const voxel_pos& pos, const world::area_map& areas) {
ZoneScopedN("Ct");
std::pair<area_<chunk_pos>, buffer::ShortIndexed::Data> out;
std::pair<area_<chunk_pos>, buffer::LodShortIndexed::LodData> out;
TracyPlot("CtLoad", static_cast<int64_t>(loadQueue.size()));
//MAYBE: clear out of range loadQueue.trim(keepDistance * keepDistance)
TracyPlot("CtLoaded", static_cast<int64_t>(loadedQueue.size()));
for(auto handle = loadedQueue.extractor(); handle.first(out);) {
const auto buffer = new buffer::ShortIndexed(GL_TRIANGLES, out.second);
const auto buffer = new buffer::LodShortIndexed(GL_TRIANGLES, out.second);
auto &bfs = buffers[out.first.first].second; //NOTE: buffer.first uninitialized (will be set in clear())
if (const auto it = bfs.find(out.first.second); it != bfs.end()) {
if(it->second != NULL)
......@@ -101,9 +124,41 @@ namespace contouring {
bfs.emplace(out.first.second, buffer);
}
}
[[maybe_unused]]
int64_t count = AbstractFlat::clear(pos, areas);
TracyPlot("CtCount", count);
size_t buffer_count = 0;
{
ZoneScopedN("CtUpdate");
const auto levelMax = loadedLevels.size();
auto it_a = buffers.begin();
while (it_a != buffers.end()) { // Remove out of range buffers
if (const auto area = areas.find(it_a->first); area != areas.end()) {
//Update
it_a->second.first = area->second->getOffset();
const auto center = glm::divide(pos - it_a->second.first.as_voxel());
auto &bfs = it_a->second.second;
auto it = bfs.begin();
while(it != bfs.end()) {
if (const auto distRatio = glm::length(glm::dvec3(center - it->first)) / keepDistance; distRatio > 1) {
if(it->second != NULL)
delete it->second;
it = bfs.erase(it);
} else {
static_cast<buffer::LodShortIndexed*>(it->second)->setLevel((1+lod_quality-distRatio)*levelMax*(1+lod_strength));
buffer_count++;
++it;
}
}
++it_a;
} else {
for(auto& buffer: it_a->second.second) {
if(buffer.second != NULL)
delete buffer.second;
}
it_a = buffers.erase(it_a);
}
}
}
TracyPlot("CtCount", static_cast<int64_t>(buffer_count));
}
void FlatDualMC::onGui() {
......@@ -112,12 +167,26 @@ namespace contouring {
ImGui::SliderFloat("Iso", &iso, 0, 1);
ImGui::Checkbox("Manifold", &manifold);
ImGui::Checkbox("Reordering", &reordering);
ImGui::SliderFloat("Lod quality", &lod_quality, -.5, 1);
ImGui::SliderFloat("Lod strength", &lod_strength, -.5, .5);
if (ImGui::CollapsingHeader("Lod levels")) {
ImGui::Columns(3, nullptr, false);
ImGui::Selectable("1", true, ImGuiSelectableFlags_Disabled);
ImGui::NextColumn();
for(int i = LEVELS.size() - 1; i >= 0; i--) {
const auto str = std::to_string(static_cast<int>(1 / LEVELS[i].first));
ImGui::Selectable(str.c_str(), &lod_levels[i]);
ImGui::NextColumn();
}
ImGui::Columns(1);
}
}
void FlatDualMC::render(const surrounding::corners &surrounding, buffer::ShortIndexed::Data &out) const {
void FlatDualMC::render(const surrounding::corners &surrounding, buffer::LodShortIndexed::LodData &out) const {
const int SIZE = CHUNK_LENGTH + 3;
std::array<dualmc::DualMC<float>::Point, SIZE * SIZE * SIZE> grid;
{
ZoneScopedN("Load");
for (int z = 0; z < SIZE; z++) {
for (int y = 0; y < SIZE; y++) {
for (int x = 0; x < SIZE; x++) {
......@@ -135,44 +204,54 @@ namespace contouring {
dualmc::DualMC<float> builder;
builder.buildTris(&grid.front(), SIZE, SIZE, SIZE, iso, world::materials::roughness.cbegin(), manifold, dmc_vertices, dmc_tris);
out.vertices.reserve(dmc_vertices.size());
out.materials.reserve(dmc_vertices.size());
out.normals.reserve(dmc_vertices.size());
auto &data = out.first;
data.vertices.reserve(dmc_vertices.size());
data.materials.reserve(dmc_vertices.size());
data.normals.reserve(dmc_vertices.size());
for (const auto& v: dmc_vertices) {
out.vertices.emplace_back(v.x, v.y, v.z);
out.materials.emplace_back(v.w);
out.normals.emplace_back(0);
data.vertices.emplace_back(v.x, v.y, v.z);
data.materials.emplace_back(v.w);
data.normals.emplace_back(0);
}
out.indices.reserve(dmc_tris.size() * 3);
data.indices.reserve(dmc_tris.size() * 3);
for (const auto& t: dmc_tris) {
glm::vec3 edge1 = out.vertices[t.i1] - out.vertices[t.i0];
glm::vec3 edge2 = out.vertices[t.i2] - out.vertices[t.i0];
glm::vec3 edge1 = data.vertices[t.i1] - data.vertices[t.i0];
glm::vec3 edge2 = data.vertices[t.i2] - data.vertices[t.i0];
glm::vec3 normal = glm::normalize(glm::cross(edge1, edge2));
if(!reordering || glm::length2(edge1) > glm::length2(edge2)) {
out.indices.push_back(t.i0);
out.indices.push_back(t.i1);
out.indices.push_back(t.i2);
data.indices.push_back(t.i0);
data.indices.push_back(t.i1);
data.indices.push_back(t.i2);
} else {
out.indices.push_back(t.i2);
out.indices.push_back(t.i0);
out.indices.push_back(t.i1);
data.indices.push_back(t.i2);
data.indices.push_back(t.i0);
data.indices.push_back(t.i1);
}
out.normals[t.i0] += normal;
out.normals[t.i1] += normal;
out.normals[t.i2] += normal;
data.normals[t.i0] += normal;
data.normals[t.i1] += normal;
data.normals[t.i2] += normal;
}
for (auto &n : out.normals) {
for (auto &n : data.normals) {
n = glm::normalize(n);
}
out.second = simplify_lod(data.indices, dmc_vertices, loadedLevels);
optimize_fetch(data);
}
meshopt_optimizeVertexCache(out.indices.data(), out.indices.data(), out.indices.size(), out.vertices.size()); //NOTE: pretty minimal gain
}
// reorder indices for overdraw, balancing overdraw and vertex cache efficiency
const float kThreshold = 1.01f; // allow up to 1% worse ACMR to get more reordering opportunities for overdraw
meshopt_optimizeOverdraw(out.indices.data(), out.indices.data(), out.indices.size(), &out.vertices.front()[0], out.vertices.size(), sizeof(glm::vec3), kThreshold);
void FlatDualMC::getModels(std::vector<std::pair<glm::mat4, buffer::Abstract *const>> &out, const std::optional<geometry::Frustum> &frustum, const glm::llvec3& offset, int density) {
const auto scaling = glm::scale(glm::mat4(1), glm::vec3(1.f / density));
for (const auto [_, area] : buffers) {
for (const auto [pos, buffer] : area.second) {
const glm::vec3 fPos = (glm::vec3(area.first.raw_as_long() + glm::multiply(pos) - offset * glm::llvec3(density)) + area.first.offset) / glm::vec3(density);
if (buffer != NULL && (!frustum.has_value() || frustum.value().contains(geometry::Box::fromMin(fPos, glm::vec3(CHUNK_LENGTH / (float)density))))) {
out.emplace_back(glm::translate(scaling, fPos * (float)density), buffer);
}
}}
}
}
......@@ -6,7 +6,7 @@
#include "../data/safe_queue.hpp"
#include "../data/safe_priority_queue.hpp"
#include "../data/circular_buffer.hpp"
#include "../render/buffer/ShortIndexed.hpp"
#include "../render/buffer/LodShortIndexed.hpp"
#include <thread>
using namespace data;
......@@ -29,9 +29,13 @@ namespace contouring {
/// @note notify for chunks entering view while moving
void onNotify(const area_<chunk_pos> &, const chunk_pos &, const world::ChunkContainer &) override;
/// Get buffers in frustum with model matrices
/// @note buffers invalidated after update
void getModels(std::vector<std::pair<glm::mat4, buffer::Abstract *const>> &out, const std::optional<geometry::Frustum> &frustum, const glm::llvec3& offset, int density) override;
protected:
safe_priority_queue_map<area_<chunk_pos>, surrounding::corners, int, area_hash> loadQueue;
safe_queue<std::pair<area_<chunk_pos>, buffer::ShortIndexed::Data>> loadedQueue;
safe_queue<std::pair<area_<chunk_pos>, buffer::LodShortIndexed::LodData>> loadedQueue;
bool running = true;
std::vector<std::thread> workers;
......@@ -41,7 +45,12 @@ namespace contouring {
float iso = .1f;
bool manifold = true;
bool reordering = true;
float lod_strength = .15;
float lod_quality = 0;
std::deque<bool> lod_levels;
std::vector<std::pair<float, float>> loadedLevels;
void render(const surrounding::corners &surrounding, buffer::ShortIndexed::Data& out) const;
void render(const surrounding::corners &surrounding, buffer::LodShortIndexed::LodData& out) const;
};
}
#pragma once
#include <meshoptimizer.h>
inline void optimize_fetch(buffer::ShortIndexed::Data& out) {
ZoneScopedN("Optimize");
std::vector<unsigned int> remap(out.indices.size());
size_t vertex_count = meshopt_optimizeVertexFetchRemap(&remap[0], out.indices.data(), out.indices.size(), out.vertices.size());
meshopt_remapIndexBuffer(out.indices.data(), out.indices.data(), out.indices.size(), &remap[0]);
meshopt_remapVertexBuffer(out.vertices.data(), out.vertices.data(), vertex_count, sizeof(glm::vec3), &remap[0]);
out.vertices.resize(vertex_count);
meshopt_remapVertexBuffer(out.normals.data(), out.normals.data(), vertex_count, sizeof(glm::vec3), &remap[0]);
out.normals.resize(vertex_count);
meshopt_remapVertexBuffer(out.materials.data(), out.materials.data(), vertex_count, sizeof(GLushort), &remap[0]);
out.materials.resize(vertex_count);
}
inline void optimize_buffer(buffer::ShortIndexed::Data& out) {
meshopt_optimizeVertexCache(out.indices.data(), out.indices.data(), out.indices.size(), out.vertices.size()); //NOTE: pretty minimal gain
// reorder indices for overdraw, balancing overdraw and vertex cache efficiency
const float kThreshold = 1.01f; // allow up to 1% worse ACMR to get more reordering opportunities for overdraw
meshopt_optimizeOverdraw(out.indices.data(), out.indices.data(), out.indices.size(), &out.vertices.front()[0], out.vertices.size(), sizeof(glm::vec3), kThreshold);
// optimize_fetch(out);
}
//TODO: quantize half-float / 8-10 int
//MAYBE: when networking meshopt_encodeVertexBuffer
template<typename I>
inline void simplify_buffer(std::vector<I> &out, const std::vector<I> &indices, const std::vector<dualmc::Vertex>& vertices, float threshold = .2f, float target_error = 1e-2f) {
out.resize(indices.size());
out.resize(meshopt_simplify(out.data(), indices.data(), indices.size(), &vertices.front().x, vertices.size(), sizeof(dualmc::Vertex), indices.size() * threshold, target_error));
}
#include <iostream>
template <typename I>
inline std::vector<size_t> simplify_lod(std::vector<I> &indices, const std::vector<dualmc::Vertex> &vertices, const std::vector<std::pair<float, float>> &levels)
{
ZoneScopedN("LOD");
typename std::vector<I> full(indices);
indices.resize(0);
std::vector<size_t> offsets;
typename std::vector<I> part;
for (const auto &[threshold, error]: levels) {
ZoneScopedN("Level");
simplify_buffer(part, full, vertices, threshold, error);
meshopt_optimizeVertexCache(part.data(), part.data(), part.size(), vertices.size());
offsets.push_back((offsets.empty() ? 0 : offsets.back()) + part.size());
indices.insert(indices.end(), part.begin(), part.end());
}
indices.reserve(indices.size() + full.size());
indices.insert(indices.end(), full.begin(), full.end());
return offsets;
}
\ No newline at end of file
......@@ -4,6 +4,7 @@
#include <mutex>
#include <condition_variable>
#include <Tracy.hpp> // NOLINT
#include <functional>
namespace data {
/// Thread safe queue
......
......@@ -130,6 +130,10 @@ UI::Actions UI::draw(options &options, state &state, const reports &reports, GLu
}
ImGui::EndCombo();
}
if(ImGui::Button("Reload")) {
actions |= Actions::ChangeContouring;
contouring::save(options.contouring_idx, state.contouring, options.contouring_data);
}
ImGui::Checkbox("Culling", &options.culling);
state.contouring->onGui();
ImGui::End();
......
#include "LodShortIndexed.hpp"
using namespace buffer;
LodShortIndexed::LodShortIndexed(GLenum shape, const ShortIndexed::Data &data, const std::vector<size_t> &offsets): ShortIndexed(shape, data), offsets(offsets) { }
LodShortIndexed::LodShortIndexed(GLenum shape, const LodShortIndexed::LodData &data): ShortIndexed(shape, data.first), offsets(data.second) { }
LodShortIndexed::~LodShortIndexed() { }
#include <iostream>
uint LodShortIndexed::draw(buffer::params params) {
if(params.vertexOnly) {
enableVertexAttrib();
} else {
enableAllAttribs();
}
enableIndex();
const auto start = getOffset(level);
const auto end = getOffset(level+1);
const auto count = end - start;
glDrawElements(Shape, count, GL_UNSIGNED_SHORT, (void *)(start*sizeof(GLushort)));
if(params.vertexOnly) {
disableVertexAttrib();
} else {
disableAllAttribs();
}
return count;
}
\ No newline at end of file
#pragma once
#include "ShortIndexed.hpp"
namespace buffer {
/// ShortIndexed with merge indices for LOD
class LodShortIndexed: public ShortIndexed {
public:
using LodData = std::pair<Data, std::vector<size_t>>;
LodShortIndexed(GLenum shape, const ShortIndexed::Data &data, const std::vector<size_t> &lod_offsets);
LodShortIndexed(GLenum shape, const LodShortIndexed::LodData &data);
virtual ~LodShortIndexed();
uint draw(params params) override;
void inline setLevel(size_t l) { level = l; }
private:
size_t level = 0;
std::vector<size_t> offsets;
constexpr inline size_t getOffset(size_t level) const {
return level <= 0 ? 0 : (level-1 < offsets.size() ? offsets[level-1] : IndexSize);
}
};
}
......@@ -41,13 +41,13 @@ namespace buffer {
uint draw(params params) override;
private:
protected:
void enableAllAttribs();
void disableAllAttribs();
void enableIndex();
GLuint IndexBufferID;
GLushort IndexSize = 0;
private:
GLuint IndexBufferID;
GLuint MaterialBufferID;
GLuint NormalBufferID;
......
......@@ -57,7 +57,7 @@ Universe::Universe(const Universe::options &options): dicts("content/zstd.dict"
entities.emplace(nullptr, glm::vec3(1), glm::vec3(2));
// Workers
for (size_t i = 0; i < 4; i++) {
for (size_t i = 0; i < 3; i++) {
workers.emplace_back([&] {
#if TRACY_ENABLE
tracy::SetThreadName("Chunks");
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment