diff --git a/apps/viewer/src/main.cpp b/apps/viewer/src/main.cpp index 0590ed0..36edf6f 100644 --- a/apps/viewer/src/main.cpp +++ b/apps/viewer/src/main.cpp @@ -23,11 +23,15 @@ int main(int argc, char** argv) parser, "immediate-swapchain", "Set swapchain mode to immediate (VK_PRESENT_MODE_IMMEDIATE_KHR)", {'i', "immediate-swapchain"} }; - args::ValueFlag widthFlag{parser, "width", "Set window width", {'w', "width"}}; - args::ValueFlag heightFlag{parser, "height", "Set window height", {'h', "height"}}; + args::ValueFlag widthFlag{parser, "width", "Set window width", {"width"}}; + args::ValueFlag heightFlag{parser, "height", "Set window height", {"height"}}; args::Flag noGuiFlag{parser, "no-gui", "Disable GUI", {"no-gui"}}; args::Positional scenePath{parser, "scene", "Path to scene file", "scene.ply"}; + args::ValueFlag benchmarkOutputFolderFlag{ + parser, "benchmark-output", "Output folder for benchmark results", {'b', "benchmark-output"} + }; + try { parser.ParseCLI(argc, argv); @@ -101,6 +105,11 @@ int main(int argc, char** argv) config.enableGui = true; } + if (benchmarkOutputFolderFlag) + { + config.benchmarkOutputFolder = std::make_optional(args::get(benchmarkOutputFolderFlag)); + } + auto width = widthFlag ? args::get(widthFlag) : 1280; auto height = heightFlag ? args::get(heightFlag) : 720; diff --git a/include/3dgs/3dgs.h b/include/3dgs/3dgs.h index eb08c4a..2eefe46 100644 --- a/include/3dgs/3dgs.h +++ b/include/3dgs/3dgs.h @@ -16,6 +16,7 @@ class VulkanSplatting { bool enableVulkanValidationLayers = false; std::optional physicalDeviceId = std::nullopt; std::string scene; + std::optional benchmarkOutputFolder = std::nullopt; float fov = 45.0f; float near = 0.2f; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 498e871..622e829 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -81,6 +81,7 @@ add_library(3dgs_cpp STATIC vulkan/targets/OpenXRStereo.h vulkan/targets/ManagedSwapchain.cpp vulkan/targets/ManagedSwapchain.h + utils/CSVWriter.h ) target_include_directories(3dgs_cpp diff --git a/src/GSScene.cpp b/src/GSScene.cpp index 6fdf1d5..5e974e1 100644 --- a/src/GSScene.cpp +++ b/src/GSScene.cpp @@ -26,6 +26,17 @@ struct VertexStorage { void GSScene::load(const std::shared_ptr&context) { auto startTime = std::chrono::high_resolution_clock::now(); + // Check if the file is a folder + auto path = std::filesystem::path(filename); + if (std::filesystem::is_directory(filename)) { + // Check if cameras.json exists + auto camerasPath = path / "cameras.json"; + if (std::filesystem::exists(camerasPath)) { + spdlog::info("Dataset folder detected, loading cameras.json"); + assert(false && "Not implemented"); + } + } + std::ifstream plyFile(filename, std::ios::binary); loadPlyHeader(plyFile); diff --git a/src/Renderer.cpp b/src/Renderer.cpp index e33634e..2f974a9 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -20,6 +20,7 @@ #include "vulkan/targets/OpenXRStereo.h" void Renderer::initialize() { + initializeCsvWriter(); initializeVulkan(); createGui(); loadSceneToGPU(); @@ -100,6 +101,13 @@ void Renderer::retrieveTimestamps() { if (configuration.enableGui) guiManager.pushMetric(metric.first, metric.second / 1000000.0); } + + if (csvWriter) { + metrics["num_instances"] = numInstances; + metrics["timestamp"] = std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()).count(); + csvWriter.value().log(metrics); + } } void Renderer::recreatePipelines() { @@ -223,6 +231,28 @@ void Renderer::createGui() { guiManager.init(); } +void Renderer::initializeCsvWriter() { + auto folder = configuration.benchmarkOutputFolder; + // Create folder if it does not exist + if (!folder.has_value()) { + return; + } + + std::filesystem::create_directories(folder.value()); + + const auto path = std::filesystem::path(folder.value()); + + // Format the date and time + const auto now = std::chrono::system_clock::now(); + const auto in_time_t = std::chrono::system_clock::to_time_t(now); + std::stringstream ss; + ss << std::put_time(std::localtime(&in_time_t), "%Y-%m-%d_%H-%M-%S"); + const auto time = ss.str(); + + const auto filename = path / (time + ".csv"); + csvWriter = std::move(CSVWriter(filename.string())); +} + void Renderer::createPrefixSumPipeline() { spdlog::debug("Creating prefix sum pipeline"); prefixSumPingBuffer = Buffer::storage(context, scene->getNumVertices() * sizeof(uint32_t), false); @@ -538,9 +568,9 @@ bool Renderer::recordRenderCommandBuffer(uint32_t currentFrame) { vk::CommandBufferAllocateInfo(commandPool.get(), vk::CommandBufferLevel::ePrimary, 1))[0]); } - uint32_t numInstances = totalSumBufferHost->readOne(); + numInstances = totalSumBufferHost->readOne(); // spdlog::debug("Num instances: {}", numInstances); - guiManager.pushTextMetric("instances", numInstances); + guiManager.pushTextMetric("instances", static_cast(numInstances)); if (numInstances > scene->getNumVertices() * sortBufferSizeMultiplier) { auto old = sortBufferSizeMultiplier; while (numInstances > scene->getNumVertices() * sortBufferSizeMultiplier) { diff --git a/src/Renderer.h b/src/Renderer.h index 457d91c..7049527 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -62,6 +62,8 @@ class Renderer { void createGui(); + void initializeCsvWriter(); + void initialize(); void handleInput(); @@ -88,6 +90,7 @@ class Renderer { private: VulkanSplatting::RendererConfiguration configuration; + std::optional csvWriter = std::nullopt; std::shared_ptr context; std::shared_ptr renderTarget; std::shared_ptr imguiManager; @@ -118,6 +121,8 @@ class Renderer { std::shared_ptr inputSet; + uint32_t numInstances; + std::atomic running = true; std::vector inflightFences; diff --git a/src/utils/CSVWriter.h b/src/utils/CSVWriter.h new file mode 100644 index 0000000..99f2285 --- /dev/null +++ b/src/utils/CSVWriter.h @@ -0,0 +1,88 @@ +#ifndef CSVWRITER_H +#define CSVWRITER_H + +#include +#include +#include +#include +#include +#include +#include + +class CSVWriter { +private: + std::vector columns; + std::ofstream file; + +public: + // Constructor that takes a vector of column names + explicit CSVWriter(const std::string& filename) { + // assert that the file does not exist + if (std::ifstream(filename)) { + throw std::runtime_error("File already exists"); + } + + file.open(filename, std::ios::out | std::ios::trunc); + } + + CSVWriter(const CSVWriter &other) = delete; + + CSVWriter(CSVWriter &&other) noexcept + : columns(std::move(other.columns)), + file(std::move(other.file)) { + } + + CSVWriter & operator=(const CSVWriter &other) = delete; + + CSVWriter & operator=(CSVWriter &&other) noexcept { + if (this == &other) + return *this; + columns = std::move(other.columns); + file = std::move(other.file); + return *this; + } + + // Log function to write a row of data + void log(const std::vector& rowData) { + if (!file.is_open()) { + throw std::runtime_error("File is not open"); + } + // Write the row data + for (size_t i = 0; i < rowData.size(); ++i) { + file << rowData[i]; + if (i < rowData.size() - 1) file << ","; + } + file << "\n"; + } + + void log(const std::unordered_map& row) { + if (columns.empty()) { + for (const auto& columnName : std::ranges::views::keys(row)) { + columns.push_back(columnName); + } + log(columns); + } + + if (!file.is_open()) { + throw std::runtime_error("File is not open"); + } + + // Write the row data + for (size_t i = 0; i < columns.size(); ++i) { + file << row.at(columns[i]); + if (i < columns.size() - 1) file << ","; + } + file << "\n"; + } + + // Destructor to close the file if it's open + ~CSVWriter() { + if (file.is_open()) { + file.close(); + } + } +}; + + + +#endif //CSVWRITER_H diff --git a/src/vulkan/QueryManager.h b/src/vulkan/QueryManager.h index e79ebc1..a9ddfc5 100644 --- a/src/vulkan/QueryManager.h +++ b/src/vulkan/QueryManager.h @@ -7,20 +7,26 @@ #include #include +#include "../utils/CSVWriter.h" + class QueryManager { public: uint32_t registerQuery(const std::string &name); + [[nodiscard]] uint32_t getQueryId(const std::string &name); - std::unordered_map parseResults(const std::vector& results); + std::unordered_map parseResults(const std::vector &results); + int nextId = 0; + private: std::mutex mutex; std::unordered_map registry; - std::unordered_map> results; + std::unordered_map > results; std::chrono::time_point lastPrint; -}; +} +;