From a0b2c6355dc55e744cbb3ea3e3298d560a091baf Mon Sep 17 00:00:00 2001 From: Ilya Doroshenko Date: Tue, 28 Apr 2026 14:36:07 +0200 Subject: [PATCH 1/3] Conan integration attempt --- CMakeLists.txt | 5 + cmake/conan.cmake | 186 +++++++++++++++++++++ conanfile.py | 27 +++ include/nbl/config/BuildConfigOptions.h.in | 2 + src/nbl/CMakeLists.txt | 19 +++ 5 files changed, 239 insertions(+) create mode 100644 cmake/conan.cmake create mode 100644 conanfile.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ba3410075..57ba67f609 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,7 @@ option(NBL_STATIC_BUILD "" OFF) # ON for static builds, OFF for shared option(NBL_COMPILER_DYNAMIC_RUNTIME "" ON) option(NBL_SANITIZE_ADDRESS OFF) option(NBL_DEBUG_RTC_ENABLED "Enable Runtime Checks for Debug builds" OFF) +option(NBL_USE_CONAN "Use Conan to fetch dependencies" ON) set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT $<$:ProgramDatabase>) # ignored on non xMSVC-ABI targets @@ -62,6 +63,10 @@ else() endif() endif() +if (NBL_USE_CONAN) + include(cmake/conan.cmake) +endif() + find_package(Vulkan) if (Vulkan_FOUND) message(STATUS "Found Vulkan SDK") diff --git a/cmake/conan.cmake b/cmake/conan.cmake new file mode 100644 index 0000000000..26edaa8016 --- /dev/null +++ b/cmake/conan.cmake @@ -0,0 +1,186 @@ +# Check if Conan is present in the system +find_program(CONAN_EXECUTABLE conan) +if (NOT CONAN_EXECUTABLE) + # Conan is not found, install it using PIP + find_program(PIP_EXECUTABLE pip) + if (NOT PIP_EXECUTABLE) + message(FATAL_ERROR "Pip executable not found. Please install python and add it to PATH to proceed with Conan installation.") + endif() + + # Install conan using pip + execute_process( + COMMAND ${PIP_EXECUTABLE} install conan + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + RESULT_VARIABLE pip_result + ) + + if (NOT pip_result EQUAL "0") + message(FATAL_ERROR "Failed to install Conan using pip. Please check your Python and pip installation.") + endif() + + # After installation, try to find Conan again + find_program(CONAN_EXECUTABLE conan) + if (NOT CONAN_EXECUTABLE) + message(FATAL_ERROR "Conan executable not found. Please ensure Conan is in your PATH.") + endif() +endif() + +# Determine the Conan compiler name based on the CMake compiler ID +if (NOT CONAN_COMPILER) + if(CMAKE_CXX_COMPILER_ID MATCHES "AppleClang") + set(CONAN_COMPILER "apple-clang") + elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CONAN_COMPILER "clang") + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + set(CONAN_COMPILER "msvc") + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(CONAN_COMPILER "gcc") + endif() +endif() + +# Extract the major version number from the CMake compiler version +if (NOT CONAN_COMPILER_VERSION) + if (CONAN_COMPILER STREQUAL "msvc") + message (STATUS "Detected MSVC version: ${CMAKE_CXX_COMPILER_VERSION}") + + # special handling for msvc to extract the major version (e.g., 143 from 14.3.0) + string (REGEX MATCH "^[0-9]+\\.[0-9]" MSVC_VERSION_MATCH ${CMAKE_CXX_COMPILER_VERSION}) + + # remove the dot to get the major version (e.g., 143 from 14.3) + string (REPLACE "." "" CONAN_COMPILER_VERSION ${MSVC_VERSION_MATCH}) + else() + string(REGEX MATCH "^[0-9]+" CONAN_COMPILER_VERSION ${CMAKE_CXX_COMPILER_VERSION}) + endif() +endif() + +# Map Architectures to Conan's expected values +if (NOT CONAN_ARCH) + if (CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64|AMD64") + set(CONAN_ARCH "x86_64") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "i386|i686") + set(CONAN_ARCH "x86") + else () + message(WARNING "Unknown architecture ${CMAKE_SYSTEM_PROCESSOR}, using it directly for Conan") + set(CONAN_ARCH ${CMAKE_SYSTEM_PROCESSOR}) + endif() +endif() + +# Map CXX_STANDARD to Conan's expected value +if (NOT CONAN_CXX_STANDARD) + if (CMAKE_CXX_STANDARD) + set(CONAN_CXX_STANDARD ${CMAKE_CXX_STANDARD}) + else() + message(WARNING "CMAKE_CXX_STANDARD is not set, defaulting to C++20 for Conan profile") + set(CONAN_CXX_STANDARD 20) # Default to C++17 if not specified + endif() +endif() + +# Determine OS-specific Conan settings +if(WIN32) + set(CONAN_OS_SPECIFIC "compiler.runtime=dynamic") + + # Append runtime version for Clang-cl + if(MSVC_TOOLSET_VERSION AND NOT CONAN_COMPILER STREQUAL "msvc") + set (CONAN_OS_SPECIFIC "${CONAN_OS_SPECIFIC}\ncompiler.runtime_version=v${MSVC_TOOLSET_VERSION}") + endif() + +elseif(APPLE) + # macOS always uses LLVM's libc++ + set(CONAN_OS_SPECIFIC "compiler.libcxx=libc++") + +elseif(UNIX) + # On Linux, determine if Clang was forced to use libc++, otherwise default to libstdc++11 + set(CONAN_LIBCXX "libstdc++11") + + if(CONAN_COMPILER STREQUAL "clang") + # Check if the developer passed -stdlib=libc++ in the CMake cache or env + if(CMAKE_CXX_FLAGS MATCHES "-stdlib=libc\\+\\+") + set(CONAN_LIBCXX "libc++") + endif() + endif() + + set(CONAN_OS_SPECIFIC "compiler.libcxx=${CONAN_LIBCXX}") +endif() + +# Set the Conan profile content +set(CONAN_PROFILE_PATH "${CMAKE_BINARY_DIR}/conan_profile.txt") +set(CONAN_PROFILE "[settings] +os=${CMAKE_SYSTEM_NAME} +arch=${CONAN_ARCH} +compiler=${CONAN_COMPILER} +compiler.version=${CONAN_COMPILER_VERSION} +compiler.cppstd=${CONAN_CXX_STANDARD} +${CONAN_OS_SPECIFIC} + +[conf] +tools.cmake.cmaketoolchain:generator=${CMAKE_GENERATOR} +") + +# Make a message with file contents +message(STATUS "Generating Conan profile at ${CONAN_PROFILE_PATH}...") +message(STATUS "Conan profile content:\n${CONAN_PROFILE}") + +file(WRITE ${CONAN_PROFILE_PATH} ${CONAN_PROFILE}) + +if (CMAKE_GENERATOR MATCHES "Ninja Multi-Config" OR CMAKE_GENERATOR MATCHES "Visual Studio") + if (CMAKE_CONFIGURATION_TYPES) + set(CONAN_CONFIGS ${CMAKE_CONFIGURATION_TYPES}) + # remove MinSizeRel if it exists, and map it to Release + + else() + set(CONAN_CONFIGS "Debug;Release;RelWithDebInfo") + endif() + + list(REMOVE_ITEM CONAN_CONFIGS "MinSizeRel") +else() + if(NOT CMAKE_BUILD_TYPE) + message(WARNING "CMAKE_BUILD_TYPE is not set, defaulting to Release for Conan.") + set(CMAKE_BUILD_TYPE "Release") + endif() + set(CONAN_CONFIGS ${CMAKE_BUILD_TYPE}) +endif() + +set(CMAKE_MAP_IMPORTED_CONFIG_MINSIZEREL "Release" CACHE STRING "Fallback to Release dependencies for MinSizeRel" FORCE) + +# Run Conan install for each configuration +foreach(CONFIG IN LISTS CONAN_CONFIGS) + message(STATUS "Running Conan install for build type: ${CONFIG}...") + + # Pass build_type dynamically + set(CONAN_CLI_ARGS + "-s:h" "build_type=${CONFIG}" + "-s:b" "build_type=Release" + ) + + # Inject MSVC runtime type dynamically for Windows + if(WIN32) + if(CONFIG STREQUAL "Debug") + set(MSVC_RUNTIME_TYPE "Debug") + else() + set(MSVC_RUNTIME_TYPE "Release") + endif() + + list(APPEND CONAN_CLI_ARGS + "-s:h" "compiler.runtime_type=${MSVC_RUNTIME_TYPE}" + "-s:b" "compiler.runtime_type=Release" + ) + endif() + + execute_process( + COMMAND ${CONAN_EXECUTABLE} install ${CMAKE_SOURCE_DIR} + --profile:all ${CONAN_PROFILE_PATH} + ${CONAN_CLI_ARGS} + --build=missing + -cc core.graph:compatibility_mode=optimized + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + RESULT_VARIABLE conan_result + ) + + if(NOT conan_result EQUAL "0") + message(FATAL_ERROR "Conan install failed for configuration: ${CONFIG}!") + endif() +endforeach() + +if(NOT conan_result EQUAL "0") + message(FATAL_ERROR "Conan install failed!") +endif() diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 0000000000..9d10cb9a6e --- /dev/null +++ b/conanfile.py @@ -0,0 +1,27 @@ +import json +from conan import ConanFile +from conan.tools.cmake import cmake_layout, CMakeToolchain, CMakeDeps + +class VeritasProject(ConanFile): + settings = "os", "compiler", "build_type", "arch" + + default_options = { + "libyuv/*:with_jpeg":False + } + + def requirements(self): + self.requires("libavif/1.4.1") + + def layout(self): + cmake_layout(self) + + def generate(self): + deps = CMakeDeps(self) + deps.configuration_types = ["Debug", "Release", "RelWithDebInfo"] + deps.build_context_activated = ["Release"] + deps.generate() + + tc = CMakeToolchain(self) + # Redirect Conan's preset output so CMake doesn't load it as the default User file + tc.user_presets_path = "" + tc.generate() \ No newline at end of file diff --git a/include/nbl/config/BuildConfigOptions.h.in b/include/nbl/config/BuildConfigOptions.h.in index d130ff4ce2..9b2b552e72 100644 --- a/include/nbl/config/BuildConfigOptions.h.in +++ b/include/nbl/config/BuildConfigOptions.h.in @@ -25,6 +25,7 @@ #cmakedefine _NBL_COMPILE_WITH_BAW_LOADER_ #cmakedefine _NBL_COMPILE_WITH_JPG_LOADER_ #cmakedefine _NBL_COMPILE_WITH_PNG_LOADER_ +#cmakedefine _NBL_COMPILE_WITH_AVIF_LOADER_ #cmakedefine _NBL_COMPILE_WITH_TGA_LOADER_ #ifdef _NBL_COMPILE_WITH_OPEN_EXR_ // ? TODO #cmakedefine _NBL_COMPILE_WITH_OPENEXR_LOADER_ @@ -41,6 +42,7 @@ #cmakedefine _NBL_COMPILE_WITH_TGA_WRITER_ #cmakedefine _NBL_COMPILE_WITH_JPG_WRITER_ #cmakedefine _NBL_COMPILE_WITH_PNG_WRITER_ +#cmakedefine _NBL_COMPILE_WITH_AVIF_WRITER_ #ifdef _NBL_COMPILE_WITH_OPEN_EXR_ // ? TODO #cmakedefine _NBL_COMPILE_WITH_OPENEXR_WRITER_ #endif diff --git a/src/nbl/CMakeLists.txt b/src/nbl/CMakeLists.txt index 9c994bfa41..6b48b2ba8b 100644 --- a/src/nbl/CMakeLists.txt +++ b/src/nbl/CMakeLists.txt @@ -44,9 +44,20 @@ if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${NBL_ROOT_PATH}/install/${PLATFORM}" CACHE PATH "Install path" FORCE) endif() +if (NBL_USE_CONAN) + # convert to lowercase + if (NOT CMAKE_GENERATOR MATCHES "Visual Studio") + list(APPEND CMAKE_PREFIX_PATH "${CMAKE_SOURCE_DIR}/build/generators") + else() + list(APPEND CMAKE_PREFIX_PATH "${CMAKE_SOURCE_DIR}/build/${CMAKE_BUILD_TYPE_LOWER}/generators") + endif() +endif() + # include utility macros/functions include(common) +find_package(libavif CONFIG REQUIRED) + #[[ Loaders and writers compile options available to edit by user All revelant _NBL_COMPILE_WITH will be there]] option(_NBL_COMPILE_WITH_MTL_LOADER_ "Compile with MTL Loader" OFF) #default off until Material Compiler 2 @@ -60,6 +71,8 @@ option(_NBL_COMPILE_WITH_JPG_LOADER_ "Compile with JPG Loader" ON) option(_NBL_COMPILE_WITH_JPG_WRITER_ "Compile with JPG Writer" ON) option(_NBL_COMPILE_WITH_PNG_LOADER_ "Compile with PNG Loader" ON) option(_NBL_COMPILE_WITH_PNG_WRITER_ "Compile with PNG Writer" ON) +option(_NBL_COMPILE_WITH_AVIF_LOADER_ "Compile with AVIF Loader" ON) +option(_NBL_COMPILE_WITH_AVIF_WRITER_ "Compile with AVIF Loader" ON) option(_NBL_COMPILE_WITH_TGA_LOADER_ "Compile with TGA Loader" ON) option(_NBL_COMPILE_WITH_TGA_WRITER_ "Compile with TGA Writer" ON) option(_NBL_COMPILE_WITH_OPENEXR_LOADER_ "Compile with OpenEXR Loader" ON) @@ -575,6 +588,12 @@ else() endif() list(APPEND PUBLIC_BUILD_INCLUDE_DIRS ${THIRD_PARTY_SOURCE_DIR}/libpng) +# libavif +if (NBL_STATIC_BUILD) + target_link_libraries(Nabla INTERFACE avif) +else() + target_link_libraries(Nabla PRIVATE avif) +endif() # OpenEXR if (_NBL_COMPILE_WITH_OPEN_EXR_) From 58d8e3629d38c278de5196cc77f9c75a028292e2 Mon Sep 17 00:00:00 2001 From: Ilya Doroshenko Date: Tue, 28 Apr 2026 17:14:46 +0200 Subject: [PATCH 2/3] Fixed Ninja Multi-Config --- conanfile.py | 9 ++++----- src/nbl/CMakeLists.txt | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/conanfile.py b/conanfile.py index 9d10cb9a6e..edfbdd8de2 100644 --- a/conanfile.py +++ b/conanfile.py @@ -1,12 +1,13 @@ import json from conan import ConanFile -from conan.tools.cmake import cmake_layout, CMakeToolchain, CMakeDeps +from conan.tools.cmake import cmake_layout, CMakeToolchain, CMakeConfigDeps class VeritasProject(ConanFile): settings = "os", "compiler", "build_type", "arch" default_options = { - "libyuv/*:with_jpeg":False + "libyuv/*:with_jpeg":False, + "dav1d/*:with_tools":False } def requirements(self): @@ -16,9 +17,7 @@ def layout(self): cmake_layout(self) def generate(self): - deps = CMakeDeps(self) - deps.configuration_types = ["Debug", "Release", "RelWithDebInfo"] - deps.build_context_activated = ["Release"] + deps = CMakeConfigDeps(self) deps.generate() tc = CMakeToolchain(self) diff --git a/src/nbl/CMakeLists.txt b/src/nbl/CMakeLists.txt index 6b48b2ba8b..7fb1eff0c5 100644 --- a/src/nbl/CMakeLists.txt +++ b/src/nbl/CMakeLists.txt @@ -46,7 +46,7 @@ endif() if (NBL_USE_CONAN) # convert to lowercase - if (NOT CMAKE_GENERATOR MATCHES "Visual Studio") + if (CMAKE_GENERATOR MATCHES "Visual Studio" OR CMAKE_GENERATOR MATCHES "Ninja Multi-Config") list(APPEND CMAKE_PREFIX_PATH "${CMAKE_SOURCE_DIR}/build/generators") else() list(APPEND CMAKE_PREFIX_PATH "${CMAKE_SOURCE_DIR}/build/${CMAKE_BUILD_TYPE_LOWER}/generators") From 9e769c6d3a68490b6623550c33ff67b4f318a3e6 Mon Sep 17 00:00:00 2001 From: Ilya Doroshenko Date: Tue, 5 May 2026 10:45:11 +0200 Subject: [PATCH 3/3] Added an AVIF software decoder --- src/nbl/CMakeLists.txt | 5 +- src/nbl/asset/IAssetManager.cpp | 7 + .../asset/interchange/CImageLoaderAVIF.cpp | 187 ++++++++++++++++++ src/nbl/asset/interchange/CImageLoaderAVIF.h | 57 ++++++ 4 files changed, 254 insertions(+), 2 deletions(-) create mode 100644 src/nbl/asset/interchange/CImageLoaderAVIF.cpp create mode 100644 src/nbl/asset/interchange/CImageLoaderAVIF.h diff --git a/src/nbl/CMakeLists.txt b/src/nbl/CMakeLists.txt index 7fb1eff0c5..4e58ba4953 100644 --- a/src/nbl/CMakeLists.txt +++ b/src/nbl/CMakeLists.txt @@ -49,7 +49,7 @@ if (NBL_USE_CONAN) if (CMAKE_GENERATOR MATCHES "Visual Studio" OR CMAKE_GENERATOR MATCHES "Ninja Multi-Config") list(APPEND CMAKE_PREFIX_PATH "${CMAKE_SOURCE_DIR}/build/generators") else() - list(APPEND CMAKE_PREFIX_PATH "${CMAKE_SOURCE_DIR}/build/${CMAKE_BUILD_TYPE_LOWER}/generators") + list(APPEND CMAKE_PREFIX_PATH "${CMAKE_SOURCE_DIR}/build/${CMAKE_BUILD_TYPE}/generators") endif() endif() @@ -72,7 +72,7 @@ option(_NBL_COMPILE_WITH_JPG_WRITER_ "Compile with JPG Writer" ON) option(_NBL_COMPILE_WITH_PNG_LOADER_ "Compile with PNG Loader" ON) option(_NBL_COMPILE_WITH_PNG_WRITER_ "Compile with PNG Writer" ON) option(_NBL_COMPILE_WITH_AVIF_LOADER_ "Compile with AVIF Loader" ON) -option(_NBL_COMPILE_WITH_AVIF_WRITER_ "Compile with AVIF Loader" ON) +option(_NBL_COMPILE_WITH_AVIF_WRITER_ "Compile with AVIF Writer" ON) option(_NBL_COMPILE_WITH_TGA_LOADER_ "Compile with TGA Loader" ON) option(_NBL_COMPILE_WITH_TGA_WRITER_ "Compile with TGA Writer" ON) option(_NBL_COMPILE_WITH_OPENEXR_LOADER_ "Compile with OpenEXR Loader" ON) @@ -227,6 +227,7 @@ set(NBL_ASSET_SOURCES # Image loaders asset/interchange/IImageLoader.cpp + asset/interchange/CImageLoaderAVIF.cpp asset/interchange/CImageLoaderJPG.cpp asset/interchange/CImageLoaderPNG.cpp asset/interchange/CImageLoaderTGA.cpp diff --git a/src/nbl/asset/IAssetManager.cpp b/src/nbl/asset/IAssetManager.cpp index 29930bccd9..7ad883407a 100644 --- a/src/nbl/asset/IAssetManager.cpp +++ b/src/nbl/asset/IAssetManager.cpp @@ -35,6 +35,10 @@ #include "nbl/asset/interchange/CImageLoaderJPG.h" #endif +#ifdef _NBL_COMPILE_WITH_AVIF_LOADER_ +#include "nbl/asset/interchange/CImageLoaderAVIF.h" +#endif + #ifdef _NBL_COMPILE_WITH_PNG_LOADER_ #include "nbl/asset/interchange/CImageLoaderPNG.h" #endif @@ -143,6 +147,9 @@ void IAssetManager::addLoadersAndWriters() #ifdef _NBL_COMPILE_WITH_PNG_LOADER_ addAssetLoader(core::make_smart_refctd_ptr()); #endif +#ifdef _NBL_COMPILE_WITH_AVIF_LOADER_ + addAssetLoader(core::make_smart_refctd_ptr()); +#endif #ifdef _NBL_COMPILE_WITH_OPENEXR_LOADER_ addAssetLoader(core::make_smart_refctd_ptr(this)); #endif diff --git a/src/nbl/asset/interchange/CImageLoaderAVIF.cpp b/src/nbl/asset/interchange/CImageLoaderAVIF.cpp new file mode 100644 index 0000000000..9469123f79 --- /dev/null +++ b/src/nbl/asset/interchange/CImageLoaderAVIF.cpp @@ -0,0 +1,187 @@ +// Copyright (C) 2023 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h + +#include "nbl/asset/compile_config.h" + +#include "CImageLoaderAVIF.h" + +#ifdef _NBL_COMPILE_WITH_AVIF_LOADER_ + +#include "nbl/system/IFile.h" + +#include "nbl/asset/ICPUBuffer.h" +#include "nbl/asset/ICPUImageView.h" + +#include "nbl/asset/interchange/CImageHasher.h" +#include "nbl/asset/interchange/IImageAssetHandlerBase.h" + +#include + +#include +#include + +namespace nbl +{ + namespace asset + { + + //! constructor + CImageLoaderAVIF::CImageLoaderAVIF() + { +#ifdef _NBL_DEBUG + setDebugName("CImageLoaderAVIF"); +#endif + } + + + + //! destructor + CImageLoaderAVIF::~CImageLoaderAVIF() + {} + + //! returns true if the file maybe is able to be loaded by this class + bool CImageLoaderAVIF::isALoadableFileFormat(system::IFile* _file, const system::logger_opt_ptr logger) const + { + if (!_file) + return false; + + uint8_t buffer[64]; + system::IFile::success_t success; + _file->read(success, buffer, 0, sizeof(buffer)); + if (!success) + return false; + + avifROData roData = { buffer, sizeof(buffer) }; + return avifPeekCompatibleFileType(&roData) == AVIF_TRUE; + } + + //! creates a surface from the file + asset::SAssetBundle CImageLoaderAVIF::loadAsset(system::IFile* _file, const asset::IAssetLoader::SAssetLoadParams& _params, asset::IAssetLoader::IAssetLoaderOverride* _override, uint32_t _hierarchyLevel) + { + if (!_file || _file->getSize() > 0xffffffffull) + return {}; + + const std::filesystem::path& Filename = _file->getFileName(); + + std::size_t fileSize = _file->getSize(); + std::unique_ptr input{ new(std::nothrow)uint8_t[fileSize] }; + if (!input) + return {}; + + system::IFile::success_t success; + _file->read(success, input.get(), 0, fileSize); + if (!success) + return {}; + + avifDecoder* decoder = avifDecoderCreate(); + if (!decoder) + return {}; + auto exiter = core::makeRAIIExiter([decoder]() { avifDecoderDestroy(decoder); }); + + avifResult result = avifDecoderSetIOMemory(decoder, reinterpret_cast(input.get()), fileSize); + if (result != AVIF_RESULT_OK) + { + _params.logger.log("Error during avifDecoderSetIOMemory: %s", system::ILogger::ELL_ERROR, avifResultToString(result)); + return {}; + } + + result = avifDecoderParse(decoder); + if (result != AVIF_RESULT_OK) + { + _params.logger.log("Error during avifDecoderParse: %s", system::ILogger::ELL_ERROR, avifResultToString(result)); + return {}; + } + + result = avifDecoderNextImage(decoder); + if (result != AVIF_RESULT_OK && result != AVIF_RESULT_WAITING_ON_IO) + { + _params.logger.log("Error during avifDecoderNextImage: %s", system::ILogger::ELL_ERROR, avifResultToString(result)); + return {}; + } + + const uint32_t width = decoder->image->width; + const uint32_t height = decoder->image->height; + + ICPUImage::SCreationParams imgInfo; + imgInfo.type = ICPUImage::ET_2D; + imgInfo.extent.width = width; + imgInfo.extent.height = height; + imgInfo.extent.depth = 1u; + imgInfo.mipLevels = 1u; + imgInfo.arrayLayers = 1u; + imgInfo.samples = ICPUImage::E_SAMPLE_COUNT_FLAGS::ESCF_1_BIT; + imgInfo.flags = static_cast(0u); + + avifRGBImage rgb; + avifRGBImageSetDefaults(&rgb, decoder->image); + + + imgInfo.format = EF_R8G8B8A8_SRGB; + rgb.format = AVIF_RGB_FORMAT_RGBA; + + if (decoder->image->depth > 8) + { + imgInfo.format = EF_R16G16B16A16_UNORM; + rgb.depth = 16; + } + else + { + rgb.depth = 8; + } + + CImageHasher contentHasher(imgInfo); + + auto regions = core::make_refctd_dynamic_array>(1u); + ICPUImage::SBufferCopy& region = regions->front(); + region.imageSubresource.aspectMask = IImage::E_ASPECT_FLAGS::EAF_COLOR_BIT; + region.imageSubresource.mipLevel = 0u; + region.imageSubresource.baseArrayLayer = 0u; + region.imageSubresource.layerCount = 1u; + region.bufferOffset = 0u; + region.bufferRowLength = asset::IImageAssetHandlerBase::calcPitchInBlocks(width, getTexelOrBlockBytesize(imgInfo.format)); + region.bufferImageHeight = 0u; //tightly packed + region.imageOffset = { 0u, 0u, 0u }; + region.imageExtent = imgInfo.extent; + + uint32_t bpp = rgb.depth == 8 ? 4 : 8; // RGBA8 vs RGBA16 + uint32_t rowspan = region.bufferRowLength * bpp; + + auto buffer = asset::ICPUBuffer::create({ rowspan * height }); + if (!buffer) + return {}; + + rgb.pixels = reinterpret_cast(buffer->getPointer()); + rgb.rowBytes = rowspan; + + result = avifImageYUVToRGB(decoder->image, &rgb); + if (result != AVIF_RESULT_OK) + { + _params.logger.log("Error during avifImageYUVToRGB: %s", system::ILogger::ELL_ERROR, avifResultToString(result)); + return {}; + } + + uint8_t* pixels = rgb.pixels; + for (uint32_t y = 0; y < height; ++y) + { + contentHasher.partialHash(0, 0, pixels + (y * rowspan), rowspan); + } + contentHasher.hashSeq(0, 0); + + core::smart_refctd_ptr image = ICPUImage::create(std::move(imgInfo)); + image->setBufferAndRegions(std::move(buffer), regions); + + auto hash = contentHasher.finalizeSeq(); + image->setContentHash(hash); + + return SAssetBundle(nullptr, { image }); + } + + } // end namespace asset +} // end namespace nbl + +#endif + + + + diff --git a/src/nbl/asset/interchange/CImageLoaderAVIF.h b/src/nbl/asset/interchange/CImageLoaderAVIF.h new file mode 100644 index 0000000000..1ef41ba1b8 --- /dev/null +++ b/src/nbl/asset/interchange/CImageLoaderAVIF.h @@ -0,0 +1,57 @@ +// Copyright (C) 2023 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h + +#ifndef __NBL_ASSET_C_IMAGE_LOADER_AVIF_H_INCLUDED__ +#define __NBL_ASSET_C_IMAGE_LOADER_AVIF_H_INCLUDED__ + +#include "nbl/core/declarations.h" + +#ifdef _NBL_COMPILE_WITH_AVIF_LOADER_ + +#include "nbl/asset/interchange/IAssetLoader.h" + + +namespace nbl +{ + namespace asset + { + + //! Surface Loader for AVIF images + class CImageLoaderAVIF : public asset::IAssetLoader + { + public: + struct SContext + { + char* filename = nullptr; + system::logger_opt_ptr logger = nullptr; + }; + private: + protected: + //! destructor + virtual ~CImageLoaderAVIF(); + + public: + //! constructor + CImageLoaderAVIF(); + + virtual bool isALoadableFileFormat(system::IFile* _file, const system::logger_opt_ptr logger) const override; + + virtual const char** getAssociatedFileExtensions() const override + { + static const char* ext[]{ "avif", nullptr }; + return ext; + } + + virtual uint64_t getSupportedAssetTypesBitfield() const override { return asset::IAsset::ET_IMAGE; } + + virtual asset::SAssetBundle loadAsset(system::IFile* _file, const asset::IAssetLoader::SAssetLoadParams& _params, asset::IAssetLoader::IAssetLoaderOverride* _override = nullptr, uint32_t _hierarchyLevel = 0u) override; + }; + + } // end namespace video +} // end namespace nbl + + +#endif +#endif +