Compare commits
3 Commits
89ca3efb3e
...
ec2122ab12
| Author | SHA1 | Date | |
|---|---|---|---|
| ec2122ab12 | |||
| a145bae065 | |||
| f0d552c47b |
@ -109,7 +109,7 @@
|
||||
"invariant": {
|
||||
"default stage buffers": true,
|
||||
"default defer buffer destroy": true,
|
||||
"default command buffer immediate": false,
|
||||
"default command buffer immediate": true,
|
||||
"multithreaded recording": true
|
||||
},
|
||||
"pipelines": {
|
||||
@ -118,7 +118,7 @@
|
||||
"vsync": true, // vsync on vulkan side rather than engine-side
|
||||
"hdr": true,
|
||||
"vxgi": false,
|
||||
"culling": true,
|
||||
"culling": false,
|
||||
"bloom": true,
|
||||
"dof": true,
|
||||
"rt": false,
|
||||
@ -299,7 +299,7 @@
|
||||
"enabled": true
|
||||
},
|
||||
"imgui": {
|
||||
"enabled": false
|
||||
"enabled": true
|
||||
},
|
||||
"fsr": {
|
||||
"enabled": true,
|
||||
@ -309,21 +309,6 @@
|
||||
"jitter scale": 0.0625,
|
||||
"preset": "native" // native (1x), quality (1.5x), balanced (1.7x), performance (2.0x), ultra (3.0x)
|
||||
},
|
||||
"reactphysics": {
|
||||
"global storage": false,
|
||||
"timescale": 0.01666666666, // 0.03333333333,
|
||||
"interpolate": true,
|
||||
"gravity": {
|
||||
"mode": "default", // default / per-object / universal
|
||||
"constant": 6.67408e-11
|
||||
},
|
||||
"debug draw": {
|
||||
"enabled": false,
|
||||
"line width": 8,
|
||||
"layer": "Gui",
|
||||
"rate": 0.0125
|
||||
}
|
||||
},
|
||||
"vr" : {
|
||||
"enable" : false,
|
||||
"manifest": "./data/openvr_manifest.json",
|
||||
@ -349,7 +334,7 @@
|
||||
}
|
||||
},
|
||||
"memory pool": {
|
||||
"enabled": true, // needs to be kept on for GC
|
||||
"enabled": true,
|
||||
"subPools": true,
|
||||
"alignment": 64,
|
||||
"override": true,
|
||||
@ -363,7 +348,7 @@
|
||||
"render modes": { "gui": true, "deferred": true },
|
||||
"limiters": {
|
||||
"deltaTime": 5,
|
||||
"framerate": "auto" // "auto" // for some reason drops to 60
|
||||
"framerate": "auto"
|
||||
},
|
||||
"threads": {
|
||||
"workers" : "auto",
|
||||
|
||||
@ -2,7 +2,8 @@
|
||||
"name": "Gui: Menu",
|
||||
"type": "Gui",
|
||||
"behaviors": [
|
||||
"GuiBehavior"
|
||||
"GuiBehavior",
|
||||
"ImguiBehavior"
|
||||
],
|
||||
"ignore": false,
|
||||
"transform": {
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
"behaviors": [
|
||||
"SceneBehavior",
|
||||
"ExtSceneBehavior",
|
||||
"ImguiBehavior",
|
||||
"BgmEmitterBehavior"
|
||||
],
|
||||
"assets": [
|
||||
|
||||
@ -192,7 +192,9 @@ void main() {
|
||||
if ( projectedSize < 0.08 ) lodLevel = 2;
|
||||
if ( projectedSize < 0.02 ) lodLevel = 3;
|
||||
lodLevel = min(lodLevel, MAX_LODS - 1);
|
||||
lodLevel = 3;
|
||||
while ( lodLevel > 0 && lodMetadata[drawCommand.instanceID].levels[lodLevel].indices == 0 ) {
|
||||
lodLevel--;
|
||||
}
|
||||
|
||||
LOD lod = lodMetadata[drawCommand.instanceID].levels[lodLevel];
|
||||
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <uf/config.h>
|
||||
#include <functional>
|
||||
|
||||
#if UF_USE_IMGUI
|
||||
|
||||
namespace ext {
|
||||
namespace imgui {
|
||||
extern UF_API bool focused;
|
||||
|
||||
void initialize();
|
||||
void tick();
|
||||
void render();
|
||||
void terminate();
|
||||
|
||||
extern UF_API bool focused;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ namespace ext {
|
||||
namespace meshopt {
|
||||
bool UF_API optimize( uf::Mesh&, float simplify = 1.0f, size_t = SIZE_MAX, bool verbose = false );
|
||||
|
||||
uf::stl::vector<float> computeLODs( size_t count, size_t maxLODs = 4, size_t minIndices = 32 );
|
||||
uf::stl::vector<float> computeLODs( size_t count, size_t maxLODs = 4, size_t minIndices = 3 );
|
||||
uf::stl::vector<pod::LODMetadata> UF_API generateLODs( uf::Mesh&, const uf::stl::vector<float>&, bool verbose = false );
|
||||
|
||||
template<typename T, typename U = uint32_t>
|
||||
|
||||
@ -19,6 +19,9 @@ namespace uf {
|
||||
uf::stl::string UF_API replace( const uf::stl::string&, const uf::stl::string&, const uf::stl::string& );
|
||||
uf::stl::string UF_API lowercase( const uf::stl::string& );
|
||||
uf::stl::string UF_API uppercase( const uf::stl::string& );
|
||||
uf::stl::string UF_API ltrim( const uf::stl::string& );
|
||||
uf::stl::string UF_API rtrim( const uf::stl::string& );
|
||||
uf::stl::string UF_API trim( const uf::stl::string& );
|
||||
uf::stl::vector<uf::stl::string> UF_API split( const uf::stl::string&, const uf::stl::string& );
|
||||
uf::stl::string UF_API si( double value, const uf::stl::string& unit, size_t precision = 3 );
|
||||
bool UF_API contains( const uf::stl::string&, const uf::stl::string& );
|
||||
|
||||
@ -726,16 +726,8 @@ void UF_API uf::initialize() {
|
||||
payload["scene"] = uf::config["engine"]["scenes"]["start"];
|
||||
/*global*/::sceneTransition.payload = payload;
|
||||
/*global*/::sceneTransition.phase = 0;
|
||||
|
||||
// auto& scene = uf::scene::loadScene( uf::config["engine"]["scenes"]["start"] );
|
||||
}
|
||||
|
||||
/*
|
||||
uf::thread::add( uf::thread::fetchWorker(), [&]{
|
||||
uf::asset::processQueue();
|
||||
});
|
||||
*/
|
||||
|
||||
uf::ready = true;
|
||||
UF_MSG_INFO("EXT took {} seconds to initialize", /*global*/::times.sys.elapsed().asDouble());
|
||||
}
|
||||
@ -743,13 +735,20 @@ void UF_API uf::initialize() {
|
||||
void UF_API uf::tick() {
|
||||
++uf::time::frame;
|
||||
|
||||
static pod::Thread& threadMain = uf::thread::get(uf::thread::mainThreadName);
|
||||
#if UF_THREAD_METRICS
|
||||
auto activeStart = std::chrono::high_resolution_clock::now();
|
||||
#endif
|
||||
|
||||
#if 1
|
||||
// skip the next tick to load the next scene to ensure nothing's happening
|
||||
if ( /*global*/::sceneTransition.phase >= 0 ) {
|
||||
auto target = /*global*/::sceneTransition.payload["scene"].as<uf::stl::string>();
|
||||
auto& phase = /*global*/::sceneTransition.phase;
|
||||
|
||||
++phase;
|
||||
|
||||
// might be necessary since i bluescreened with a dedicated thread
|
||||
#if UF_USE_VULKAN
|
||||
uf::renderer::flushCommandBuffers();
|
||||
#endif
|
||||
@ -775,9 +774,6 @@ void UF_API uf::tick() {
|
||||
uf::renderer::flushCommandBuffers();
|
||||
#endif
|
||||
uf::renderer::synchronize();
|
||||
|
||||
// uf::scene::tick();
|
||||
// uf::renderer::tick();
|
||||
|
||||
return;
|
||||
}
|
||||
@ -844,7 +840,7 @@ void UF_API uf::tick() {
|
||||
}
|
||||
|
||||
/* Tick Main Thread Queue */ {
|
||||
uf::thread::process( uf::thread::get(uf::thread::mainThreadName) );
|
||||
uf::thread::process( threadMain );
|
||||
}
|
||||
#if UF_USE_ULTRALIGHT
|
||||
/* Ultralight-UX */ if ( /*global*/::config.engine.ext.ultralight.enabled ) {
|
||||
@ -865,6 +861,66 @@ void UF_API uf::tick() {
|
||||
ext::imgui::tick();
|
||||
}
|
||||
#endif
|
||||
|
||||
// perform GC on entities
|
||||
if ( /*global*/::config.engine.gc.enabled ) {
|
||||
TIMER( /*global*/::config.engine.gc.every ) {
|
||||
size_t collected = uf::instantiator::collect( /*global*/::config.engine.gc.mode );
|
||||
if ( collected > 0 ) {
|
||||
if ( /*global*/::config.engine.gc.announce ) UF_MSG_DEBUG("GC collected {} unused entities", (int) collected);
|
||||
}
|
||||
}
|
||||
}
|
||||
#if UF_THREAD_METRICS
|
||||
// Mark the end of active work and the start of idle/sleep time
|
||||
auto idleStart = std::chrono::high_resolution_clock::now();
|
||||
#endif
|
||||
|
||||
#if !UF_ENV_DREAMCAST
|
||||
if ( /*global*/::times.limiter > 0 ) {
|
||||
static auto nextFrameTime = std::chrono::steady_clock::now();
|
||||
|
||||
double limiterMs = /*global*/::times.limiter * 1000.0;
|
||||
auto limiterDuration = std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::duration<double, std::milli>(limiterMs));
|
||||
nextFrameTime += limiterDuration;
|
||||
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
if ( now < nextFrameTime ) {
|
||||
auto timeRemaining = nextFrameTime - now;
|
||||
if ( timeRemaining > std::chrono::milliseconds(2) ) std::this_thread::sleep_for(timeRemaining - std::chrono::milliseconds(1));
|
||||
while ( std::chrono::steady_clock::now() < nextFrameTime ) std::this_thread::yield();
|
||||
} else {
|
||||
nextFrameTime = std::chrono::steady_clock::now();
|
||||
}
|
||||
}
|
||||
|
||||
auto& controller = uf::scene::getCurrentScene().getController();
|
||||
if ( /*global*/::requestDedicatedRenderThread && controller.getName() == "Player" ) {
|
||||
/*global*/::requestDedicatedRenderThread = false;
|
||||
uf::renderer::settings::experimental::dedicatedThread = true;
|
||||
UF_MSG_DEBUG("Dedicated render requested");
|
||||
}
|
||||
#if UF_USE_VULKAN
|
||||
if ( /*global*/::requestDeferredCommandBufferSubmit && controller.getName() == "Player" ) {
|
||||
/*global*/::requestDeferredCommandBufferSubmit = false;
|
||||
uf::renderer::settings::defaultCommandBufferImmediate = false;
|
||||
UF_MSG_DEBUG("Defer command buffer submit requested");
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if UF_THREAD_METRICS
|
||||
auto tickEnd = std::chrono::high_resolution_clock::now();
|
||||
|
||||
std::chrono::duration<float, std::milli> activeTime = idleStart - activeStart;
|
||||
std::chrono::duration<float, std::milli> idleTime = tickEnd - idleStart;
|
||||
std::chrono::duration<float, std::milli> frameTime = tickEnd - activeStart;
|
||||
|
||||
threadMain.metrics.activeTimeMs.store(activeTime.count(), std::memory_order_relaxed);
|
||||
threadMain.metrics.idleTimeMs.store(idleTime.count(), std::memory_order_relaxed);
|
||||
threadMain.metrics.totalFrameTimeMs.store(frameTime.count(), std::memory_order_relaxed);
|
||||
#endif
|
||||
|
||||
/* FPS Print */ if ( /*global*/::config.engine.fps.print ) {
|
||||
++/*global*/::times.frames;
|
||||
++/*global*/::times.total.frames;
|
||||
@ -881,48 +937,6 @@ void UF_API uf::tick() {
|
||||
/*global*/::times.frames = 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto& controller = uf::scene::getCurrentScene().getController();
|
||||
|
||||
// to-do: handle when the memory pool is disabled, because entities are NOT cleaned up at all
|
||||
// should also handle entity deletion when GC is disabled
|
||||
if ( /*global*/::config.engine.gc.enabled ) {
|
||||
TIMER( /*global*/::config.engine.gc.every ) {
|
||||
size_t collected = uf::instantiator::collect( /*global*/::config.engine.gc.mode );
|
||||
if ( collected > 0 ) {
|
||||
if ( /*global*/::config.engine.gc.announce ) UF_MSG_DEBUG("GC collected {} unused entities", (int) collected);
|
||||
}
|
||||
}
|
||||
}
|
||||
#if !UF_ENV_DREAMCAST
|
||||
if ( /*global*/::times.limiter > 0 ) {
|
||||
double limiter = /*global*/::times.limiter * 1000.0;
|
||||
|
||||
static uf::Timer<long long> timer(false);
|
||||
if ( !timer.running() ) timer.start();
|
||||
auto elapsed = timer.elapsed().asMilliseconds();
|
||||
if ( elapsed < limiter ) {
|
||||
std::chrono::duration<double, std::milli> delta(limiter - elapsed);
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(delta);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(duration.count()));
|
||||
}
|
||||
timer.reset();
|
||||
}
|
||||
|
||||
|
||||
if ( /*global*/::requestDedicatedRenderThread && controller.getName() == "Player" ) {
|
||||
/*global*/::requestDedicatedRenderThread = false;
|
||||
uf::renderer::settings::experimental::dedicatedThread = true;
|
||||
UF_MSG_DEBUG("Dedicated render requested");
|
||||
}
|
||||
#if UF_USE_VULKAN
|
||||
if ( /*global*/::requestDeferredCommandBufferSubmit && controller.getName() == "Player" ) {
|
||||
/*global*/::requestDeferredCommandBufferSubmit = false;
|
||||
uf::renderer::settings::defaultCommandBufferImmediate = false;
|
||||
UF_MSG_DEBUG("Defer command buffer submit requested");
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
void UF_API uf::render() {
|
||||
if ( uf::scene::scenes.empty() ) return;
|
||||
|
||||
@ -23,8 +23,10 @@
|
||||
#define UF_DEBUG_TIMER_MULTITRACE_END(...)
|
||||
#endif
|
||||
|
||||
#define UF_GRAPH_SPARSE_READ_MESH 1
|
||||
|
||||
#define UF_GRAPH_EXTENDED 1
|
||||
#define UF_GRAPH_SPARSE_READ_MESH 1
|
||||
// to-do: fix LOD1+ breaking, fix physics mesh not updating
|
||||
|
||||
namespace {
|
||||
bool newGraphAdded = true;
|
||||
@ -39,6 +41,14 @@ namespace {
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
inline uint64_t fnv1aHash(const uf::stl::vector<int8_t>& values) {
|
||||
uint64_t hash = 1469598103934665603ULL;
|
||||
for (bool v : values) {
|
||||
hash ^= static_cast<uint64_t>(static_cast<uint8_t>(v));
|
||||
hash *= 1099511628211ULL;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
size_t allocateObjectID( pod::Graph::Storage& storage ) {
|
||||
return storage.entities.keys.size();
|
||||
@ -1280,14 +1290,16 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
|
||||
pod::Instance::Bounds bounds = {};
|
||||
size_t baseInstanceID = ::allocateInstanceID( storage, graph.primitives[node.mesh] );
|
||||
// setup instances
|
||||
for ( auto i = 0; i < primitives.size(); ++i ) {
|
||||
auto& primitive = primitives[i];
|
||||
for ( auto drawID = 0; drawID < primitives.size(); ++drawID ) {
|
||||
auto& primitive = primitives[drawID];
|
||||
auto& instance = primitive.instance;
|
||||
size_t instanceID = baseInstanceID + i;
|
||||
size_t instanceID = baseInstanceID + drawID;
|
||||
|
||||
instance.objectID = node.object;
|
||||
instance.jointID = graphMetadataJson["renderer"]["skinned"].as<bool>() ? 0 : -1;
|
||||
|
||||
primitive.drawCommand.instanceID = instanceID;
|
||||
|
||||
bounds.min = uf::vector::min( bounds.min, instance.bounds.min );
|
||||
bounds.max = uf::vector::max( bounds.max, instance.bounds.max );
|
||||
|
||||
@ -1295,7 +1307,7 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
|
||||
auto& attribute = mesh.indirect.attributes.front();
|
||||
auto& buffer = mesh.buffers[mesh.isInterleaved(mesh.indirect.interleaved) ? mesh.indirect.interleaved : attribute.buffer];
|
||||
pod::DrawCommand* drawCommands = (pod::DrawCommand*) buffer.data();
|
||||
auto& drawCommand = drawCommands[i];
|
||||
auto& drawCommand = drawCommands[drawID];
|
||||
drawCommand.instanceID = instanceID;
|
||||
}
|
||||
}
|
||||
@ -1615,6 +1627,7 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
}
|
||||
}
|
||||
|
||||
bool meshUpdated = false;
|
||||
auto model = uf::transform::model( transform );
|
||||
auto& mesh = storage.meshes.map[graph.meshes[node.mesh]];
|
||||
auto& primitives = storage.primitives.map[graph.primitives[node.mesh]];
|
||||
@ -1623,6 +1636,11 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
float radius = graph.settings.stream.radius;
|
||||
float radiusSquared = radius * radius;
|
||||
|
||||
// force update if entity isn't already bound to the graphic
|
||||
if ( !entity.hasComponent<uf::renderer::Graphic>() ) {
|
||||
meshUpdated = true;
|
||||
}
|
||||
|
||||
// disable if not tagged for streaming
|
||||
// to-do: check tag
|
||||
if ( graph.settings.stream.tag != "" && node.name != graph.settings.stream.tag ) {
|
||||
@ -1640,7 +1658,7 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
pod::DrawCommand* drawCommands = (pod::DrawCommand*) buffer.data();
|
||||
// queues
|
||||
uf::stl::unordered_map<size_t, uf::stl::vector<pod::Range>> ranges;
|
||||
uf::stl::vector<bool> queuedDrawIDs( primitives.size(), false ); // this is to maintain draw command order because apparently my code requires draw commands to stay in order
|
||||
uf::stl::vector<int8_t> queuedLODs( primitives.size(), -1 ); // this is to maintain draw command order because apparently my code requires draw commands to stay in order
|
||||
// fallbacks for when no draw calls are requested (mainly for the collision mesh)
|
||||
float closestDistance = std::numeric_limits<float>::max();
|
||||
size_t closestDrawID = 0;
|
||||
@ -1661,24 +1679,37 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
closestDrawID = drawID;
|
||||
}
|
||||
// queue if we're within the radius
|
||||
if ( (queuedDrawIDs[drawID] = distanceSquared <= radiusSquared) ) {
|
||||
if ( distanceSquared <= radiusSquared ) {
|
||||
found = true;
|
||||
|
||||
int8_t lodLevel = 0;
|
||||
// deduce a simple ratio [0.0 to 1.0] of how far we are into the streaming radius
|
||||
float distRatio = distanceSquared / radiusSquared;
|
||||
if ( distRatio > 0.6f ) lodLevel = 3;
|
||||
else if ( distRatio > 0.3f ) lodLevel = 2;
|
||||
else if ( distRatio > 0.1f ) lodLevel = 1;
|
||||
|
||||
while ( lodLevel > 0 && primitive.lod.levels[lodLevel].indices == 0 ) {
|
||||
lodLevel--;
|
||||
}
|
||||
|
||||
queuedLODs[drawID] = lodLevel;
|
||||
}
|
||||
}
|
||||
|
||||
// insert closest primitive if all are out of range (because of cringe logic)
|
||||
if ( !found ) {
|
||||
queuedDrawIDs[closestDrawID] = true;
|
||||
queuedLODs[closestDrawID] = 0;
|
||||
}
|
||||
|
||||
// bail if no update is detected
|
||||
auto drawCommandHash = ::fnv1aHash(queuedDrawIDs);
|
||||
auto drawCommandHash = ::fnv1aHash(queuedLODs);
|
||||
graph.settings.stream.lastUpdate = uf::physics::time::current;
|
||||
|
||||
if ( drawCommandHash == graph.settings.stream.hash ) {
|
||||
return;
|
||||
}
|
||||
graph.settings.stream.hash = drawCommandHash;
|
||||
meshUpdated = true;
|
||||
|
||||
// read from disk
|
||||
#if UF_GRAPH_SPARSE_READ_MESH
|
||||
@ -1707,14 +1738,24 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
mesh.vertex.count = 0;
|
||||
mesh.index.count = 0;
|
||||
|
||||
for (size_t drawID = 0; drawID < queuedDrawIDs.size(); ++drawID) {
|
||||
bool queued = queuedDrawIDs[drawID];
|
||||
for (size_t drawID = 0; drawID < queuedLODs.size(); ++drawID) {
|
||||
auto lodLevel = queuedLODs[drawID];
|
||||
auto& primitive = primitives[drawID];
|
||||
auto& drawCommand = drawCommands[drawID];
|
||||
|
||||
// reset from LOD0
|
||||
//primitives[drawID].drawCommand.instances = 1;
|
||||
primitives[drawID].drawCommand.indexID = primitives[drawID].lod.levels[0].indexID;
|
||||
primitives[drawID].drawCommand.indices = primitives[drawID].lod.levels[0].indices;
|
||||
primitives[drawID].drawCommand.vertexID = primitives[drawID].lod.levels[0].vertexID;
|
||||
primitives[drawID].drawCommand.vertices = primitives[drawID].lod.levels[0].vertices;
|
||||
|
||||
// copy from primitive
|
||||
drawCommand = primitive.drawCommand;
|
||||
|
||||
// disable draw call
|
||||
if ( !queued ) {
|
||||
drawCommand.instances = 0;
|
||||
if ( lodLevel < 0 ) {
|
||||
//drawCommand.instances = 0;
|
||||
drawCommand.vertices = 0;
|
||||
drawCommand.indices = 0;
|
||||
drawCommand.vertexID = 0;
|
||||
@ -1722,26 +1763,33 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// queue up ranges to read from disk
|
||||
auto& lod = primitive.lod.levels[lodLevel];
|
||||
|
||||
// queue up ranges to read from disk using LOD bounds
|
||||
for (auto& attribute : mesh.index.attributes) {
|
||||
auto size = attribute.descriptor.size;
|
||||
ranges[attribute.buffer].emplace_back(pod::Range{
|
||||
primitive.drawCommand.indexID * size,
|
||||
primitive.drawCommand.indices * size,
|
||||
lod.indexID * size,
|
||||
lod.indices * size,
|
||||
});
|
||||
}
|
||||
for (auto& attribute : mesh.vertex.attributes) {
|
||||
auto size = attribute.descriptor.size;
|
||||
ranges[attribute.buffer].emplace_back(pod::Range{
|
||||
primitive.drawCommand.vertexID * size,
|
||||
primitive.drawCommand.vertices * size,
|
||||
lod.vertexID * size,
|
||||
lod.vertices * size,
|
||||
});
|
||||
}
|
||||
|
||||
// reset draw call and remap
|
||||
drawCommand = primitive.drawCommand;
|
||||
// reset draw call and remap to local compacted buffers
|
||||
drawCommand.vertices = lod.vertices;
|
||||
drawCommand.indices = lod.indices;
|
||||
drawCommand.vertexID = mesh.vertex.count;
|
||||
drawCommand.indexID = mesh.index.count;
|
||||
|
||||
// synchronize primitive
|
||||
primitives[drawID].drawCommand = drawCommands[drawID];
|
||||
|
||||
// increment remap indices
|
||||
mesh.vertex.count += drawCommand.vertices;
|
||||
mesh.index.count += drawCommand.indices;
|
||||
@ -1762,17 +1810,34 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
#else
|
||||
// disable remaining draw commands
|
||||
for ( auto drawID = 0; drawID < primitives.size(); ++drawID ) {
|
||||
bool queued = queuedDrawIDs[drawID];
|
||||
if ( !queued ) {
|
||||
drawCommands[drawID].instances = 0;
|
||||
int8_t lodLevel = queuedLODs[drawID];
|
||||
// reset from LOD0
|
||||
//primitives[drawID].drawCommand.instances = 1;
|
||||
primitives[drawID].drawCommand.indexID = primitives[drawID].lod.levels[0].indexID;
|
||||
primitives[drawID].drawCommand.indices = primitives[drawID].lod.levels[0].indices;
|
||||
primitives[drawID].drawCommand.vertexID = primitives[drawID].lod.levels[0].vertexID;
|
||||
primitives[drawID].drawCommand.vertices = primitives[drawID].lod.levels[0].vertices;
|
||||
|
||||
// copy from primitive
|
||||
drawCommands[drawID] = primitives[drawID].drawCommand;
|
||||
|
||||
if ( lodLevel < 0 ) {
|
||||
//drawCommands[drawID].instances = 0;
|
||||
drawCommands[drawID].vertices = 0;
|
||||
drawCommands[drawID].indices = 0;
|
||||
drawCommands[drawID].indexID = 0;
|
||||
drawCommands[drawID].vertexID = 0;
|
||||
} else {
|
||||
drawCommands[drawID] = primitives[drawID].drawCommand;
|
||||
// to-do: LOD pick here for OpenGL
|
||||
auto& lod = primitives[drawID].lod.levels[lodLevel];
|
||||
|
||||
drawCommands[drawID].indexID = lod.indexID;
|
||||
drawCommands[drawID].indices = lod.indices;
|
||||
drawCommands[drawID].vertexID = lod.vertexID;
|
||||
drawCommands[drawID].vertices = lod.vertices;
|
||||
}
|
||||
|
||||
// synchronize primitive
|
||||
primitives[drawID].drawCommand = drawCommands[drawID];
|
||||
}
|
||||
|
||||
#define STREAM_MESH_DATA( N ) \
|
||||
@ -1868,10 +1933,12 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // this shouldn't be reached
|
||||
} else {
|
||||
// load mesh if not already loaded
|
||||
#define LOAD_MESH_DATA( N ) \
|
||||
for ( auto& attribute : mesh.N.attributes ) {\
|
||||
if ( !mesh.buffers[attribute.buffer].empty() || mesh.buffer_paths.empty() ) continue;\
|
||||
meshUpdated = true;\
|
||||
uf::io::readAsBuffer( mesh.buffers[attribute.buffer], mesh.buffer_paths[attribute.buffer] );\
|
||||
}
|
||||
|
||||
@ -1879,6 +1946,8 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
LOAD_MESH_DATA( vertex );
|
||||
}
|
||||
|
||||
if ( !meshUpdated ) return;
|
||||
|
||||
// in the event streamed in mesh data from any pathway isn't already converted
|
||||
{
|
||||
#if UF_ENV_DREAMCAST && GL_QUANTIZED_SHORT
|
||||
@ -1908,6 +1977,8 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
uf::renderer::states::rebuild = true;
|
||||
#endif
|
||||
|
||||
::newGraphAdded = true; // force rebuffering the draw commands
|
||||
|
||||
// update graphic
|
||||
if ( graphMetadataJson["renderer"]["render"].as<bool>() ) {
|
||||
bool exists = entity.hasComponent<uf::renderer::Graphic>();
|
||||
|
||||
@ -552,6 +552,8 @@ void ext::gltf::load( pod::Graph& graph, const uf::stl::string& filename, const
|
||||
}
|
||||
|
||||
auto& mesh = storage.meshes[keyName];
|
||||
auto& primitives = storage.primitives[keyName];
|
||||
|
||||
UF_MSG_DEBUG("Optimizing mesh at level {}: {}", level, keyName);
|
||||
if ( !ext::meshopt::optimize( mesh, simplify, level, print ) ) {
|
||||
UF_MSG_ERROR("Mesh optimization failed: {}", keyName );
|
||||
@ -563,9 +565,10 @@ void ext::gltf::load( pod::Graph& graph, const uf::stl::string& filename, const
|
||||
UF_MSG_ERROR("LOD generation failed: {}", keyName );
|
||||
} else {
|
||||
UF_MSG_DEBUG("Generated {} LODs: {}", factors.size() - 1, keyName);
|
||||
auto& primitives = storage.primitives[keyName];
|
||||
UF_ASSERT( primitives.size() == lodMetadata.size() );
|
||||
for ( auto i = 0; i < primitives.size(); ++i ) primitives[i].lod = lodMetadata[i];
|
||||
for ( auto i = 0; i < primitives.size(); ++i ) {
|
||||
primitives[i].lod = lodMetadata[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -573,6 +576,21 @@ void ext::gltf::load( pod::Graph& graph, const uf::stl::string& filename, const
|
||||
UF_MSG_DEBUG( "Optimized mesh" );
|
||||
}
|
||||
#endif
|
||||
{
|
||||
// update primitive info
|
||||
for ( auto& keyName : graph.meshes ) {
|
||||
auto& mesh = storage.meshes[keyName];
|
||||
auto& primitives = storage.primitives[keyName];
|
||||
|
||||
UF_ASSERT( primitives.size() == mesh.indirect.count );
|
||||
auto& attribute = mesh.indirect.attributes.front();
|
||||
auto& buffer = mesh.buffers[mesh.isInterleaved(mesh.indirect.interleaved) ? mesh.indirect.interleaved : attribute.buffer];
|
||||
pod::DrawCommand* drawCommands = (pod::DrawCommand*) buffer.data();
|
||||
for ( auto drawID = 0; drawID < primitives.size(); ++drawID ) {
|
||||
primitives[drawID].drawCommand = drawCommands[drawID];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( graph.metadata["exporter"]["enabled"].as<bool>() ) {
|
||||
#if !UF_ENV_DREAMCAST
|
||||
|
||||
@ -385,6 +385,12 @@ if ( meshopt.should ) {
|
||||
meshlet.primitive.drawCommand.vertexID = vertexID;
|
||||
meshlet.primitive.drawCommand.vertices = meshlet.vertices.size();
|
||||
|
||||
// copy to LOD metadata
|
||||
meshlet.primitive.lod.levels[0].indexID = indexID;
|
||||
meshlet.primitive.lod.levels[0].indices = meshlet.indices.size();
|
||||
meshlet.primitive.lod.levels[0].vertexID = vertexID;
|
||||
meshlet.primitive.lod.levels[0].vertices = meshlet.vertices.size();
|
||||
|
||||
drawCommands.emplace_back(meshlet.primitive.drawCommand);
|
||||
|
||||
primitives.emplace_back( meshlet.primitive );
|
||||
|
||||
58
engine/src/ext/imgui/behavior.cpp
Normal file
58
engine/src/ext/imgui/behavior.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
#include "behavior.h"
|
||||
|
||||
#include <uf/utils/hook/hook.h>
|
||||
#include <uf/utils/io/fmt.h>
|
||||
#include <uf/utils/io/payloads.h>
|
||||
#include <uf/utils/io/console.h>
|
||||
#include <uf/utils/window/payloads.h>
|
||||
#include <uf/utils/thread/thread.h>
|
||||
|
||||
#if UF_USE_IMGUI
|
||||
#include <uf/ext/imgui/imgui.h>
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/imgui_stdlib.h>
|
||||
|
||||
#include "consoleWindow.inl"
|
||||
#include "threadMetrics.inl"
|
||||
#endif
|
||||
|
||||
UF_BEHAVIOR_REGISTER_CPP(ext::ImguiBehavior)
|
||||
UF_BEHAVIOR_TRAITS_CPP(ext::ImguiBehavior, ticks = false, renders = false, thread = "")
|
||||
#define this (&self)
|
||||
void ext::ImguiBehavior::initialize( uf::Object& self ) {
|
||||
auto& metadata = this->getComponent<ext::ImguiBehavior::Metadata>();
|
||||
auto& metadataJson = this->getComponent<uf::Serializer>();
|
||||
|
||||
#if UF_USE_IMGUI
|
||||
this->addHook( "gui:IMGUI.tick", [&](){
|
||||
tick( self );
|
||||
} );
|
||||
#endif
|
||||
|
||||
::consoleWindow.position.x = uf::renderer::settings::width - ::consoleWindow.size.x;
|
||||
|
||||
UF_BEHAVIOR_METADATA_BIND_SERIALIZER_HOOKS(metadata, metadataJson);
|
||||
}
|
||||
void ext::ImguiBehavior::tick( uf::Object& self ) {
|
||||
#if UF_USE_IMGUI
|
||||
// console window
|
||||
{
|
||||
bool opened;
|
||||
::consoleWindow.Draw("Console", opened);
|
||||
}
|
||||
// thread metrics window
|
||||
{
|
||||
bool opened = true;
|
||||
::threadMetrics.Draw("Thread Metrics", opened);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
void ext::ImguiBehavior::render( uf::Object& self ){
|
||||
|
||||
}
|
||||
void ext::ImguiBehavior::destroy( uf::Object& self ){}
|
||||
void ext::ImguiBehavior::Metadata::serialize( uf::Object& self, uf::Serializer& serializer ){
|
||||
}
|
||||
void ext::ImguiBehavior::Metadata::deserialize( uf::Object& self, uf::Serializer& serializer ){
|
||||
}
|
||||
#undef this
|
||||
20
engine/src/ext/imgui/behavior.h
Normal file
20
engine/src/ext/imgui/behavior.h
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <uf/config.h>
|
||||
#include <uf/ext/ext.h>
|
||||
#include <uf/engine/entity/entity.h>
|
||||
#include <uf/engine/scene/scene.h>
|
||||
#include <uf/utils/math/vector.h>
|
||||
#include <uf/utils/renderer/renderer.h>
|
||||
|
||||
|
||||
namespace ext {
|
||||
namespace ImguiBehavior {
|
||||
UF_BEHAVIOR_DEFINE_TYPE();
|
||||
EXT_BEHAVIOR_DEFINE_TRAITS();
|
||||
EXT_BEHAVIOR_DEFINE_FUNCTIONS();
|
||||
UF_BEHAVIOR_DEFINE_METADATA(
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
194
engine/src/ext/imgui/consoleWindow.inl
Normal file
194
engine/src/ext/imgui/consoleWindow.inl
Normal file
@ -0,0 +1,194 @@
|
||||
namespace {
|
||||
struct ConsoleWindow {
|
||||
ImGuiTextFilter filter;
|
||||
int historyPos = -1;
|
||||
|
||||
struct {
|
||||
bool automatic = true;
|
||||
bool bottom = false;
|
||||
} scroll;
|
||||
|
||||
uf::stl::string commandBuf;
|
||||
|
||||
pod::Vector2ui size{800, 600};
|
||||
pod::Vector2ui position{64, 32};
|
||||
|
||||
void Draw( const uf::stl::string& title, bool& open ) {
|
||||
ImGui::SetNextWindowSize(ImVec2(size.x, size.y), ImGuiCond_FirstUseEver);
|
||||
ImGui::SetNextWindowPos(ImVec2(position.x, position.y), ImGuiCond_FirstUseEver);
|
||||
|
||||
if (!ImGui::Begin(title.c_str(), &open)) {
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ImGui::BeginPopupContextItem() ) {
|
||||
if ( ImGui::MenuItem("Close Console") ) open = false;
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
DrawToolbar();
|
||||
ImGui::Separator();
|
||||
DrawLog();
|
||||
ImGui::Separator();
|
||||
DrawInput();
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
private:
|
||||
void DrawToolbar() {
|
||||
if ( ImGui::SmallButton("Clear") ) uf::console::clear();
|
||||
|
||||
ImGui::SameLine();
|
||||
bool copyToClipboard = ImGui::SmallButton("Copy");
|
||||
|
||||
if ( ImGui::Button("Options") ) {
|
||||
ImGui::OpenPopup("Options");
|
||||
}
|
||||
if ( ImGui::BeginPopup("Options") ) {
|
||||
ImGui::Checkbox("Auto-scroll", &scroll.automatic);
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
filter.Draw("Filter (\"incl,-excl\") (\"error\")", 180);
|
||||
|
||||
if ( copyToClipboard ) {
|
||||
ImGui::LogToClipboard();
|
||||
for ( auto& item : uf::console::log ) {
|
||||
if (filter.PassFilter(item.c_str())) ImGui::LogText("%s\n", item.c_str());
|
||||
}
|
||||
ImGui::LogFinish();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawLog() {
|
||||
const float footerReserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
|
||||
ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footerReserve), false, ImGuiWindowFlags_HorizontalScrollbar);
|
||||
|
||||
if ( ImGui::BeginPopupContextWindow() ) {
|
||||
if ( ImGui::Selectable("Clear") ) uf::console::clear();
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1));
|
||||
|
||||
for ( auto& item : uf::console::log ) {
|
||||
if ( !filter.PassFilter(item.c_str()) ) continue;
|
||||
|
||||
ImVec4 color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
bool hasColor = false;
|
||||
|
||||
if ( uf::string::matched(item, "^\\[ERROR\\]") ) {
|
||||
color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f); hasColor = true;
|
||||
} else if ( uf::string::matched(item, "^\\> ")) {
|
||||
color = ImVec4(1.0f, 0.8f, 0.6f, 1.0f); hasColor = true;
|
||||
}
|
||||
|
||||
if ( hasColor ) ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||
ImGui::TextUnformatted(item.c_str());
|
||||
if ( hasColor ) ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
if ( scroll.bottom || (scroll.automatic && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) ) {
|
||||
ImGui::SetScrollHereY(1.0f);
|
||||
}
|
||||
scroll.bottom = false;
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void DrawInput() {
|
||||
bool reclaimFocus = false;
|
||||
ImGuiInputTextFlags inputTextFlags = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory;
|
||||
|
||||
if ( ImGui::InputText("Input", &commandBuf, inputTextFlags, &TextEditCallbackStub, (void*) this) ) {
|
||||
uf::string::trim( commandBuf );
|
||||
if ( !commandBuf.empty() ) {
|
||||
uf::console::execute(commandBuf);
|
||||
commandBuf.clear();
|
||||
}
|
||||
historyPos = -1;
|
||||
scroll.bottom = true;
|
||||
reclaimFocus = true;
|
||||
}
|
||||
|
||||
ImGui::SetItemDefaultFocus();
|
||||
if ( reclaimFocus ) ImGui::SetKeyboardFocusHere(-1);
|
||||
}
|
||||
|
||||
static int TextEditCallbackStub( ImGuiInputTextCallbackData* data ) {
|
||||
ConsoleWindow* console = (ConsoleWindow*) data->UserData;
|
||||
return console->TextEditCallback(data);
|
||||
}
|
||||
|
||||
int TextEditCallback( ImGuiInputTextCallbackData* data ) {
|
||||
switch ( data->EventFlag ) {
|
||||
case ImGuiInputTextFlags_CallbackCompletion: {
|
||||
const char* end = data->Buf + data->CursorPos;
|
||||
const char* start = end;
|
||||
while ( start > data->Buf ) {
|
||||
const char c = start[-1];
|
||||
if (c == ' ' || c == '\t' || c == ',' || c == ';') break;
|
||||
start--;
|
||||
}
|
||||
|
||||
uf::stl::string input{ start, end };
|
||||
uf::stl::vector<uf::stl::string> candidates;
|
||||
for ( auto& pair : uf::console::commands ) {
|
||||
if (pair.first.find(input) == 0) candidates.emplace_back(pair.first);
|
||||
}
|
||||
|
||||
if ( candidates.empty() ) {
|
||||
UF_MSG_ERROR("No match for `{}`", input);
|
||||
} else if ( candidates.size() == 1 ) {
|
||||
data->DeleteChars((int)(start - data->Buf), (int)(end - start));
|
||||
data->InsertChars(data->CursorPos, candidates[0].c_str());
|
||||
data->InsertChars(data->CursorPos, " ");
|
||||
} else {
|
||||
int matchLen = (int)(end - start);
|
||||
while ( true ) {
|
||||
if ( matchLen >= candidates[0].size() ) break;
|
||||
char c = candidates[0][matchLen];
|
||||
bool allMatch = true;
|
||||
for ( size_t i = 1; i < candidates.size(); i++ ) {
|
||||
if ( matchLen >= candidates[i].size() || candidates[i][matchLen] != c ) {
|
||||
allMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( !allMatch ) break;
|
||||
matchLen++;
|
||||
}
|
||||
|
||||
if ( matchLen > (int) (end - start) ) {
|
||||
data->DeleteChars((int)(start - data->Buf), (int)(end - start));
|
||||
data->InsertChars(data->CursorPos, candidates[0].substr(0, matchLen).c_str());
|
||||
} else {
|
||||
UF_MSG_DEBUG("Matches: {}", uf::string::join(candidates, " "));
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case ImGuiInputTextFlags_CallbackHistory: {
|
||||
const int prevPos = historyPos;
|
||||
if ( data->EventKey == ImGuiKey_UpArrow ) {
|
||||
if ( historyPos == -1 ) historyPos = (int)uf::console::history.size() - 1;
|
||||
else if ( historyPos > 0 ) historyPos--;
|
||||
} else if ( data->EventKey == ImGuiKey_DownArrow ) {
|
||||
if ( historyPos != -1 && ++historyPos >= (int)uf::console::history.size() ) historyPos = -1;
|
||||
}
|
||||
|
||||
if ( prevPos != historyPos ) {
|
||||
const char* historyStr = (historyPos >= 0) ? uf::console::history[historyPos].c_str() : "";
|
||||
data->DeleteChars(0, data->BufTextLen);
|
||||
data->InsertChars(0, historyStr);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
} consoleWindow;
|
||||
}
|
||||
@ -2,8 +2,10 @@
|
||||
#include <uf/ext/imgui/imgui.h>
|
||||
#include <uf/utils/math/vector.h>
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/imgui_stdlib.h>
|
||||
#include <uf/spec/renderer/universal.h>
|
||||
#include <uf/utils/math/physics.h>
|
||||
#include <uf/utils/window/payloads.h>
|
||||
|
||||
#if UF_USE_VULKAN
|
||||
#include <imgui/backends/imgui_impl_vulkan.h>
|
||||
@ -14,235 +16,35 @@
|
||||
#if UF_ENV_WINDOWS
|
||||
#include <imgui/backends/imgui_impl_win32.h>
|
||||
#endif
|
||||
|
||||
#include <imgui/imgui_stdlib.h>
|
||||
|
||||
#include <uf/utils/io/fmt.h>
|
||||
#include <uf/utils/io/payloads.h>
|
||||
#include <uf/utils/io/console.h>
|
||||
#include <uf/utils/window/payloads.h>
|
||||
#if UF_ENV_LINUX
|
||||
// to-do: linux
|
||||
#endif
|
||||
|
||||
bool ext::imgui::focused = false;
|
||||
|
||||
namespace {
|
||||
#if UF_USE_VULKAN
|
||||
VkDescriptorPool descriptorPool{};
|
||||
uf::renderer::RenderMode* boundRenderMode = NULL;
|
||||
VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
|
||||
#endif
|
||||
|
||||
struct ConsoleWindow {
|
||||
ImGuiTextFilter filter;
|
||||
int history = 0;
|
||||
|
||||
struct {
|
||||
bool automatic = true;
|
||||
bool bottom = false;
|
||||
} scroll;
|
||||
|
||||
uf::stl::unordered_map<uf::stl::string, std::function<uf::stl::string(const uf::stl::string&)>> commands;
|
||||
|
||||
pod::Vector2ui size{800, 600};
|
||||
pod::Vector2ui position{64, 32};
|
||||
|
||||
void Draw( const uf::stl::string& title, bool& open ) {
|
||||
ImGui::SetNextWindowSize(ImVec2(size.x, size.y), ImGuiCond_FirstUseEver);
|
||||
ImGui::SetNextWindowPos(ImVec2(position.x, position.y), ImGuiCond_FirstUseEver);
|
||||
|
||||
if (!ImGui::Begin(title.c_str(), &open)) {
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ImGui::BeginPopupContextItem() ) {
|
||||
if ( ImGui::MenuItem("Close Console") ) open = false;
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
if ( ImGui::SmallButton("Clear") ) uf::console::clear();
|
||||
|
||||
ImGui::SameLine();
|
||||
bool copyToClipboard = ImGui::SmallButton("Copy");
|
||||
ImGui::Separator();
|
||||
|
||||
if ( ImGui::BeginPopup("Options") ) {
|
||||
ImGui::Checkbox("Auto-scroll", &this->scroll.bottom);
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
if ( ImGui::Button("Options") ) {
|
||||
ImGui::OpenPopup("Options");
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
this->filter.Draw("Filter (\"incl,-excl\") (\"error\")", 180);
|
||||
ImGui::Separator();
|
||||
|
||||
const float footerReserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
|
||||
ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footerReserve), false, ImGuiWindowFlags_HorizontalScrollbar);
|
||||
if ( ImGui::BeginPopupContextWindow() ) {
|
||||
if ( ImGui::Selectable("Clear") ) uf::console::clear();
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1));
|
||||
|
||||
if ( copyToClipboard ) ImGui::LogToClipboard();
|
||||
for ( auto& item : uf::console::log ) {
|
||||
if ( !this->filter.PassFilter(item.c_str()) ) continue;
|
||||
|
||||
ImVec4 color = ImVec4(0, 0, 0, 0);
|
||||
|
||||
if ( uf::string::matched(item, "^\\[ERROR\\]") ) color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f);
|
||||
else if ( uf::string::matched(item, "^\\> ") ) color = ImVec4(1.0f, 0.8f, 0.6f, 1.0f);
|
||||
|
||||
if ( color.w > 0.0f ) ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||
ImGui::TextUnformatted( item.c_str() );
|
||||
if ( color.w > 0.0f ) ImGui::PopStyleColor();
|
||||
}
|
||||
if ( copyToClipboard ) ImGui::LogFinish();
|
||||
|
||||
if ( this->scroll.bottom || (this->scroll.automatic && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) ) {
|
||||
ImGui::SetScrollHereY(1.0f);
|
||||
}
|
||||
this->scroll.bottom = false;
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::EndChild();
|
||||
ImGui::Separator();
|
||||
|
||||
bool reclaimFocus = false;
|
||||
ImGuiInputTextFlags inputTextFlags = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory;
|
||||
|
||||
uf::stl::string command;
|
||||
if ( ImGui::InputText("Input", &command, inputTextFlags, &TextEditCallbackStub, (void *)this) ) {
|
||||
this->history = -1;
|
||||
this->scroll.bottom = true;
|
||||
reclaimFocus = true;
|
||||
|
||||
|
||||
// to-do: add a way to either asynchronously invoke commands or not
|
||||
uf::console::execute( command );
|
||||
/*
|
||||
uf::thread::queue( uf::thread::asyncThreadName, [=](){
|
||||
uf::console::execute( command );
|
||||
});
|
||||
*/
|
||||
/*
|
||||
// this blocks
|
||||
uf::thread::queue( uf::thread::fetchWorker(), [=](){
|
||||
uf::console::execute( command );
|
||||
});
|
||||
*/
|
||||
/*
|
||||
// this still blocks
|
||||
auto tasks = uf::thread::schedule(true);
|
||||
tasks.queue([=](){
|
||||
uf::console::execute( command );
|
||||
});
|
||||
uf::thread::execute( tasks );
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
ImGui::SetItemDefaultFocus();
|
||||
if ( reclaimFocus ) ImGui::SetKeyboardFocusHere(-1);
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
static int TextEditCallbackStub( ImGuiInputTextCallbackData *data ) {
|
||||
ConsoleWindow *console = (ConsoleWindow*) data->UserData;
|
||||
return console->TextEditCallback(data);
|
||||
}
|
||||
|
||||
int TextEditCallback( ImGuiInputTextCallbackData* data ) {
|
||||
switch ( data->EventFlag ) {
|
||||
case ImGuiInputTextFlags_CallbackCompletion: {
|
||||
const char* end = data->Buf + data->CursorPos;
|
||||
const char* start = end;
|
||||
while ( start > data->Buf ) {
|
||||
const char c = start[-1];
|
||||
if (c == ' ' || c == '\t' || c == ',' || c == ';')
|
||||
break;
|
||||
start--;
|
||||
}
|
||||
|
||||
uf::stl::string input{ start, end };
|
||||
uf::stl::vector<uf::stl::string> candidates;
|
||||
for ( auto& pair : uf::console::commands ) {
|
||||
if ( pair.first.find(input) == 0 ) candidates.emplace_back( input );
|
||||
}
|
||||
if ( candidates.empty() ) {
|
||||
UF_MSG_ERROR("No match for `{}`", input);
|
||||
} else if ( candidates.size() == 1 ) {
|
||||
data->DeleteChars((int)(start - data->Buf), (int)(end - start));
|
||||
data->InsertChars(data->CursorPos, candidates[0].c_str());
|
||||
data->InsertChars(data->CursorPos, " ");
|
||||
} else {
|
||||
/*
|
||||
int match = (int) (end - start);
|
||||
while ( true ) {
|
||||
int c = 0;
|
||||
bool allCandidatesMatches = true;
|
||||
for ( auto i = 0; i < candidates.size() && allCandidatesMatches; ++i ) {
|
||||
if ( i == 0 ) c = uf::string::uppercase( candidates[i].at(match) );
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
} break;
|
||||
case ImGuiInputTextFlags_CallbackHistory: {
|
||||
const int previousPosition = this->history;
|
||||
if ( data->EventKey == ImGuiKey_UpArrow ) {
|
||||
if (this->history == -1) this->history = uf::console::history.size() - 1;
|
||||
else if (this->history > 0) this->history--;
|
||||
} else if ( data->EventKey == ImGuiKey_DownArrow ) {
|
||||
if ( this->history != -1 && ++this->history >= uf::console::history.size() ) this->history = -1;
|
||||
}
|
||||
|
||||
if ( previousPosition != this->history ) {
|
||||
const char* history = (this->history >= 0) ? uf::console::history[this->history].c_str() : "";
|
||||
data->DeleteChars(0, data->BufTextLen);
|
||||
data->InsertChars(0, history);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
ConsoleWindow console;
|
||||
bool initialized = false;
|
||||
}
|
||||
void ext::imgui::initialize() {
|
||||
#if UF_USE_VULKAN
|
||||
if ( !uf::renderer::hasRenderMode("Gui", true) ) return;
|
||||
#endif
|
||||
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange; //
|
||||
// io.ConfigFlags |= ImGuiConfigFlags_NoMouse; //
|
||||
io.DisplaySize = ImVec2(uf::renderer::settings::width,uf::renderer::settings::height);
|
||||
io.MouseDrawCursor = false;
|
||||
io.IniFilename = NULL;
|
||||
|
||||
uf::hooks.addHook( "window:Mouse.CursorVisibility", [&]( pod::payloads::windowMouseCursorVisibility& payload ){
|
||||
io.MouseDrawCursor = payload.mouse.visible;
|
||||
});
|
||||
|
||||
::console.position.x = uf::renderer::settings::width - ::console.size.x;
|
||||
|
||||
void initPlatform() {
|
||||
#if UF_ENV_WINDOWS
|
||||
ImGui_ImplWin32_Init(uf::renderer::device.window->getHandle());
|
||||
ImGui_ImplWin32_Init(uf::renderer::device.window->getHandle());
|
||||
#endif
|
||||
#if UF_ENV_LINUX
|
||||
// to-do: linux
|
||||
#endif
|
||||
}
|
||||
|
||||
void initRenderer() {
|
||||
#if UF_USE_VULKAN
|
||||
auto& renderMode = uf::renderer::getRenderMode("Gui", true);
|
||||
{
|
||||
VkDescriptorPoolSize poolSizes[] =
|
||||
{
|
||||
auto& renderMode = uf::renderer::getRenderMode("Gui", true);
|
||||
::boundRenderMode = &renderMode;
|
||||
|
||||
// create descriptor pool
|
||||
VkDescriptorPoolSize poolSizes[] = {
|
||||
{ VK_DESCRIPTOR_TYPE_SAMPLER, 1000 },
|
||||
{ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 },
|
||||
{ VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 },
|
||||
@ -261,99 +63,155 @@ void ext::imgui::initialize() {
|
||||
poolInfo.maxSets = 1000 * IM_ARRAYSIZE(poolSizes);
|
||||
poolInfo.poolSizeCount = (uint32_t)IM_ARRAYSIZE(poolSizes);
|
||||
poolInfo.pPoolSizes = poolSizes;
|
||||
VK_CHECK_RESULT(vkCreateDescriptorPool(uf::renderer::device, &poolInfo, NULL, &::descriptorPool));
|
||||
}
|
||||
{
|
||||
VK_CHECK_RESULT(vkCreateDescriptorPool(uf::renderer::device, &poolInfo, NULL, &descriptorPool));
|
||||
|
||||
// initialize ImGui vulkan backend
|
||||
ImGui_ImplVulkan_InitInfo imguiInitInfo = {};
|
||||
imguiInitInfo.Instance = uf::renderer::device.instance;
|
||||
imguiInitInfo.PhysicalDevice = uf::renderer::device.physicalDevice;
|
||||
imguiInitInfo.Device = uf::renderer::device.logicalDevice;
|
||||
imguiInitInfo.QueueFamily = uf::renderer::device.queueFamilyIndices.graphics;
|
||||
imguiInitInfo.Queue = uf::renderer::device.getQueue( uf::renderer::QueueEnum::GRAPHICS );
|
||||
imguiInitInfo.Queue = uf::renderer::device.getQueue(uf::renderer::QueueEnum::GRAPHICS);
|
||||
imguiInitInfo.PipelineCache = uf::renderer::device.pipelineCache;
|
||||
imguiInitInfo.DescriptorPool = ::descriptorPool;
|
||||
imguiInitInfo.DescriptorPool = descriptorPool;
|
||||
imguiInitInfo.Subpass = 0;
|
||||
imguiInitInfo.MinImageCount = uf::renderer::swapchain.buffers;
|
||||
imguiInitInfo.ImageCount = uf::renderer::swapchain.buffers;
|
||||
imguiInitInfo.MSAASamples = VK_SAMPLE_COUNT_1_BIT;
|
||||
imguiInitInfo.Allocator = NULL;
|
||||
imguiInitInfo.CheckVkResultFn = NULL;
|
||||
|
||||
imguiInitInfo.Allocator = nullptr;
|
||||
imguiInitInfo.CheckVkResultFn = nullptr;
|
||||
ImGui_ImplVulkan_Init(&imguiInitInfo, renderMode.renderTarget.renderPass);
|
||||
}
|
||||
{
|
||||
// Use any command queue
|
||||
|
||||
// upload fonts
|
||||
auto commandBuffer = uf::renderer::device.fetchCommandBuffer(uf::renderer::QueueEnum::GRAPHICS);
|
||||
ImGui_ImplVulkan_CreateFontsTexture(commandBuffer.handle);
|
||||
uf::renderer::device.flushCommandBuffer(commandBuffer);
|
||||
ImGui_ImplVulkan_DestroyFontUploadObjects();
|
||||
}
|
||||
|
||||
{
|
||||
renderMode.bindCallback( 0, [&]( VkCommandBuffer commandBuffer, size_t _ ){
|
||||
// bind render callback
|
||||
renderMode.bindCallback(0, [&]( VkCommandBuffer cb, size_t _ ) {
|
||||
ImDrawData* drawData = ImGui::GetDrawData();
|
||||
ImGui_ImplVulkan_RenderDrawData(drawData, commandBuffer);
|
||||
if (drawData) ImGui_ImplVulkan_RenderDrawData(drawData, cb);
|
||||
});
|
||||
}
|
||||
|
||||
#elif UF_USE_OPENGL
|
||||
ImGui_ImplOpenGL2_Init();
|
||||
ImGui_ImplOpenGL2_Init();
|
||||
#endif
|
||||
}
|
||||
|
||||
void newFramePlatform() {
|
||||
#if UF_ENV_WINDOWS
|
||||
ImGui_ImplWin32_NewFrame();
|
||||
#endif
|
||||
#if UF_ENV_LINUX
|
||||
// to-do: linux
|
||||
#endif
|
||||
}
|
||||
|
||||
void newFrameRenderer() {
|
||||
#if UF_USE_VULKAN
|
||||
auto& renderMode = uf::renderer::getRenderMode("Gui", true);
|
||||
renderMode.rerecord = true;
|
||||
ImGui_ImplVulkan_NewFrame();
|
||||
#elif UF_USE_OPENGL
|
||||
ImGui_ImplOpenGL2_NewFrame();
|
||||
#endif
|
||||
}
|
||||
|
||||
void shutdownPlatform() {
|
||||
#if UF_ENV_WINDOWS
|
||||
ImGui_ImplWin32_Shutdown();
|
||||
#endif
|
||||
#if UF_ENV_LINUX
|
||||
// to-do: linux
|
||||
#endif
|
||||
}
|
||||
|
||||
void shutdownRenderer() {
|
||||
#if UF_USE_VULKAN
|
||||
ImGui_ImplVulkan_Shutdown();
|
||||
if ( descriptorPool != VK_NULL_HANDLE ) {
|
||||
vkDestroyDescriptorPool(uf::renderer::device, descriptorPool, nullptr);
|
||||
descriptorPool = VK_NULL_HANDLE;
|
||||
}
|
||||
#elif UF_USE_OPENGL
|
||||
ImGui_ImplOpenGL2_Shutdown();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void ext::imgui::initialize() {
|
||||
#if UF_USE_VULKAN
|
||||
if ( !uf::renderer::hasRenderMode("Gui", true) ) return;
|
||||
#endif
|
||||
|
||||
::initialized = true;
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange;
|
||||
io.DisplaySize = ImVec2((float) uf::renderer::settings::width, (float) uf::renderer::settings::height);
|
||||
io.MouseDrawCursor = false;
|
||||
io.IniFilename = nullptr;
|
||||
|
||||
uf::hooks.addHook("window:Mouse.CursorVisibility", [&](pod::payloads::windowMouseCursorVisibility& payload) {
|
||||
ImGui::GetIO().MouseDrawCursor = payload.mouse.visible;
|
||||
});
|
||||
|
||||
::initPlatform();
|
||||
::initRenderer();
|
||||
|
||||
::initialized = true;
|
||||
}
|
||||
|
||||
void ext::imgui::tick() {
|
||||
if ( !::initialized ) ext::imgui::initialize();
|
||||
if ( !::initialized ) return;
|
||||
if ( !::initialized ) ext::imgui::initialize();
|
||||
if ( !::initialized ) return;
|
||||
// no GUI render mode found
|
||||
if ( !uf::renderer::hasRenderMode("Gui", true) ) return;
|
||||
auto& renderMode = uf::renderer::getRenderMode("Gui", true);
|
||||
|
||||
// check if rendermode changed
|
||||
if ( ::boundRenderMode != &renderMode ) {
|
||||
::shutdownRenderer();
|
||||
::initRenderer();
|
||||
}
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.DeltaTime = uf::physics::time::delta;
|
||||
io.DisplaySize = ImVec2(uf::renderer::settings::width,uf::renderer::settings::height);
|
||||
io.DisplaySize = ImVec2((float)uf::renderer::settings::width, (float)uf::renderer::settings::height);
|
||||
|
||||
ext::imgui::focused = io.WantCaptureKeyboard || io.WantCaptureMouse;
|
||||
|
||||
#if UF_USE_VULKAN
|
||||
auto& renderMode = uf::renderer::getRenderMode("Gui", true);
|
||||
renderMode.rerecord = true;
|
||||
|
||||
ImGui_ImplVulkan_NewFrame();
|
||||
#elif UF_USE_OPENGL
|
||||
ImGui_ImplOpenGL2_NewFrame();
|
||||
#endif
|
||||
|
||||
#if UF_ENV_WINDOWS
|
||||
ImGui_ImplWin32_NewFrame();
|
||||
#endif
|
||||
::newFrameRenderer();
|
||||
::newFramePlatform();
|
||||
ImGui::NewFrame();
|
||||
|
||||
auto& scene = uf::scene::getCurrentScene();
|
||||
if ( scene.globalFindByName("Gui: Menu") ) {
|
||||
bool opened;
|
||||
console.Draw("Console", opened);
|
||||
}
|
||||
|
||||
|
||||
uf::hooks.call("gui:IMGUI.tick");
|
||||
|
||||
ImGui::Render();
|
||||
}
|
||||
|
||||
void ext::imgui::render() {
|
||||
if ( !::initialized ) return;
|
||||
if (!::initialized) return;
|
||||
|
||||
#if UF_USE_OPENGL
|
||||
auto renderMode = uf::renderer::getCurrentRenderMode();
|
||||
if ( !renderMode || renderMode->getName() != "Gui" ) return;
|
||||
auto data = ImGui::GetDrawData();
|
||||
if ( !data ) return;
|
||||
ImGui_ImplOpenGL2_RenderDrawData(data);
|
||||
#endif
|
||||
}
|
||||
void ext::imgui::terminate() {
|
||||
if ( !::initialized ) return;
|
||||
#if UF_USE_VULKAN
|
||||
ImGui_ImplVulkan_Shutdown();
|
||||
#elif UF_USE_OPENGL
|
||||
ImGui_ImplOpenGL2_Shutdown();
|
||||
#endif
|
||||
#if UF_ENV_WINDOWS
|
||||
ImGui_ImplWin32_Shutdown();
|
||||
#endif
|
||||
|
||||
ImGui::DestroyContext();
|
||||
ImDrawData* data = ImGui::GetDrawData();
|
||||
if (data) ImGui_ImplOpenGL2_RenderDrawData(data);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
void ext::imgui::terminate() {
|
||||
if (!::initialized) return;
|
||||
|
||||
::shutdownRenderer();
|
||||
::shutdownPlatform();
|
||||
ImGui::DestroyContext();
|
||||
|
||||
::initialized = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
109
engine/src/ext/imgui/threadMetrics.inl
Normal file
109
engine/src/ext/imgui/threadMetrics.inl
Normal file
@ -0,0 +1,109 @@
|
||||
namespace {
|
||||
struct ThreadMetricsWindow {
|
||||
static const int HISTORY_SIZE = 120;
|
||||
|
||||
struct ThreadHistory {
|
||||
float activeTime[HISTORY_SIZE] = { 0.0f };
|
||||
float idleTime[HISTORY_SIZE] = { 0.0f };
|
||||
float totalTime[HISTORY_SIZE] = { 0.0f };
|
||||
int offset = 0;
|
||||
bool filled = false;
|
||||
};
|
||||
|
||||
uf::stl::unordered_map<uf::stl::string, ThreadHistory> histories;
|
||||
|
||||
pod::Vector2ui size{400, 500};
|
||||
pod::Vector2ui position{32, 32};
|
||||
|
||||
void Draw(const uf::stl::string& title, bool& open) {
|
||||
ImGui::SetNextWindowSize(ImVec2(size.x, size.y), ImGuiCond_FirstUseEver);
|
||||
ImGui::SetNextWindowPos(ImVec2(position.x, position.y), ImGuiCond_FirstUseEver);
|
||||
|
||||
if (!ImGui::Begin(title.c_str(), &open)) {
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
#if UF_THREAD_METRICS
|
||||
auto metrics = uf::thread::collectStats();
|
||||
|
||||
if (ImGui::BeginTable("ThreadMetricsTable", 2, ImGuiTableFlags_BordersInnerH | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable)) {
|
||||
ImGui::TableSetupColumn("Thread Info", ImGuiTableColumnFlags_WidthFixed, 150.0f);
|
||||
ImGui::TableSetupColumn("History", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
for (auto& [name, stats] : metrics) {
|
||||
float active = std::get<0>(stats);
|
||||
float idle = std::get<1>(stats);
|
||||
float total = std::get<2>(stats);
|
||||
uint32_t tasks = std::get<3>(stats);
|
||||
|
||||
auto& history = histories[name];
|
||||
|
||||
history.activeTime[history.offset] = active;
|
||||
history.idleTime[history.offset] = idle;
|
||||
history.totalTime[history.offset] = total;
|
||||
history.offset++;
|
||||
if (history.offset >= HISTORY_SIZE) {
|
||||
history.offset = 0;
|
||||
history.filled = true;
|
||||
}
|
||||
|
||||
int count = history.filled ? HISTORY_SIZE : (history.offset == 0 ? 1 : history.offset);
|
||||
float avgActive = 0.0f, avgIdle = 0.0f, avgTotal = 0.0f;
|
||||
|
||||
for (int i = 0; i < count; ++i) {
|
||||
avgActive += history.activeTime[i];
|
||||
avgIdle += history.idleTime[i];
|
||||
avgTotal += history.totalTime[i];
|
||||
}
|
||||
avgActive /= count;
|
||||
avgIdle /= count;
|
||||
avgTotal /= count;
|
||||
|
||||
ImGui::TableNextRow();
|
||||
|
||||
ImGui::PushID(name.c_str());
|
||||
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::TextUnformatted(name.c_str());
|
||||
ImGui::Text("Tasks: %u", tasks);
|
||||
ImGui::Text("Time: %.2f ms", avgTotal);
|
||||
|
||||
float utilization = (avgTotal > 0.0f) ? (avgActive / avgTotal) : 0.0f;
|
||||
char utilText[32];
|
||||
snprintf(utilText, sizeof(utilText), "Load: %.0f%%", utilization * 100.0f);
|
||||
|
||||
if (utilization > 0.8f) ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.8f, 0.2f, 0.2f, 1.0f));
|
||||
ImGui::ProgressBar(utilization, ImVec2(-FLT_MIN, 0), utilText);
|
||||
if (utilization > 0.8f) ImGui::PopStyleColor();
|
||||
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
|
||||
float graphWidth = ImGui::GetContentRegionAvail().x;
|
||||
float max_val = 16.6f;
|
||||
|
||||
char overlayActive[32];
|
||||
snprintf(overlayActive, sizeof(overlayActive), "Act: %.2fms", avgActive);
|
||||
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.2f, 0.7f, 0.2f, 1.0f));
|
||||
ImGui::PlotHistogram("##Act", history.activeTime, HISTORY_SIZE, history.offset, overlayActive, 0.0f, max_val, ImVec2(graphWidth, 30));
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
char overlayIdle[32];
|
||||
snprintf(overlayIdle, sizeof(overlayIdle), "Idl: %.2fms", avgIdle);
|
||||
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.5f, 0.5f, 0.5f, 0.5f));
|
||||
ImGui::PlotHistogram("##Idl", history.idleTime, HISTORY_SIZE, history.offset, overlayIdle, 0.0f, max_val, ImVec2(graphWidth, 20));
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
#else
|
||||
ImGui::TextColored(ImVec4(1,0,0,1), "UF_THREAD_METRICS is disabled!");
|
||||
#endif
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
} threadMetrics;
|
||||
}
|
||||
@ -66,7 +66,7 @@ bool ext::meshopt::optimize( uf::Mesh& mesh, float simplify, size_t o, bool verb
|
||||
uf::stl::vector<meshopt_Stream> streams;
|
||||
for ( auto& attr : mesh.vertex.attributes ) {
|
||||
const uint8_t* basePtr = (const uint8_t*)attr.pointer + srcVertexOffset * attr.stride;
|
||||
streams.emplace_back({ basePtr, attr.descriptor.size, attr.stride });
|
||||
streams.emplace_back(meshopt_Stream{ basePtr, attr.descriptor.size, attr.stride });
|
||||
}
|
||||
|
||||
// deduplicate vertices
|
||||
@ -90,7 +90,7 @@ bool ext::meshopt::optimize( uf::Mesh& mesh, float simplify, size_t o, bool verb
|
||||
size_t optimizedIndexCount = srcIndexCount;
|
||||
if ( 0.0f < simplify && simplify < 1.0f ) {
|
||||
uf::stl::vector<uint32_t> simplified(srcIndexCount);
|
||||
float targetError = 1e-2f / simplify;
|
||||
float targetError = FLT_MAX; // 1e-2f / simplify;
|
||||
float realError = 0.0f;
|
||||
|
||||
optimizedIndexCount = meshopt_simplify(
|
||||
@ -233,16 +233,18 @@ uf::stl::vector<pod::LODMetadata> ext::meshopt::generateLODs( uf::Mesh& mesh, co
|
||||
|
||||
// source from LOD0
|
||||
auto& cmd0 = lodMetadata[cmdIdx].levels[0];
|
||||
size_t previousIndicesCount = lodMetadata[cmdIdx].levels[lodIdx - 1].indices;
|
||||
|
||||
uf::stl::vector<uint32_t> baseIndices(cmd0.indices);
|
||||
for ( size_t i = 0; i < cmd0.indices; ++i ) baseIndices[i] = outIndices[cmd0.indexID + i];
|
||||
// copy from LOD0
|
||||
lodMetadata[cmdIdx].levels[lodIdx] = lodMetadata[cmdIdx].levels[0];
|
||||
|
||||
// generate LOD
|
||||
if ( 0.0f < simplify && simplify < 1.0f ) {
|
||||
float targetError = 1e-2f / simplify;
|
||||
float targetError = FLT_MAX; // 1e-2f / simplify;
|
||||
float realError = 0.0f;
|
||||
size_t currentIndicesCount = cmd0.indices;
|
||||
uf::stl::vector<uint32_t> baseIndices(cmd0.indices);
|
||||
for ( size_t i = 0; i < cmd0.indices; ++i ) baseIndices[i] = outIndices[cmd0.indexID + i];
|
||||
|
||||
uf::stl::vector<uint32_t> lodIndices = baseIndices;
|
||||
|
||||
const float* basePositions = (const float*) (outVertices[posAttrIdx].data() + cmd0.vertexID * mesh.vertex.attributes[posAttrIdx].stride);
|
||||
@ -254,6 +256,7 @@ uf::stl::vector<pod::LODMetadata> ext::meshopt::generateLODs( uf::Mesh& mesh, co
|
||||
);
|
||||
|
||||
// couldn't simplify further, use previous LOD
|
||||
size_t previousIndicesCount = lodMetadata[cmdIdx].levels[lodIdx - 1].indices;
|
||||
if ( currentIndicesCount == previousIndicesCount ) {
|
||||
lodMetadata[cmdIdx].levels[lodIdx] = lodMetadata[cmdIdx].levels[lodIdx - 1];
|
||||
continue;
|
||||
@ -288,9 +291,6 @@ uf::stl::vector<pod::LODMetadata> ext::meshopt::generateLODs( uf::Mesh& mesh, co
|
||||
meshopt_remapVertexBuffer(packed.data(), srcPtr, cmd0.vertices, attr.stride, fetchRemap.data());
|
||||
outVertices[a].insert(outVertices[a].end(), packed.begin(), packed.end());
|
||||
}
|
||||
} else {
|
||||
// no simplification, just use LOD0 (shouldn't happen)
|
||||
lodMetadata[cmdIdx].levels[lodIdx] = lodMetadata[cmdIdx].levels[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,13 +189,12 @@ uf::stl::vector<uint8_t>& uf::io::readAsBuffer( uf::stl::vector<uint8_t>& buffer
|
||||
buffer.resize(totalBytes);
|
||||
|
||||
// Read each range
|
||||
for (const auto& r : ranges) {
|
||||
is.seekg(r.start, std::ios::beg);
|
||||
size_t oldSize = buffer.size();
|
||||
buffer.resize(oldSize + r.len);
|
||||
is.read(reinterpret_cast<char*>(buffer.data() + oldSize), r.len);
|
||||
buffer.resize(oldSize + static_cast<size_t>(is.gcount()));
|
||||
}
|
||||
size_t currentOffset = 0;
|
||||
for (const auto& r : ranges) {
|
||||
is.seekg(r.start, std::ios::beg);
|
||||
is.read(reinterpret_cast<char*>(buffer.data() + currentOffset), r.len);
|
||||
currentOffset += static_cast<size_t>(is.gcount());
|
||||
}
|
||||
}
|
||||
|
||||
uf::stl::string expected;
|
||||
|
||||
@ -232,7 +232,7 @@ void impl::buildMeshBVH( pod::BVH& bvh, const uf::Mesh& mesh, pod::BVH::index_t
|
||||
auto aabb = impl::computeTriangleAABB( tri );
|
||||
auto triID = triIndexID + (view.index.first / 3);
|
||||
|
||||
if ( triID != bounds.size() ) UF_MSG_DEBUG("triID={}, bounds.size()={}", triID, bounds.size());
|
||||
// if ( triID != bounds.size() ) UF_MSG_DEBUG("triID={}, bounds.size()={}", triID, bounds.size());
|
||||
|
||||
bounds.emplace_back( aabb );
|
||||
bvh.indices.emplace_back( triID ); // triID => mesh.index.buffer[triID * 3];
|
||||
|
||||
@ -516,6 +516,9 @@ void uf::physics::destroy( pod::PhysicsBody& body ) {
|
||||
if ( body.collider.type == pod::ShapeType::MESH ) {
|
||||
if ( body.collider.mesh.bvh ) delete body.collider.mesh.bvh;
|
||||
}
|
||||
if ( body.collider.type == pod::ShapeType::CONVEX_HULL ) {
|
||||
if ( body.collider.convexHull.bvh ) delete body.collider.convexHull.bvh;
|
||||
}
|
||||
}
|
||||
|
||||
pod::RayQuery uf::physics::rayCast( const pod::Ray& ray, const pod::PhysicsBody& body, float maxDistance ) {
|
||||
|
||||
@ -407,7 +407,11 @@ uf::Mesh::View uf::Mesh::makeView( size_t i, const uf::stl::vector<uf::stl::stri
|
||||
uf::stl::vector<uf::Mesh::View> uf::Mesh::makeViews( const uf::stl::vector<uf::stl::string>& wanted, size_t lod ) const {
|
||||
uf::stl::vector<uf::Mesh::View> views;
|
||||
if ( indirect.count > 0 ) {
|
||||
for ( auto i = 0; i < indirect.count; i++ ) views.emplace_back(makeView(i, wanted, lod));
|
||||
for ( auto i = 0; i < indirect.count; i++ ) {
|
||||
auto view = makeView( i, wanted, lod );
|
||||
if ( view.index.count == 0 && view.vertex.count == 0 ) continue;
|
||||
views.emplace_back( view );
|
||||
}
|
||||
} else {
|
||||
views.emplace_back( makeView(wanted, lod) );
|
||||
}
|
||||
|
||||
@ -81,6 +81,25 @@ uf::stl::vector<const char*> uf::string::cStrings( const uf::stl::vector<uf::stl
|
||||
return v;
|
||||
}
|
||||
|
||||
uf::stl::string uf::string::ltrim( const uf::stl::string& str ) {
|
||||
auto s = str;
|
||||
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
|
||||
return !std::isspace(ch);
|
||||
}));
|
||||
return s;
|
||||
}
|
||||
|
||||
uf::stl::string uf::string::rtrim( const uf::stl::string& str ) {
|
||||
auto s = str;
|
||||
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
|
||||
return !std::isspace(ch);
|
||||
}).base(), s.end());
|
||||
return s;
|
||||
}
|
||||
uf::stl::string uf::string::trim( const uf::stl::string& str ) {
|
||||
return uf::string::rtrim( uf::string::ltrim( str ) );
|
||||
}
|
||||
|
||||
uf::stl::string uf::string::replace( const uf::stl::string& string, const uf::stl::string& search, const uf::stl::string& replace ) {
|
||||
uf::stl::string result = string;
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user