// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #if COMPILE_WITH_TEXTURE_TOOL && COMPILE_WITH_DIRECTXTEX #include "TextureTool.h" #include "Engine/Core/Log.h" #include "Engine/Core/Math/Math.h" #include "Engine/Platform/File.h" #include "Engine/Platform/CriticalSection.h" #include "Engine/Platform/ConditionVariable.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/Textures/TextureUtils.h" #include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Graphics/PixelFormatExtensions.h" #if USE_EDITOR #include "Engine/Graphics/GPUDevice.h" #endif // Import DirectXTex library // Source: https://github.com/Microsoft/DirectXTex #if PLATFORM_XBOX_SCARLETT #include "Engine/Platform/Win32/IncludeWindowsHeaders.h" DECLARE_HANDLE(HMONITOR); #endif #include namespace { FORCE_INLINE PixelFormat ToPixelFormat(const DXGI_FORMAT format) { return static_cast(format); } FORCE_INLINE DXGI_FORMAT ToDxgiFormat(const PixelFormat format) { return static_cast(format); } HRESULT Compress(const DirectX::Image* srcImages, size_t nimages, const DirectX::TexMetadata& metadata, DXGI_FORMAT format, DWORD compress, float threshold, DirectX::ScratchImage& cImages) { #if USE_EDITOR if ((format == DXGI_FORMAT_BC7_UNORM || format == DXGI_FORMAT_BC7_UNORM_SRGB || format == DXGI_FORMAT_BC6H_UF16 || format == DXGI_FORMAT_BC6H_SF16) && GPUDevice::Instance && GPUDevice::Instance->GetState() == GPUDevice::DeviceState::Ready && GPUDevice::Instance->GetRendererType() == RendererType::DirectX11) { // Use GPU compression GPUDevice::Instance->Locker.Lock(); if (GPUDevice::Instance->IsRendering()) { const auto result = DirectX::Compress((ID3D11Device*)GPUDevice::Instance->GetNativePtr(), srcImages, nimages, metadata, format, compress, 1.0f, cImages); GPUDevice::Instance->Locker.Unlock(); return result; } GPUDevice::Instance->Locker.Unlock(); class GPUCompressTask : public GPUTask { ConditionVariable* _signal; const DirectX::Image* _srcImages; size_t _nimages; const DirectX::TexMetadata& _metadata; DXGI_FORMAT _format; DWORD _compress; DirectX::ScratchImage& _cImages; public: HRESULT CompressResult = E_FAIL; GPUCompressTask(ConditionVariable& signal, const DirectX::Image* srcImages, size_t nimages, const DirectX::TexMetadata& metadata, DXGI_FORMAT format, DWORD compress, DirectX::ScratchImage& cImages) : GPUTask(Type::Custom) , _signal(&signal) , _srcImages(srcImages) , _nimages(nimages) , _metadata(metadata) , _format(format) , _compress(compress) , _cImages(cImages) { } Result run(GPUTasksContext* context) override { CompressResult = DirectX::Compress((ID3D11Device*)context->GetDevice()->GetNativePtr(), _srcImages, _nimages, _metadata, _format, _compress, 1.0f, _cImages); return CompressResult == S_OK ? Result::Ok : Result::Failed; } void OnSync() override { GPUTask::OnSync(); _signal->NotifyOne(); } void OnCancel() override { GPUTask::OnCancel(); _signal->NotifyOne(); } void OnFail() override { GPUTask::OnFail(); _signal->NotifyOne(); } }; ConditionVariable signal; CriticalSection mutex; auto task = New(signal, srcImages, nimages, metadata, format, compress, cImages); task->Start(); mutex.Lock(); signal.Wait(mutex); mutex.Unlock(); return task->CompressResult; } #endif return DirectX::Compress(srcImages, nimages, metadata, format, compress, threshold, cImages); } } bool TextureTool::ExportTextureDirectXTex(ImageType type, const StringView& path, const TextureData& textureData) { // Get source data const auto& srcData = *textureData.GetData(0, 0); // Setup image container DirectX::Image image; image.width = textureData.Width; image.height = textureData.Height; image.format = ToDxgiFormat(textureData.Format); image.rowPitch = srcData.RowPitch; image.slicePitch = srcData.DepthPitch; image.pixels = (uint8_t*)srcData.Data.Get(); // Save HRESULT result; switch (type) { case ImageType::DDS: { const bool isCubeTexture = textureData.GetArraySize() == 6; DirectX::TexMetadata metadata; metadata.width = image.width; metadata.height = image.height; metadata.depth = 1; metadata.arraySize = textureData.GetArraySize(); metadata.mipLevels = textureData.GetMipLevels(); metadata.miscFlags = isCubeTexture ? DirectX::TEX_MISC_TEXTURECUBE : 0; metadata.miscFlags2 = 0; metadata.format = image.format; metadata.dimension = DirectX::TEX_DIMENSION_TEXTURE2D; Array images; images.Resize((int32)(metadata.mipLevels * metadata.arraySize)); for (size_t arrayIndex = 0; arrayIndex < metadata.arraySize; arrayIndex++) { for (size_t mipIndex = 0; mipIndex < metadata.mipLevels; mipIndex++) { auto& src = *textureData.GetData((int32)arrayIndex, (int32)mipIndex); auto& img = images[(int32)(metadata.mipLevels * arrayIndex + mipIndex)]; img.width = Math::Max(1, image.width >> mipIndex); img.height = Math::Max(1, image.height >> mipIndex); img.format = image.format; img.rowPitch = src.RowPitch; img.slicePitch = src.DepthPitch; img.pixels = (uint8_t*)src.Data.Get(); } } result = DirectX::SaveToDDSFile(images.Get(), images.Count(), metadata, DirectX::DDS_FLAGS_NONE, *path); break; } case ImageType::TGA: result = SaveToTGAFile(image, *path); break; case ImageType::PNG: case ImageType::BMP: case ImageType::GIF: case ImageType::TIFF: case ImageType::JPEG: { const DirectX::Image* img = ℑ DirectX::ScratchImage tmp; if (DirectX::IsCompressed(image.format)) { result = Decompress(image, DXGI_FORMAT_R8G8B8A8_UNORM, tmp); if (FAILED(result)) { LOG(Error, "Cannot decompress texture, error: {0:x}", static_cast(result)); return true; } img = tmp.GetImage(0, 0, 0); } else if (image.format == DXGI_FORMAT_R10G10B10A2_UNORM || image.format == DXGI_FORMAT_R11G11B10_FLOAT) { result = DirectX::Convert(image, DXGI_FORMAT_R8G8B8A8_UNORM, DirectX::TEX_FILTER_DEFAULT, DirectX::TEX_THRESHOLD_DEFAULT, tmp); if (FAILED(result)) { LOG(Error, "Cannot convert texture, error: {0:x}", static_cast(result)); return true; } img = tmp.GetImage(0, 0, 0); } DirectX::WICCodecs codec; switch (type) { case ImageType::PNG: codec = DirectX::WIC_CODEC_PNG; break; case ImageType::BMP: codec = DirectX::WIC_CODEC_BMP; break; case ImageType::GIF: codec = DirectX::WIC_CODEC_GIF; break; case ImageType::TIFF: codec = DirectX::WIC_CODEC_TIFF; break; case ImageType::JPEG: codec = DirectX::WIC_CODEC_JPEG; break; default: ; } result = DirectX::SaveToWICFile(*img, DirectX::WIC_FLAGS_FORCE_SRGB, GetWICCodec(codec), *path); break; } case ImageType::HDR: result = SaveToHDRFile(image, *path); break; default: result = E_NOTIMPL; break; } if (FAILED(result)) { LOG(Error, "Exporting texture to '{0}' error: {1:x}", *path, (uint32)result); return true; } return false; } HRESULT LoadFromRAWFile(const StringView& path, 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) { 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; } // Setup image DirectX::Image img; img.format = DXGI_FORMAT_R16_UNORM; img.width = size; img.height = size; img.rowPitch = data.Count() / size; img.slicePitch = data.Count(); // Link data img.pixels = data.Get(); // Init return image.InitializeFromImage(img); } bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path, TextureData& textureData, bool& hasAlpha) { // Load image data DirectX::ScratchImage image; HRESULT result; switch (type) { case ImageType::BMP: case ImageType::GIF: case ImageType::TIFF: case ImageType::JPEG: case ImageType::PNG: result = DirectX::LoadFromWICFile(*path, DirectX::WIC_FLAGS_NONE, nullptr, image); break; case ImageType::DDS: result = DirectX::LoadFromDDSFile(*path, DirectX::DDS_FLAGS_NONE, nullptr, image); break; case ImageType::TGA: result = DirectX::LoadFromTGAFile(*path, nullptr, image); break; case ImageType::HDR: result = DirectX::LoadFromHDRFile(*path, nullptr, image); break; case ImageType::RAW: result = LoadFromRAWFile(path, image); break; default: result = DXGI_ERROR_INVALID_CALL; break; } if (FAILED(result)) { LOG(Warning, "Failed to import texture from file. Result: {0:x}", static_cast(result)); return true; } // Convert into texture data auto& meta = image.GetMetadata(); textureData.Width = (int32)meta.width; textureData.Height = (int32)meta.height; textureData.Depth = (int32)meta.depth; textureData.Format = ToPixelFormat(meta.format); textureData.Items.Resize(1); textureData.Items.Resize((int32)meta.arraySize); for (int32 arrayIndex = 0; arrayIndex < (int32)meta.arraySize; arrayIndex++) { auto& item = textureData.Items[arrayIndex]; item.Mips.Resize((int32)meta.mipLevels); for (int32 mipIndex = 0; mipIndex < (int32)meta.mipLevels; mipIndex++) { auto& mip = item.Mips[mipIndex]; const auto img = image.GetImage(mipIndex, arrayIndex, 0); mip.RowPitch = (uint32)img->rowPitch; mip.DepthPitch = (uint32)img->slicePitch; mip.Lines = (uint32)img->height; mip.Data.Copy(img->pixels, mip.DepthPitch); } #if USE_EDITOR if (!hasAlpha) hasAlpha |= !image.IsAlphaAllOpaque(); #endif } return false; } HRESULT CustomGenerateMipMap(DirectX::ScratchImage& mipChain, size_t item, size_t mip) { const DirectX::TexMetadata& metadata = mipChain.GetMetadata(); if (mip <= 0 || item < 0 || item > metadata.arraySize) return E_INVALIDARG; const DirectX::Image* srcImg = mipChain.GetImage(mip - 1, item, 0); const DirectX::Image* dstImg = mipChain.GetImage(mip, item, 0); const float srcWidth = (float)srcImg->width; const float srcHeight = (float)srcImg->height; const float dstWidth = (float)dstImg->width; const float dstHeight = (float)dstImg->height; const uint8_t* srcData = srcImg->pixels; const uint8_t* dstData = dstImg->pixels; if (metadata.format == DXGI_FORMAT_R32G32B32A32_FLOAT) { // 2x2 linear filter for (size_t y = 0; y < dstImg->height; y++) { float dy = y / dstHeight; float sy = dy * srcHeight; size_t p0y = Math::FloorToInt(sy); float pdy = sy - p0y; size_t p1y = Math::Min(p0y + 1, srcImg->height - 1); for (size_t x = 0; x < dstImg->width; x++) { float dx = x / dstWidth; float sx = dx * srcWidth; size_t p0x = Math::FloorToInt(sx); float pdx = sx - p0x; size_t p1x = Math::Min(p0x + 1, srcImg->width - 1); Vector4 pA = *(Vector4*)(srcData + srcImg->rowPitch * p0y + sizeof(Vector4) * p0x); Vector4 pB = *(Vector4*)(srcData + srcImg->rowPitch * p0y + sizeof(Vector4) * p1x); Vector4 pC = *(Vector4*)(srcData + srcImg->rowPitch * p1y + sizeof(Vector4) * p0x); Vector4 pD = *(Vector4*)(srcData + srcImg->rowPitch * p1y + sizeof(Vector4) * p1x); Vector4 pAB; Vector4::Lerp(pA, pB, pdx, pAB); Vector4 pCD; Vector4::Lerp(pC, pD, pdx, pCD); Vector4 p; Vector4::Lerp(pAB, pCD, pdy, p); *(Vector4*)(dstData + dstImg->rowPitch * y + sizeof(Vector4) * x) = p; } } return S_OK; } return E_FAIL; } HRESULT CustomGenerateMipMaps(const DirectX::Image* srcImages, size_t nimages, const DirectX::TexMetadata& metadata, size_t levels, DirectX::ScratchImage& mipChain) { // Get source images Array baseImages; baseImages.Resize((int32)metadata.arraySize); for (size_t item = 0; item < metadata.arraySize; item++) { const size_t index = metadata.ComputeIndex(0, item, 0); if (index >= nimages) return E_FAIL; const DirectX::Image& src = srcImages[index]; if (!src.pixels) return E_POINTER; if (src.format != metadata.format || src.width != metadata.width || src.height != metadata.height) { // All base images must be the same format, width, and height return E_FAIL; } baseImages[(int32)item] = src; } // Setup mip chain DirectX::TexMetadata mdata2 = metadata; mdata2.mipLevels = levels; HRESULT hr = mipChain.Initialize(mdata2); if (FAILED(hr)) return hr; // Copy base image(s) to top of mip chain for (size_t item = 0; item < nimages; item++) { const DirectX::Image& src = baseImages[(int32)item]; const DirectX::Image* dest = mipChain.GetImage(0, item, 0); if (!dest) { mipChain.Release(); return E_POINTER; } assert(src.format == dest->format); uint8_t* pDest = dest->pixels; if (!pDest) { mipChain.Release(); return E_POINTER; } const uint8_t* pSrc = src.pixels; size_t rowPitch = src.rowPitch; for (size_t h = 0; h < metadata.height; h++) { const size_t msize = Math::Min(dest->rowPitch, rowPitch); Platform::MemoryCopy(pDest, pSrc, msize); pSrc += rowPitch; pDest += dest->rowPitch; } } // Generate mip maps for each array slice for (size_t item = 0; item < mdata2.arraySize; item++) { for (size_t mip = 1; mip < mdata2.mipLevels; mip++) { hr = CustomGenerateMipMap(mipChain, item, mip); if (FAILED(hr)) { mipChain.Release(); return hr; } } } return S_OK; } bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path, TextureData& textureData, const Options& options, String& errorMsg, bool& hasAlpha) { #define SET_CURRENT_IMG(x) currentImage = &x #define GET_TMP_IMG() (currentImage != &image1 ? image1 : image2) DirectX::ScratchImage* currentImage; DirectX::ScratchImage image1; DirectX::ScratchImage image2; TextureData internalData; // Load image data HRESULT result; switch (type) { case ImageType::BMP: case ImageType::GIF: case ImageType::TIFF: case ImageType::JPEG: case ImageType::PNG: result = DirectX::LoadFromWICFile(*path, DirectX::WIC_FLAGS_NONE, nullptr, image1); break; case ImageType::DDS: result = DirectX::LoadFromDDSFile(*path, DirectX::DDS_FLAGS_NONE, nullptr, image1); break; case ImageType::TGA: result = DirectX::LoadFromTGAFile(*path, nullptr, image1); break; case ImageType::HDR: result = DirectX::LoadFromHDRFile(*path, nullptr, image1); break; case ImageType::RAW: result = LoadFromRAWFile(path, image1); break; case ImageType::Internal: { if (options.InternalLoad.IsBinded()) { if (!options.InternalLoad(internalData)) { ASSERT(internalData.Items.Count() == 1 && internalData.Items[0].Mips.Count() == 1); // Only single 2D texture image is supported for now DirectX::Image img; auto& mip = internalData.Items[0].Mips[0]; img.width = internalData.Width; img.height = internalData.Height; img.format = ToDxgiFormat(internalData.Format); img.rowPitch = mip.RowPitch; img.slicePitch = mip.DepthPitch; img.pixels = mip.Data.Get(); result = image1.InitializeFromImage(img); } else { result = E_FAIL; } } else { result = DXGI_ERROR_INVALID_CALL; break; } } break; default: result = DXGI_ERROR_INVALID_CALL; break; } if (FAILED(result)) { errorMsg = String::Format(TEXT("Result: {0:x}"), static_cast(result)); return true; } SET_CURRENT_IMG(image1); // Check if resize source image const int32 sourceWidth = static_cast(currentImage->GetMetadata().width); const int32 sourceHeight = static_cast(currentImage->GetMetadata().height); int32 width = Math::Clamp(options.Resize ? options.SizeX : static_cast(sourceWidth * options.Scale), 1, options.MaxSize); int32 height = Math::Clamp(options.Resize ? options.SizeY : static_cast(sourceHeight * options.Scale), 1, options.MaxSize); if (sourceWidth != width || sourceHeight != height) { auto& tmpImg = GET_TMP_IMG(); // During resizing we need to keep texture aspect ratio const bool keepAspectRatio = false; // TODO: expose as import option if (keepAspectRatio) { const float aspectRatio = static_cast(sourceWidth) / sourceHeight; if (width >= height) height = Math::CeilToInt(width / aspectRatio); else width = Math::CeilToInt(height / aspectRatio); } // Resize source texture LOG(Info, "Resizing texture from {0}x{1} to {2}x{3}.", sourceWidth, sourceHeight, width, height); result = DirectX::Resize(*currentImage->GetImages(), width, height, DirectX::TEX_FILTER_LINEAR, tmpImg); if (FAILED(result)) { errorMsg = String::Format(TEXT("Cannot resize texture, error: {0:x}"), static_cast(result)); return true; } // Use converted image SET_CURRENT_IMG(tmpImg); } // Cache data float alphaThreshold = 0.3f; bool isPowerOfTwo = Math::IsPowerOfTwo(width) && Math::IsPowerOfTwo(height); DXGI_FORMAT sourceDxgiFormat = currentImage->GetMetadata().format; PixelFormat targetFormat = TextureUtils::ToPixelFormat(options.Type, width, height, options.Compress); if (options.sRGB) targetFormat = PixelFormatExtensions::TosRGB(targetFormat); DXGI_FORMAT targetDxgiFormat = ToDxgiFormat(targetFormat); // Check mip levels int32 sourceMipLevels = (int32)currentImage->GetMetadata().mipLevels; bool hasSourceMipLevels = isPowerOfTwo && sourceMipLevels > 1; bool useMipLevels = isPowerOfTwo && (options.GenerateMipMaps || hasSourceMipLevels) && (width > 1 || height > 1); int32 arraySize = (int32)currentImage->GetMetadata().arraySize; int32 mipLevels = MipLevelsCount(width, height, useMipLevels); if (useMipLevels && !options.GenerateMipMaps && mipLevels != sourceMipLevels) { errorMsg = String::Format(TEXT("Imported texture has not full mip chain, loaded mips count: {0}, expected: {1}"), sourceMipLevels, mipLevels); return true; } // Allocate memory for texture data auto& data = textureData.Items; data.Resize(arraySize); for (int32 arrayIndex = 0; arrayIndex < arraySize; arrayIndex++) { auto& sliceData = data[arrayIndex]; sliceData.Mips.Resize(mipLevels); } // Decompress if texture is compressed (next steps need decompressed input data, for eg. mip maps generation or format changing) if (DirectX::IsCompressed(sourceDxgiFormat)) { auto& tmpImg = GET_TMP_IMG(); sourceDxgiFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; result = Decompress(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(), sourceDxgiFormat, tmpImg); if (FAILED(result)) { errorMsg = String::Format(TEXT("Cannot decompress texture, error: {0:x}"), static_cast(result)); return true; } SET_CURRENT_IMG(tmpImg); } // Fix sRGB problem if (DirectX::IsSRGB(sourceDxgiFormat)) { sourceDxgiFormat = ToDxgiFormat(PixelFormatExtensions::ToNonsRGB(ToPixelFormat(sourceDxgiFormat))); ((DirectX::TexMetadata&)currentImage->GetMetadata()).format = sourceDxgiFormat; for (size_t i = 0; i < currentImage->GetImageCount(); i++) ((DirectX::Image*)currentImage->GetImages())[i].format = sourceDxgiFormat; } // Remove alpha if source texture has it but output should not, valid for compressed output only (DirectX seams to use alpha to pre-multiply colors because BC1 format has no place for alpha) if (DirectX::HasAlpha(sourceDxgiFormat) && options.Type == TextureFormatType::ColorRGB && options.Compress) { auto& tmpImg = GET_TMP_IMG(); TransformImage(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(), [](DirectX::XMVECTOR* outPixels, const DirectX::XMVECTOR* inPixels, size_t width, size_t y) { UNREFERENCED_PARAMETER(y); for (size_t j = 0; j < width; j++) { outPixels[j] = DirectX::XMVectorSelect(DirectX::g_XMOne, inPixels[j], DirectX::g_XMSelect1110); } }, tmpImg); if (FAILED(result)) { errorMsg = String::Format(TEXT("Cannot transform texture to remove unwanted alpha channel, error: {0:x}"), static_cast(result)); return true; } // Use converted image SET_CURRENT_IMG(tmpImg); } // Check flip/rotate source image if (options.FlipY) { auto& tmpImg = GET_TMP_IMG(); DWORD flags = DirectX::TEX_FR_FLIP_VERTICAL; result = FlipRotate(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(), flags, tmpImg); if (FAILED(result)) { errorMsg = String::Format(TEXT("Cannot rotate/flip texture, error: {0:x}"), static_cast(result)); return true; } // Use converted image SET_CURRENT_IMG(tmpImg); } // Generate mip maps chain if (useMipLevels && options.GenerateMipMaps) { auto& tmpImg = GET_TMP_IMG(); // Check if use custom filter (lightmaps are imported in Vector4 HDR format and generated mip maps by DirectXTex have some issues) if (sourceDxgiFormat == DXGI_FORMAT_R32G32B32A32_FLOAT) { result = CustomGenerateMipMaps(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(), mipLevels, tmpImg); } else { result = GenerateMipMaps(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(), DirectX::TEX_FILTER_SEPARATE_ALPHA, mipLevels, tmpImg); } if (FAILED(result)) { errorMsg = String::Format(TEXT("Cannot generate texture mip maps chain, error: {1:x}"), *path, static_cast(result)); return true; } SET_CURRENT_IMG(tmpImg); } // Preserve mipmap alpha coverage (if requested) if (DirectX::HasAlpha(currentImage->GetMetadata().format) && options.PreserveAlphaCoverage && useMipLevels) { auto& tmpImg = GET_TMP_IMG(); auto& info = currentImage->GetMetadata(); result = tmpImg.Initialize(info); if (FAILED(result)) { errorMsg = String::Format(TEXT("Failed initialize image, error: {1:x}"), *path, static_cast(result)); return true; } for (size_t item = 0; item < info.arraySize; ++item) { auto img = currentImage->GetImage(0, item, 0); ASSERT(img); 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)); return true; } } SET_CURRENT_IMG(tmpImg); } // Ensure that there are some mip maps in the source texture ASSERT((int32)currentImage->GetMetadata().mipLevels >= mipLevels); // Compress mip maps or convert image if (targetDxgiFormat != sourceDxgiFormat) { auto& tmpImg = GET_TMP_IMG(); if (DirectX::IsCompressed(targetDxgiFormat)) result = ::Compress(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(), targetDxgiFormat, DirectX::TEX_COMPRESS_DEFAULT | DirectX::TEX_COMPRESS_PARALLEL, alphaThreshold, tmpImg); else result = DirectX::Convert(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(), targetDxgiFormat, DirectX::TEX_FILTER_DEFAULT, alphaThreshold, tmpImg); if (FAILED(result)) { errorMsg = String::Format(TEXT("Cannot compress texture, error: {0:x}"), static_cast(result)); return true; } SET_CURRENT_IMG(tmpImg); } // Setup texture data header textureData.Width = width; textureData.Height = height; textureData.Depth = 1; textureData.Format = targetFormat; // Save texture data for (int32 arrayIndex = 0; arrayIndex < arraySize; arrayIndex++) { for (int32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) { auto mipData = textureData.GetData(arrayIndex, mipIndex); auto image = currentImage->GetImage(mipIndex, arrayIndex, 0); if (image == nullptr) { errorMsg = String::Format(TEXT("Missing output image for mip{0} (array slice: {1})"), mipIndex, arrayIndex); return true; } mipData->DepthPitch = (uint32)image->slicePitch; mipData->RowPitch = (uint32)image->rowPitch; mipData->Lines = (uint32)image->height; mipData->Data.Copy(image->pixels, static_cast(image->slicePitch)); } #if USE_EDITOR if (!hasAlpha) hasAlpha |= !currentImage->IsAlphaAllOpaque(); #endif } return false; } bool TextureTool::ConvertDirectXTex(TextureData& dst, const TextureData& src, const PixelFormat dstFormat) { HRESULT result; DirectX::ScratchImage dstImage; DirectX::ScratchImage tmpImage; DirectX::ScratchImage srcImage; auto width = src.Width; auto height = src.Height; auto arraySize = src.GetArraySize(); auto mipLevels = src.GetMipLevels(); auto srcFormatDxgi = ToDxgiFormat(src.Format); auto dstFormatDxgi = ToDxgiFormat(dstFormat); // Prepare source data result = srcImage.Initialize2D(srcFormatDxgi, width, height, arraySize, mipLevels); if (FAILED(result)) { LOG(Warning, "Cannot init source image. Error: {0:x}", static_cast(result)); return true; } for (int32 arrayIndex = 0; arrayIndex < arraySize; arrayIndex++) { for (int32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) { const auto mipData = src.GetData(arrayIndex, mipIndex); auto image = srcImage.GetImage(mipIndex, arrayIndex, 0); if (image == nullptr) { LOG(Warning, "Missing source image for mip{0} (array slice: {1})", mipIndex, arrayIndex); return true; } // Copy data auto sptr = mipData->Data.Get(); auto dptr = reinterpret_cast(image->pixels); size_t spitch = mipData->RowPitch; size_t dpitch = image->rowPitch; if (spitch == dpitch) { Platform::MemoryCopy(dptr, sptr, image->slicePitch); } else { const size_t size = Math::Min(dpitch, spitch); for (size_t y = 0; y < mipData->Lines; y++) { Platform::MemoryCopy(dptr, sptr, size); sptr += spitch; dptr += dpitch; } } } } // Allocate memory for texture data auto& data = dst.Items; data.Resize(arraySize); for (int32 arrayIndex = 0; arrayIndex < arraySize; arrayIndex++) { auto& sliceData = data[arrayIndex]; sliceData.Mips.Resize(mipLevels); } // Check if need to decompress data DirectX::ScratchImage* inImage = &srcImage; if (DirectX::IsCompressed(srcFormatDxgi)) { result = DirectX::Decompress(srcImage.GetImages(), srcImage.GetImageCount(), srcImage.GetMetadata(), DXGI_FORMAT_UNKNOWN, tmpImage); if (FAILED(result)) { LOG(Warning, "Cannot decompress image. Error: {0:x}", static_cast(result)); return true; } inImage = &tmpImage; } // Check if compress data DirectX::ScratchImage* outImage = &dstImage; if (DirectX::IsCompressed(dstFormatDxgi)) { result = ::Compress(inImage->GetImages(), inImage->GetImageCount(), inImage->GetMetadata(), dstFormatDxgi, DirectX::TEX_COMPRESS_DEFAULT, DirectX::TEX_THRESHOLD_DEFAULT, dstImage); if (FAILED(result)) { LOG(Warning, "Cannot compress image. Error: {0:x}", static_cast(result)); return true; } } // Check if convert data else if (inImage->GetMetadata().format != dstFormatDxgi) { result = DirectX::Convert(inImage->GetImages(), inImage->GetImageCount(), inImage->GetMetadata(), dstFormatDxgi, DirectX::TEX_FILTER_DEFAULT, DirectX::TEX_THRESHOLD_DEFAULT, dstImage); if (FAILED(result)) { LOG(Warning, "Cannot convert image. Error: {0:x}", static_cast(result)); return true; } } else { // Use decompressed image output outImage = inImage; } // Save data for (int32 arrayIndex = 0; arrayIndex < arraySize; arrayIndex++) { for (int32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) { auto mipData = dst.GetData(arrayIndex, mipIndex); auto image = outImage->GetImage(mipIndex, arrayIndex, 0); if (image == nullptr) { LOG(Warning, "Missing output image for mip{0} (array slice: {1})", mipIndex, arrayIndex); return true; } mipData->DepthPitch = (uint32)image->slicePitch; mipData->RowPitch = (uint32)image->rowPitch; mipData->Lines = (uint32)image->height; mipData->Data.Copy(image->pixels, static_cast(image->slicePitch)); } } // Setup texture data dst.Width = src.Width; dst.Height = src.Height; dst.Depth = src.Depth; dst.Format = dstFormat; return false; } bool TextureTool::ResizeDirectXTex(TextureData& dst, const TextureData& src, int32 dstWidth, int32 dstHeight) { HRESULT result; DirectX::ScratchImage dstImage; DirectX::ScratchImage tmpImage; DirectX::ScratchImage srcImage; auto width = src.Width; auto height = src.Height; auto arraySize = src.GetArraySize(); auto mipLevels = src.GetMipLevels(); auto srcFormatDxgi = ToDxgiFormat(src.Format); // Prepare source data result = srcImage.Initialize2D(srcFormatDxgi, width, height, arraySize, mipLevels); if (FAILED(result)) { LOG(Warning, "Cannot init source image. Error: {0:x}", static_cast(result)); return true; } for (int32 arrayIndex = 0; arrayIndex < arraySize; arrayIndex++) { for (int32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) { const auto mipData = src.GetData(arrayIndex, mipIndex); auto image = srcImage.GetImage(mipIndex, arrayIndex, 0); if (image == nullptr) { LOG(Warning, "Missing source image for mip{0} (array slice: {1})", mipIndex, arrayIndex); return true; } // Copy data auto sptr = mipData->Data.Get(); auto dptr = reinterpret_cast(image->pixels); size_t spitch = mipData->RowPitch; size_t dpitch = image->rowPitch; if (spitch == dpitch) { Platform::MemoryCopy(dptr, sptr, image->slicePitch); } else { const size_t size = Math::Min(dpitch, spitch); for (size_t y = 0; y < mipData->Lines; y++) { Platform::MemoryCopy(dptr, sptr, size); sptr += spitch; dptr += dpitch; } } } } // Resize texture DirectX::ScratchImage* inImage = &srcImage; DirectX::ScratchImage* outImage = &dstImage; result = DirectX::Resize(inImage->GetImages(), inImage->GetImageCount(), inImage->GetMetadata(), dstWidth, dstHeight, DirectX::TEX_FILTER_DEFAULT, dstImage); if (FAILED(result)) { LOG(Warning, "Cannot resize image. Error: {0:x}", static_cast(result)); return true; } // Generate missing mipmaps if the input image had any DirectX::ScratchImage mipsImage; if (outImage->GetMetadata().mipLevels == 1 && mipLevels != 1) { result = DirectX::GenerateMipMaps(*dstImage.GetImage(0, 0, 0), DirectX::TEX_FILTER_DEFAULT, 0, mipsImage); if (FAILED(result)) { LOG(Warning, "Cannot generate mip maps. Error: {0:x}", static_cast(result)); return true; } outImage = &mipsImage; } mipLevels = (int32)outImage->GetMetadata().mipLevels; // Allocate memory for texture data auto& data = dst.Items; data.Resize(arraySize); for (int32 arrayIndex = 0; arrayIndex < arraySize; arrayIndex++) { auto& sliceData = data[arrayIndex]; sliceData.Mips.Resize(mipLevels); } // Save data for (int32 arrayIndex = 0; arrayIndex < arraySize; arrayIndex++) { for (int32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) { auto mipData = dst.GetData(arrayIndex, mipIndex); const auto image = outImage->GetImage(mipIndex, arrayIndex, 0); if (image == nullptr) { LOG(Warning, "Missing output image for mip{0} (array slice: {1})", mipIndex, arrayIndex); return true; } mipData->DepthPitch = (uint32)image->slicePitch; mipData->RowPitch = (uint32)image->rowPitch; mipData->Lines = (uint32)image->height; mipData->Data.Copy(image->pixels, static_cast(image->slicePitch)); } } // Setup texture data dst.Width = dstWidth; dst.Height = dstHeight; dst.Depth = src.Depth; dst.Format = src.Format; return false; } #endif