From 66f114dbf78bdbe42a5ea4905b0414c347ae45c5 Mon Sep 17 00:00:00 2001 From: "C. J. Howard" Date: Tue, 28 Jun 2022 20:40:55 +0800 Subject: [PATCH] Add support for loading OpenEXR images --- CMakeLists.txt | 2 + src/game/state/boot.cpp | 6 +- src/resources/behavior-tree-loader.cpp | 2 +- src/resources/entity-archetype-loader.cpp | 2 +- src/resources/file-buffer-loader.cpp | 2 +- src/resources/image-loader.cpp | 104 +++++++++++++++------- src/resources/json-loader.cpp | 2 +- src/resources/material-loader.cpp | 2 +- src/resources/mesh-loader.cpp | 2 +- src/resources/model-loader.cpp | 2 +- src/resources/resource-loader.hpp | 5 +- src/resources/resource-manager.cpp | 14 +-- src/resources/resource-manager.hpp | 43 ++++----- src/resources/shader-program-loader.cpp | 4 +- src/resources/string-table-loader.cpp | 4 +- src/resources/text-file-loader.cpp | 2 +- src/resources/texture-loader.cpp | 2 +- src/resources/typeface-loader.cpp | 2 +- 18 files changed, 121 insertions(+), 81 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6159500..df838bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ project(antkeeper VERSION ${VERSION_STRING} LANGUAGES CXX) # Find dependency packages find_package(dr_wav REQUIRED CONFIG) find_package(stb REQUIRED CONFIG) +find_package(tinyexr REQUIRED CONFIG) find_package(glad REQUIRED CONFIG) find_package(EnTT REQUIRED CONFIG) find_package(OpenGL REQUIRED) @@ -21,6 +22,7 @@ find_package(freetype REQUIRED CONFIG) set(STATIC_LIBS dr_wav stb + tinyexr glad EnTT SDL2::SDL2-static diff --git a/src/game/state/boot.cpp b/src/game/state/boot.cpp index 7943ea5..c8d71e0 100644 --- a/src/game/state/boot.cpp +++ b/src/game/state/boot.cpp @@ -300,13 +300,13 @@ void boot::setup_resources() // Mount mods for (const std::filesystem::path& mod_path: mod_paths) - ctx.resource_manager->mount((ctx.mods_path / mod_path).string()); + ctx.resource_manager->mount(ctx.mods_path / mod_path); // Mount config path - ctx.resource_manager->mount(ctx.config_path.string()); + ctx.resource_manager->mount(ctx.config_path); // Mount data package - ctx.resource_manager->mount(ctx.data_package_path.string()); + ctx.resource_manager->mount(ctx.data_package_path); // Include resource search paths in order of priority ctx.resource_manager->include("/shaders/"); diff --git a/src/resources/behavior-tree-loader.cpp b/src/resources/behavior-tree-loader.cpp index f695e68..8438618 100644 --- a/src/resources/behavior-tree-loader.cpp +++ b/src/resources/behavior-tree-loader.cpp @@ -142,7 +142,7 @@ static void load_node_children(entity::ebt::composite_node* node, const nlohmann } template <> -entity::ebt::node* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file) +entity::ebt::node* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path) { // Read file into buffer std::size_t size = static_cast(PHYSFS_fileLength(file)); diff --git a/src/resources/entity-archetype-loader.cpp b/src/resources/entity-archetype-loader.cpp index 28f717c..55bd08f 100644 --- a/src/resources/entity-archetype-loader.cpp +++ b/src/resources/entity-archetype-loader.cpp @@ -237,7 +237,7 @@ static bool load_component(entity::archetype& archetype, resource_manager& resou } template <> -entity::archetype* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file) +entity::archetype* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path) { // Allocate archetype entity::archetype* archetype = new entity::archetype(resource_manager->get_archetype_registry()); diff --git a/src/resources/file-buffer-loader.cpp b/src/resources/file-buffer-loader.cpp index 1989e41..d75f114 100644 --- a/src/resources/file-buffer-loader.cpp +++ b/src/resources/file-buffer-loader.cpp @@ -22,7 +22,7 @@ #include template <> -file_buffer* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file) +file_buffer* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path) { file_buffer* buffer = new file_buffer(); diff --git a/src/resources/image-loader.cpp b/src/resources/image-loader.cpp index e967395..c9274aa 100644 --- a/src/resources/image-loader.cpp +++ b/src/resources/image-loader.cpp @@ -23,56 +23,92 @@ #include #include #include +#include template <> -image* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file) +image* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path) { unsigned char* buffer; int size; - int width; - int height; - int channels; - bool hdr; - void* pixels; + ::image* image = nullptr; // Read input stream into buffer size = static_cast(PHYSFS_fileLength(file)); buffer = new unsigned char[size]; PHYSFS_readBytes(file, buffer, size); - - // Determine if image is in an HDR format - hdr = (stbi_is_hdr_from_memory(buffer, size) != 0); - - // Set vertical flip on load in order to upload pixels correctly to OpenGL - stbi_set_flip_vertically_on_load(true); - // Load image data - if (hdr) + // Select loader according to file extension + if (path.extension() == ".exr") { - pixels = stbi_loadf_from_memory(buffer, size, &width, &height, &channels, 0); + // Load OpenEXR with TinyEXR + + float* pixels = nullptr; + int width = 0; + int height = 0; + const char* error = nullptr; + int status = LoadEXRFromMemory(&pixels, &width, &height, buffer, size, &error); + + if (status != TINYEXR_SUCCESS) + { + delete[] buffer; + std::string error_string(error); + FreeEXRErrorMessage(error); + throw std::runtime_error("TinyEXR error (" + std::to_string(status) + "): " + error_string); + } + + // Create image + std::size_t component_size = sizeof(float); + image = new ::image(); + image->format(component_size, 4); + image->resize(static_cast(width), static_cast(height)); + std::memcpy(image->get_pixels(), pixels, image->get_size()); + + // Free loaded pixels + free(pixels); } else { - pixels = stbi_load_from_memory(buffer, size, &width, &height, &channels, 0); - } - - // Free file buffer - delete[] buffer; - - // Check if image was loaded - if (!pixels) - { - throw std::runtime_error("STBI failed to load image from memory."); + // Load all other formats with stb_image + + // Determine if image is in an HDR format + bool hdr = (stbi_is_hdr_from_memory(buffer, size) != 0); + + // Set vertical flip on load in order to upload pixels correctly to OpenGL + stbi_set_flip_vertically_on_load(true); + + // Load image data + void* pixels = nullptr; + int width = 0; + int height = 0; + int channels = 0; + if (hdr) + { + pixels = stbi_loadf_from_memory(buffer, size, &width, &height, &channels, 0); + } + else + { + pixels = stbi_load_from_memory(buffer, size, &width, &height, &channels, 0); + } + + // Free file buffer + delete[] buffer; + + // Check if image was loaded + if (!pixels) + { + throw std::runtime_error("STBI failed to load image from memory."); + } + + // Create image + std::size_t component_size = (hdr) ? sizeof(float) : sizeof(unsigned char); + image = new ::image(); + image->format(component_size, channels); + image->resize(static_cast(width), static_cast(height)); + std::memcpy(image->get_pixels(), pixels, image->get_size()); + + // Free loaded image data + stbi_image_free(pixels); } - - std::size_t component_size = (hdr) ? sizeof(float) : sizeof(unsigned char); - ::image* image = new ::image(); - image->format(component_size, channels); - image->resize(static_cast(width), static_cast(height)); - std::memcpy(image->get_pixels(), pixels, image->get_size()); - - // Free loaded image data - stbi_image_free(pixels); return image; } diff --git a/src/resources/json-loader.cpp b/src/resources/json-loader.cpp index f4c8e45..517586f 100644 --- a/src/resources/json-loader.cpp +++ b/src/resources/json-loader.cpp @@ -23,7 +23,7 @@ #include template <> -json* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file) +json* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path) { // Read file into buffer std::size_t size = static_cast(PHYSFS_fileLength(file)); diff --git a/src/resources/material-loader.cpp b/src/resources/material-loader.cpp index 54abb12..f11d512 100644 --- a/src/resources/material-loader.cpp +++ b/src/resources/material-loader.cpp @@ -223,7 +223,7 @@ static bool load_matrix_property(render::material* material, const std::string& } template <> -render::material* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file) +render::material* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path) { // Read file into buffer std::size_t size = static_cast(PHYSFS_fileLength(file)); diff --git a/src/resources/mesh-loader.cpp b/src/resources/mesh-loader.cpp index ea80ec6..96291d7 100644 --- a/src/resources/mesh-loader.cpp +++ b/src/resources/mesh-loader.cpp @@ -26,7 +26,7 @@ #include template <> -geom::mesh* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file) +geom::mesh* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path) { std::string line; std::vector vertices; diff --git a/src/resources/model-loader.cpp b/src/resources/model-loader.cpp index d854b32..074e073 100644 --- a/src/resources/model-loader.cpp +++ b/src/resources/model-loader.cpp @@ -357,7 +357,7 @@ model* resource_loader::load(resource_manager* resource_manager, PHYSFS_F */ template <> -render::model* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file) +render::model* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path) { // Read file into buffer std::size_t size = static_cast(PHYSFS_fileLength(file)); diff --git a/src/resources/resource-loader.hpp b/src/resources/resource-loader.hpp index 60e2d95..87d199c 100644 --- a/src/resources/resource-loader.hpp +++ b/src/resources/resource-loader.hpp @@ -20,6 +20,7 @@ #ifndef RESOURCE_LOADER_HPP #define RESOURCE_LOADER_HPP +#include #include class resource_manager; @@ -41,7 +42,7 @@ public: * @param file PhysicsFS file handle. * @return Pointer to the loaded resource. */ - static T* load(resource_manager* resourceManager, PHYSFS_File* file); + static T* load(resource_manager* resourceManager, PHYSFS_File* file, const std::filesystem::path& path); /** * Saves resource data. @@ -50,7 +51,7 @@ public: * @param file PhysicsFS file handle. * @param resource Pointer to the resource data. */ - static void save(resource_manager* resourceManager, PHYSFS_File* file, const T* resource); + static void save(resource_manager* resourceManager, PHYSFS_File* file, const std::filesystem::path& path, const T* resource); }; /// getline function for PhysicsFS file handles diff --git a/src/resources/resource-manager.cpp b/src/resources/resource-manager.cpp index ccd5db4..e9d73b9 100644 --- a/src/resources/resource-manager.cpp +++ b/src/resources/resource-manager.cpp @@ -56,10 +56,10 @@ resource_manager::~resource_manager() } } -bool resource_manager::mount(const std::string& path) +bool resource_manager::mount(const std::filesystem::path& path) { - logger->push_task("Mounting path \"" + path + "\""); - if (!PHYSFS_mount(path.c_str(), nullptr, 1)) + logger->push_task("Mounting path \"" + path.string() + "\""); + if (!PHYSFS_mount(path.string().c_str(), nullptr, 1)) { logger->error(std::string("PhysicsFS error: ") + PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); logger->pop_task(EXIT_FAILURE); @@ -72,10 +72,10 @@ bool resource_manager::mount(const std::string& path) } } -void resource_manager::unload(const std::string& name) +void resource_manager::unload(const std::filesystem::path& path) { // Check if resource is in the cache - auto it = resource_cache.find(name); + auto it = resource_cache.find(path); if (it != resource_cache.end()) { // Decrement the resource handle reference count @@ -86,7 +86,7 @@ void resource_manager::unload(const std::string& name) { if (logger) { - logger->push_task("Unloading resource \"" + name + "\""); + logger->push_task("Unloading resource \"" + path.string() + "\""); } delete it->second; @@ -102,7 +102,7 @@ void resource_manager::unload(const std::string& name) } } -void resource_manager::include(const std::string& search_path) +void resource_manager::include(const std::filesystem::path& search_path) { search_paths.push_back(search_path); } diff --git a/src/resources/resource-manager.hpp b/src/resources/resource-manager.hpp index cbb6d36..54912c8 100644 --- a/src/resources/resource-manager.hpp +++ b/src/resources/resource-manager.hpp @@ -30,6 +30,7 @@ #include #include #include +#include /** * Loads resources. @@ -47,14 +48,14 @@ public: */ ~resource_manager(); - bool mount(const std::string& path); + bool mount(const std::filesystem::path& path); /** * Adds a path to be searched when a resource is requested. * * @param path Search path. */ - void include(const std::string& path); + void include(const std::filesystem::path& path); /** * Loads the requested resource. If the resource has already been loaded it will be retrieved from the resource cache and its reference count incremented. @@ -64,14 +65,14 @@ public: * @return Pointer to the requested resource, or nullptr if the resource could not be found nor loaded. */ template - T* load(const std::string& name); + T* load(const std::filesystem::path& path); /** * Decrements a resource's reference count and unloads the resource if it's unreferenced. * * @param path Path to the resource, relative to the search paths. */ - void unload(const std::string& name); + void unload(const std::filesystem::path& path); /** * Saves the specified resource. @@ -81,28 +82,28 @@ public: * @param path Path to the resource. */ template - void save(const T* resource, const std::string& path); + void save(const T* resource, const std::filesystem::path& path); entt::registry& get_archetype_registry(); private: - std::map resource_cache; - std::list search_paths; + std::map resource_cache; + std::list search_paths; entt::registry archetype_registry; debug::logger* logger; }; template -T* resource_manager::load(const std::string& name) +T* resource_manager::load(const std::filesystem::path& path) { // Check if resource is in the cache - auto it = resource_cache.find(name); + auto it = resource_cache.find(path); if (it != resource_cache.end()) { /* if (logger) { - logger->log("Fetched resource \"" + name + "\""); + logger->log("Fetched resource \"" + path.string() + "\""); } */ @@ -118,18 +119,18 @@ T* resource_manager::load(const std::string& name) if (logger) { - logger->push_task("Loading resource \"" + name + "\""); + logger->push_task("Loading resource \"" + path.string() + "\""); } // Resource not cached, look for file in search paths T* data = nullptr; bool found = false; - for (const std::string& search_path: search_paths) + for (const std::filesystem::path& search_path: search_paths) { - std::string path = search_path + name; + std::filesystem::path full_path = search_path / path; // Check if file exists - if (!PHYSFS_exists(path.c_str())) + if (!PHYSFS_exists(full_path.string().c_str())) { continue; } @@ -138,7 +139,7 @@ T* resource_manager::load(const std::string& name) found = true; // Open file for reading - PHYSFS_File* file = PHYSFS_openRead(path.c_str()); + PHYSFS_File* file = PHYSFS_openRead(full_path.string().c_str()); if (!file) { logger->error(std::string("PhysicsFS error: ") + PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); @@ -148,7 +149,7 @@ T* resource_manager::load(const std::string& name) // Load opened file try { - data = resource_loader::load(this, file); + data = resource_loader::load(this, file, full_path); } catch (const std::exception& e) { @@ -181,7 +182,7 @@ T* resource_manager::load(const std::string& name) resource->reference_count = 1; // Add resource to the cache - resource_cache[name] = resource; + resource_cache[path] = resource; if (logger) { @@ -192,12 +193,12 @@ T* resource_manager::load(const std::string& name) } template -void resource_manager::save(const T* resource, const std::string& path) +void resource_manager::save(const T* resource, const std::filesystem::path& path) { - logger->push_task("Saving resource to \"" + path + "\""); + logger->push_task("Saving resource to \"" + path.string() + "\""); // Open file for writing - PHYSFS_File* file = PHYSFS_openWrite(path.c_str()); + PHYSFS_File* file = PHYSFS_openWrite(path.string().c_str()); if (!file) { logger->error(std::string("PhysicsFS error: ") + PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); @@ -209,7 +210,7 @@ void resource_manager::save(const T* resource, const std::string& path) int status = EXIT_SUCCESS; try { - resource_loader::save(this, file, resource); + resource_loader::save(this, file, path, resource); } catch (const std::exception& e) { diff --git a/src/resources/shader-program-loader.cpp b/src/resources/shader-program-loader.cpp index 74f8ea8..1b887d3 100644 --- a/src/resources/shader-program-loader.cpp +++ b/src/resources/shader-program-loader.cpp @@ -76,10 +76,10 @@ static void handle_includes(text_file* source, resource_manager* resource_manage } template <> -gl::shader_program* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file) +gl::shader_program* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path) { // Load shader template source - text_file source_lines = *resource_loader::load(resource_manager, file); + text_file source_lines = *resource_loader::load(resource_manager, file, path); // Handle `#pragma include` directives handle_includes(&source_lines, resource_manager); diff --git a/src/resources/string-table-loader.cpp b/src/resources/string-table-loader.cpp index d810261..a8828de 100644 --- a/src/resources/string-table-loader.cpp +++ b/src/resources/string-table-loader.cpp @@ -93,7 +93,7 @@ static string_table_row parse_row(const std::string& line) } template <> -string_table* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file) +string_table* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path) { string_table* table = new string_table(); std::string line; @@ -108,7 +108,7 @@ string_table* resource_loader::load(resource_manager* resource_man } template <> -void resource_loader::save(resource_manager* resource_manager, PHYSFS_File* file, const string_table* table) +void resource_loader::save(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path, const string_table* table) { const char* delimeter = ","; const char* newline = "\n"; diff --git a/src/resources/text-file-loader.cpp b/src/resources/text-file-loader.cpp index 2332870..c0a526d 100644 --- a/src/resources/text-file-loader.cpp +++ b/src/resources/text-file-loader.cpp @@ -22,7 +22,7 @@ #include template <> -text_file* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file) +text_file* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path) { text_file* text = new text_file(); std::string line; diff --git a/src/resources/texture-loader.cpp b/src/resources/texture-loader.cpp index 34fa8ea..c4c7059 100644 --- a/src/resources/texture-loader.cpp +++ b/src/resources/texture-loader.cpp @@ -31,7 +31,7 @@ #include template <> -gl::texture_2d* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file) +gl::texture_2d* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path) { // Read file into buffer std::size_t size = static_cast(PHYSFS_fileLength(file)); diff --git a/src/resources/typeface-loader.cpp b/src/resources/typeface-loader.cpp index bcd517c..c73d635 100644 --- a/src/resources/typeface-loader.cpp +++ b/src/resources/typeface-loader.cpp @@ -25,7 +25,7 @@ #include FT_FREETYPE_H template <> -type::typeface* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file) +type::typeface* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path) { FT_Error error = 0;