diff --git a/Source/Engine/Content/Assets/Texture.cpp b/Source/Engine/Content/Assets/Texture.cpp index 4e3aec94d..6d8ad9f05 100644 --- a/Source/Engine/Content/Assets/Texture.cpp +++ b/Source/Engine/Content/Assets/Texture.cpp @@ -186,3 +186,34 @@ Texture* Texture::FromFile(const StringView& path, bool generateMips) } return texture; } + +bool Texture::LoadMemory(Span data, ImageFormat format, bool generateMips) +{ + if (!IsVirtual()) + { + LOG(Error, "Loading image from file is supported only for virtual textures."); + return true; + } + + TextureData textureData; + if (TextureTool::ImportTexture(data, format, textureData)) + { + return true; + } + + auto initData = New(); + initData->FromTextureData(textureData, generateMips); + + return Init(initData); +} + +Texture* Texture::FromMemory(Span data, ImageFormat format, bool generateMips) +{ + auto texture = Content::CreateVirtualAsset(); + if (texture->LoadMemory(data, format, generateMips)) + { + texture->DeleteObject(); + texture = nullptr; + } + return texture; +} diff --git a/Source/Engine/Content/Assets/Texture.h b/Source/Engine/Content/Assets/Texture.h index b8ef5a01c..18e150ef8 100644 --- a/Source/Engine/Content/Assets/Texture.h +++ b/Source/Engine/Content/Assets/Texture.h @@ -49,6 +49,25 @@ public: /// The loaded texture (virtual asset) or null if fails. API_FUNCTION() static Texture* FromFile(const StringView& path, bool generateMips = false); + /// + /// Loads the texture from the image memory. Supported data formats depend on a runtime platform. All platform support loading PNG, BMP, TGA, HDR and JPEG files. + /// + /// Valid only for virtual assets. + /// The source image file data (eg. raw PNG file contents). + /// The source image file format (from extension). + /// True if generate mipmaps for the imported texture. + /// True if fails, otherwise false. + API_FUNCTION() bool LoadMemory(Span data, ImageFormat format, bool generateMips = false); + + /// + /// Loads the texture from the image memory and creates the virtual texture asset for it. Supported data formats depend on a runtime platform. All platform support loading PNG, BMP, TGA, HDR and JPEG files. + /// + /// The source image file data (eg. raw PNG file contents). + /// The source image file format (from extension). + /// True if generate mipmaps for the imported texture. + /// The loaded texture (virtual asset) or null if fails. + API_FUNCTION() static Texture* FromMemory(Span data, ImageFormat format, bool generateMips = false); + public: // [TextureBase] #if USE_EDITOR diff --git a/Source/Engine/Graphics/Textures/ITextureOwner.h b/Source/Engine/Graphics/Textures/ITextureOwner.h index 2d6cbb60c..516f12b0e 100644 --- a/Source/Engine/Graphics/Textures/ITextureOwner.h +++ b/Source/Engine/Graphics/Textures/ITextureOwner.h @@ -8,6 +8,35 @@ class GPUTexture; class Task; +/// +/// Enumeration of different image formats. +/// +API_ENUM() enum class ImageFormat +{ + // .dds + DDS, + // .tga + TGA, + // .png + PNG, + // .bmp + BMP, + // .gif + GIF, + // .tiff + TIFF, + // .jpg/.jpeg + JPEG, + // .hdr + HDR, + // .raw + RAW, + // .exr + EXR, + // Custom + Internal, +}; + /// /// Interface for objects that can manage streamable texture /// diff --git a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp index 679df790e..83ea45e39 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp @@ -6,7 +6,6 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Math/Math.h" #include "Engine/Core/Config/GraphicsSettings.h" -#include "Engine/Platform/File.h" #include "Engine/Platform/CriticalSection.h" #include "Engine/Platform/ConditionVariable.h" #include "Engine/Graphics/RenderTools.h" @@ -290,21 +289,13 @@ bool TextureTool::ExportTextureDirectXTex(ImageType type, const StringView& path return false; } -HRESULT LoadFromRAWFile(const StringView& path, DirectX::ScratchImage& image) +HRESULT LoadFromRAWFile(Span bytes, DirectX::ScratchImage& image) { // Assume 16-bit, grayscale .RAW file in little-endian byte order - // Load raw bytes from file - Array data; - if (File::ReadAllBytes(path, data)) - { - LOG(Warning, "Failed to load file data."); - return ERROR_PATH_NOT_FOUND; - } - // Check size - const auto size = (int32)Math::Sqrt(data.Count() / 2.0f); - if (data.Count() != size * size * 2) + const auto size = (int32)Math::Sqrt(bytes.Length() / 2.0f); + if (bytes.Length() != size * size * 2) { LOG(Warning, "Invalid RAW file data size or format. Use 16-bit .RAW file in little-endian byte order (square dimensions)."); return ERROR_BAD_FORMAT; @@ -315,25 +306,24 @@ HRESULT LoadFromRAWFile(const StringView& path, DirectX::ScratchImage& image) img.format = DXGI_FORMAT_R16_UNORM; img.width = size; img.height = size; - img.rowPitch = data.Count() / size; - img.slicePitch = data.Count(); + img.rowPitch = bytes.Length() / size; + img.slicePitch = bytes.Length(); // Link data - img.pixels = data.Get(); + img.pixels = bytes.Get(); // Init return image.InitializeFromImage(img); } -HRESULT LoadFromEXRFile(const StringView& path, DirectX::ScratchImage& image) +HRESULT LoadFromEXRFile(Span bytes, DirectX::ScratchImage& image) { #if USE_EDITOR // Load exr file - AnsiPathTempFile tempFile(path); float* pixels; int width, height; const char* err = nullptr; - int ret = LoadEXR(&pixels, &width, &height, tempFile.Path.Get(), &err); + int ret = LoadEXRFromMemory(&pixels, &width, &height, bytes.Get(), bytes.Length(), &err); if (ret != TINYEXR_SUCCESS) { if (err) @@ -368,7 +358,7 @@ HRESULT LoadFromEXRFile(const StringView& path, DirectX::ScratchImage& image) #endif } -bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path, TextureData& textureData, bool& hasAlpha) +bool TextureTool::ImportTextureDirectXTex(ImageType type, Span bytes, TextureData& textureData, bool& hasAlpha) { // Load image data DirectX::ScratchImage image; @@ -380,22 +370,22 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path case ImageType::TIFF: case ImageType::JPEG: case ImageType::PNG: - result = DirectX::LoadFromWICFile(*path, DirectX::WIC_FLAGS_NONE, nullptr, image); + result = DirectX::LoadFromWICMemory(bytes.Get(), bytes.Length(), DirectX::WIC_FLAGS_NONE, nullptr, image); break; case ImageType::DDS: - result = DirectX::LoadFromDDSFile(*path, DirectX::DDS_FLAGS_NONE, nullptr, image); + result = DirectX::LoadFromDDSMemory(bytes.Get(), bytes.Length(), DirectX::DDS_FLAGS_NONE, nullptr, image); break; case ImageType::TGA: - result = DirectX::LoadFromTGAFile(*path, nullptr, image); + result = DirectX::LoadFromTGAMemory(bytes.Get(), bytes.Length(), nullptr, image); break; case ImageType::HDR: - result = DirectX::LoadFromHDRFile(*path, nullptr, image); + result = DirectX::LoadFromHDRMemory(bytes.Get(), bytes.Length(), nullptr, image); break; case ImageType::RAW: - result = LoadFromRAWFile(path, image); + result = LoadFromRAWFile(bytes, image); break; case ImageType::EXR: - result = LoadFromEXRFile(path, image); + result = LoadFromEXRFile(bytes, image); break; default: result = DXGI_ERROR_INVALID_CALL; @@ -581,7 +571,7 @@ HRESULT CustomGenerateMipMaps(const DirectX::Image* srcImages, size_t nimages, c return S_OK; } -bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path, TextureData& textureData, const Options& options, String& errorMsg, bool& hasAlpha) +bool TextureTool::ImportTextureDirectXTex(ImageType type, Span bytes, TextureData& textureData, const Options& options, String& errorMsg, bool& hasAlpha) { #define SET_CURRENT_IMG(x) currentImage = &x #define GET_TMP_IMG() (currentImage != &image1 ? image1 : image2) @@ -599,22 +589,22 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path case ImageType::TIFF: case ImageType::JPEG: case ImageType::PNG: - result = DirectX::LoadFromWICFile(*path, DirectX::WIC_FLAGS_NONE, nullptr, image1); + result = DirectX::LoadFromWICMemory(bytes.Get(), bytes.Length(), DirectX::WIC_FLAGS_NONE, nullptr, image1); break; case ImageType::DDS: - result = DirectX::LoadFromDDSFile(*path, DirectX::DDS_FLAGS_NONE, nullptr, image1); + result = DirectX::LoadFromDDSMemory(bytes.Get(), bytes.Length(), DirectX::DDS_FLAGS_NONE, nullptr, image1); break; case ImageType::TGA: - result = DirectX::LoadFromTGAFile(*path, nullptr, image1); + result = DirectX::LoadFromTGAMemory(bytes.Get(), bytes.Length(), nullptr, image1); break; case ImageType::HDR: - result = DirectX::LoadFromHDRFile(*path, nullptr, image1); + result = DirectX::LoadFromHDRMemory(bytes.Get(), bytes.Length(), nullptr, image1); break; case ImageType::RAW: - result = LoadFromRAWFile(path, image1); + result = LoadFromRAWFile(bytes, image1); break; case ImageType::EXR: - result = LoadFromEXRFile(path, image1); + result = LoadFromEXRFile(bytes, image1); break; case ImageType::Internal: { @@ -724,7 +714,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path } if (options.GenerateMipMaps && !isPowerOfTwo) { - LOG(Warning, "Cannot generate mip maps for texture '{}' that size is not power of two. Use Resize or Max Size to change dimensions.", StringUtils::GetFileName(path), width, height); + LOG(Warning, "Cannot generate mip maps for texture that size is not power of two ({}x{}). Use Resize or Max Size to change dimensions.", width, height); } // Allocate memory for texture data @@ -987,7 +977,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path } if (FAILED(result)) { - errorMsg = String::Format(TEXT("Cannot generate texture mip maps chain, error: {1:x}"), *path, static_cast(result)); + errorMsg = String::Format(TEXT("Cannot generate texture mip maps chain, error: {0:x}"), (uint32)result); return true; } SET_CURRENT_IMG(tmpImg); @@ -1002,7 +992,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path result = tmpImg.Initialize(info); if (FAILED(result)) { - errorMsg = String::Format(TEXT("Failed initialize image, error: {1:x}"), *path, static_cast(result)); + errorMsg = String::Format(TEXT("Failed initialize image, error: {0:x}"), (uint32)result); return true; } @@ -1014,7 +1004,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path result = ScaleMipMapsAlphaForCoverage(img, info.mipLevels, info, item, options.PreserveAlphaCoverageReference, tmpImg); if (FAILED(result)) { - errorMsg = String::Format(TEXT("Failed to scale mip maps alpha for coverage, error: {1:x}"), *path, static_cast(result)); + errorMsg = String::Format(TEXT("Failed to scale mip maps alpha for coverage, error: {0:x}"), (uint32)result); return true; } } diff --git a/Source/Engine/Tools/TextureTool/TextureTool.cpp b/Source/Engine/Tools/TextureTool/TextureTool.cpp index 4cb807c54..98f572924 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.cpp @@ -7,6 +7,7 @@ #include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Types/TimeSpan.h" #include "Engine/Core/Math/Vector2.h" +#include "Engine/Platform/File.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Serialization/JsonWriter.h" #include "Engine/Serialization/JsonTools.h" @@ -224,12 +225,20 @@ bool TextureTool::ImportTexture(const StringView& path, TextureData& textureData if (GetImageType(path, type)) return true; + // Load raw bytes from file + Array bytes; + if (File::ReadAllBytes(path, bytes)) + { + LOG(Warning, "Failed to load file data."); + return true; + } + // Import bool hasAlpha = false; #if COMPILE_WITH_DIRECTXTEX - const auto failed = ImportTextureDirectXTex(type, path, textureData, hasAlpha); + const auto failed = ImportTextureDirectXTex(type, ToSpan(bytes), textureData, hasAlpha); #elif COMPILE_WITH_STB - const auto failed = ImportTextureStb(type, path, textureData, hasAlpha); + const auto failed = ImportTextureStb(type, ToSpan(bytes), textureData, hasAlpha); #else const auto failed = true; LOG(Warning, "Importing textures is not supported on this platform."); @@ -269,6 +278,14 @@ bool TextureTool::ImportTexture(const StringView& path, TextureData& textureData return true; } + // Load raw bytes from file + Array bytes; + if (File::ReadAllBytes(path, bytes)) + { + LOG(Warning, "Failed to load file data."); + return true; + } + // Clamp values options.MaxSize = Math::Clamp(options.MaxSize, 1, GPU_MAX_TEXTURE_SIZE); options.SizeX = Math::Clamp(options.SizeX, 1, GPU_MAX_TEXTURE_SIZE); @@ -277,9 +294,9 @@ bool TextureTool::ImportTexture(const StringView& path, TextureData& textureData // Import bool hasAlpha = false; #if COMPILE_WITH_DIRECTXTEX - const auto failed = ImportTextureDirectXTex(type, path, textureData, options, errorMsg, hasAlpha); + const auto failed = ImportTextureDirectXTex(type, ToSpan(bytes), textureData, options, errorMsg, hasAlpha); #elif COMPILE_WITH_STB - const auto failed = ImportTextureStb(type, path, textureData, options, errorMsg, hasAlpha); + const auto failed = ImportTextureStb(type, ToSpan(bytes), textureData, options, errorMsg, hasAlpha); #else const auto failed = true; LOG(Warning, "Importing textures is not supported on this platform."); @@ -300,6 +317,22 @@ bool TextureTool::ImportTexture(const StringView& path, TextureData& textureData return failed; } +bool TextureTool::ImportTexture(const Span& data, ImageType type, TextureData& textureData) +{ + PROFILE_CPU(); + PROFILE_MEM(GraphicsTextures); + bool hasAlpha = false; +#if COMPILE_WITH_DIRECTXTEX + const auto failed = ImportTextureDirectXTex(type, data, textureData, hasAlpha); +#elif COMPILE_WITH_STB + const auto failed = ImportTextureStb(type, data, textureData, hasAlpha); +#else + const auto failed = true; + LOG(Warning, "Importing textures is not supported on this platform."); +#endif + return failed; +} + bool TextureTool::ExportTexture(const StringView& path, const TextureData& textureData) { PROFILE_CPU(); diff --git a/Source/Engine/Tools/TextureTool/TextureTool.h b/Source/Engine/Tools/TextureTool/TextureTool.h index 9dfd5dc84..8e60b8cb0 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.h +++ b/Source/Engine/Tools/TextureTool/TextureTool.h @@ -136,6 +136,8 @@ API_CLASS(Namespace="FlaxEngine.Tools", Static) class FLAXENGINE_API TextureTool void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; }; + typedef ImageFormat ImageType; + public: #if USE_EDITOR /// @@ -147,7 +149,7 @@ public: #endif /// - /// Imports the texture. + /// Imports the texture from file. /// /// The file path. /// The output data. @@ -155,7 +157,7 @@ public: static bool ImportTexture(const StringView& path, TextureData& textureData); /// - /// Imports the texture. + /// Imports the texture from file with custom options. /// /// The file path. /// The output data. @@ -164,6 +166,15 @@ public: /// True if fails, otherwise false. static bool ImportTexture(const StringView& path, TextureData& textureData, Options options, String& errorMsg); + /// + /// Imports the texture from memory. + /// + /// The file data. + /// The file type. + /// The output data. + /// True if fails, otherwise false. + static bool ImportTexture(const Span& data, ImageType type, TextureData& textureData); + /// /// Exports the texture. /// @@ -222,35 +233,20 @@ public: #endif private: - enum class ImageType - { - DDS, - TGA, - PNG, - BMP, - GIF, - TIFF, - JPEG, - HDR, - RAW, - EXR, - Internal, - }; - static bool GetImageType(const StringView& path, ImageType& type); static bool Transform(TextureData& texture, const Function& transformation); #if COMPILE_WITH_DIRECTXTEX static bool ExportTextureDirectXTex(ImageType type, const StringView& path, const TextureData& textureData); - static bool ImportTextureDirectXTex(ImageType type, const StringView& path, TextureData& textureData, bool& hasAlpha); - static bool ImportTextureDirectXTex(ImageType type, const StringView& path, TextureData& textureData, const Options& options, String& errorMsg, bool& hasAlpha); + static bool ImportTextureDirectXTex(ImageType type, Span bytes, TextureData& textureData, bool& hasAlpha); + static bool ImportTextureDirectXTex(ImageType type, Span bytes, TextureData& textureData, const Options& options, String& errorMsg, bool& hasAlpha); static bool ConvertDirectXTex(TextureData& dst, const TextureData& src, const PixelFormat dstFormat); static bool ResizeDirectXTex(TextureData& dst, const TextureData& src, int32 dstWidth, int32 dstHeight); #endif #if COMPILE_WITH_STB static bool ExportTextureStb(ImageType type, const StringView& path, const TextureData& textureData); - static bool ImportTextureStb(ImageType type, const StringView& path, TextureData& textureData, bool& hasAlpha); - static bool ImportTextureStb(ImageType type, const StringView& path, TextureData& textureData, const Options& options, String& errorMsg, bool& hasAlpha); + static bool ImportTextureStb(ImageType type, Span bytes, TextureData& textureData, bool& hasAlpha); + static bool ImportTextureStb(ImageType type, Span bytes, TextureData& textureData, const Options& options, String& errorMsg, bool& hasAlpha); static bool ConvertStb(TextureData& dst, const TextureData& src, const PixelFormat dstFormat); static bool ResizeStb(PixelFormat format, TextureMipData& dstMip, const TextureMipData& srcMip, int32 dstMipWidth, int32 dstMipHeight); static bool ResizeStb(TextureData& dst, const TextureData& src, int32 dstWidth, int32 dstHeight); diff --git a/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp b/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp index 28aa1e9f7..630b55291 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp @@ -339,15 +339,8 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const return result != 0; } -bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, TextureData& textureData, bool& hasAlpha) +bool TextureTool::ImportTextureStb(ImageType type, Span bytes, TextureData& textureData, bool& hasAlpha) { - Array fileData; - if (File::ReadAllBytes(path, fileData)) - { - LOG(Warning, "Failed to read data from file."); - return true; - } - switch (type) { case ImageType::PNG: @@ -358,13 +351,12 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu case ImageType::TGA: { int width, height, components; - stbi_uc* stbData = stbi_load_from_memory(fileData.Get(), fileData.Count(), &width, &height, &components, 4); + stbi_uc* stbData = stbi_load_from_memory(bytes.Get(), bytes.Length(), &width, &height, &components, 4); if (!stbData) { LOG(Warning, "Failed to load image. {0}", String(stbi_failure_reason())); return false; } - fileData.Resize(0); // Setup texture data textureData.Width = width; @@ -399,8 +391,8 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu // Assume 16-bit, grayscale .RAW file in little-endian byte order // Check size - const auto size = (int32)Math::Sqrt(fileData.Count() / 2.0f); - if (fileData.Count() != size * size * 2) + const auto size = (int32)Math::Sqrt(bytes.Length() / 2.0f); + if (bytes.Length() != size * size * 2) { LOG(Warning, "Invalid RAW file data size or format. Use 16-bit .RAW file in little-endian byte order (square dimensions)."); return true; @@ -414,10 +406,10 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu textureData.Items.Resize(1); textureData.Items[0].Mips.Resize(1); auto& mip = textureData.Items[0].Mips[0]; - mip.RowPitch = fileData.Count() / size; - mip.DepthPitch = fileData.Count(); + mip.RowPitch = bytes.Length() / size; + mip.DepthPitch = bytes.Length(); mip.Lines = size; - mip.Data.Copy(fileData); + mip.Data.Copy(bytes); break; } @@ -559,7 +551,7 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu return false; } -bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, TextureData& textureData, const Options& options, String& errorMsg, bool& hasAlpha) +bool TextureTool::ImportTextureStb(ImageType type, Span bytes, TextureData& textureData, const Options& options, String& errorMsg, bool& hasAlpha) { // Load image data if (type == ImageType::Internal) @@ -576,7 +568,7 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu else { stbi_set_flip_vertically_on_load_thread(options.FlipY); - bool failed = ImportTextureStb(type, path, textureData, hasAlpha); + bool failed = ImportTextureStb(type, bytes, textureData, hasAlpha); stbi_set_flip_vertically_on_load_thread(false); if (failed) { @@ -629,7 +621,7 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu } if (options.GenerateMipMaps && !isPowerOfTwo) { - LOG(Warning, "Cannot generate mip maps for texture '{}' that size is not power of two. Use Resize or Max Size to change dimensions.", StringUtils::GetFileName(path), width, height); + LOG(Warning, "Cannot generate mip maps for texture that size is not power of two ({}x{}). Use Resize or Max Size to change dimensions.", width, height); } // Decompress if texture is compressed (next steps need decompressed input data, for eg. mip maps generation or format changing)