Commit 23dc6070 authored by Shu's avatar Shu 🍠
Browse files

Threads

parent 47177748
......@@ -3,6 +3,7 @@
## Data
- [x] Generate noise
- [x] Density
- [ ] Robin hood map / hopscotch_map
- [ ] Octree world
- [ ] Serialize
- [ ] In memory RLE
......@@ -18,7 +19,7 @@
- https://speciesdevblog.files.wordpress.com/2012/11/biomemap.png
- [ ] Leak test
- Valgrind
- [ ] EnkiTS
- Massif
- [ ] Server
- [ ] ZeroMQ
......@@ -49,6 +50,8 @@
- [ ] LOD
- [ ] Dual MC
- [ ] Collision
- [ ] Dynamic index size
- [ ] Chunk size performance
- [ ] Render with glBufferSubData
- [x] Frustum Culling
- [ ] Occlusion Culling
......
......@@ -3,7 +3,9 @@
#include "../data/glm.hpp"
#include "../render/buffer/Buffer.hpp"
#include "../data/geometry/Frustum.hpp"
#include "../data/geometry/Faces.hpp"
#include <memory>
#include <toml.h>
class World;
class Chunk;
......@@ -19,12 +21,14 @@ namespace contouring {
virtual void update(const camera_pos &pos) = 0;
/// Chunk data change
virtual void onUpdate(const chunk_pos &pos, const std::unordered_map<chunk_pos, std::shared_ptr<Chunk>>& data) = 0; // TODO: get precise border data
virtual void onUpdate(const chunk_pos &pos, const std::unordered_map<chunk_pos, std::shared_ptr<Chunk>>& data, Faces neighbors) = 0;
/// Chunk existante ping
/// @note notify for chunks entering view while moving
virtual void onNotify(const chunk_pos &pos, const std::unordered_map<chunk_pos, std::shared_ptr<Chunk>> &data) = 0;
/// Display ImGui config
virtual void onGui() = 0;
/// Get options
virtual std::string getOptions() = 0;
/// Get buffers in frustum with model matrices
/// @note buffers invalidated after update
......
......@@ -19,6 +19,20 @@ namespace contouring {
}
}
AbstractFlat::AbstractFlat(const std::string& str): Abstract() {
auto opt = toml::parse(str);
loadDistance = opt["load_distance"].value_or(loadDistance);
keepDistance = opt["keep_distance"].value_or(keepDistance);
}
std::string AbstractFlat::getOptions() {
std::ostringstream ss;
ss << toml::table({
{"load_distance", loadDistance},
{"keep_distance", keepDistance}
});
return ss.str();
}
void AbstractFlat::onGui() {
ImGui::SliderInt("Load Distance", &loadDistance, 1, keepDistance);
ImGui::SliderInt("Keep Distance", &keepDistance, loadDistance+1, 21);
......
......@@ -8,8 +8,8 @@ namespace contouring {
/// Generating mesh for chunks 1:1
class AbstractFlat: public Abstract {
public:
AbstractFlat(): Abstract() { }
virtual ~AbstractFlat() { }
AbstractFlat(const std::string &str);
virtual ~AbstractFlat() {}
/// Each frame ping. Used to clear out of range
void update(const camera_pos &) override;
......@@ -17,6 +17,8 @@ namespace contouring {
/// Display ImGui config
void onGui() override;
std::string getOptions() override;
/// Get buffers in frustum with model matrices
/// @note buffers invalidated after update
void getModels(std::vector<std::pair<glm::mat4, Buffer *const>> &out, const std::optional<Frustum> &frustum, float scale) override;
......@@ -32,7 +34,7 @@ namespace contouring {
std::unordered_map<chunk_pos, Buffer *> buffers;
chunk_pos center = chunk_pos(INT_MAX);
int loadDistance = 3; //TODO: handle config
int loadDistance = 3;
int keepDistance = 4;
};
}
\ No newline at end of file
......@@ -12,9 +12,10 @@ namespace contouring {
virtual ~Dummy() { }
void update(const camera_pos &) override { }
void onUpdate(const chunk_pos &, const std::unordered_map<chunk_pos, std::shared_ptr<Chunk>> &) override { }
void onUpdate(const chunk_pos &, const std::unordered_map<chunk_pos, std::shared_ptr<Chunk>> &, Faces) override {}
void onNotify(const chunk_pos &, const std::unordered_map<chunk_pos, std::shared_ptr<Chunk>> &) override { }
void onGui() override { }
std::string getOptions() override { return ""; }
void getModels(std::vector<std::pair<glm::mat4, Buffer *const>> &, const std::optional<Frustum>&, float) override { }
};
}
\ No newline at end of file
#include "FlatSurroundingBox.hpp"
#include "boxing.hpp"
#include "../render/buffer/ShortIndexedBuffer.hpp"
#include "../world/Chunk.hpp"
#include <Remotery.h>
#include <imgui.h>
namespace contouring {
void FlatSurroundingBox::onUpdate(const chunk_pos &pos, const std::unordered_map<chunk_pos, std::shared_ptr<Chunk>> &data) {
if(!inLoadRange(pos))
return;
surrounding::chunks surrounding;
if(surrounding::load(surrounding, pos, data)) {
std::vector<VertexData> vertices;
render(surrounding, vertices);
ShortIndexedBuffer::Data data(vertices);
const auto buffer = new ShortIndexedBuffer(GL_TRIANGLES, data);
const auto it = buffers.find(pos);
if (it != buffers.end()) {
if(it->second != NULL)
delete it->second;
FlatSurroundingBox::FlatSurroundingBox(const std::string &opt) : AbstractFlat(opt) {
for (size_t i = 1; i <= 4; i++) {
workers.push_back(std::thread([&] {
while (running) {
std::pair<chunk_pos, surrounding::chunks> ctx;
loadQueue.wait();
if (loadQueue.pop(ctx)) {
rmt_ScopedCPUSample(ProcessContouring, 0);
std::vector<VertexData> vertices;
render(ctx.second, vertices);
{
rmt_ScopedCPUSample(Index, 0);
loadedQueue.push({ctx.first, ShortIndexedBuffer::Data(vertices)});
}
}
}
}));
}
}
FlatSurroundingBox::~FlatSurroundingBox() {
running = false;
loadQueue.notify();
it->second = buffer;
} else {
buffers.emplace(pos, buffer);
for(auto& worker: workers) {
if (worker.joinable())
worker.join();
}
}
void FlatSurroundingBox::enqueue(const chunk_pos &pos, const std::unordered_map<chunk_pos, std::shared_ptr<Chunk>> &data) {
rmt_ScopedCPUSample(EnqueueContouring, RMTSF_Aggregate);
const auto dist2 = glm::length2(pos - center);
if (dist2 <= loadDistance * loadDistance) {
surrounding::chunks surrounding;
if(surrounding::load(surrounding, pos, data)) {
loadQueue.push(pos, surrounding, -dist2);
}
}
}
void FlatSurroundingBox::onUpdate(const chunk_pos &pos, const std::unordered_map<chunk_pos, std::shared_ptr<Chunk>> &data, Faces neighbors) {
enqueue(pos, data);
if (neighbors && Faces::Right)
enqueue(pos + g_face_offsets[static_cast<int>(Face::Right)], data);
if (neighbors && Faces::Left)
enqueue(pos + g_face_offsets[static_cast<int>(Face::Left)], data);
if (neighbors && Faces::Up)
enqueue(pos + g_face_offsets[static_cast<int>(Face::Up)], data);
if (neighbors && Faces::Down)
enqueue(pos + g_face_offsets[static_cast<int>(Face::Down)], data);
if (neighbors && Faces::Forward)
enqueue(pos + g_face_offsets[static_cast<int>(Face::Forward)], data);
if (neighbors && Faces::Backward)
enqueue(pos + g_face_offsets[static_cast<int>(Face::Backward)], data);
}
void FlatSurroundingBox::onNotify(const chunk_pos &pos, const std::unordered_map<chunk_pos, std::shared_ptr<Chunk>> &data) {
if (!inLoadRange(pos) || buffers.find(pos) != buffers.end())
return;
surrounding::chunks surrounding;
if(surrounding::load(surrounding, pos, data)) {
std::vector<VertexData> vertices;
render(surrounding, vertices);
ShortIndexedBuffer::Data data(vertices);
if (buffers.find(pos) == buffers.end()) {
enqueue(pos, data);
}
}
const auto buffer = new ShortIndexedBuffer(GL_TRIANGLES, data);
const auto it = buffers.find(pos);
void FlatSurroundingBox::update(const camera_pos& pos) {
AbstractFlat::update(pos);
std::pair<chunk_pos, ShortIndexedBuffer::Data> out;
reports.load.push(loadQueue.size());
//MAYBE: clear out of range loadQueue.trim(keepDistance * keepDistance)
reports.loaded.push(loadedQueue.size());
while(loadedQueue.pop(out)) {
const auto buffer = new ShortIndexedBuffer(GL_TRIANGLES, out.second);
const auto it = buffers.find(out.first);
if (it != buffers.end()) {
if(it->second != NULL)
delete it->second;
it->second = buffer;
} else {
buffers.emplace(pos, buffer);
buffers.emplace(out.first, buffer);
}
}
reports.count.push(buffers.size());
}
void FlatSurroundingBox::onGui() {
ImGui::PlotHistogram("Count", reports.count.buffer.get(), reports.count.size, 0, std::to_string(reports.count.current()).c_str(), 0);
ImGui::PlotHistogram("Loading", reports.load.buffer.get(), reports.load.size, 0, std::to_string(reports.load.current()).c_str(), 0);
ImGui::PlotHistogram("Waiting", reports.loaded.buffer.get(), reports.loaded.size, 0, std::to_string(reports.loaded.current()).c_str(), 0);
ImGui::Separator();
AbstractFlat::onGui();
}
bool FlatSurroundingBox::isTransparent(const surrounding::chunks &surrounding, const std::pair<ushort, ushort> &idx) {
......
......@@ -3,19 +3,45 @@
#include "AbstractFlat.hpp"
#include "surrounding.hpp"
class VertexData;
#include "../data/safe_queue.hpp"
#include "../data/safe_priority_queue.hpp"
#include "../data/circular_buffer.hpp"
#include "../render/buffer/ShortIndexedBuffer.hpp"
#include <thread>
#define REPORT_BUFFER_SIZE 128
namespace contouring {
class FlatSurroundingBox: public AbstractFlat {
public:
FlatSurroundingBox(): AbstractFlat() { }
~FlatSurroundingBox() { }
FlatSurroundingBox(const std::string&);
virtual ~FlatSurroundingBox();
void update(const camera_pos&) override;
void onGui() override;
/// Chunk data change
void onUpdate(const chunk_pos &, const std::unordered_map<chunk_pos, std::shared_ptr<Chunk>> &) override;
void onUpdate(const chunk_pos &, const std::unordered_map<chunk_pos, std::shared_ptr<Chunk>> &, Faces) override;
/// Chunk existante ping
/// @note notify for chunks entering view while moving
void onNotify(const chunk_pos &, const std::unordered_map<chunk_pos, std::shared_ptr<Chunk>> &) override;
protected:
safe_priority_queue_map<chunk_pos, surrounding::chunks, int> loadQueue;
safe_queue<std::pair<chunk_pos, ShortIndexedBuffer::Data>> loadedQueue;
struct report {
circular_buffer<float> count = circular_buffer<float>(REPORT_BUFFER_SIZE, 0); // MAYBE: store int
circular_buffer<float> load = circular_buffer<float>(REPORT_BUFFER_SIZE, 0);
circular_buffer<float> loaded = circular_buffer<float>(REPORT_BUFFER_SIZE, 0);
} reports;
bool running = true;
std::vector<std::thread> workers;
void enqueue(const chunk_pos &, const std::unordered_map<chunk_pos, std::shared_ptr<Chunk>> &);
private:
static inline bool isTransparent(const surrounding::chunks &surrounding, const std::pair<ushort, ushort> &idx);
static void render(const surrounding::chunks &surrounding, std::vector<VertexData> &vertices);
......
#include "index.hpp"
#include "Dummy.hpp"
#include "FlatSurroundingBox.hpp"
namespace contouring {
int idxByName(const std::string& name) {
for (size_t i = 0; i < names.size(); i++) {
if(name == names[i])
return (int)i;
}
return 0;
}
std::shared_ptr<Abstract> load(int idx, const std::map<std::string, std::string>& data) {
switch (idx) {
case 1:
return std::make_shared<Dummy>();
default:
return std::make_shared<FlatSurroundingBox>(data.at(names[0]));
}
}
void save(int idx, std::shared_ptr<Abstract>& ct, std::map<std::string, std::string>& data) {
data.insert_or_assign(names[idx], ct->getOptions());
}
}
\ No newline at end of file
#pragma once
#include "Abstract.hpp"
namespace contouring {
static const std::array<std::string, 2> names = {"FlatBox", "Dummy"};
static const std::string cnames = "FlatBox\0Dummy\0";
int idxByName(const std::string &name);
std::shared_ptr<Abstract> load(int idx, const std::map<std::string, std::string> &data);
void save(int idx, std::shared_ptr<Abstract> &ct, std::map<std::string, std::string> &data);
}
\ No newline at end of file
#pragma once
#include <tuple>
#include <vector>
#include <unordered_set>
#include <unordered_map>
#include <mutex>
#include <condition_variable>
#include <algorithm>
/// Thread safe queue with unique keys updating priority and value
template <class K, class V, class W>
class safe_priority_queue_map {
private:
static bool cmpByWeight(const std::pair<K, W> &a, const std::pair<K, W> &b) {
return a.second < b.second;
}
std::vector<std::pair<K, W>> heap;
std::unordered_map<K, V> map;
std::mutex mutex;
std::condition_variable cv;
public:
void push(const K& key, const V& val, const W& weight) {
std::unique_lock<std::mutex> lock(mutex);
heap.push_back({key, weight});
std::push_heap(heap.begin(), heap.end(), cmpByWeight);
map.insert_or_assign(key, val);
cv.notify_one();
}
bool pop(std::pair<K, V>& out) {
std::unique_lock<std::mutex> lock(mutex);
if (heap.empty())
return false;
std::pop_heap(heap.begin(), heap.end(), cmpByWeight);
const auto priority = heap.back();
heap.pop_back();
const auto it = map.find(priority.first);
if(it == map.end())
return false;
out = std::make_pair(it->first, it->second);
map.erase(it);
return true;
}
bool empty() {
std::unique_lock<std::mutex> lock(mutex);
return heap.empty();
}
size_t size() {
std::unique_lock<std::mutex> lock(mutex);
return map.size();
}
void notify() {
cv.notify_all();
}
void wait() {
std::unique_lock<std::mutex> lock(mutex);
if (heap.empty())
cv.wait(lock);
}
};
/// Thread safe queue with unique keys updating priority
template <class K, class W>
class safe_priority_queue {
private:
static bool cmpByWeight(const std::pair<K, W> &a, const std::pair<K, W> &b) {
return a.second < b.second;
}
std::vector<std::pair<K, W>> heap;
std::unordered_set<K> set;
std::mutex mutex;
std::condition_variable cv;
public:
void push(const K& key, const W& weight) {
std::unique_lock<std::mutex> lock(mutex);
heap.push_back({key, weight});
std::push_heap(heap.begin(), heap.end(), cmpByWeight);
set.insert(key);
cv.notify_one();
}
bool pop(K& out) {
std::unique_lock<std::mutex> lock(mutex);
if (heap.empty())
return false;
std::pop_heap(heap.begin(), heap.end(), cmpByWeight);
const auto priority = heap.back();
heap.pop_back();
const auto it = set.find(priority.first);
if(it == set.end())
return false;
out = *it;
set.erase(it);
return true;
}
bool empty() {
std::unique_lock<std::mutex> lock(mutex);
return heap.empty();
}
size_t size() {
std::unique_lock<std::mutex> lock(mutex);
return set.size();
}
void notify() {
cv.notify_all();
}
void wait() {
std::unique_lock<std::mutex> lock(mutex);
if (heap.empty())
cv.wait(lock);
}
};
#pragma once
#include <queue>
#include <mutex>
#include <condition_variable>
/// Thread safe queue
template <class T>
class safe_queue {
private:
std::queue<T> queue;
std::mutex mutex;
std::condition_variable cv;
public:
void push(const T& in) {
std::unique_lock<std::mutex> lock(mutex);
queue.push(in);
cv.notify_one();
}
bool pop(T& out) {
std::unique_lock<std::mutex> lock(mutex);
if (queue.empty())
return false;
out = queue.front();
queue.pop();
return true;
}
bool empty() {
std::unique_lock<std::mutex> lock(mutex);
return queue.empty();
}
size_t size() {
std::unique_lock<std::mutex> lock(mutex);
return queue.size();
}
void notify() {
cv.notify_all();
}
void wait() {
std::unique_lock<std::mutex> lock(mutex);
if(queue.empty())
cv.wait(lock);
}
};
#ifndef SAFE_UNIQUE_QUEUE_HPP
#define SAFE_UNIQUE_QUEUE_HPP
#pragma once
#include <queue>
#include <unordered_set>
......@@ -12,7 +11,7 @@ class safe_unique_queue {
private:
std::queue<T> queue;
std::unordered_set<T> set;
TracyLockable(std::mutex, mutex);
std::mutex mutex;
std::condition_variable cv;
public:
......@@ -72,5 +71,3 @@ public:
cv.wait(lock);
}
};
#endif
\ No newline at end of file
......@@ -14,6 +14,7 @@
#include "../render/Renderer.hpp"
#include "../world/World.hpp"
#include "../control/Camera.hpp"
#include "../contouring/index.hpp"
inline ImColor fromHex(const std::string& str) {
int rgb[3] = {UCHAR_MAX};
......@@ -48,7 +49,11 @@ struct options {
world.keepDistance = config["world"]["keep_distance"].value_or(world.keepDistance);
voxel_size = config["world"]["unit_size"].value_or(1.f);
//TODO: contouring
culling = config["mesh"]["culling"].value_or(true);
contouring_idx = contouring::idxByName(config["mesh"]["mode"].value_or(std::string("")));
for(const auto name: contouring::names) {
contouring_data.emplace(name, config["mesh"]["options"][name].value_or(std::string("")));
}
camera.far = config["camera"]["far"].value_or(camera.far);
camera.near = config["camera"]["near"].value_or(camera.near);
......@@ -87,6 +92,16 @@ struct options {
{"keep_distance", world.keepDistance},
{"unit_size", voxel_size}
}));
config.insert_or_assign("mesh", toml::table({
{"culling", culling},
{"mode", contouring::names[contouring_idx]},
{"options", toml::table()}
}));
for(auto opt: contouring_data) {
if(!opt.second.empty())
config["mesh"]["options"].as_table()->insert_or_assign(opt.first, opt.second);
}
config.insert_or_assign("camera", toml::table({
{"far", camera.far},
{"near", camera.near},
......@@ -124,8 +139,9 @@ struct options {
float voxel_size;
bool show_debug_contouring;
int contouring_idx = 0;
bool culling = true; // TODO: move to contouring
bool culling;
int contouring_idx;
std::map<std::string, std::string> contouring_data;