From ff2225650dce6c0fd36dc644ebb7f9d75646603f Mon Sep 17 00:00:00 2001 From: Mikhail Arepev Date: Wed, 29 Apr 2026 23:40:15 +0300 Subject: [PATCH 1/4] Simple viewer which add specified models to scene via pagedlod --- tests/CMakeLists.txt | 1 + tests/vsgpagedlodtest/CMakeLists.txt | 13 ++ tests/vsgpagedlodtest/vsgpagedlodtest.cpp | 155 ++++++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 tests/vsgpagedlodtest/CMakeLists.txt create mode 100644 tests/vsgpagedlodtest/vsgpagedlodtest.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 50de1851..7feb3074 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,3 +4,4 @@ add_subdirectory(vsgtriangles) add_subdirectory(vsgunicode) add_subdirectory(vsgperformance) add_subdirectory(vsgcast) +add_subdirectory(vsgpagedlodtest) diff --git a/tests/vsgpagedlodtest/CMakeLists.txt b/tests/vsgpagedlodtest/CMakeLists.txt new file mode 100644 index 00000000..d863b11a --- /dev/null +++ b/tests/vsgpagedlodtest/CMakeLists.txt @@ -0,0 +1,13 @@ +set(HEADERS DatabasePagerAutoscale.h) +set(SOURCES vsgpagedlodtest.cpp) + +add_executable(vsgpagedlodtest ${HEADERS} ${SOURCES}) + +target_link_libraries(vsgpagedlodtest vsg::vsg) + +if (vsgXchange_FOUND) + target_compile_definitions(vsgpagedlodtest PRIVATE vsgXchange_FOUND) + target_link_libraries(vsgpagedlodtest vsgXchange::vsgXchange) +endif() + +install(TARGETS vsgpagedlodtest RUNTIME DESTINATION bin) diff --git a/tests/vsgpagedlodtest/vsgpagedlodtest.cpp b/tests/vsgpagedlodtest/vsgpagedlodtest.cpp new file mode 100644 index 00000000..82243da5 --- /dev/null +++ b/tests/vsgpagedlodtest/vsgpagedlodtest.cpp @@ -0,0 +1,155 @@ +#include + +#ifdef vsgXchange_FOUND +# include +#endif + +#include + +int main(int argc, char** argv) +{ + try + { + // set up defaults and read command line arguments to override them + vsg::CommandLine arguments(&argc, argv); + + // create windowTraits using the any command line arugments to configure settings + auto windowTraits = vsg::WindowTraits::create(arguments); + + // set up vsg::Options to pass in filepaths, ReaderWriters and other IO related options to use when reading and writing files. + auto options = vsg::Options::create(); + options->fileCache = vsg::getEnv("VSG_FILE_CACHE"); + options->paths = vsg::getEnvPaths("VSG_FILE_PATH"); + +#ifdef vsgXchange_all + // add vsgXchange's support for reading and writing 3rd party file formats + options->add(vsgXchange::all::create()); +#endif + + auto maxPagedLOD = arguments.value(1500, "--maxPagedLOD"); + auto useSharedObjects = arguments.value(false, "--shareObjects"); + + options->readOptions(arguments); + + if (arguments.errors()) return arguments.writeErrorMessages(std::cerr); + + if (argc <= 1) + { + std::cout << "Please specify 3d-models on the command line." << std::endl; + return 1; + } + + vsg::dvec3 center(0.0, 0.0, 0.0); + vsg::dvec3 shift(0.0, 2.0, 0.0); + auto vsg_scene = vsg::Group::create(); + + // read any 3d-models files + for (int i = 1; i < argc; ++i) + { + vsg::Path filename = arguments[i]; + + auto object = vsg::read(filename, options); + if (vsg::fileExists(filename)) + { + int index = (i - 1); + vsg::dvec3 position = shift * static_cast(index); + auto transform = vsg::MatrixTransform::create(vsg::translate(position)); + + auto pagedLOD = vsg::PagedLOD::create(); + pagedLOD->filename = filename; + pagedLOD->options = options; + pagedLOD->bound = vsg::dsphere(center, 1.0); + transform->addChild(pagedLOD); + vsg_scene->addChild(transform); + + std::cout << "Sucessfully added pagedLOD for file " << filename << std::endl; + } + else + { + std::cout << "Unable to find file " << filename << std::endl; + } + } + + if (vsg_scene->children.empty()) + { + std::cout << "No 3d models added" << std::endl; + return 1; + } + + // create the viewer and assign window(s) to it + auto viewer = vsg::Viewer::create(); + auto window = vsg::Window::create(windowTraits); + if (!window) + { + std::cout << "Could not create window." << std::endl; + return 1; + } + + viewer->addWindow(window); + + // compute position the camera + vsg::dvec3 eyeShift(0.0, -5, 2.0); + vsg::dvec3 up(0.0, 0.0, 1.0); + auto lookAt = vsg::LookAt::create(center + eyeShift, center, up); + + auto perspective = vsg::Perspective::create(30.0, + static_cast(window->extent2D().width) / static_cast(window->extent2D().height), + 0.01, + 1000.0); + auto viewportState = vsg::ViewportState::create(window->extent2D()); + auto camera = vsg::Camera::create(perspective, lookAt, viewportState); + + // add close handler to respond to the close window button and pressing escape + viewer->addEventHandler(vsg::CloseHandler::create(viewer)); + + viewer->addEventHandler(vsg::Trackball::create(camera)); + auto view = vsg::View::create(camera); + view->addChild(vsg::createHeadlight()); + view->addChild(vsg_scene); + + auto renderGraph = vsg::RenderGraph::create(window, view); + auto commandGraph = vsg::CommandGraph::create(window, renderGraph); + viewer->assignRecordAndSubmitTaskAndPresentation({commandGraph}); + + viewer->compile(); + + // set targetMaxNumPagedLODWithHighResSubgraphs after Viewer::compile() as it will assign any DatabasePager if required. + if (maxPagedLOD >= 0) + { + for (auto& task : viewer->recordAndSubmitTasks) + { + if (task->databasePager) task->databasePager->targetMaxNumPagedLODWithHighResSubgraphs = maxPagedLOD; + std::cout << "Applied databasePager->targetMaxNumPagedLODWithHighResSubgraphs = " << maxPagedLOD << std::endl; + } + } + + if (useSharedObjects) + { + options->sharedObjects = vsg::SharedObjects::create(); + } + + viewer->start_point() = vsg::clock::now(); + + // rendering main loop + while (viewer->advanceToNextFrame()) + { + // pass any events into EventHandlers assigned to the Viewer + viewer->handleEvents(); + + viewer->update(); + + viewer->recordAndSubmit(); + + viewer->present(); + } + } + catch (const vsg::Exception& ve) + { + for (int i = 0; i < argc; ++i) std::cerr << argv[i] << " "; + std::cerr << "\n[Exception] - " << ve.message << " result = " << ve.result << std::endl; + return 1; + } + + // clean up done automatically thanks to ref_ptr<> + return 0; +} From c4d930fe7a620c9673d5fac2352a952a0e66ab26 Mon Sep 17 00:00:00 2001 From: Mikhail Arepev Date: Wed, 29 Apr 2026 23:43:32 +0300 Subject: [PATCH 2/4] Add custom read thread to databasePager for autoscaling models at demo scene --- .../vsgpagedlodtest/DatabasePagerAutoscale.h | 143 ++++++++++++++++++ tests/vsgpagedlodtest/vsgpagedlodtest.cpp | 13 ++ 2 files changed, 156 insertions(+) create mode 100644 tests/vsgpagedlodtest/DatabasePagerAutoscale.h diff --git a/tests/vsgpagedlodtest/DatabasePagerAutoscale.h b/tests/vsgpagedlodtest/DatabasePagerAutoscale.h new file mode 100644 index 00000000..0b3de5cd --- /dev/null +++ b/tests/vsgpagedlodtest/DatabasePagerAutoscale.h @@ -0,0 +1,143 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +class DatabasePagerAutoscale : public vsg::Inherit +{ +public: + void start(uint32_t numReadThreads = 4) override + { + vsg::debug("DatabasePager::start(", numReadThreads, ")"); + + // + // set up read thread(s) + // + auto readThread = [](DatabasePagerAutoscale& databasePager, const std::string& threadName) { + vsg::debug("Started DatabaseThread read thread"); + + auto local_instrumentation = shareOrDuplicateForThreadSafety(databasePager.instrumentation); + if (local_instrumentation) local_instrumentation->setThreadName(threadName); + + while (databasePager.status->active()) + { + auto plod = databasePager._requestQueue->take_when_available(databasePager.frameCount.load()); + if (plod) + { + CPU_INSTRUMENTATION_L1_NC(databasePager.instrumentation, "DatabasePager read", COLOR_PAGER); + + uint64_t frameDelta = databasePager.frameCount - plod->frameHighResLastUsed.load(); + + if (frameDelta > 1 || !vsg::compare_exchange(plod->requestStatus, vsg::PagedLOD::ReadRequest, vsg::PagedLOD::Reading)) + { + vsg::debug("Expire read request : databasePager.frameCount = ", databasePager.frameCount, ", plod->frameHighResLastUsed.load() = ", plod->frameHighResLastUsed.load()); + databasePager.requestDiscarded(plod); + continue; + } + + ++plod->loadAttempts; + + vsg::ref_ptr subgraph = plod->pending; + vsg::ref_ptr read_object; + + if (!subgraph) + { + // vsg::info("read ", subgraph, " from ", plod->filename, " frameDelta = ", frameDelta, ", databasePager.frameCount = ", databasePager.frameCount, ", plod->frameHighResLastUsed.load() = ", plod->frameHighResLastUsed.load(), ", numActiveRequests = ", databasePager.numActiveRequests.load()); + read_object = vsg::read(plod->filename, plod->options); + + // Add scale node to show each model with the same size + if (vsg::ref_ptr node = read_object.cast()) + { + vsg::ComputeBounds computeBounds; + node->accept(computeBounds); + + vsg::dvec3 center = (computeBounds.bounds.min + computeBounds.bounds.max) * 0.5; + double radius = vsg::length(computeBounds.bounds.max - computeBounds.bounds.min) * 0.5; + double radius_inv = 1.0 / radius; + auto scale = vsg::MatrixTransform::create(vsg::scale(radius_inv, radius_inv, radius_inv) * vsg::translate(-center)); + scale->addChild(node); + subgraph = scale; + plod->bound = vsg::dsphere(center * radius_inv, 1.0); + } + } + else + { + // vsg::info("already read ", subgraph, " from ", plod->filename, " frameDelta = ", frameDelta, ", databasePager.frameCount = ", databasePager.frameCount, ", plod->frameHighResLastUsed.load() = ", plod->frameHighResLastUsed.load(), ", numActiveRequests = ", databasePager.numActiveRequests.load()); + } + + if (subgraph && compare_exchange(plod->requestStatus, vsg::PagedLOD::Reading, vsg::PagedLOD::Compiling)) + { + { + std::scoped_lock lock(databasePager.pendingPagedLODMutex); + plod->pending = subgraph; + } + + try + { + // compile plod + if (auto result = databasePager.compileManager->compile(subgraph)) + { + plod->requestStatus.exchange(vsg::PagedLOD::MergeRequest); + + // info("DatabaserPager::start() compiled ", subgraph, ", success after ", plod->loadAttempts.load(), " loadAttempts"); + + // move to the merge queue; + databasePager._toMergeQueue->add(plod, result); + } + else + { + debug("DatabaserPager::start() unable to compile subgraph, discarding request ", subgraph); + databasePager.requestDiscarded(plod); + } + } + catch (...) + { + debug("DatabaserPager::start() compile threw exception, discarding request ", subgraph); + databasePager.requestDiscarded(plod); + } + } + else + { + if (auto read_error = read_object.cast()) + vsg::warn(read_error->message); + else + vsg::warn("Failed to read ", plod, " ", plod->filename); + + databasePager.requestDiscarded(plod); + } + } + else + { + // sleep for a frame. + std::this_thread::sleep_for(std::chrono::milliseconds(16 * 2)); + } + } + vsg::debug("Finished DatabaseThread read thread"); + }; + + auto deleteThread = [](DatabasePagerAutoscale& databasePager, const std::string& threadName) { + vsg::debug("Started DatabaseThread deletethread"); + + auto local_instrumentation = shareOrDuplicateForThreadSafety(databasePager.instrumentation); + if (local_instrumentation) local_instrumentation->setThreadName(threadName); + + while (databasePager.status->active()) + { + databasePager.deleteQueue->wait_then_clear(); + } + vsg::debug("Finished DatabaseThread delete thread"); + }; + + for (uint32_t i = 0; i < numReadThreads; ++i) + { + threads.emplace_back(readThread, std::ref(*this), vsg::make_string("DatabasePager read thread ", i)); + } + + threads.emplace_back(deleteThread, std::ref(*this), "DatabasePager delete thread "); + } +}; diff --git a/tests/vsgpagedlodtest/vsgpagedlodtest.cpp b/tests/vsgpagedlodtest/vsgpagedlodtest.cpp index 82243da5..0ec324df 100644 --- a/tests/vsgpagedlodtest/vsgpagedlodtest.cpp +++ b/tests/vsgpagedlodtest/vsgpagedlodtest.cpp @@ -6,6 +6,8 @@ #include +#include "DatabasePagerAutoscale.h" + int main(int argc, char** argv) { try @@ -26,6 +28,7 @@ int main(int argc, char** argv) options->add(vsgXchange::all::create()); #endif + auto defaultDatabasePager = arguments.value(false, "--defaultDatabasePager"); auto maxPagedLOD = arguments.value(1500, "--maxPagedLOD"); auto useSharedObjects = arguments.value(false, "--shareObjects"); @@ -111,6 +114,16 @@ int main(int argc, char** argv) auto commandGraph = vsg::CommandGraph::create(window, renderGraph); viewer->assignRecordAndSubmitTaskAndPresentation({commandGraph}); + // set custom databasepager which show each model with the same size for better demo scene + if (!defaultDatabasePager) + { + for (auto& task : viewer->recordAndSubmitTasks) + { + task->databasePager = DatabasePagerAutoscale::create(); + std::cout << "Applied custom databasePager with autoscale models to the same size" << std::endl; + } + } + viewer->compile(); // set targetMaxNumPagedLODWithHighResSubgraphs after Viewer::compile() as it will assign any DatabasePager if required. From 707015c0400e1e0a660fd8f7b3af6ef9b4ce97c1 Mon Sep 17 00:00:00 2001 From: Mikhail Arepev Date: Thu, 30 Apr 2026 01:03:45 +0300 Subject: [PATCH 3/4] Removed accidental read() call --- tests/vsgpagedlodtest/vsgpagedlodtest.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/vsgpagedlodtest/vsgpagedlodtest.cpp b/tests/vsgpagedlodtest/vsgpagedlodtest.cpp index 0ec324df..08a8a449 100644 --- a/tests/vsgpagedlodtest/vsgpagedlodtest.cpp +++ b/tests/vsgpagedlodtest/vsgpagedlodtest.cpp @@ -51,7 +51,6 @@ int main(int argc, char** argv) { vsg::Path filename = arguments[i]; - auto object = vsg::read(filename, options); if (vsg::fileExists(filename)) { int index = (i - 1); From 5c4e80283732e5fb4705d70814ce3cbd79580c6c Mon Sep 17 00:00:00 2001 From: Mikhail Arepev Date: Sun, 3 May 2026 21:55:45 +0300 Subject: [PATCH 4/4] Place models to scene by grid like vsgdynamicload example --- tests/vsgpagedlodtest/vsgpagedlodtest.cpp | 25 +++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/tests/vsgpagedlodtest/vsgpagedlodtest.cpp b/tests/vsgpagedlodtest/vsgpagedlodtest.cpp index 08a8a449..688cc376 100644 --- a/tests/vsgpagedlodtest/vsgpagedlodtest.cpp +++ b/tests/vsgpagedlodtest/vsgpagedlodtest.cpp @@ -42,10 +42,16 @@ int main(int argc, char** argv) return 1; } - vsg::dvec3 center(0.0, 0.0, 0.0); - vsg::dvec3 shift(0.0, 2.0, 0.0); auto vsg_scene = vsg::Group::create(); + vsg::dvec3 origin(0.0, 0.0, 0.0); + vsg::dvec3 primary(2.0, 0.0, 0.0); + vsg::dvec3 secondary(0.0, 2.0, 0.0); + + int numModels = argc - 1; + int numColumns = static_cast(std::ceil(std::sqrt(static_cast(numModels)))); + int numRows = static_cast(std::ceil(static_cast(numModels) / static_cast(numColumns))); + // read any 3d-models files for (int i = 1; i < argc; ++i) { @@ -54,13 +60,15 @@ int main(int argc, char** argv) if (vsg::fileExists(filename)) { int index = (i - 1); - vsg::dvec3 position = shift * static_cast(index); + vsg::dvec3 position = origin + + primary * static_cast(index % numColumns) + + secondary * static_cast(index / numColumns); auto transform = vsg::MatrixTransform::create(vsg::translate(position)); auto pagedLOD = vsg::PagedLOD::create(); pagedLOD->filename = filename; pagedLOD->options = options; - pagedLOD->bound = vsg::dsphere(center, 1.0); + pagedLOD->bound = vsg::dsphere(origin, 1.0); transform->addChild(pagedLOD); vsg_scene->addChild(transform); @@ -90,10 +98,15 @@ int main(int argc, char** argv) viewer->addWindow(window); // compute position the camera - vsg::dvec3 eyeShift(0.0, -5, 2.0); + double viewingDistance = std::sqrt(static_cast(numModels)) * 3.0; + vsg::dvec3 eyeShift(0.0, -viewingDistance, 0.0); vsg::dvec3 up(0.0, 0.0, 1.0); - auto lookAt = vsg::LookAt::create(center + eyeShift, center, up); + vsg::dvec3 centre = origin + + primary * (static_cast(numColumns - 1) * 0.5) + + secondary * (static_cast(numRows - 1) * 0.5); + // set up the camera + auto lookAt = vsg::LookAt::create(centre + eyeShift, centre, up); auto perspective = vsg::Perspective::create(30.0, static_cast(window->extent2D().width) / static_cast(window->extent2D().height), 0.01,