diff --git a/.gitea/workflows/build_pc.yml b/.gitea/workflows/build_pc.yml index 781fdcf..fb286c3 100644 --- a/.gitea/workflows/build_pc.yml +++ b/.gitea/workflows/build_pc.yml @@ -41,8 +41,34 @@ jobs: - name: Test working-directory: ${{github.workspace}}/build run: ctest -C ${{env.BUILD_TYPE}} + - name: Archive artifacts Windows + if: matrix.os == 'windows_x64' + uses: actions/upload-artifact@v3 + with: + name: 'Artifacts-windows' + path: | + ${{github.workspace}}/build/examples/${{env.BUILD_TYPE}} + ${{github.workspace}}/build/tests/${{env.BUILD_TYPE}} + if-no-files-found: error + retention-days: 30 + overwrite: true + - name: Archive artifacts + if: matrix.os != 'windows_x64' + uses: actions/upload-artifact@v3 + with: + name: 'Artifacts-${{matrix.os}}' + path: | + ${{github.workspace}}/build/examples + ${{github.workspace}}/build/tests + !${{github.workspace}}/build/**/CMakeFiles + !${{github.workspace}}/build/**/*.cmake + !${{github.workspace}}/build/**/Makefile + if-no-files-found: error + retention-days: 30 + overwrite: true + + - #TODO archive executables build_iOS: diff --git a/openVulkanoCpp/AR/ArRecorder.cpp b/openVulkanoCpp/AR/ArRecorder.cpp index 5d42e6c..2da2c74 100644 --- a/openVulkanoCpp/AR/ArRecorder.cpp +++ b/openVulkanoCpp/AR/ArRecorder.cpp @@ -26,6 +26,7 @@ namespace OpenVulkano::AR namespace { constexpr std::string_view RECORDING_METADATA_FILENAME = "ArRecording.xml"; + constexpr int JPEG_QUALITY = 85; std::filesystem::path GeneratePath(const std::filesystem::path& baseDir, std::string_view name) { @@ -56,7 +57,7 @@ namespace OpenVulkano::AR void ArRecorder::WriteColorImage(ArFrame* arFrame, MultiPartArchiveWriter* colorWriter, const std::filesystem::path* path, bool highRes) const { - BlockProfiler profile("Save AR Frame - Image"); + //BlockProfiler profile("Save AR Frame - Image"); std::string fileName = GetFileName(arFrame->GetFrameId(), "jpg"); #ifndef TURBO_JPEG if (arFrame->GetCameraImageAsJpeg([&fileName, this](const char* data, size_t len){ m_colorWriter->AddFile(fileName.c_str(), data, len); })) @@ -95,7 +96,7 @@ namespace OpenVulkano::AR uint8_t* outBuffer = nullptr; unsigned long size = 0; - if (tjCompressFromYUVPlanes(handle, buffers, resX, nullptr, resY, TJSAMP_420, &outBuffer, &size, 95, TJFLAG_FASTDCT)) [[unlikely]] + if (tjCompressFromYUVPlanes(handle, buffers, resX, nullptr, resY, TJSAMP_420, &outBuffer, &size, JPEG_QUALITY, TJFLAG_FASTDCT)) [[unlikely]] Logger::AR->error("Failed to create JPEG! {}", tjGetErrorStr()); else [[likely]] { @@ -120,7 +121,7 @@ namespace OpenVulkano::AR auto depthImg = arFrame->GetDepthImage(); std::vector> buffers(2); { // TODO handle alternative depth formats!!!! - BlockProfiler profile("Save AR Frame - Depth"); + //BlockProfiler profile("Save AR Frame - Depth"); PfmHeader depthHeader(static_cast(depthImg.depth.resolution.x), static_cast(depthImg.depth.resolution.y), 5.0f, false); std::string header = depthHeader.ToString(); buffers[0].first = header.c_str(); @@ -133,7 +134,7 @@ namespace OpenVulkano::AR } { - BlockProfiler profile("Save AR Frame - Confi"); + //BlockProfiler profile("Save AR Frame - Confi"); PnmHeader confidenceHeader(static_cast(depthImg.confidence.resolution.x), static_cast(depthImg.confidence.resolution.y), false, 2); std::string header = confidenceHeader.ToString(); buffers[0].first = header.c_str(); @@ -148,7 +149,7 @@ namespace OpenVulkano::AR void ArRecorder::WriteMetadata(ArFrame* frame, MultiPartArchiveWriter* metaWriter) { - BlockProfiler profileMeta("Save AR Frame - Meta"); + //BlockProfiler profileMeta("Save AR Frame - Meta"); std::string metaContent = frame->GetFrameMetadata().ToXML(); std::string fileName = GetFileName(frame->GetFrameId(), "meta"); metaWriter->AddFile(fileName.c_str(), metaContent.c_str(), metaContent.size()); @@ -159,7 +160,7 @@ namespace OpenVulkano::AR if (frame->IsSaved()) return; frame->SetSaved(); bool useHighResWriter = highRes && m_settings.highResFramesInSeparateArchive; - BlockProfiler profile("Save AR Frame"); + //BlockProfiler profile("Save AR Frame"); WriteMetadata(frame, useHighResWriter ? m_highResWriter.get() : m_metadataWriter.get()); WriteColorImage(frame, useHighResWriter ? m_highResWriter.get() : m_colorWriter.get(), nullptr, highRes); WriteDepthImage(frame, diff --git a/openVulkanoCpp/AR/Provider/Playback/ArSessionPlayback.cpp b/openVulkanoCpp/AR/Provider/Playback/ArSessionPlayback.cpp index 0603463..7a902b3 100644 --- a/openVulkanoCpp/AR/Provider/Playback/ArSessionPlayback.cpp +++ b/openVulkanoCpp/AR/Provider/Playback/ArSessionPlayback.cpp @@ -95,6 +95,7 @@ namespace OpenVulkano::AR::Playback catch (const std::exception& e) { Logger::AR->error("Failed to read AR frame: {}", e.what()); + break; } } Stop(); diff --git a/openVulkanoCpp/Extensions/RymlConverters.hpp b/openVulkanoCpp/Extensions/RymlConverters.hpp index 6261a5e..63ac35c 100644 --- a/openVulkanoCpp/Extensions/RymlConverters.hpp +++ b/openVulkanoCpp/Extensions/RymlConverters.hpp @@ -7,6 +7,7 @@ #pragma once #include "Math/Math.hpp" +#include "Math/AABB.hpp" #include #include #include @@ -49,6 +50,10 @@ namespace c4 m[1][3], m[2][3], m[3][3]); } + template + size_t to_chars(ryml::substr buf, const OpenVulkano::Math::AABB& bbox) + { return ryml::format(buf, "({},{},{}),({},{},{})", bbox.min.x, bbox.min.y, bbox.min.z, bbox.max.x, bbox.max.y, bbox.max.z); } + template bool from_chars(ryml::csubstr buf, OpenVulkano::Math::Vector2* v) { @@ -108,4 +113,11 @@ namespace c4 m[1][3], m[2][3], m[3][3]); return ret != ryml::yml::npos; } + + template + bool from_chars(ryml::csubstr buf, OpenVulkano::Math::AABB* bbox) + { + size_t ret = ryml::unformat(buf, "({},{},{}),({},{},{})", bbox->min.x, bbox->min.y, bbox->min.z, bbox->max.x, bbox->max.y, bbox->max.z); + return ret != ryml::yml::npos; + } } \ No newline at end of file diff --git a/openVulkanoCpp/Extensions/YamlCppConverters.hpp b/openVulkanoCpp/Extensions/YamlCppConverters.hpp index 1d273a0..5bb5b2e 100644 --- a/openVulkanoCpp/Extensions/YamlCppConverters.hpp +++ b/openVulkanoCpp/Extensions/YamlCppConverters.hpp @@ -7,7 +7,10 @@ #pragma once #include "Base/UUID.hpp" +#include "Math/AABB.hpp" #include +#include +#include namespace YAML { @@ -29,4 +32,23 @@ namespace YAML return false; } }; -} \ No newline at end of file + + template<> + struct convert + { + static Node encode(const OpenVulkano::Math::AABB& bbox) + { + return Node(fmt::format("({},{},{}),({},{},{})", bbox.min.x, bbox.min.y, bbox.min.z, bbox.max.x, bbox.max.y, bbox.max.z)); + } + + static bool decode(const Node& node, OpenVulkano::Math::AABB& bbox) + { + if (node.IsScalar()) + { + size_t ret = c4::unformat(c4::to_csubstr(node.Scalar().c_str()), "({},{},{}),({},{},{})", bbox.min.x, bbox.min.y, bbox.min.z, bbox.max.x, bbox.max.y, bbox.max.z); + return ret != c4::csubstr::npos; + } + return false; + } + }; +} diff --git a/openVulkanoCpp/Host/WebResourceLoader.cpp b/openVulkanoCpp/Host/WebResourceLoader.cpp index d5ade2b..578f8d4 100644 --- a/openVulkanoCpp/Host/WebResourceLoader.cpp +++ b/openVulkanoCpp/Host/WebResourceLoader.cpp @@ -21,6 +21,7 @@ namespace OpenVulkano { +#ifdef HAS_CURL namespace { size_t CurlDownloader(void* contents, size_t size, size_t memBlockCount, void* userData) @@ -31,10 +32,11 @@ namespace OpenVulkano return totalSize; } } +#endif bool WebResourceLoader::IsUrl(const std::string& str) { - return str.find("http://") == 0 || str.find("https://") == 0 || str.find("ftp://") == 0; + return Utils::StartsWith(str, "http://") || Utils::StartsWith(str, "https://") || Utils::StartsWith(str, "ftp://"); } WebResourceLoader::WebResourceLoader() diff --git a/openVulkanoCpp/IO/Archive/ArchiveReader.cpp b/openVulkanoCpp/IO/Archive/ArchiveReader.cpp index 6841685..b8efd2e 100644 --- a/openVulkanoCpp/IO/Archive/ArchiveReader.cpp +++ b/openVulkanoCpp/IO/Archive/ArchiveReader.cpp @@ -57,6 +57,14 @@ namespace OpenVulkano bool ArchiveReader::Open(const char* archiveFile) { + if (archiveFile[0] == '\0') + { + if (m_logger) + { + m_logger->error("Unable to open archive file with an empty name!"); + } + return false; + } PrepOpen(); ChkErr(archive_read_open_filename(m_archive, archiveFile, BUFFER_SIZE)); ReadNextHeader(); diff --git a/openVulkanoCpp/IO/Archive/LibArchiveHelper.hpp b/openVulkanoCpp/IO/Archive/LibArchiveHelper.hpp index 2e7fee7..5607cb2 100644 --- a/openVulkanoCpp/IO/Archive/LibArchiveHelper.hpp +++ b/openVulkanoCpp/IO/Archive/LibArchiveHelper.hpp @@ -88,7 +88,7 @@ namespace OpenVulkano else if (archiveResult <= ARCHIVE_FAILED) lvl = spdlog::level::level_enum::err; const char* errorString = archive_error_string(arch); if (logger) logger->log(lvl, errorString ? errorString : "Unknown error while handling archive"); - if (archiveResult == ARCHIVE_FAILED) throw std::runtime_error(errorString); + if (archiveResult == ARCHIVE_FAILED || archiveResult == ARCHIVE_FATAL) throw std::runtime_error(errorString); return false; } } diff --git a/openVulkanoCpp/IO/FileDescription.hpp b/openVulkanoCpp/IO/FileDescription.hpp index 23bff4f..a793af2 100644 --- a/openVulkanoCpp/IO/FileDescription.hpp +++ b/openVulkanoCpp/IO/FileDescription.hpp @@ -19,7 +19,7 @@ namespace OpenVulkano std::string path; size_t size; std::filesystem::perms permissions; - time_t createTime, modTime; + time_t createTime = {}, modTime = {}; static FileDescription MakeDescriptionForFile(const char* path, size_t size) { diff --git a/openVulkanoCpp/IO/MemMappedFile.hpp b/openVulkanoCpp/IO/MemMappedFile.hpp index 72ef781..e007564 100644 --- a/openVulkanoCpp/IO/MemMappedFile.hpp +++ b/openVulkanoCpp/IO/MemMappedFile.hpp @@ -27,6 +27,8 @@ namespace OpenVulkano READ_WRITE }; + MemMappedFile() : m_data(nullptr), m_size(0) {} + MemMappedFile(const std::filesystem::path& path, FileMode fileMode = READ_ONLY); [[nodiscard]] bool IsOpen() const { return m_data; } diff --git a/tests/IO/Archive/ArchiveReader.cpp b/tests/IO/Archive/ArchiveReader.cpp new file mode 100644 index 0000000..59b9b8f --- /dev/null +++ b/tests/IO/Archive/ArchiveReader.cpp @@ -0,0 +1,154 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#include + +#include +#include +#include + +#include "IO/Archive/ArchiveReader.hpp" +#include "IO/Archive/ArchiveWriter.hpp" +#include "IO/AppFolders.hpp" + +using namespace OpenVulkano; + +namespace +{ + std::string dummyArchiveDir() + { + return AppFolders::GetAppTempDir().string(); + } + + std::string dummyArchivePath() + { + return dummyArchiveDir() + "/test_archive.zip"; + } + + void makeDummyArchive() + { + ArchiveWriter writer(dummyArchivePath().c_str()); + FileDescription desc; + + desc.path = "stream1.txt"; + desc.size = 20; + desc.type = std::filesystem::file_type::regular; + desc.permissions = (std::filesystem::perms) 0644; + + { + auto stream = writer.AddFileStream(desc); + stream.write("Streaming data..", 17); + stream.flush(); + } + + desc.path = "stream2.txt"; + desc.size = 23; + desc.type = std::filesystem::file_type::regular; + desc.permissions = (std::filesystem::perms) 0644; + + { + auto stream = writer.AddFileStream(desc); + stream.write("Other streaming data..", 22); + stream.flush(); + } + } +} + +TEST_CASE("Dummy archive creation, default constructor", "[ArchiveReader]") +{ + makeDummyArchive(); + ArchiveReader reader; + REQUIRE(reader.IsOpen() == false); + + reader.Open(""); + REQUIRE(reader.IsOpen() == false); +} + +TEST_CASE("Open Archive from File", "[ArchiveReader]") +{ + { + std::string testArchive = dummyArchivePath(); + ArchiveReader reader; + bool opened = reader.Open(testArchive); + REQUIRE(opened == true); + REQUIRE(reader.IsOpen() == true); + REQUIRE(reader.HasNext() == true); + } + + { + const char* invalidArchive = "invalid_archive.tar"; + ArchiveReader reader; + REQUIRE_THROWS_AS(reader.Open(invalidArchive), std::runtime_error); + } +} + +TEST_CASE("Open Archive from Directory with Pattern", "[ArchiveReader]") +{ + std::filesystem::path testDir = dummyArchiveDir(); + std::string pattern = R"(.*\.zip$)"; + + { + size_t outSize = 0; + ArchiveReader reader; + bool opened = reader.Open(testDir, pattern, &outSize); + REQUIRE(opened == true); + REQUIRE(reader.IsOpen() == true); + REQUIRE(outSize > 0); + } + + { + size_t outSize = 0; + std::string invalidPattern = R"(\.invalid$)"; + ArchiveReader reader; + bool opened = reader.Open(testDir, invalidPattern, &outSize); + REQUIRE(opened == false); + REQUIRE(reader.IsOpen() == false); + REQUIRE(outSize == 0); + } +} + +TEST_CASE("ExtractNext and Skip Functions", "[ArchiveReader]") +{ + std::string testArchive = dummyArchivePath(); + ArchiveReader reader(testArchive); + + size_t extractedFiles = reader.ExtractRemaining(dummyArchiveDir().c_str()); + REQUIRE(extractedFiles > 0); + reader.SkipNext(); + REQUIRE(reader.HasNext() == false); + + bool foundDirectory = reader.SkipTill(std::filesystem::file_type::directory); + REQUIRE(foundDirectory == false); + REQUIRE(reader.HasNext() == false); +} + +TEST_CASE("Get Next File Operations", "[ArchiveReader]") +{ + std::string testArchive = dummyArchivePath(); + + ArchiveReader reader(testArchive); + + { + auto nextFile = reader.GetNextFile(); + REQUIRE(nextFile.has_value() == true); + REQUIRE(nextFile->first.size > 0); + REQUIRE(nextFile->second.Size() == nextFile->first.size); + } + + { + auto nextFile = reader.GetNextFileAsVector(); + REQUIRE(nextFile.has_value() == true); + REQUIRE(nextFile->first.size > 0); + REQUIRE(nextFile->second.size() == nextFile->first.size); + } + + { + std::ostringstream stream; + auto fileDescription = reader.StreamNextFile(stream); + REQUIRE(fileDescription.has_value() == false); + REQUIRE(stream.str().empty()); + } +} diff --git a/tests/IO/Archive/ArchiveWriter.cpp b/tests/IO/Archive/ArchiveWriter.cpp new file mode 100644 index 0000000..116e794 --- /dev/null +++ b/tests/IO/Archive/ArchiveWriter.cpp @@ -0,0 +1,180 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#include + +#include +#include +#include + +#include "IO/Archive/ArchiveReader.hpp" +#include "IO/Archive/ArchiveWriter.hpp" +#include "IO/AppFolders.hpp" +#include "IO/FileDescription.hpp" + +using namespace OpenVulkano; + +TEST_CASE("AddFile from buffer", "[ArchiveWriter]") +{ + auto tempDir = AppFolders::GetAppTempDir(); + auto archivePath = tempDir / "test_archive_from_buffer.zip"; + + { + ArchiveWriter writer(archivePath.string().c_str()); + std::string buffer = "Hello"; + FileDescription desc; + desc.type = std::filesystem::file_type::regular; + desc.path = "test.txt"; + desc.size = buffer.size(); + desc.permissions = (std::filesystem::perms) 0644; + REQUIRE(writer.AddFile(desc, buffer.c_str()) == true); + } + + ArchiveReader reader(archivePath.string().c_str()); + REQUIRE(reader.IsOpen()); + REQUIRE(reader.HasNext() == true); + auto [fileDesc, fileData] = reader.GetNextFileAsVector().value(); + REQUIRE(fileDesc.path == "test.txt"); + REQUIRE(std::string(fileData.begin(), fileData.end()) == "Hello"); +} + +TEST_CASE("AddFile from multiple buffers", "[ArchiveWriter]") +{ + auto tempDir = AppFolders::GetAppTempDir(); + auto archivePath = tempDir / "test_archive_from_buffers.zip"; + + { + ArchiveWriter writer(archivePath.string().c_str()); + FileDescription desc; + desc.type = std::filesystem::file_type::regular; + desc.path = "test.txt"; + desc.size = 10; + desc.permissions = (std::filesystem::perms) 0644; + + const char* buffer1 = "Hello"; + const char* buffer2 = "World"; + std::vector> buffers = { { buffer1, std::strlen(buffer1) }, + { buffer2, std::strlen(buffer2) } }; + REQUIRE(writer.AddFile(desc, buffers) == true); + } + + ArchiveReader reader(archivePath.string().c_str()); + REQUIRE(reader.IsOpen()); + + REQUIRE(reader.HasNext() == true); + auto [fileDesc, fileData] = reader.GetNextFileAsVector().value(); + REQUIRE(fileDesc.path == "test.txt"); + REQUIRE(std::string(fileData.begin(), fileData.end()) == "HelloWorld"); +} + +TEST_CASE("AddFile from file", "[ArchiveWriter]") +{ + auto tempDir = AppFolders::GetAppTempDir(); + auto testFilePath = tempDir / "test.txt"; + auto archivePath = tempDir / "test_archive_from_file.zip"; + + std::ofstream testFile(testFilePath); + testFile << "Hello from file"; + testFile.close(); + + { + ArchiveWriter writer(archivePath.string().c_str()); + REQUIRE(writer.AddFile(testFilePath.string().c_str(), "test.txt") == true); + } + + ArchiveReader reader(archivePath.string().c_str()); + REQUIRE(reader.IsOpen()); + + REQUIRE(reader.HasNext() == true); + auto [fileDesc, fileData] = reader.GetNextFileAsVector().value(); + REQUIRE(fileDesc.path == "test.txt"); + REQUIRE(std::string(fileData.begin(), fileData.end()) == "Hello from file"); +} + +TEST_CASE("AddFileStream", "[ArchiveWriter]") +{ + auto tempDir = AppFolders::GetAppTempDir(); + auto archivePath = tempDir / "test_archive_stream.zip"; + + { + ArchiveWriter writer(archivePath.string().c_str()); + std::string buffer = "Streamed data."; + FileDescription desc; + desc.type = std::filesystem::file_type::regular; + desc.path = "stream.txt"; + desc.size = buffer.size(); + desc.permissions = (std::filesystem::perms) 0644; + + ArchiveOStream stream = writer.AddFileStream(desc); + stream << buffer; + stream.Close(); + } + + ArchiveReader reader(archivePath.string().c_str()); + REQUIRE(reader.IsOpen()); + + REQUIRE(reader.HasNext() == true); + auto [fileDesc, fileData] = reader.GetNextFileAsVector().value(); + REQUIRE(fileDesc.path == "stream.txt"); + REQUIRE(std::string(fileData.begin(), fileData.end()) == "Streamed data."); +} + +TEST_CASE("Compression settings", "[ArchiveWriter]") +{ + auto tempDir = AppFolders::GetAppTempDir(); + auto archivePath = tempDir / "test_archive_compression.zip"; + + { + ArchiveConfiguration config(ArchiveType::ZIP, CompressionType::GZIP, 9); + ArchiveWriter writer(archivePath.string().c_str(), config); + + std::string buffer = "Hello"; + FileDescription desc; + desc.type = std::filesystem::file_type::regular; + desc.path = "compressed.txt"; + desc.size = buffer.size(); + desc.permissions = (std::filesystem::perms) 0644; + writer.SetShouldCompressFunction([](const FileDescription&) { return true; }); + REQUIRE(writer.AddFile(desc, buffer.c_str()) == true); + } + + ArchiveReader reader(archivePath.string().c_str()); + REQUIRE(reader.IsOpen()); + + REQUIRE(reader.HasNext() == true); + auto [fileDesc, fileData] = reader.GetNextFileAsVector().value(); + REQUIRE(fileDesc.path == "compressed.txt"); + REQUIRE(std::string(fileData.begin(), fileData.end()) == "Hello"); +} + +TEST_CASE("Uncompressed settings", "[ArchiveWriter]") +{ + auto tempDir = AppFolders::GetAppTempDir(); + auto archivePath = tempDir / "test_archive_uncompressed.zip"; + + { + ArchiveConfiguration config(ArchiveType::ZIP, CompressionType::NONE, 0); + ArchiveWriter writer(archivePath.string().c_str(), config); + + std::string buffer = "Hello"; + FileDescription desc; + desc.type = std::filesystem::file_type::regular; + desc.path = "uncompressed.txt"; + desc.size = buffer.size(); + desc.permissions = (std::filesystem::perms) 0644; + + writer.SetShouldCompressFunction([](const FileDescription&) { return false; }); + REQUIRE(writer.AddFile(desc, buffer.c_str()) == true); + } + + ArchiveReader reader(archivePath.string().c_str()); + REQUIRE(reader.IsOpen()); + + REQUIRE(reader.HasNext() == true); + auto [fileDesc, fileData] = reader.GetNextFileAsVector().value(); + REQUIRE(fileDesc.path == "uncompressed.txt"); + REQUIRE(std::string(fileData.begin(), fileData.end()) == "Hello"); +} \ No newline at end of file