From c24b013b6b9d6c7c576f637a19b6441b52fd7007 Mon Sep 17 00:00:00 2001 From: Laura Hermanns Date: Sat, 6 Jul 2024 16:05:49 -0400 Subject: [PATCH] [Examples] Refactored asset loading in examples. - Replaced filename resolution via FindResourcePath() with abstraction to load entire asset via ReadAsset(). - Replaced individual uses of stbi_load() with new ImageReader class. - Started with asset loading for Android. - Also use different header guard for examples: Renamed "LLGL_*" to "LLGLEXAMPLES_*". TODO: Android asset reading will need connection to JNI to access 'AAssetManager'. --- examples/Cpp/CMakeLists.txt | 14 ++- examples/Cpp/ExampleBase/Android/AppUtils.c | 56 +++++++++ examples/Cpp/ExampleBase/Android/AppUtils.h | 29 +++++ examples/Cpp/ExampleBase/DDSImageReader.cpp | 26 +++-- examples/Cpp/ExampleBase/DDSImageReader.h | 10 +- examples/Cpp/ExampleBase/ExampleBase.cpp | 67 +++++------ examples/Cpp/ExampleBase/FileUtils.cpp | 123 +++++++++++++++++--- examples/Cpp/ExampleBase/FileUtils.h | 50 +++++++- examples/Cpp/ExampleBase/GeometryUtils.cpp | 21 ++-- examples/Cpp/ExampleBase/GeometryUtils.h | 4 +- examples/Cpp/ExampleBase/ImageReader.cpp | 70 +++++++++++ examples/Cpp/ExampleBase/ImageReader.h | 49 ++++++++ examples/Cpp/ExampleBase/PerlinNoise.h | 4 +- examples/Cpp/ExampleBase/Stopwatch.h | 5 +- examples/Cpp/Instancing/Example.cpp | 45 ++++--- examples/Cpp/PBR/Example.cpp | 54 +++++---- examples/Cpp/Texturing/Example.cpp | 31 +---- 17 files changed, 488 insertions(+), 170 deletions(-) create mode 100644 examples/Cpp/ExampleBase/Android/AppUtils.c create mode 100644 examples/Cpp/ExampleBase/Android/AppUtils.h create mode 100644 examples/Cpp/ExampleBase/ImageReader.cpp create mode 100644 examples/Cpp/ExampleBase/ImageReader.h diff --git a/examples/Cpp/CMakeLists.txt b/examples/Cpp/CMakeLists.txt index 6efb10f7d5..f24a1da769 100644 --- a/examples/Cpp/CMakeLists.txt +++ b/examples/Cpp/CMakeLists.txt @@ -21,16 +21,20 @@ if(APPLE) else() find_source_files(FilesExampleBaseMacOS OBJC "${EXAMPLEBASE_PROJECT_DIR}/macOS") endif() -endif(APPLE) +elseif(LLGL_ANDROID_PLATFORM) + find_source_files(FilesExampleBaseAndroid CXX "${EXAMPLEBASE_PROJECT_DIR}/Android") +endif() set(FilesExampleBaseAll ${FilesExampleBase}) if(APPLE) if(LLGL_IOS_PLATFORM) - set(FilesExampleBaseAll ${FilesExampleBaseAll} ${FilesExampleBaseIOS}) + list(APPEND FilesExampleBaseAll ${FilesExampleBaseIOS}) else() - set(FilesExampleBaseAll ${FilesExampleBaseAll} ${FilesExampleBaseMacOS}) + list(APPEND FilesExampleBaseAll ${FilesExampleBaseMacOS}) endif() set_source_files_properties("${EXAMPLEBASE_PROJECT_DIR}/ExampleBase.cpp" PROPERTIES COMPILE_FLAGS -xobjective-c++) +elseif(LLGL_ANDROID_PLATFORM) + list(APPEND FilesExampleBaseAll ${FilesExampleBaseAndroid}) endif() find_project_source_files( FilesExample_Animation "${EXAMPLE_CPP_PROJECTS_DIR}/Animation" ) @@ -130,7 +134,9 @@ if(APPLE) else() source_group("Sources\\macOS" FILES ${FilesExampleBaseMacOS}) endif() -endif(APPLE) +elseif(LLGL_ANDROID_PLATFORM) + source_group("Sources\\Android" FILES ${FilesExampleBaseAndroid}) +endif() # === Include directories === diff --git a/examples/Cpp/ExampleBase/Android/AppUtils.c b/examples/Cpp/ExampleBase/Android/AppUtils.c new file mode 100644 index 0000000000..73db181136 --- /dev/null +++ b/examples/Cpp/ExampleBase/Android/AppUtils.c @@ -0,0 +1,56 @@ +/* + * AppUtils.c (Android) + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#include "AppUtils.h" +#include + + +static int AndroidFileRead(void* userData, char* buffer, int size) +{ + return AAsset_read((AAsset*)userData, buffer, (size_t)size); +} + +static int AndroidFileWrite(void* userData, const char* buffer, int size) +{ + return EACCES; // dummy - cannot provide access to write to APK +} + +static fpos_t AndroidFileSeek(void* userData, fpos_t offset, int whence) +{ + return AAsset_seek((AAsset*)userData, offset, whence); +} + +static int AndroidFileClose(void* userData) +{ + AAsset_close((AAsset*)userData); + return 0; +} + +static AAssetManager* g_assetMngr = NULL; + +void AndroidSetAssetManager(AAssetManager* assetMngr) +{ + g_assetMngr = assetMngr; +} + +FILE* AndroidOpenFile(const char* filename, const char* mode) +{ + if (g_assetMngr == NULL) + return NULL; // No asset manager provided + + if (mode[0] == 'w') + return NULL; // Write access is not supported on APK package + + // Open asset via AAssetManager + AAsset* asset = AAssetManager_open(g_assetMngr, filename, AASSET_MODE_STREAMING); + if (asset == NULL) + return NULL; // Failed to open asset + + // Open FILE descriptor with custom read/seek functions + return funopen(asset, AndroidFileRead, AndroidFileWrite, AndroidFileSeek, AndroidFileClose); +} + diff --git a/examples/Cpp/ExampleBase/Android/AppUtils.h b/examples/Cpp/ExampleBase/Android/AppUtils.h new file mode 100644 index 0000000000..2da18137ac --- /dev/null +++ b/examples/Cpp/ExampleBase/Android/AppUtils.h @@ -0,0 +1,29 @@ +/* + * AppUtils.h (Android) + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#ifndef __USE_BSD +#define __USE_BSD /* Defines funopen() */ +#endif + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Sets the current asset manager. +void AndroidSetAssetManager(AAssetManager* assetMngr); + +// Opens an STD file stream via the AAssetManager. +FILE* AndroidOpenFile(const char* filename, const char* mode); + +#define fopen(NAME, MODE) AndroidOpenFile(NAME, MODE) + +#ifdef __cplusplus +} // /extern "C" +#endif diff --git a/examples/Cpp/ExampleBase/DDSImageReader.cpp b/examples/Cpp/ExampleBase/DDSImageReader.cpp index be54f6e0d6..557d0dba40 100644 --- a/examples/Cpp/ExampleBase/DDSImageReader.cpp +++ b/examples/Cpp/ExampleBase/DDSImageReader.cpp @@ -12,6 +12,7 @@ #include #include #include +#include static const std::uint32_t ddsMagicNumber = 0x20534444; // 'DDS ' @@ -131,21 +132,22 @@ T ReadValue(std::istream& stream) return value; } -void DDSImageReader::LoadFromFile(const std::string& filename) +bool DDSImageReader::LoadFromFile(const std::string& filename) { // Open file for reading - const std::string path = FindResourcePath(filename); - std::ifstream file(path, std::ios::binary); - if (!file.good()) - throw std::runtime_error("failed to load DDS image from file: " + path); + AssetReader reader = ReadAsset(filename); + if (!reader) + return false; // Read magic number - if (ReadValue(file) != ddsMagicNumber) - throw std::runtime_error("invalid magic number in DDS image: " + path); + if (reader.Read() != ddsMagicNumber) + { + LLGL::Log::Errorf("invalid magic number in DDS image: %s\n", filename.c_str()); + return false; + } // Rerad DDS header - DDSHeader header; - ReadValue(file, header); + DDSHeader header = reader.Read(); DDSHeaderDX10 headerDX10; ::memset(&headerDX10, 0, sizeof(headerDX10)); @@ -207,7 +209,7 @@ void DDSImageReader::LoadFromFile(const std::string& filename) /*else if (IsFourCC("DX10")) { // Read header extension - ReadValue(file, headerDX10); + (void)reader.Read(); hasDX10Header = true; }*/ else @@ -244,7 +246,9 @@ void DDSImageReader::LoadFromFile(const std::string& filename) data_.resize(bufferSize); - file.read(data_.data(), static_cast(data_.size())); + reader.Read(data_.data(), static_cast(data_.size())); + + return true; } LLGL::ImageView DDSImageReader::GetImageView(std::uint32_t mipLevel) const diff --git a/examples/Cpp/ExampleBase/DDSImageReader.h b/examples/Cpp/ExampleBase/DDSImageReader.h index 9894f1e4ef..e90fe43144 100644 --- a/examples/Cpp/ExampleBase/DDSImageReader.h +++ b/examples/Cpp/ExampleBase/DDSImageReader.h @@ -5,8 +5,8 @@ * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). */ -#ifndef LLGL_DDS_IMAGE_READER_H -#define LLGL_DDS_IMAGE_READER_H +#ifndef LLGLEXAMPLES_DDS_IMAGE_READER_H +#define LLGLEXAMPLES_DDS_IMAGE_READER_H #include @@ -15,14 +15,14 @@ #include -// Perlin noise generator class. +// Image reader class to load DXT compressed textures from file. class DDSImageReader { public: - // Loads the specified DDS texture from file. - void LoadFromFile(const std::string& filename); + // Loads the specified DDS image from file. + bool LoadFromFile(const std::string& filename); // Returns the image view for the sepcified MIP-map that can be passed to RenderSystem::CreateTexture or RenderSystem::WriteTexture. LLGL::ImageView GetImageView(std::uint32_t mipLevel = 0) const; diff --git a/examples/Cpp/ExampleBase/ExampleBase.cpp b/examples/Cpp/ExampleBase/ExampleBase.cpp index 76c8f77692..b896c9743a 100644 --- a/examples/Cpp/ExampleBase/ExampleBase.cpp +++ b/examples/Cpp/ExampleBase/ExampleBase.cpp @@ -8,6 +8,7 @@ #include #include #include +#include "ImageReader.h" #include "FileUtils.h" #include @@ -26,6 +27,10 @@ #endif #include +#ifdef LLGL_OS_ANDROID +# include "Android/AppUtils.h" +#endif + #define IMMEDIATE_SUBMIT_CMDBUFFER 0 @@ -483,6 +488,18 @@ ExampleBase::ExampleBase(const LLGL::UTF8String& title) rendererDesc.flags |= g_Config.flags; renderer = LLGL::RenderSystem::Load(rendererDesc); + // Fallback to null device if selected renderer cannot be loaded + if (!renderer) + { + LLGL::Log::Errorf("Failed to load \"%s\" module. Falling back to \"Null\" device.\n", rendererDesc.moduleName.c_str()); + renderer = LLGL::RenderSystem::Load("Null"); + if (!renderer) + { + LLGL::Log::Errorf("Failed to load \"Null\" module. Exiting.\n"); + exit(1); + } + } + // Apply device limits (not for GL, because we won't have a valid GL context until we create our first swap chain) if (renderer->GetRendererID() == LLGL::RendererID::OpenGL) samples_ = g_Config.samples; @@ -514,8 +531,8 @@ ExampleBase::ExampleBase(const LLGL::UTF8String& title) commandQueue = renderer->GetCommandQueue(); // Print renderer information - const auto& info = renderer->GetRendererInfo(); - const auto swapChainRes = swapChain->GetResolution(); + const LLGL::RendererInfo& info = renderer->GetRendererInfo(); + const LLGL::Extent2D swapChainRes = swapChain->GetResolution(); LLGL::Log::Printf( "render system:\n" @@ -775,40 +792,17 @@ void ExampleBase::ThrowIfFailed(LLGL::PipelineState* pso) LLGL::Texture* LoadTextureWithRenderer(LLGL::RenderSystem& renderSys, const std::string& filename, long bindFlags, LLGL::Format format) { - // Get format informationm - const auto formatAttribs = LLGL::GetFormatAttribs(format); - // Load image data from file (using STBI library, see https://github.com/nothings/stb) - int width = 0, height = 0, components = 0; - - const std::string path = FindResourcePath(filename); - stbi_uc* imageBuffer = stbi_load(path.c_str(), &width, &height, &components, static_cast(formatAttribs.components)); - if (!imageBuffer) - throw std::runtime_error("failed to load texture from file: \"" + path + "\""); - - // Initialize source image descriptor to upload image data onto hardware texture - LLGL::ImageView imageView; + ImageReader reader; + if (!reader.LoadFromFile(filename)) { - // Set image color format - imageView.format = formatAttribs.format; - - // Set image data type (unsigned char = 8-bit unsigned integer) - imageView.dataType = LLGL::DataType::UInt8; - - // Set image buffer source for texture initial data - imageView.data = imageBuffer; - - // Set image buffer size - imageView.dataSize = static_cast(width*height*4); + // Create dummy texture on load failure + return renderSys.CreateTexture(LLGL::Texture2DDesc(format, 1, 1)); } // Create texture and upload image data onto hardware texture - LLGL::TextureDescriptor texDesc = LLGL::Texture2DDesc(format, width, height, bindFlags); - texDesc.debugName = filename.c_str(); - LLGL::Texture* tex = renderSys.CreateTexture(texDesc, &imageView); - - // Release image data - stbi_image_free(imageBuffer); + LLGL::ImageView imageView = reader.GetImageView(); + LLGL::Texture* tex = renderSys.CreateTexture(reader.GetTextureDesc(), &imageView); // Show info LLGL::Log::Printf("loaded texture: %s\n", filename.c_str()); @@ -818,17 +812,8 @@ LLGL::Texture* LoadTextureWithRenderer(LLGL::RenderSystem& renderSys, const std: bool SaveTextureWithRenderer(LLGL::RenderSystem& renderSys, LLGL::Texture& texture, const std::string& filename, std::uint32_t mipLevel) { - #if 0//TESTING - - mipLevel = 1; - LLGL::Extent3D texSize{ 150, 256, 1 }; - - #else - // Get texture dimension - auto texSize = texture.GetMipExtent(mipLevel); - - #endif + const LLGL::Extent3D texSize = texture.GetMipExtent(mipLevel); // Read texture image data std::vector imageBuffer(texSize.width * texSize.height); diff --git a/examples/Cpp/ExampleBase/FileUtils.cpp b/examples/Cpp/ExampleBase/FileUtils.cpp index 4b144ae301..7d673273a6 100644 --- a/examples/Cpp/ExampleBase/FileUtils.cpp +++ b/examples/Cpp/ExampleBase/FileUtils.cpp @@ -8,29 +8,59 @@ #include "FileUtils.h" #include #include +#include +#include +#include #if defined LLGL_OS_IOS # include "iOS/AppUtils.h" #elif defined LLGL_OS_MACOS # include "macOS/AppUtils.h" +#elif defined LLGL_OS_ANDROID +# include "Android/AppUtils.h" #endif /* - * Global helper functions + * AssetReader class */ -std::string FindResourcePath(const std::string& filename) +AssetReader::AssetReader(std::vector&& content) : + content_ { std::move(content) } { - auto FileExists = [](const std::string& path) -> bool - { - std::ifstream file{ path }; - return file.good(); - }; +} + +std::size_t AssetReader::Read(void* data, std::size_t dataSize) +{ + dataSize = std::min(dataSize, content_.size() - readPos_); + ::memcpy(data, &(content_[readPos_]), dataSize); + readPos_ += dataSize; + return dataSize; +} + +bool AssetReader::IsValid() const +{ + return !content_.empty(); +} - if (FileExists(filename)) - return filename; +AssetReader::operator bool () const +{ + return IsValid(); +} + +/* + * Global helper functions + */ + +static bool FileExists(const std::string& filename) +{ + std::ifstream file{ filename }; + return file.good(); +}; + +static std::string FindAssetFilename(const std::string& name) +{ #if defined LLGL_OS_IOS || defined LLGL_OS_MACOS // Returns filename for resource from main NSBundle @@ -40,27 +70,88 @@ std::string FindResourcePath(const std::string& filename) else return FindNSResourcePath(filename); + #elif defined LLGL_OS_ANDROID + + // Handle filename resolution with AAssetManager on Android + return name; + #else + // Return input name if it's a valid filename + if (FileExists(name)) + return name; + // Search file in resource dependent paths - auto extPos = filename.find_last_of('.'); - const std::string ext = (extPos != std::string::npos ? filename.substr(extPos + 1) : ""); + auto extPos = name.find_last_of('.'); + const std::string ext = (extPos != std::string::npos ? name.substr(extPos + 1) : ""); const std::string mediaRoot = "../../Media/"; if (ext == "obj") { - if (FileExists(mediaRoot + "Models/" + filename)) - return mediaRoot + "Models/" + filename; + if (FileExists(mediaRoot + "Models/" + name)) + return mediaRoot + "Models/" + name; } else if (ext == "png" || ext == "jpg" || ext == "tga" || ext == "dds") { - if (FileExists(mediaRoot + "Textures/" + filename)) - return mediaRoot + "Textures/" + filename; + if (FileExists(mediaRoot + "Textures/" + name)) + return mediaRoot + "Textures/" + name; } #endif - return filename; + return name; +} + +std::vector ReadAsset(const std::string& name, std::string* outFullPath) +{ + // Get full filename for asset + const std::string filename = FindAssetFilename(name); + if (outFullPath != nullptr) + *outFullPath = filename; + + #if defined LLGL_OS_ANDROID + + // Load asset from compressed APK package via AAssetManager + FILE* file = fopen(name.c_str(), "rb"); + if (file == nullptr) + { + LLGL::Log::Errorf("failed to load asset: %s\n", name.c_str()); + return {}; + } + + // Read entire content of file into output container + std::vector content; + + fseek(file, 0, SEEK_END); + long fileSize = ftell(file); + fseek(file, 0, SEEK_SET); + if (fileSize > 0) + { + content.resize(static_cast(fileSize)); + fread(content.data(), content.size(), 1, file); + } + fclose(file); + + return content; + + #else + + // Load asset directly from file + std::ifstream file{ filename, std::ios_base::binary }; + if (!file.good()) + { + LLGL::Log::Errorf("failed to load asset: %s\n", name.c_str()); + return {}; + } + + // Read entire content of file stream into output container + return std::vector + { + std::istreambuf_iterator{file}, + std::istreambuf_iterator{} + }; + + #endif } diff --git a/examples/Cpp/ExampleBase/FileUtils.h b/examples/Cpp/ExampleBase/FileUtils.h index 80a46ea19c..2800601210 100644 --- a/examples/Cpp/ExampleBase/FileUtils.h +++ b/examples/Cpp/ExampleBase/FileUtils.h @@ -5,18 +5,62 @@ * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). */ -#ifndef LLGL_FILE_UTILS_H -#define LLGL_FILE_UTILS_H +#ifndef LLGLEXAMPLES_FILE_UTILS_H +#define LLGLEXAMPLES_FILE_UTILS_H #include +#include +#include /* * Global helper functions */ -std::string FindResourcePath(const std::string& filename); +// Helper class to abstract reading assets for multiple platforms. +class AssetReader +{ + + public: + + AssetReader() = default; + + AssetReader(AssetReader&&) = default; + AssetReader& operator = (AssetReader&&) = default; + + // Take ownership of the specified content to read an asset. + AssetReader(std::vector&& content); + + // Returns true if this asset reader has any content. + bool IsValid() const; + + // Shotcut to IsValid(). + operator bool () const; + + // Main function to read data from the asset. + std::size_t Read(void* data, std::size_t dataSize); + + // Generic version to read a single value/struct. + template + T Read() + { + static_assert(std::is_trivially_constructible::value, "AssetReader::Read(): T must be trivially constructible"); + T value = {}; + Read(&value, sizeof(T)); + return value; + } + + public: + + std::vector content_; + std::size_t readPos_ = 0; + +}; + +// Returns the content of the specified asset. +// If the file could not be found, an empty container is returned and an error is reported to the log. +std::vector ReadAsset(const std::string& name, std::string* outFullPath = nullptr); #endif diff --git a/examples/Cpp/ExampleBase/GeometryUtils.cpp b/examples/Cpp/ExampleBase/GeometryUtils.cpp index dcb5824d0a..d9796b42b9 100644 --- a/examples/Cpp/ExampleBase/GeometryUtils.cpp +++ b/examples/Cpp/ExampleBase/GeometryUtils.cpp @@ -7,14 +7,14 @@ #include "GeometryUtils.h" #include "FileUtils.h" -#include #include +#include #include /* - * Global helper functions - */ +* Global helper functions +*/ std::vector LoadObjModel(const std::string& filename) { @@ -26,10 +26,9 @@ std::vector LoadObjModel(const std::string& filename) TriangleMesh LoadObjModel(std::vector& vertices, const std::string& filename) { // Read obj file - const std::string path = FindResourcePath(filename); - std::ifstream file(path); - if (!file.good()) - throw std::runtime_error("failed to load model from file: \"" + path + "\""); + std::vector fileContent = ReadAsset(filename); + if (fileContent.empty()) + throw std::runtime_error("failed to load model from file: \"" + filename + "\""); // Initialize triangle mesh TriangleMesh mesh; @@ -38,9 +37,13 @@ TriangleMesh LoadObjModel(std::vector& vertices, const std::stri std::vector coords, normals; std::vector texCoords; - std::string line; + // Convert file content into a stream (this code used to read the file via std::ifstream) + std::stringstream stream; + std::copy(fileContent.begin(), fileContent.end(), std::ostream_iterator{ stream }); + // Read each line - while (std::getline(file, line)) + std::string line; + while (std::getline(stream, line)) { std::stringstream s; s << line; diff --git a/examples/Cpp/ExampleBase/GeometryUtils.h b/examples/Cpp/ExampleBase/GeometryUtils.h index c93e2b7397..018ea402a4 100644 --- a/examples/Cpp/ExampleBase/GeometryUtils.h +++ b/examples/Cpp/ExampleBase/GeometryUtils.h @@ -5,8 +5,8 @@ * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). */ -#ifndef LLGL_GEOMETRY_UTILS_H -#define LLGL_GEOMETRY_UTILS_H +#ifndef LLGLEXAMPLES_GEOMETRY_UTILS_H +#define LLGLEXAMPLES_GEOMETRY_UTILS_H #include diff --git a/examples/Cpp/ExampleBase/ImageReader.cpp b/examples/Cpp/ExampleBase/ImageReader.cpp new file mode 100644 index 0000000000..429d4f92f9 --- /dev/null +++ b/examples/Cpp/ExampleBase/ImageReader.cpp @@ -0,0 +1,70 @@ +/* + * ImageReader.cpp + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#include "ImageReader.h" +#include "FileUtils.h" +#include +#include +#include + + +bool ImageReader::LoadFromFile(const std::string& filename) +{ + // Read image asset + std::string assetPath; + std::vector content = ReadAsset(filename, &assetPath); + if (content.empty()) + return false; + + // Load all images from file (using STBI library, see https://github.com/nothings/stb) + int w = 0, h = 0, c = 0; + stbi_uc* imageData = stbi_load_from_memory(reinterpret_cast(content.data()), static_cast(content.size()), &w, &h, &c, 4); + if (!imageData) + { + LLGL::Log::Errorf("failed to load image from file: \"%s\"", assetPath.c_str()); + return false; + } + + data_ = std::vector + { + reinterpret_cast(imageData), + reinterpret_cast(imageData + w*h*4) + }; + + stbi_image_free(imageData); + + // Store meta data + name_ = filename; + texDesc_.debugName = name_.c_str(); + texDesc_.type = LLGL::TextureType::Texture2D; + texDesc_.format = LLGL::Format::RGBA8UNorm; + texDesc_.extent.width = static_cast(w); + texDesc_.extent.height = static_cast(h); + texDesc_.extent.depth = 1; + + return true; +} + +LLGL::ImageView ImageReader::GetImageView() const +{ + LLGL::ImageView imageView; + { + imageView.format = LLGL::ImageFormat::RGBA; + imageView.dataType = LLGL::DataType::UInt8; + imageView.data = data_.data(); + imageView.dataSize = data_.size(); + } + return imageView; +} + +void ImageReader::AppendImageDataTo(std::vector& outBuffer) +{ + const std::size_t outBufferOffset = outBuffer.size(); + outBuffer.resize(outBufferOffset + data_.size()); + ::memcpy(outBuffer.data() + outBufferOffset, data_.data(), data_.size()); +} + diff --git a/examples/Cpp/ExampleBase/ImageReader.h b/examples/Cpp/ExampleBase/ImageReader.h new file mode 100644 index 0000000000..69c4e7fa57 --- /dev/null +++ b/examples/Cpp/ExampleBase/ImageReader.h @@ -0,0 +1,49 @@ +/* + * ImageReader.h + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +#ifndef LLGLEXAMPLES_IMAGE_READER_H +#define LLGLEXAMPLES_IMAGE_READER_H + + +#include +#include +#include +#include + + +// Image reader class to load common image formats (using STB lib). +class ImageReader +{ + + public: + + // Loads the specified image from file. + bool LoadFromFile(const std::string& filename); + + // Returns the image view for the first MIP-map that can be passed to RenderSystem::CreateTexture or RenderSystem::WriteTexture. + LLGL::ImageView GetImageView() const; + + // Appends the data of the loaded image to the specified output buffer. + void AppendImageDataTo(std::vector& outBuffer); + + // Returns the texture descriptor. + inline const LLGL::TextureDescriptor& GetTextureDesc() const + { + return texDesc_; + } + + private: + + std::string name_; + LLGL::TextureDescriptor texDesc_; + std::vector data_; + +}; + + +#endif + diff --git a/examples/Cpp/ExampleBase/PerlinNoise.h b/examples/Cpp/ExampleBase/PerlinNoise.h index 02476a4f35..17a96f6f2a 100644 --- a/examples/Cpp/ExampleBase/PerlinNoise.h +++ b/examples/Cpp/ExampleBase/PerlinNoise.h @@ -5,8 +5,8 @@ * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). */ -#ifndef LLGL_PERLIN_NOISE_H -#define LLGL_PERLIN_NOISE_H +#ifndef LLGLEXAMPLES_PERLIN_NOISE_H +#define LLGLEXAMPLES_PERLIN_NOISE_H #include diff --git a/examples/Cpp/ExampleBase/Stopwatch.h b/examples/Cpp/ExampleBase/Stopwatch.h index a133731806..d0fb145387 100644 --- a/examples/Cpp/ExampleBase/Stopwatch.h +++ b/examples/Cpp/ExampleBase/Stopwatch.h @@ -5,13 +5,14 @@ * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). */ -#ifndef LLGL_STOPWATCH_H -#define LLGL_STOPWATCH_H +#ifndef LLGLEXAMPLES_STOPWATCH_H +#define LLGLEXAMPLES_STOPWATCH_H #include +// Simple class to store changes in time (in seconds) from Start() to Stop() calls. class Stopwatch { diff --git a/examples/Cpp/Instancing/Example.cpp b/examples/Cpp/Instancing/Example.cpp index 465e685733..6a7478ad1e 100644 --- a/examples/Cpp/Instancing/Example.cpp +++ b/examples/Cpp/Instancing/Example.cpp @@ -6,8 +6,7 @@ */ #include -#include -#include +#include #include @@ -212,14 +211,14 @@ class Example_Instancing : public ExampleBase { std::string filename; - std::vector arrayImageBuffer; + std::vector arrayImageBuffer; // Load all array images - int width = 0, height = 0; + std::uint32_t width = 0, height = 0; - auto numImages = (numPlantImages + 1); + std::uint32_t numImages = 0; - for (std::uint32_t i = 0; i < numImages; ++i) + for (std::uint32_t i = 0; i <= numPlantImages; ++i) { // Setup filename for "Plants_N.png" where N is from 0 to 9 if (i < numPlantImages) @@ -227,32 +226,28 @@ class Example_Instancing : public ExampleBase else filename = "Grass.jpg"; - filename = FindResourcePath(filename); - - // Load all images from file (using STBI library, see https://github.com/nothings/stb) - int w = 0, h = 0, c = 0; - unsigned char* imageBuffer = stbi_load(filename.c_str(), &w, &h, &c, 4); - if (!imageBuffer) - throw std::runtime_error("failed to load texture from file: \"" + filename + "\""); + // Load image asset + ImageReader reader; + if (!reader.LoadFromFile(filename)) + continue; // Copy image buffer into array image buffer - if ( ( width != 0 && height != 0 ) && ( width != w || height != h ) ) - throw std::runtime_error("image size mismatch"); - - width = w; - height = h; - - auto imageBufferSize = w*h*4; - auto imageBufferOffset = arrayImageBuffer.size(); - arrayImageBuffer.resize(imageBufferOffset + imageBufferSize); + const LLGL::Extent3D imageExtent = reader.GetTextureDesc().extent; + if ( ( width != 0 && height != 0 ) && ( width != imageExtent.width || height != imageExtent.height ) ) + { + LLGL::Log::Errorf("image size mismatch for image \"%s\"\n", filename.c_str()); + continue; + } - ::memcpy(arrayImageBuffer.data() + imageBufferOffset, imageBuffer, imageBufferSize); + width = imageExtent.width; + height = imageExtent.height; - // Release temporary image data - stbi_image_free(imageBuffer); + reader.AppendImageDataTo(arrayImageBuffer); // Show info LLGL::Log::Printf("loaded texture: %s\n", filename.c_str()); + + ++numImages; } // Create array texture object with 'numImages' layers diff --git a/examples/Cpp/PBR/Example.cpp b/examples/Cpp/PBR/Example.cpp index 7c4c6c9b01..b2e9716a65 100644 --- a/examples/Cpp/PBR/Example.cpp +++ b/examples/Cpp/PBR/Example.cpp @@ -6,8 +6,7 @@ */ #include -#include -#include +#include class Example_PBR : public ExampleBase @@ -217,39 +216,39 @@ class Example_PBR : public ExampleBase pipelineMeshes = renderer->CreatePipelineState(pipelineDescMeshes); } - void LoadImage(const std::string& filename, int& texWidth, int& texHeight, std::vector& imageData) + bool LoadImageSlice(const std::string& filename, std::uint32_t& texWidth, std::uint32_t& texHeight, std::vector& imageData) { // Print information about current texture - const std::string path = FindResourcePath(filename); - LLGL::Log::Printf("load image: \"%s\"\n", path.c_str()); + LLGL::Log::Printf("load image: \"%s\"\n", filename.c_str()); // Load image data from file (using STBI library, see http://nothings.org/stb_image.h) - int w = 0, h = 0, n = 0; - unsigned char* buf = stbi_load(path.c_str(), &w, &h, &n, 4); - if (!buf) - throw std::runtime_error("failed to load image: \"" + filename + "\""); + ImageReader imageReader; + imageReader.LoadFromFile(filename); // Check if image size is compatible + const LLGL::Extent3D& imageExtent = imageReader.GetTextureDesc().extent; if (texWidth == 0) { - texWidth = w; - texHeight = h; + texWidth = imageExtent.width; + texHeight = imageExtent.height; + } + else if (imageExtent.width != texWidth || imageExtent.height != texHeight) + { + LLGL::Log::Errorf("size mismatch for texture array while loading image: \"%s\"", filename.c_str()); + return false; } - else if (w != texWidth || h != texHeight) - throw std::runtime_error("size mismatch for texture array while loading image: \"" + filename + "\""); // Copy into array - auto offset = imageData.size(); - auto bufSize = static_cast(w*h*4); + auto offset = imageData.size(); + auto bufSize = imageReader.GetImageView().dataSize; imageData.resize(offset + bufSize); - ::memcpy(&(imageData[offset]), buf, bufSize); + ::memcpy(&(imageData[offset]), imageReader.GetImageView().data, bufSize); - // Release image data - stbi_image_free(buf); + return true; } - void FillImage(int& texWidth, int& texHeight, std::vector& imageData) + void FillImageSlice(std::uint32_t& texWidth, std::uint32_t& texHeight, std::vector& imageData) { // Initialize texture size with default value if necessary if (texWidth == 0) @@ -269,15 +268,22 @@ class Example_PBR : public ExampleBase LLGL::Texture* LoadTextureArray(const LLGL::TextureType texType, const std::initializer_list& texFilenames) { // Load image data - int texWidth = 0, texHeight = 0; + std::uint32_t texWidth = 0, texHeight = 0; std::vector imageData; + std::uint32_t numImageSlices = 0; - for (auto filename : texFilenames) + for (const std::string& filename : texFilenames) { if (filename.empty()) - FillImage(texWidth, texHeight, imageData); + { + FillImageSlice(texWidth, texHeight, imageData); + ++numImageSlices; + } else - LoadImage("PBR/" + filename, texWidth, texHeight, imageData); + { + if (LoadImageSlice("PBR/" + filename, texWidth, texHeight, imageData)) + ++numImageSlices; + } } // Define initial texture data @@ -297,7 +303,7 @@ class Example_PBR : public ExampleBase texDesc.extent.width = static_cast(texWidth); texDesc.extent.height = static_cast(texHeight); texDesc.extent.depth = 1; - texDesc.arrayLayers = static_cast(texFilenames.size()); + texDesc.arrayLayers = numImageSlices; } auto tex = renderer->CreateTexture(texDesc, &srcImageView); diff --git a/examples/Cpp/Texturing/Example.cpp b/examples/Cpp/Texturing/Example.cpp index 2cfe1ec281..4b2811c233 100644 --- a/examples/Cpp/Texturing/Example.cpp +++ b/examples/Cpp/Texturing/Example.cpp @@ -8,8 +8,8 @@ #include #include #include +#include #include -#include class Example_Texturing : public ExampleBase @@ -114,29 +114,11 @@ class Example_Texturing : public ExampleBase void LoadUncompressedTexture(const std::string& filename) { // Load image data from file (using STBI library, see http://nothings.org/stb_image.h) - int texWidth = 0, texHeight = 0, texComponents = 0; - - const std::string path = FindResourcePath(filename); - - unsigned char* imageBuffer = stbi_load(path.c_str(), &texWidth, &texHeight, &texComponents, 0); - if (!imageBuffer) - throw std::runtime_error("failed to load image from file: " + path); + ImageReader reader; + reader.LoadFromFile(filename); // Initialize source image descriptor to upload image data onto hardware texture - LLGL::ImageView imageView; - { - // Set image color format - imageView.format = (texComponents == 4 ? LLGL::ImageFormat::RGBA : LLGL::ImageFormat::RGB); - - // Set image data type (unsigned char = 8-bit unsigned integer) - imageView.dataType = LLGL::DataType::UInt8; - - // Set image buffer source for texture initial data - imageView.data = imageBuffer; - - // Set image buffer size - imageView.dataSize = static_cast(texWidth*texHeight*texComponents); - } + LLGL::ImageView imageView = reader.GetImageView(); // Upload image data onto hardware texture and stop the time timer.Start(); @@ -151,7 +133,7 @@ class Example_Texturing : public ExampleBase texDesc.format = LLGL::Format::BGRA8UNorm;//RGBA8UNorm; //BGRA8UNorm // Texture size - texDesc.extent = { static_cast(texWidth), static_cast(texHeight), 1u }; + texDesc.extent = reader.GetTextureDesc().extent; // Generate all MIP-map levels for this texture texDesc.miscFlags = LLGL::MiscFlags::GenerateMips; @@ -160,9 +142,6 @@ class Example_Texturing : public ExampleBase } double texCreationTime = static_cast(timer.Stop()) / static_cast(timer.GetFrequency()); LLGL::Log::Printf("texture creation time: %f ms\n", texCreationTime * 1000.0); - - // Release image data - stbi_image_free(imageBuffer); } void LoadCompressedTexture(const std::string& filename)