Files
UnrealEngineUWP/Engine/Source/Runtime/ImageWriteQueue/Private/ImageWriteTask.cpp
Julien StJean 547c7f160d Optimisation to the legacy texture import and improvement to texture wrapper
Minor change to FImageWrapperBase. GetRaw and GetCompressed now consume the array with the same name instead of having to do a copy of it.
I changed the api IImageWrapper::GetCompressed to return a TArray64<uint8> instead of returning a const TArray64<uint8>&.
Added the format RGBAF to the struct ERGBFormat. Changed the engine code using the EXR image wrapper to reflect that.
The EXR image wrapper now avoid doing an unessary copy of the compressed image when calling compress.

Improvement to the performence of the function UTextureFactory::ImportImage. We now use the magic bytes of the file for certains format to skip some tests.

Here is some performance metrics I captured on my desktop (6 core, 12 threads XEON)

Importing a folder of tiff files (22 files, 4.16 GB Total)
Before: 66.152738 seconds
After: 43.609245 seconds

#jira UEENT-3822
#rb Alexis.Matte

[CL 16128765 by Julien StJean in ue5-main branch]
2021-04-27 11:59:02 -04:00

245 lines
7.2 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ImageWriteTask.h"
#include "ImageWriteQueue.h"
#include "HAL/IConsoleManager.h"
#include "HAL/PlatformFileManager.h"
#include "HAL/FileManager.h"
#include "Misc/Paths.h"
#include "Misc/ScopeLock.h"
#include "Misc/FileHelper.h"
#include "Async/Async.h"
#include "Modules/ModuleManager.h"
#include "IImageWrapperModule.h"
struct FGlobalImageWrappers
{
IImageWrapper* FindOrCreateImageWrapper(EImageFormat InFormat)
{
FScopeLock ScopeLock(&ImageWrappersCriticalSection);
// Try and find an available image wrapper of the correct format first
for (int32 Index = 0; Index < AvailableImageWrappers.Num(); ++Index)
{
if (AvailableImageWrappers[Index].Get<0>() == InFormat)
{
IImageWrapper* Wrapper = AvailableImageWrappers[Index].Get<1>();
AvailableImageWrappers.RemoveAtSwap(Index, 1, false);
return Wrapper;
}
}
// Create a new one if none other could be used
IImageWrapperModule* ImageWrapperModule = FModuleManager::GetModulePtr<IImageWrapperModule>("ImageWrapper");
if (!ensure(ImageWrapperModule))
{
return nullptr;
}
TSharedPtr<IImageWrapper> NewImageWrapper = ImageWrapperModule->CreateImageWrapper(InFormat);
if (!ensureMsgf(NewImageWrapper.IsValid(), TEXT("Unable to create an image wrapper for the desired format.")))
{
return nullptr;
}
AllImageWrappers.Add(MakeTuple(InFormat, NewImageWrapper.ToSharedRef()));
return NewImageWrapper.Get();
}
void ReturnImageWrapper(IImageWrapper* InWrapper)
{
FScopeLock ScopeLock(&ImageWrappersCriticalSection);
// Try and find an available image wrapper of the correct format first
for (const TTuple<EImageFormat, TSharedRef<IImageWrapper>>& Pair : AllImageWrappers)
{
if (&Pair.Get<1>().Get() == InWrapper)
{
AvailableImageWrappers.Add(MakeTuple(Pair.Get<0>(), InWrapper));
return;
}
}
checkf(false, TEXT("Unable to find image wrapper in list of owned wrappers - this is invalid"));
}
private:
FCriticalSection ImageWrappersCriticalSection;
TArray< TTuple<EImageFormat, IImageWrapper*> > AvailableImageWrappers;
TArray< TTuple<EImageFormat, TSharedRef<IImageWrapper>> > AllImageWrappers;
} GImageWrappers;
static const TCHAR* GetFormatExtension(EImageFormat InImageFormat)
{
switch (InImageFormat)
{
case EImageFormat::PNG: return TEXT(".png");
case EImageFormat::JPEG: return TEXT(".jpg");
case EImageFormat::GrayscaleJPEG: return TEXT(".jpg");
case EImageFormat::BMP: return TEXT(".bmp");
case EImageFormat::ICO: return TEXT(".ico");
case EImageFormat::EXR: return TEXT(".exr");
case EImageFormat::ICNS: return TEXT(".icns");
}
return nullptr;
}
bool FImageWriteTask::RunTask()
{
bool bSuccess = WriteToDisk();
if (OnCompleted)
{
AsyncTask(ENamedThreads::GameThread, [bSuccess, LocalOnCompleted = MoveTemp(OnCompleted)] { LocalOnCompleted(bSuccess); });
}
return bSuccess;
}
void FImageWriteTask::OnAbandoned()
{
if (OnCompleted)
{
AsyncTask(ENamedThreads::GameThread, [LocalOnCompleted = MoveTemp(OnCompleted)] { LocalOnCompleted(false); });
}
}
bool FImageWriteTask::InitializeWrapper(IImageWrapper* InWrapper, EImageFormat WrapperFormat)
{
const void* RawPtr = nullptr;
int64 SizeBytes = 0;
if (PixelData->GetRawData(RawPtr, SizeBytes))
{
uint8 BitDepth = PixelData->GetBitDepth();
FIntPoint Size = PixelData->GetSize();
ERGBFormat PixelLayout = PixelData->GetPixelLayout();
return InWrapper->SetRaw(RawPtr, SizeBytes, Size.X, Size.Y, PixelLayout, BitDepth);
}
return false;
}
bool FImageWriteTask::WriteBitmap()
{
uint8 NumChannels = PixelData->GetNumChannels();
uint8 BitDepth = PixelData->GetBitDepth();
FIntPoint Size = PixelData->GetSize();
if (BitDepth != 8 || NumChannels != 4)
{
return false;
}
const void* RawPtr = nullptr;
int64 SizeBytes = 0;
if (PixelData->GetRawData(RawPtr, SizeBytes))
{
return FFileHelper::CreateBitmap(*Filename, Size.X, Size.Y, static_cast<const FColor*>(RawPtr));
}
return false;
}
void FImageWriteTask::PreProcess()
{
FImagePixelData* Data = PixelData.Get();
for (const FPixelPreProcessor& PreProcessor : PixelPreProcessors)
{
// PreProcessors are assumed to be valid.
PreProcessor(Data);
}
}
bool FImageWriteTask::WriteToDisk()
{
// Ensure that the payload filename has the correct extension for the format (have to special case jpeg since they can be both *.jpg and *.jpeg)
const TCHAR* FormatExtension = GetFormatExtension(Format);
if (FormatExtension && !Filename.EndsWith(FormatExtension) && (Format != EImageFormat::JPEG || !Filename.EndsWith(TEXT(".jpeg"))))
{
Filename = FPaths::GetBaseFilename(Filename, false) + FormatExtension;
}
bool bSuccess = EnsureWritableFile();
if (bSuccess)
{
PreProcess();
// bitmap support with IImageWrapper is flaky so it needs its own codepath for now
if (Format == EImageFormat::BMP)
{
bSuccess = WriteBitmap();
}
else
{
IImageWrapper* ImageWrapper = GImageWrappers.FindOrCreateImageWrapper(Format);
if (ImageWrapper)
{
if (InitializeWrapper(ImageWrapper, Format))
{
const TArray64<uint8> CompressedFile = ImageWrapper->GetCompressed(CompressionQuality);
uint64 TotalNumberOfBytes, NumberOfFreeBytes;
if (FPlatformMisc::GetDiskTotalAndFreeSpace(FPaths::GetPath(Filename), TotalNumberOfBytes, NumberOfFreeBytes))
{
if (NumberOfFreeBytes < (uint64)CompressedFile.Num() + 4096)
{
UE_LOG(LogImageWriteQueue, Error, TEXT("Failed to write image to '%s'. There is not enough free space on the disk."), *Filename);
return false;
}
}
else
{
uint32 ErrorCode = FPlatformMisc::GetLastError();
TCHAR ErrorBuffer[1024];
FPlatformMisc::GetSystemErrorMessage(ErrorBuffer, 1024, ErrorCode);
UE_LOG(LogImageWriteQueue, Warning, TEXT("Fail to check free space for %s. Error: %u (%s)"), *FPaths::GetPath(Filename), ErrorCode, ErrorBuffer);
}
IFileManager* FileManager = &IFileManager::Get();
bSuccess = FFileHelper::SaveArrayToFile(CompressedFile, *Filename);
}
GImageWrappers.ReturnImageWrapper(ImageWrapper);
}
}
}
if (!bSuccess)
{
UE_LOG(LogImageWriteQueue, Error, TEXT("Failed to write image to '%s'. The pixel format may not be compatible with this image type, or there was an error writing to that filename."), *Filename);
}
return bSuccess;
}
bool FImageWriteTask::EnsureWritableFile()
{
FString Directory = FPaths::GetPath(Filename);
if (!IFileManager::Get().DirectoryExists(*Directory))
{
IFileManager::Get().MakeDirectory(*Directory);
}
// If the file doesn't exist, we're ok to continue
if (IFileManager::Get().FileSize(*Filename) == -1)
{
return true;
}
// If we're allowed to overwrite the file, and we deleted it ok, we can continue
else if (bOverwriteFile && FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*Filename))
{
return true;
}
// We can't write to the file
else
{
UE_LOG(LogImageWriteQueue, Error, TEXT("Failed to write image to '%s'. Should Overwrite: %d - If we should have overwritten the file, we failed to delete the file. If we shouldn't have overwritten the file the file already exists so we can't replace it."), *Filename, bOverwriteFile);
return false;
}
}