fix: restore backpack material import output

This commit is contained in:
2026-04-02 22:34:25 +08:00
parent 71923267e9
commit 5ff97b437a
13 changed files with 212 additions and 23 deletions

View File

@@ -138,6 +138,7 @@ inline DXGI_FORMAT ToD3D12(Format format) {
case Format::R8_UNorm: return DXGI_FORMAT_R8_UNORM;
case Format::R8G8_UNorm: return DXGI_FORMAT_R8G8_UNORM;
case Format::R8G8B8A8_UNorm: return DXGI_FORMAT_R8G8B8A8_UNORM;
case Format::R8G8B8A8_SRGB: return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
case Format::R16_UInt: return DXGI_FORMAT_R16_UINT;
case Format::R16G16B16A16_Float: return DXGI_FORMAT_R16G16B16A16_FLOAT;
case Format::R32G32B32A32_Float: return DXGI_FORMAT_R32G32B32A32_FLOAT;
@@ -167,6 +168,7 @@ inline Format FromD3D12(DXGI_FORMAT format) {
case DXGI_FORMAT_R8_UNORM: return Format::R8_UNorm;
case DXGI_FORMAT_R8G8_UNORM: return Format::R8G8_UNorm;
case DXGI_FORMAT_R8G8B8A8_UNORM: return Format::R8G8B8A8_UNorm;
case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: return Format::R8G8B8A8_SRGB;
case DXGI_FORMAT_R16_UINT: return Format::R16_UInt;
case DXGI_FORMAT_R16G16B16A16_FLOAT: return Format::R16G16B16A16_Float;
case DXGI_FORMAT_R32G32B32A32_FLOAT: return Format::R32G32B32A32_Float;

View File

@@ -132,6 +132,9 @@ inline void ToOpenGLFormat(OpenGLFormat fmt, GLint& internalFormat, GLenum& glFo
case OpenGLFormat::RGBA8:
internalFormat = GL_RGBA8; glFormat = GL_RGBA; glType = GL_UNSIGNED_BYTE;
break;
case OpenGLFormat::RGBA8_SRGB:
internalFormat = GL_SRGB8_ALPHA8; glFormat = GL_RGBA; glType = GL_UNSIGNED_BYTE;
break;
case OpenGLFormat::RGBA16F:
internalFormat = GL_RGBA16F; glFormat = GL_RGBA; glType = GL_HALF_FLOAT;
break;

View File

@@ -27,7 +27,8 @@ enum class OpenGLFormat {
Depth24Stencil8,
Depth32F,
CompressedDXT1,
CompressedDXT5
CompressedDXT5,
RGBA8_SRGB
};
enum class OpenGLInternalFormat {
@@ -40,7 +41,8 @@ enum class OpenGLInternalFormat {
Depth24Stencil8 = 38,
Depth32F = 31,
CompressedDXT1 = 21,
CompressedDXT5 = 22
CompressedDXT5 = 22,
RGBA8_SRGB = 39
};
class OpenGLTexture : public RHITexture {

View File

@@ -317,7 +317,8 @@ enum class Format : uint32_t {
R32G32B32A32_UInt,
R32_UInt,
R32G32_Float,
R32G32B32_Float
R32G32B32_Float,
R8G8B8A8_SRGB
};
enum class ResourceStates : uint32_t {

View File

@@ -77,6 +77,8 @@ inline VkFormat ToVulkanFormat(Format format) {
return VK_FORMAT_R8G8_UNORM;
case Format::R8G8B8A8_UNorm:
return VK_FORMAT_R8G8B8A8_UNORM;
case Format::R8G8B8A8_SRGB:
return VK_FORMAT_R8G8B8A8_SRGB;
case Format::R16_UInt:
return VK_FORMAT_R16_UINT;
case Format::R16_Float:
@@ -107,6 +109,7 @@ inline uint32_t GetFormatSize(Format format) {
case Format::R8G8_UNorm:
return 2;
case Format::R8G8B8A8_UNorm:
case Format::R8G8B8A8_SRGB:
return 4;
case Format::R16_UInt:
case Format::R16_Float:
@@ -136,6 +139,9 @@ inline Format ToRHIFormat(VkFormat format) {
case VK_FORMAT_R8G8B8A8_UNORM:
case VK_FORMAT_B8G8R8A8_UNORM:
return Format::R8G8B8A8_UNorm;
case VK_FORMAT_R8G8B8A8_SRGB:
case VK_FORMAT_B8G8R8A8_SRGB:
return Format::R8G8B8A8_SRGB;
case VK_FORMAT_R16_UINT:
return Format::R16_UInt;
case VK_FORMAT_R16_SFLOAT:

View File

@@ -1,5 +1,6 @@
#pragma once
#include <XCEngine/Core/Asset/ImportSettings.h>
#include <XCEngine/Core/IO/IResourceLoader.h>
#include "Texture.h"
@@ -15,7 +16,11 @@ public:
Containers::Array<Containers::String> GetSupportedExtensions() const override;
bool CanLoad(const Containers::String& path) const override;
LoadResult Load(const Containers::String& path, const ImportSettings* settings = nullptr) override;
LoadResult LoadFromMemory(const Containers::String& path, const void* data, size_t dataSize) const;
LoadResult LoadFromMemory(
const Containers::String& path,
const void* data,
size_t dataSize,
const ImportSettings* settings = nullptr) const;
ImportSettings* GetDefaultSettings() const override;
};

View File

@@ -108,6 +108,7 @@ uint32_t GetFormatBytesPerPixel(Format format) {
case Format::R8G8_UNorm:
return 2;
case Format::R8G8B8A8_UNorm:
case Format::R8G8B8A8_SRGB:
return 4;
case Format::R16_Float:
return 2;

View File

@@ -145,6 +145,8 @@ OpenGLFormat ToOpenGLTextureFormat(Format format) {
return OpenGLFormat::RG32F;
case Format::R8G8B8A8_UNorm:
return OpenGLFormat::RGBA8;
case Format::R8G8B8A8_SRGB:
return OpenGLFormat::RGBA8_SRGB;
case Format::R16G16B16A16_Float:
return OpenGLFormat::RGBA16F;
case Format::R32G32B32A32_Float:

View File

@@ -10,8 +10,9 @@ namespace {
RHI::Format ToRHITextureFormat(Resources::TextureFormat format) {
switch (format) {
case Resources::TextureFormat::RGBA8_UNORM:
case Resources::TextureFormat::RGBA8_SRGB:
return RHI::Format::R8G8B8A8_UNorm;
case Resources::TextureFormat::RGBA8_SRGB:
return RHI::Format::R8G8B8A8_SRGB;
default:
return RHI::Format::Unknown;
}

View File

@@ -6,6 +6,7 @@
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Texture/Texture.h>
#include <XCEngine/Resources/Texture/TextureLoader.h>
#include <XCEngine/Resources/Texture/TextureImportSettings.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Core/Math/Matrix4.h>
#include <assimp/Importer.hpp>
@@ -14,6 +15,7 @@
#include <assimp/postprocess.h>
#include <assimp/scene.h>
#include <algorithm>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <limits>
@@ -131,6 +133,22 @@ Containers::String BuildSubResourcePath(const Containers::String& sourcePath,
return Containers::String(path.c_str());
}
TextureImportSettings BuildMaterialTextureImportSettings(const char* propertyName) {
TextureImportSettings settings;
(void)propertyName;
settings.SetSRGB(false);
settings.SetTargetFormat(TextureFormat::RGBA8_UNORM);
return settings;
}
std::string BuildTextureCacheKey(const std::string& pathKey, const TextureImportSettings& settings) {
std::string cacheKey = pathKey;
cacheKey += settings.GetSRGB() ? "|srgb" : "|linear";
cacheKey += "|fmt=";
cacheKey += std::to_string(static_cast<int>(settings.GetTargetFormat()));
return cacheKey;
}
Containers::String GetResourceNameFromPath(const Containers::String& path) {
const std::filesystem::path filePath(path.CStr());
const std::string fileName = filePath.filename().string();
@@ -243,8 +261,9 @@ Texture* CreateRawTexture(const Containers::String& texturePath,
Texture* LoadEmbeddedTexture(const aiTexture& embeddedTexture,
const Containers::String& texturePath,
const TextureImportSettings& settings,
TextureImportContext& context) {
const std::string cacheKey(texturePath.CStr());
const std::string cacheKey = BuildTextureCacheKey(texturePath.CStr(), settings);
const auto cacheIt = context.textureCache.find(cacheKey);
if (cacheIt != context.textureCache.end()) {
return cacheIt->second;
@@ -254,7 +273,8 @@ Texture* LoadEmbeddedTexture(const aiTexture& embeddedTexture,
if (embeddedTexture.mHeight == 0) {
LoadResult result = context.textureLoader.LoadFromMemory(texturePath,
embeddedTexture.pcData,
embeddedTexture.mWidth);
embeddedTexture.mWidth,
&settings);
if (result) {
texture = static_cast<Texture*>(result.resource);
}
@@ -272,7 +292,11 @@ Texture* LoadEmbeddedTexture(const aiTexture& embeddedTexture,
}
texture = CreateRawTexture(texturePath,
TextureFormat::RGBA8_UNORM,
settings.GetTargetFormat() != TextureFormat::Unknown
? settings.GetTargetFormat()
: (settings.GetSRGB()
? TextureFormat::RGBA8_SRGB
: TextureFormat::RGBA8_UNORM),
embeddedTexture.mWidth,
embeddedTexture.mHeight,
rgbaPixels.data(),
@@ -288,25 +312,30 @@ Texture* LoadEmbeddedTexture(const aiTexture& embeddedTexture,
}
Texture* LoadExternalTexture(const std::filesystem::path& textureFilePath,
const TextureImportSettings& settings,
TextureImportContext& context) {
const std::string normalizedPath = textureFilePath.lexically_normal().string();
const auto cacheIt = context.textureCache.find(normalizedPath);
const std::string cacheKey = BuildTextureCacheKey(normalizedPath, settings);
const auto cacheIt = context.textureCache.find(cacheKey);
if (cacheIt != context.textureCache.end()) {
return cacheIt->second;
}
LoadResult result = context.textureLoader.Load(Containers::String(normalizedPath.c_str()));
LoadResult result = context.textureLoader.Load(Containers::String(normalizedPath.c_str()), &settings);
if (!result) {
return nullptr;
}
Texture* texture = static_cast<Texture*>(result.resource);
context.textureCache.emplace(normalizedPath, texture);
context.textureCache.emplace(cacheKey, texture);
context.ownedTextures.push_back(texture);
return texture;
}
Texture* LoadTextureReference(const aiString& textureReference, TextureImportContext& context) {
Texture* LoadTextureReference(
const aiString& textureReference,
const TextureImportSettings& settings,
TextureImportContext& context) {
if (textureReference.length == 0) {
return nullptr;
}
@@ -327,7 +356,7 @@ Texture* LoadTextureReference(const aiString& textureReference, TextureImportCon
"embedded_texture",
embeddedTextureIndex,
Containers::String(extension.c_str()));
return LoadEmbeddedTexture(*embeddedTexture, texturePath, context);
return LoadEmbeddedTexture(*embeddedTexture, texturePath, settings, context);
}
std::filesystem::path resolvedPath(textureReference.C_Str());
@@ -335,11 +364,12 @@ Texture* LoadTextureReference(const aiString& textureReference, TextureImportCon
resolvedPath = context.sourceDirectory / resolvedPath;
}
return LoadExternalTexture(resolvedPath, context);
return LoadExternalTexture(resolvedPath, settings, context);
}
Texture* LoadMaterialTexture(const aiMaterial& assimpMaterial,
std::initializer_list<aiTextureType> textureTypes,
const TextureImportSettings& settings,
TextureImportContext& context) {
for (aiTextureType textureType : textureTypes) {
if (assimpMaterial.GetTextureCount(textureType) == 0) {
@@ -351,7 +381,7 @@ Texture* LoadMaterialTexture(const aiMaterial& assimpMaterial,
continue;
}
Texture* texture = LoadTextureReference(texturePath, context);
Texture* texture = LoadTextureReference(texturePath, settings, context);
if (texture != nullptr) {
return texture;
}
@@ -360,12 +390,27 @@ Texture* LoadMaterialTexture(const aiMaterial& assimpMaterial,
return nullptr;
}
bool HasMaterialTexture(
const aiMaterial& assimpMaterial,
std::initializer_list<aiTextureType> textureTypes) {
for (aiTextureType textureType : textureTypes) {
if (assimpMaterial.GetTextureCount(textureType) > 0) {
return true;
}
}
return false;
}
void ImportMaterialProperties(const aiMaterial& assimpMaterial, Material& material) {
float opacity = 1.0f;
if (assimpMaterial.Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS) {
material.SetFloat("opacity", opacity);
}
const bool hasBaseColorTexture =
HasMaterialTexture(assimpMaterial, { aiTextureType_BASE_COLOR, aiTextureType_DIFFUSE });
aiColor4D baseColor;
if (assimpMaterial.Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR, baseColor) == AI_SUCCESS) {
material.SetFloat4("baseColor",
@@ -374,7 +419,9 @@ void ImportMaterialProperties(const aiMaterial& assimpMaterial, Material& materi
aiColor3D diffuseColor;
if (assimpMaterial.Get(AI_MATKEY_COLOR_DIFFUSE, diffuseColor) == AI_SUCCESS) {
material.SetFloat4("baseColor",
Math::Vector4(diffuseColor.r, diffuseColor.g, diffuseColor.b, opacity));
hasBaseColorTexture
? Math::Vector4(1.0f, 1.0f, 1.0f, opacity)
: Math::Vector4(diffuseColor.r, diffuseColor.g, diffuseColor.b, opacity));
}
}
@@ -416,7 +463,8 @@ void ImportMaterialTextures(const aiMaterial& assimpMaterial,
TextureImportContext& context) {
auto assignTexture = [&](const char* propertyName,
std::initializer_list<aiTextureType> textureTypes) {
Texture* texture = LoadMaterialTexture(assimpMaterial, textureTypes, context);
const TextureImportSettings settings = BuildMaterialTextureImportSettings(propertyName);
Texture* texture = LoadMaterialTexture(assimpMaterial, textureTypes, settings, context);
if (texture != nullptr) {
material.SetTexture(Containers::String(propertyName), ResourceHandle<Texture>(texture));
}

View File

@@ -2,6 +2,7 @@
#include <XCEngine/Core/Asset/ArtifactFormats.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Resources/Texture/TextureImportSettings.h>
#include <stb_image.h>
#include <filesystem>
#include <fstream>
@@ -50,6 +51,25 @@ LoadResult CreateTextureResource(const Containers::String& path,
return LoadResult(texture);
}
TextureFormat ResolveDecodedTextureFormat(const ImportSettings* settings, bool isHdrTexture) {
if (isHdrTexture) {
return TextureFormat::RGBA32_FLOAT;
}
const auto* textureSettings = dynamic_cast<const TextureImportSettings*>(settings);
if (textureSettings == nullptr) {
return TextureFormat::RGBA8_UNORM;
}
if (textureSettings->GetTargetFormat() != TextureFormat::Unknown) {
return textureSettings->GetTargetFormat();
}
return textureSettings->GetSRGB()
? TextureFormat::RGBA8_SRGB
: TextureFormat::RGBA8_UNORM;
}
LoadResult LoadTextureArtifact(const Containers::String& path) {
std::filesystem::path resolvedPath(path.CStr());
if (!resolvedPath.is_absolute() && !std::filesystem::exists(resolvedPath)) {
@@ -124,8 +144,6 @@ bool TextureLoader::CanLoad(const Containers::String& path) const {
}
LoadResult TextureLoader::Load(const Containers::String& path, const ImportSettings* settings) {
(void)settings;
if (IsBuiltinTexturePath(path)) {
return CreateBuiltinTextureResource(path);
}
@@ -149,10 +167,14 @@ LoadResult TextureLoader::Load(const Containers::String& path, const ImportSetti
return LoadResult(Containers::String("Failed to read file: ") + path);
}
return LoadFromMemory(path, fileData.Data(), fileData.Size());
return LoadFromMemory(path, fileData.Data(), fileData.Size(), settings);
}
LoadResult TextureLoader::LoadFromMemory(const Containers::String& path, const void* data, size_t dataSize) const {
LoadResult TextureLoader::LoadFromMemory(
const Containers::String& path,
const void* data,
size_t dataSize,
const ImportSettings* settings) const {
if (data == nullptr || dataSize == 0) {
return LoadResult(Containers::String("Texture data is empty: ") + path);
}
@@ -180,7 +202,7 @@ LoadResult TextureLoader::LoadFromMemory(const Containers::String& path, const v
4u *
sizeof(float);
LoadResult result = CreateTextureResource(path,
TextureFormat::RGBA32_FLOAT,
ResolveDecodedTextureFormat(settings, true),
static_cast<Core::uint32>(width),
static_cast<Core::uint32>(height),
pixels,
@@ -204,7 +226,7 @@ LoadResult TextureLoader::LoadFromMemory(const Containers::String& path, const v
4u *
sizeof(stbi_uc);
LoadResult result = CreateTextureResource(path,
TextureFormat::RGBA8_UNORM,
ResolveDecodedTextureFormat(settings, false),
static_cast<Core::uint32>(width),
static_cast<Core::uint32>(height),
pixels,

View File

@@ -11,6 +11,7 @@
#include <chrono>
#include <filesystem>
#include <fstream>
#include <thread>
using namespace XCEngine::Resources;
@@ -51,6 +52,23 @@ bool PumpAsyncLoadsUntilIdle(ResourceManager& manager,
return !manager.IsAsyncLoading();
}
void FlipLastByte(const std::filesystem::path& path) {
std::ifstream input(path, std::ios::binary);
ASSERT_TRUE(input.is_open());
std::vector<char> bytes(
(std::istreambuf_iterator<char>(input)),
std::istreambuf_iterator<char>());
ASSERT_FALSE(bytes.empty());
bytes.back() ^= 0x01;
std::ofstream output(path, std::ios::binary | std::ios::trunc);
ASSERT_TRUE(output.is_open());
output.write(bytes.data(), static_cast<std::streamsize>(bytes.size()));
ASSERT_TRUE(static_cast<bool>(output));
}
TEST(MeshLoader, GetResourceType) {
MeshLoader loader;
EXPECT_EQ(loader.GetResourceType(), ResourceType::Mesh);
@@ -172,6 +190,7 @@ TEST(MeshLoader, ImportsMaterialTexturesFromObj) {
EXPECT_EQ(diffuseTexture->GetWidth(), 2u);
EXPECT_EQ(diffuseTexture->GetHeight(), 2u);
EXPECT_EQ(diffuseTexture->GetPixelDataSize(), 16u);
EXPECT_EQ(diffuseTexture->GetFormat(), TextureFormat::RGBA8_UNORM);
EXPECT_EQ(mesh->GetTextures().Size(), 1u);
delete mesh;
@@ -291,6 +310,64 @@ TEST(MeshLoader, AssetDatabaseCreatesModelArtifactAndReusesItWithoutReimport) {
fs::remove_all(projectRoot);
}
TEST(MeshLoader, AssetDatabaseReimportsModelWhenDependencyChanges) {
namespace fs = std::filesystem;
using namespace std::chrono_literals;
const fs::path projectRoot = fs::temp_directory_path() / "xc_mesh_dependency_reimport_test";
const fs::path assetsDir = projectRoot / "Assets";
fs::remove_all(projectRoot);
fs::create_directories(assetsDir);
fs::copy_file(GetMeshFixturePath("textured_triangle.obj"),
assetsDir / "textured_triangle.obj",
fs::copy_options::overwrite_existing);
fs::copy_file(GetMeshFixturePath("textured_triangle.mtl"),
assetsDir / "textured_triangle.mtl",
fs::copy_options::overwrite_existing);
fs::copy_file(GetMeshFixturePath("checker.bmp"),
assetsDir / "checker.bmp",
fs::copy_options::overwrite_existing);
AssetDatabase database;
database.Initialize(projectRoot.string().c_str());
AssetDatabase::ResolvedAsset firstResolve;
ASSERT_TRUE(database.EnsureArtifact("Assets/textured_triangle.obj", ResourceType::Mesh, firstResolve));
ASSERT_TRUE(firstResolve.artifactReady);
const String firstArtifactPath = firstResolve.artifactMainPath;
database.Shutdown();
std::this_thread::sleep_for(50ms);
{
std::ofstream mtlOutput(assetsDir / "textured_triangle.mtl", std::ios::app);
ASSERT_TRUE(mtlOutput.is_open());
mtlOutput << "\n# force dependency reimport\n";
ASSERT_TRUE(static_cast<bool>(mtlOutput));
}
database.Initialize(projectRoot.string().c_str());
AssetDatabase::ResolvedAsset secondResolve;
ASSERT_TRUE(database.EnsureArtifact("Assets/textured_triangle.obj", ResourceType::Mesh, secondResolve));
ASSERT_TRUE(secondResolve.artifactReady);
EXPECT_NE(firstArtifactPath, secondResolve.artifactMainPath);
const String secondArtifactPath = secondResolve.artifactMainPath;
database.Shutdown();
std::this_thread::sleep_for(50ms);
FlipLastByte(assetsDir / "checker.bmp");
database.Initialize(projectRoot.string().c_str());
AssetDatabase::ResolvedAsset thirdResolve;
ASSERT_TRUE(database.EnsureArtifact("Assets/textured_triangle.obj", ResourceType::Mesh, thirdResolve));
ASSERT_TRUE(thirdResolve.artifactReady);
EXPECT_NE(secondArtifactPath, thirdResolve.artifactMainPath);
EXPECT_TRUE(fs::exists(thirdResolve.artifactMainPath.CStr()));
database.Shutdown();
fs::remove_all(projectRoot);
}
TEST(MeshLoader, ResourceManagerLoadsModelByAssetRefFromProjectAssets) {
namespace fs = std::filesystem;

View File

@@ -2,6 +2,7 @@
#include <XCEngine/Core/Asset/AssetDatabase.h>
#include <XCEngine/Core/Asset/AssetRef.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Resources/Texture/TextureImportSettings.h>
#include <XCEngine/Resources/Texture/TextureLoader.h>
#include <XCEngine/Core/Asset/ResourceTypes.h>
#include <XCEngine/Core/Containers/Array.h>
@@ -64,6 +65,24 @@ TEST(TextureLoader, LoadValidBmpTexture) {
delete texture;
}
TEST(TextureLoader, LoadValidBmpTextureAsSRGBWhenRequested) {
TextureLoader loader;
TextureImportSettings settings;
settings.SetSRGB(true);
const std::string path = GetTextureFixturePath("checker.bmp");
LoadResult result = loader.Load(path.c_str(), &settings);
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
auto* texture = static_cast<Texture*>(result.resource);
EXPECT_EQ(texture->GetWidth(), 2u);
EXPECT_EQ(texture->GetHeight(), 2u);
EXPECT_EQ(texture->GetFormat(), TextureFormat::RGBA8_SRGB);
delete texture;
}
TEST(TextureLoader, AssetDatabaseCreatesTextureArtifactAndReusesItWithoutReimport) {
namespace fs = std::filesystem;
using namespace std::chrono_literals;