Files
UnrealEngine/Engine/Source/ThirdParty/Windows/glTF-Toolkit/glTF-Toolkit.Test/GLTFTextureCompressionUtilsTests.cpp
2025-05-18 13:04:45 +08:00

271 lines
14 KiB
C++

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
#include "pch.h"
#include <CppUnitTest.h>
#include "GLTFSDK/IStreamWriter.h"
#include "GLTFSDK/Constants.h"
#include "GLTFSDK/Serialize.h"
#include "GLTFSDK/Deserialize.h"
#include "GLTFSDK/GLBResourceReader.h"
#include "GLTFSDK/GLTFResourceWriter.h"
#include "GLTFTextureCompressionUtils.h"
#include "Helpers/WStringUtils.h"
#include "Helpers/StreamMock.h"
#include "Helpers/TestUtils.h"
#include <DirectXTex.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace Microsoft::glTF;
using namespace Microsoft::glTF::Toolkit;
namespace Microsoft::glTF::Toolkit::Test
{
// Note: some tests are using BC3 since it's faster to run that algorithm vs. BC7
TEST_CLASS(GLTFTextureCompressionUtilsTests)
{
const char* c_baseColorPng = "Resources\\gltf\\WaterBottle_ORM\\WaterBottle_baseColor.png";
const char* c_baseColorBC7 = "Resources\\gltf\\WaterBottle_ORM\\WaterBottle_baseColor.DDS";
const char* c_waterBottleORMJson = "Resources\\gltf\\WaterBottle_ORM\\WaterBottle.gltf";
const char* c_nonMultipleOf4TextureJson = "Resources\\gltf\\TextureTest\\TextureTest.gltf";
TEST_METHOD(GLTFTextureCompressionUtils_CompressImage_BC7)
{
// Load png
auto png = TestUtils::ReadLocalAsset(TestUtils::GetAbsolutePath(c_baseColorPng));
std::vector<uint8_t> pngData = StreamUtils::ReadBinaryFull<uint8_t>(*png);
// ddsImage <= load DDS
DirectX::ScratchImage ddsImage;
DirectX::TexMetadata info;
DirectX::LoadFromDDSFile(TestUtils::GetAbsolutePathW(c_baseColorBC7).c_str(), DirectX::WIC_FLAGS_NONE, &info, ddsImage);
// compressedPng <= convert using BC7
DirectX::ScratchImage compressedPng;
DirectX::LoadFromWICMemory(pngData.data(), pngData.size(), DirectX::WIC_FLAGS_NONE, &info, compressedPng);
GLTFTextureCompressionUtils::CompressImage(compressedPng, TextureCompression::BC7);
auto ddsMip0 = ddsImage.GetImage(0, 0, 0);
size_t ddsImageSize = ddsMip0->height * ddsMip0->width;
Assert::AreEqual(ddsImageSize, compressedPng.GetPixelsSize(), L"ddsImage and compressedPng lengths are not the same");
Assert::IsTrue(memcmp(ddsMip0->pixels, compressedPng.GetPixels(), ddsImageSize), L"ddsImage and compressedPng are not the same");
}
TEST_METHOD(GLTFTextureCompressionUtils_CompressTextureAsDDS_NoCompression)
{
// This asset has all textures
TestUtils::LoadAndExecuteGLTFTest(c_waterBottleORMJson, [](auto doc, auto path)
{
auto compressedDoc = GLTFTextureCompressionUtils::CompressTextureAsDDS(std::make_shared<TestStreamReader>(path), doc, doc.textures.Get("0"), TextureCompression::None, "");
// Check that nothing changed
Assert::IsTrue(doc == compressedDoc);
});
}
TEST_METHOD(GLTFTextureCompressionUtils_CompressTextureAsDDS_CompressBC3_NoMips_Retain)
{
// This asset has all textures
TestUtils::LoadAndExecuteGLTFTest(c_waterBottleORMJson, [](auto doc, auto path)
{
auto maxTextureSize = std::numeric_limits<size_t>::max();
auto generateMipMaps = false;
auto retainOriginalImages = true;
auto compressedDoc = GLTFTextureCompressionUtils::CompressTextureAsDDS(std::make_shared<TestStreamReader>(path), doc, doc.textures.Get("0"), TextureCompression::BC3, "", maxTextureSize, generateMipMaps, retainOriginalImages);
auto originalTexture = doc.textures.Get("0");
auto compressedTexture = compressedDoc.textures.Get("0");
// Check that the image has not been replaced
Assert::IsTrue(originalTexture.imageId == compressedTexture.imageId);
// Check that the image has been added
Assert::IsTrue(doc.images.Size() + 1 == compressedDoc.images.Size());
// Check that the texture now has the extension
Assert::IsTrue(originalTexture.extensions.size() + 1 == compressedTexture.extensions.size());
// Check the new extension is not empty
auto ddsExtension = compressedTexture.extensions.at(std::string(EXTENSION_MSFT_TEXTURE_DDS));
Assert::IsFalse(ddsExtension.empty());
// Check the new extension contains a DDS image
rapidjson::Document ddsJson;
ddsJson.Parse(ddsExtension.c_str());
Assert::IsTrue(ddsJson["source"].IsInt());
auto ddsImageId = std::to_string(ddsJson["source"].GetInt());
Assert::IsTrue(compressedDoc.images.Get(ddsImageId).mimeType == "image/vnd-ms.dds");
Assert::IsTrue(compressedDoc.images.Get(ddsImageId).uri == "texture_0_nomips_BC3.dds");
});
}
TEST_METHOD(GLTFTextureCompressionUtils_CompressTextureAsDDS_CompressBC3_NoMips_Replace)
{
// This asset has all textures
TestUtils::LoadAndExecuteGLTFTest(c_waterBottleORMJson, [](auto doc, auto path)
{
auto maxTextureSize = std::numeric_limits<size_t>::max();
auto generateMipMaps = false;
auto retainOriginalImages = false;
auto compressedDoc = GLTFTextureCompressionUtils::CompressTextureAsDDS(std::make_shared<TestStreamReader>(path), doc, doc.textures.Get("0"), TextureCompression::BC3, "", maxTextureSize, generateMipMaps, retainOriginalImages);
auto originalTexture = doc.textures.Get("0");
auto compressedTexture = compressedDoc.textures.Get("0");
// Check that the texture is still pointing to the same image
Assert::IsTrue(originalTexture.imageId == compressedTexture.imageId);
// Check that an image has not been added
Assert::IsTrue(doc.images.Size() == compressedDoc.images.Size());
// Check that the texture now has the extension
Assert::IsTrue(originalTexture.extensions.size() + 1 == compressedTexture.extensions.size());
// Check the new extension is not empty
auto ddsExtension = compressedTexture.extensions.at(std::string(EXTENSION_MSFT_TEXTURE_DDS));
Assert::IsFalse(ddsExtension.empty());
// Check the new extension contains a DDS image
rapidjson::Document ddsJson;
ddsJson.Parse(ddsExtension.c_str());
Assert::IsTrue(ddsJson["source"].IsInt());
auto ddsImageId = std::to_string(ddsJson["source"].GetInt());
Assert::IsTrue(compressedDoc.images.Get(ddsImageId).mimeType == "image/vnd-ms.dds");
Assert::IsTrue(compressedDoc.images.Get(ddsImageId).uri == "texture_0_nomips_BC3.dds");
// Check the extension points to the same image as the source (image was replaced)
Assert::AreEqual(compressedTexture.imageId, ddsImageId);
});
}
TEST_METHOD(GLTFTextureCompressionUtils_CompressTextureAsDDS_CompressBC7_Mips_Retain)
{
// This asset has all textures
TestUtils::LoadAndExecuteGLTFTest(c_waterBottleORMJson, [](auto doc, auto path)
{
auto maxTextureSize = std::numeric_limits<size_t>::max();
auto generateMipMaps = true;
auto retainOriginalImages = true;
auto compressedDoc = GLTFTextureCompressionUtils::CompressTextureAsDDS(std::make_shared<TestStreamReader>(path), doc, doc.textures.Get("0"), TextureCompression::BC7, "", maxTextureSize, generateMipMaps, retainOriginalImages);
auto originalTexture = doc.textures.Get("0");
auto compressedTexture = compressedDoc.textures.Get("0");
// Check that the image has not been replaced
Assert::IsTrue(originalTexture.imageId == compressedTexture.imageId);
// Check that the image has been added
Assert::IsTrue(doc.images.Size() + 1 == compressedDoc.images.Size());
// Check that the texture now has the extension
Assert::IsTrue(originalTexture.extensions.size() + 1 == compressedTexture.extensions.size());
// Check the new extension is not empty
auto ddsExtension = compressedTexture.extensions.at(std::string(EXTENSION_MSFT_TEXTURE_DDS));
Assert::IsFalse(ddsExtension.empty());
// Check the new extension contains a DDS image
rapidjson::Document ddsJson;
ddsJson.Parse(ddsExtension.c_str());
Assert::IsTrue(ddsJson["source"].IsInt());
auto ddsImageId = std::to_string(ddsJson["source"].GetInt());
Assert::IsTrue(compressedDoc.images.Get(ddsImageId).mimeType == "image/vnd-ms.dds");
Assert::IsTrue(compressedDoc.images.Get(ddsImageId).uri == "texture_0_BC7.dds");
});
}
TEST_METHOD(GLTFTextureCompressionUtils_CompressAllTexturesForWindowsMR_Retain)
{
// This asset has all textures
TestUtils::LoadAndExecuteGLTFTest(c_waterBottleORMJson, [](auto doc, auto path)
{
auto maxTextureSize = std::numeric_limits<size_t>::max();
auto retainOriginalImages = true;
auto compressedDoc = GLTFTextureCompressionUtils::CompressAllTexturesForWindowsMR(std::make_shared<TestStreamReader>(path), doc, "", maxTextureSize, retainOriginalImages);
// Check that the materials and textures have not been replaced
// Check that the textures has not been replaced
Assert::IsTrue(doc.textures.Size() == compressedDoc.textures.Size());
Assert::IsTrue(doc.materials.Size() == compressedDoc.materials.Size());
// Check that the images have been added (base, emissive, RMO and normal)
Assert::AreEqual(doc.images.Size() + 4, compressedDoc.images.Size());
auto originalMaterial = doc.materials.Get("0");
auto compressedMaterial = compressedDoc.materials.Get("0");
// Check that all relevant textures now have the extension
Assert::IsTrue(doc.textures.Get(originalMaterial.metallicRoughness.baseColorTexture.textureId).extensions.size() + 1 == compressedDoc.textures.Get(compressedMaterial.metallicRoughness.baseColorTexture.textureId).extensions.size());
Assert::IsTrue(doc.textures.Get(originalMaterial.emissiveTexture.textureId).extensions.size() + 1 == compressedDoc.textures.Get(compressedMaterial.emissiveTexture.textureId).extensions.size());
// TODO: read the WMR (MSFT_packing...) textures as well
// Check the new extension is not empty
auto ddsExtension = compressedDoc.textures.Get(compressedMaterial.emissiveTexture.textureId).extensions.at(std::string(EXTENSION_MSFT_TEXTURE_DDS));
Assert::IsFalse(ddsExtension.empty());
// Check the new extension contains a DDS image
rapidjson::Document ddsJson;
ddsJson.Parse(ddsExtension.c_str());
Assert::IsTrue(ddsJson["source"].IsInt());
auto ddsImageId = std::to_string(ddsJson["source"].GetInt());
Assert::IsTrue(compressedDoc.images.Get(ddsImageId).mimeType == "image/vnd-ms.dds");
std::string expectedSuffix = "_BC7.dds";
Assert::IsTrue(compressedDoc.images.Get(ddsImageId).uri.compare(9, expectedSuffix.size(), expectedSuffix) == 0); // The emissive texture should have mips and be BC7
});
}
TEST_METHOD(GLTFTextureCompressionUtils_CompressTextureAsDDS_NotMultipleOf4)
{
// This asset has all textures
TestUtils::LoadAndExecuteGLTFTest(c_nonMultipleOf4TextureJson, [](auto doc, auto path)
{
auto maxTextureSize = std::numeric_limits<size_t>::max();
auto generateMipMaps = false;
auto retainOriginalImages = true;
auto compressedDoc = GLTFTextureCompressionUtils::CompressTextureAsDDS(std::make_shared<TestStreamReader>(path), doc, doc.textures.Get("0"), TextureCompression::BC3, "", maxTextureSize, generateMipMaps, retainOriginalImages);
auto originalUri = compressedDoc.images.Get("0").uri;
auto compressedUri = compressedDoc.images.Get("1").uri;
auto basePath = TestUtils::GetBasePath(path.c_str());
// load original
DirectX::ScratchImage originalImage;
DirectX::TexMetadata originalInfo;
DirectX::LoadFromWICFile(WStringUtils::ToWString(basePath + originalUri).c_str(), DirectX::WIC_FLAGS_NONE, &originalInfo, originalImage);
Assert::IsTrue(101 == originalInfo.width);
Assert::IsTrue(51 == originalInfo.height);
// load compressed
DirectX::ScratchImage compressedImage;
DirectX::TexMetadata compressedInfo;
DirectX::LoadFromDDSFile(WStringUtils::ToWString(compressedUri).c_str(), DirectX::DDS_FLAGS_NONE, &compressedInfo, compressedImage);
// Check resize
Assert::IsTrue(104 == compressedInfo.width);
Assert::IsTrue(52 == compressedInfo.height);
});
}
};
}