Fix FBX winding for Nahida preview
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <XCEngine/Core/Asset/AssetDatabase.h>
|
#include <XCEngine/Core/Asset/AssetDatabase.h>
|
||||||
|
|
||||||
|
#include <XCEngine/Core/Asset/ArtifactContainer.h>
|
||||||
#include <XCEngine/Core/Asset/ArtifactFormats.h>
|
#include <XCEngine/Core/Asset/ArtifactFormats.h>
|
||||||
#include <XCEngine/Debug/Logger.h>
|
#include <XCEngine/Debug/Logger.h>
|
||||||
#include <XCEngine/RHI/D3D12/D3D12Shader.h>
|
#include <XCEngine/RHI/D3D12/D3D12Shader.h>
|
||||||
@@ -18,6 +19,7 @@
|
|||||||
#include <XCEngine/Resources/BuiltinResources.h>
|
#include <XCEngine/Resources/BuiltinResources.h>
|
||||||
#include "Resources/GaussianSplat/Internal/GaussianSplatPlyImporter.h"
|
#include "Resources/GaussianSplat/Internal/GaussianSplatPlyImporter.h"
|
||||||
#include <XCEngine/Resources/Shader/ShaderLoader.h>
|
#include <XCEngine/Resources/Shader/ShaderLoader.h>
|
||||||
|
#include <XCEngine/Resources/Texture/TextureImportHeuristics.h>
|
||||||
#include <XCEngine/Resources/Texture/TextureLoader.h>
|
#include <XCEngine/Resources/Texture/TextureLoader.h>
|
||||||
#include <XCEngine/Resources/UI/UIDocumentCompiler.h>
|
#include <XCEngine/Resources/UI/UIDocumentCompiler.h>
|
||||||
#include <XCEngine/Resources/Volume/VolumeField.h>
|
#include <XCEngine/Resources/Volume/VolumeField.h>
|
||||||
@@ -63,7 +65,24 @@ Containers::String ToContainersString(const std::string& value) {
|
|||||||
|
|
||||||
Containers::String NormalizeArtifactPathString(const fs::path& path);
|
Containers::String NormalizeArtifactPathString(const fs::path& path);
|
||||||
Containers::String NormalizeArtifactPathString(const Containers::String& path);
|
Containers::String NormalizeArtifactPathString(const Containers::String& path);
|
||||||
|
bool SerializeTextureArtifactPayload(const Texture& texture,
|
||||||
|
Containers::Array<Core::uint8>& outPayload);
|
||||||
|
bool SerializeMaterialArtifactPayload(
|
||||||
|
const Material& material,
|
||||||
|
Containers::Array<Core::uint8>& outPayload,
|
||||||
|
const std::unordered_map<const Texture*, Containers::String>& textureArtifactPaths,
|
||||||
|
const std::unordered_map<const Texture*, AssetRef>& textureAssetRefs,
|
||||||
|
const AssetDatabase* assetDatabase);
|
||||||
|
bool SerializeShaderArtifactPayload(const Shader& shader,
|
||||||
|
Containers::Array<Core::uint8>& outPayload);
|
||||||
|
bool WriteSingleEntryArtifactContainerFile(const fs::path& artifactPath,
|
||||||
|
ResourceType resourceType,
|
||||||
|
const Containers::Array<Core::uint8>& payload);
|
||||||
constexpr const char* kModelSubAssetManifestFileName = "subassets.tsv";
|
constexpr const char* kModelSubAssetManifestFileName = "subassets.tsv";
|
||||||
|
constexpr const char* kSourceAssetDbFileName = "assets.db";
|
||||||
|
constexpr const char* kArtifactDbFileName = "artifacts.db";
|
||||||
|
constexpr const char* kLegacySourceAssetDbDirectory = "SourceAssetDB";
|
||||||
|
constexpr const char* kLegacyArtifactDbDirectory = "ArtifactDB";
|
||||||
|
|
||||||
struct ModelSubAssetManifestEntry {
|
struct ModelSubAssetManifestEntry {
|
||||||
LocalID localID = kInvalidLocalID;
|
LocalID localID = kInvalidLocalID;
|
||||||
@@ -114,6 +133,39 @@ bool IsProjectRelativePath(const fs::path& path) {
|
|||||||
generic.rfind("../", 0) != 0;
|
generic.rfind("../", 0) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TryMigrateLegacyLibraryDatabaseFile(
|
||||||
|
const fs::path& libraryRoot,
|
||||||
|
const char* legacyDirectoryName,
|
||||||
|
const char* fileName) {
|
||||||
|
const fs::path legacyFilePath = libraryRoot / legacyDirectoryName / fileName;
|
||||||
|
const fs::path currentFilePath = libraryRoot / fileName;
|
||||||
|
|
||||||
|
std::error_code ec;
|
||||||
|
if (fs::exists(currentFilePath, ec)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ec.clear();
|
||||||
|
if (!fs::exists(legacyFilePath, ec)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::rename(legacyFilePath, currentFilePath, ec);
|
||||||
|
if (ec) {
|
||||||
|
ec.clear();
|
||||||
|
fs::copy_file(legacyFilePath, currentFilePath, fs::copy_options::overwrite_existing, ec);
|
||||||
|
if (ec) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ec.clear();
|
||||||
|
fs::remove(legacyFilePath, ec);
|
||||||
|
}
|
||||||
|
|
||||||
|
ec.clear();
|
||||||
|
fs::remove(legacyFilePath.parent_path(), ec);
|
||||||
|
}
|
||||||
|
|
||||||
void AddUniqueDependencyPath(const fs::path& path,
|
void AddUniqueDependencyPath(const fs::path& path,
|
||||||
std::unordered_set<std::string>& seenPaths,
|
std::unordered_set<std::string>& seenPaths,
|
||||||
std::vector<fs::path>& outPaths) {
|
std::vector<fs::path>& outPaths) {
|
||||||
@@ -133,6 +185,23 @@ void AddUniqueDependencyPath(const fs::path& path,
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool IsCurrentShaderArtifactFile(const fs::path& artifactPath) {
|
bool IsCurrentShaderArtifactFile(const fs::path& artifactPath) {
|
||||||
|
Containers::Array<Core::uint8> data;
|
||||||
|
Containers::String payloadError;
|
||||||
|
if (ReadArtifactContainerMainEntryPayload(
|
||||||
|
NormalizeArtifactPathString(artifactPath),
|
||||||
|
ResourceType::Shader,
|
||||||
|
data,
|
||||||
|
&payloadError)) {
|
||||||
|
if (data.Size() < sizeof(ShaderArtifactFileHeader)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderArtifactFileHeader header = {};
|
||||||
|
std::memcpy(&header, data.Data(), sizeof(header));
|
||||||
|
return std::memcmp(header.magic, "XCSHD06", 7) == 0 &&
|
||||||
|
header.schemaVersion == kShaderArtifactSchemaVersion;
|
||||||
|
}
|
||||||
|
|
||||||
std::ifstream input(artifactPath, std::ios::binary);
|
std::ifstream input(artifactPath, std::ios::binary);
|
||||||
if (!input.is_open()) {
|
if (!input.is_open()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -330,12 +399,19 @@ bool TryPrecompileShaderVariantForBackend(const Containers::String& shaderPath,
|
|||||||
|
|
||||||
RHI::CompiledSpirvShader compiledShader = {};
|
RHI::CompiledSpirvShader compiledShader = {};
|
||||||
std::string errorMessage;
|
std::string errorMessage;
|
||||||
|
const RHI::SpirvTargetEnvironment spirvTargetEnvironment =
|
||||||
|
(targetBackend == ShaderBackend::OpenGL &&
|
||||||
|
compileDesc.sourceLanguage == RHI::ShaderLanguage::HLSL)
|
||||||
|
? RHI::SpirvTargetEnvironment::Vulkan
|
||||||
|
: (targetBackend == ShaderBackend::Vulkan
|
||||||
|
? RHI::SpirvTargetEnvironment::Vulkan
|
||||||
|
: RHI::SpirvTargetEnvironment::OpenGL);
|
||||||
const bool compiled =
|
const bool compiled =
|
||||||
targetBackend == ShaderBackend::Vulkan
|
targetBackend == ShaderBackend::Vulkan
|
||||||
? RHI::CompileVulkanShader(compileDesc, compiledShader, &errorMessage)
|
? RHI::CompileVulkanShader(compileDesc, compiledShader, &errorMessage)
|
||||||
: RHI::CompileSpirvShader(
|
: RHI::CompileSpirvShader(
|
||||||
compileDesc,
|
compileDesc,
|
||||||
RHI::SpirvTargetEnvironment::OpenGL,
|
spirvTargetEnvironment,
|
||||||
compiledShader,
|
compiledShader,
|
||||||
&errorMessage);
|
&errorMessage);
|
||||||
if (!compiled) {
|
if (!compiled) {
|
||||||
@@ -705,7 +781,7 @@ bool TryResolveModelSubAssetArtifactPath(const fs::path& manifestPath,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WriteString(std::ofstream& stream, const Containers::String& value) {
|
void WriteString(std::ostream& stream, const Containers::String& value) {
|
||||||
const Core::uint32 length = static_cast<Core::uint32>(value.Length());
|
const Core::uint32 length = static_cast<Core::uint32>(value.Length());
|
||||||
stream.write(reinterpret_cast<const char*>(&length), sizeof(length));
|
stream.write(reinterpret_cast<const char*>(&length), sizeof(length));
|
||||||
if (length > 0) {
|
if (length > 0) {
|
||||||
@@ -713,6 +789,45 @@ void WriteString(std::ofstream& stream, const Containers::String& value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Containers::Array<Core::uint8> ToByteArray(const std::string& text) {
|
||||||
|
Containers::Array<Core::uint8> bytes;
|
||||||
|
if (text.empty()) {
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes.ResizeUninitialized(text.size());
|
||||||
|
std::memcpy(bytes.Data(), text.data(), text.size());
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SerializeTextureArtifactPayload(const Texture& texture,
|
||||||
|
Containers::Array<Core::uint8>& outPayload) {
|
||||||
|
std::ostringstream output(std::ios::binary | std::ios::out);
|
||||||
|
|
||||||
|
TextureArtifactHeader header;
|
||||||
|
header.textureType = static_cast<Core::uint32>(texture.GetTextureType());
|
||||||
|
header.textureFormat = static_cast<Core::uint32>(texture.GetFormat());
|
||||||
|
header.width = texture.GetWidth();
|
||||||
|
header.height = texture.GetHeight();
|
||||||
|
header.depth = texture.GetDepth();
|
||||||
|
header.mipLevels = texture.GetMipLevels();
|
||||||
|
header.arraySize = texture.GetArraySize();
|
||||||
|
header.pixelDataSize = static_cast<Core::uint64>(texture.GetPixelDataSize());
|
||||||
|
|
||||||
|
output.write(reinterpret_cast<const char*>(&header), sizeof(header));
|
||||||
|
if (texture.GetPixelDataSize() > 0) {
|
||||||
|
output.write(static_cast<const char*>(texture.GetPixelData()), texture.GetPixelDataSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!output) {
|
||||||
|
outPayload.Clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outPayload = ToByteArray(output.str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Containers::String ReadString(std::ifstream& stream) {
|
Containers::String ReadString(std::ifstream& stream) {
|
||||||
Core::uint32 length = 0;
|
Core::uint32 length = 0;
|
||||||
stream.read(reinterpret_cast<char*>(&length), sizeof(length));
|
stream.read(reinterpret_cast<char*>(&length), sizeof(length));
|
||||||
@@ -730,35 +845,24 @@ Containers::String ReadString(std::ifstream& stream) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool WriteTextureArtifactFile(const fs::path& artifactPath, const Texture& texture) {
|
bool WriteTextureArtifactFile(const fs::path& artifactPath, const Texture& texture) {
|
||||||
|
Containers::Array<Core::uint8> payload;
|
||||||
|
if (!SerializeTextureArtifactPayload(texture, payload)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
std::ofstream output(artifactPath, std::ios::binary | std::ios::trunc);
|
std::ofstream output(artifactPath, std::ios::binary | std::ios::trunc);
|
||||||
if (!output.is_open()) {
|
if (!output.is_open()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!payload.Empty()) {
|
||||||
TextureArtifactHeader header;
|
output.write(reinterpret_cast<const char*>(payload.Data()),
|
||||||
header.textureType = static_cast<Core::uint32>(texture.GetTextureType());
|
static_cast<std::streamsize>(payload.Size()));
|
||||||
header.textureFormat = static_cast<Core::uint32>(texture.GetFormat());
|
|
||||||
header.width = texture.GetWidth();
|
|
||||||
header.height = texture.GetHeight();
|
|
||||||
header.depth = texture.GetDepth();
|
|
||||||
header.mipLevels = texture.GetMipLevels();
|
|
||||||
header.arraySize = texture.GetArraySize();
|
|
||||||
header.pixelDataSize = static_cast<Core::uint64>(texture.GetPixelDataSize());
|
|
||||||
|
|
||||||
output.write(reinterpret_cast<const char*>(&header), sizeof(header));
|
|
||||||
if (texture.GetPixelDataSize() > 0) {
|
|
||||||
output.write(static_cast<const char*>(texture.GetPixelData()), texture.GetPixelDataSize());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return static_cast<bool>(output);
|
return static_cast<bool>(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WriteVolumeFieldArtifactFile(const fs::path& artifactPath, const VolumeField& volumeField) {
|
bool WriteVolumeFieldArtifactFile(const fs::path& artifactPath, const VolumeField& volumeField) {
|
||||||
std::ofstream output(artifactPath, std::ios::binary | std::ios::trunc);
|
Containers::Array<Core::uint8> payload;
|
||||||
if (!output.is_open()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
VolumeFieldArtifactHeader header;
|
VolumeFieldArtifactHeader header;
|
||||||
header.storageKind = static_cast<Core::uint32>(volumeField.GetStorageKind());
|
header.storageKind = static_cast<Core::uint32>(volumeField.GetStorageKind());
|
||||||
header.boundsMin = volumeField.GetBounds().GetMin();
|
header.boundsMin = volumeField.GetBounds().GetMin();
|
||||||
@@ -774,9 +878,22 @@ bool WriteVolumeFieldArtifactFile(const fs::path& artifactPath, const VolumeFiel
|
|||||||
header.gridClass = volumeField.GetGridClass();
|
header.gridClass = volumeField.GetGridClass();
|
||||||
header.payloadSize = static_cast<Core::uint64>(volumeField.GetPayloadSize());
|
header.payloadSize = static_cast<Core::uint64>(volumeField.GetPayloadSize());
|
||||||
|
|
||||||
output.write(reinterpret_cast<const char*>(&header), sizeof(header));
|
payload.Resize(sizeof(header) + volumeField.GetPayloadSize());
|
||||||
|
std::memcpy(payload.Data(), &header, sizeof(header));
|
||||||
if (volumeField.GetPayloadSize() > 0) {
|
if (volumeField.GetPayloadSize() > 0) {
|
||||||
output.write(static_cast<const char*>(volumeField.GetPayloadData()), volumeField.GetPayloadSize());
|
std::memcpy(
|
||||||
|
payload.Data() + sizeof(header),
|
||||||
|
volumeField.GetPayloadData(),
|
||||||
|
volumeField.GetPayloadSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ofstream output(artifactPath, std::ios::binary | std::ios::trunc);
|
||||||
|
if (!output.is_open()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!payload.Empty()) {
|
||||||
|
output.write(reinterpret_cast<const char*>(payload.Data()), static_cast<std::streamsize>(payload.Size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return static_cast<bool>(output);
|
return static_cast<bool>(output);
|
||||||
@@ -856,17 +973,41 @@ Containers::String ResolveTextureBindingPath(
|
|||||||
|
|
||||||
return NormalizeArtifactPathString(material.GetTextureBindingPath(bindingIndex));
|
return NormalizeArtifactPathString(material.GetTextureBindingPath(bindingIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WriteMaterialArtifactFile(
|
bool WriteMaterialArtifactFile(
|
||||||
const fs::path& artifactPath,
|
const fs::path& artifactPath,
|
||||||
const Material& material,
|
const Material& material,
|
||||||
const std::unordered_map<const Texture*, Containers::String>& textureArtifactPaths = {},
|
const std::unordered_map<const Texture*, Containers::String>& textureArtifactPaths = {},
|
||||||
const std::unordered_map<const Texture*, AssetRef>& textureAssetRefs = {},
|
const std::unordered_map<const Texture*, AssetRef>& textureAssetRefs = {},
|
||||||
const AssetDatabase* assetDatabase = nullptr) {
|
const AssetDatabase* assetDatabase = nullptr) {
|
||||||
|
Containers::Array<Core::uint8> payload;
|
||||||
|
if (!SerializeMaterialArtifactPayload(
|
||||||
|
material,
|
||||||
|
payload,
|
||||||
|
textureArtifactPaths,
|
||||||
|
textureAssetRefs,
|
||||||
|
assetDatabase)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
std::ofstream output(artifactPath, std::ios::binary | std::ios::trunc);
|
std::ofstream output(artifactPath, std::ios::binary | std::ios::trunc);
|
||||||
if (!output.is_open()) {
|
if (!output.is_open()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!payload.Empty()) {
|
||||||
|
output.write(reinterpret_cast<const char*>(payload.Data()),
|
||||||
|
static_cast<std::streamsize>(payload.Size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<bool>(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SerializeMaterialArtifactPayload(
|
||||||
|
const Material& material,
|
||||||
|
Containers::Array<Core::uint8>& outPayload,
|
||||||
|
const std::unordered_map<const Texture*, Containers::String>& textureArtifactPaths,
|
||||||
|
const std::unordered_map<const Texture*, AssetRef>& textureAssetRefs,
|
||||||
|
const AssetDatabase* assetDatabase) {
|
||||||
|
std::ostringstream output(std::ios::binary | std::ios::out);
|
||||||
|
|
||||||
MaterialArtifactFileHeader fileHeader;
|
MaterialArtifactFileHeader fileHeader;
|
||||||
output.write(reinterpret_cast<const char*>(&fileHeader), sizeof(fileHeader));
|
output.write(reinterpret_cast<const char*>(&fileHeader), sizeof(fileHeader));
|
||||||
@@ -929,15 +1070,32 @@ bool WriteMaterialArtifactFile(
|
|||||||
WriteString(output, ResolveTextureBindingPath(material, bindingIndex, textureArtifactPaths));
|
WriteString(output, ResolveTextureBindingPath(material, bindingIndex, textureArtifactPaths));
|
||||||
}
|
}
|
||||||
|
|
||||||
return static_cast<bool>(output);
|
if (!output) {
|
||||||
}
|
outPayload.Clear();
|
||||||
|
|
||||||
bool WriteShaderArtifactFile(const fs::path& artifactPath, const Shader& shader) {
|
|
||||||
std::ofstream output(artifactPath, std::ios::binary | std::ios::trunc);
|
|
||||||
if (!output.is_open()) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outPayload = ToByteArray(output.str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WriteSingleEntryArtifactContainerFile(const fs::path& artifactPath,
|
||||||
|
ResourceType resourceType,
|
||||||
|
const Containers::Array<Core::uint8>& payload) {
|
||||||
|
ArtifactContainerWriter writer;
|
||||||
|
ArtifactContainerEntry entry;
|
||||||
|
entry.name = "main";
|
||||||
|
entry.resourceType = resourceType;
|
||||||
|
entry.localID = kMainAssetLocalID;
|
||||||
|
entry.payload = payload;
|
||||||
|
writer.AddEntry(std::move(entry));
|
||||||
|
return writer.WriteToFile(NormalizeArtifactPathString(artifactPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SerializeShaderArtifactPayload(const Shader& shader,
|
||||||
|
Containers::Array<Core::uint8>& outPayload) {
|
||||||
|
std::ostringstream output(std::ios::binary | std::ios::out);
|
||||||
|
|
||||||
ShaderArtifactFileHeader fileHeader;
|
ShaderArtifactFileHeader fileHeader;
|
||||||
output.write(reinterpret_cast<const char*>(&fileHeader), sizeof(fileHeader));
|
output.write(reinterpret_cast<const char*>(&fileHeader), sizeof(fileHeader));
|
||||||
|
|
||||||
@@ -1037,6 +1195,30 @@ bool WriteShaderArtifactFile(const fs::path& artifactPath, const Shader& shader)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!output) {
|
||||||
|
outPayload.Clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outPayload = ToByteArray(output.str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WriteShaderArtifactFile(const fs::path& artifactPath, const Shader& shader) {
|
||||||
|
Containers::Array<Core::uint8> payload;
|
||||||
|
if (!SerializeShaderArtifactPayload(shader, payload)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ofstream output(artifactPath, std::ios::binary | std::ios::trunc);
|
||||||
|
if (!output.is_open()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!payload.Empty()) {
|
||||||
|
output.write(reinterpret_cast<const char*>(payload.Data()),
|
||||||
|
static_cast<std::streamsize>(payload.Size()));
|
||||||
|
}
|
||||||
|
|
||||||
return static_cast<bool>(output);
|
return static_cast<bool>(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1103,10 +1285,20 @@ void AssetDatabase::Initialize(const Containers::String& projectRoot) {
|
|||||||
m_projectRoot = NormalizePathString(projectRoot);
|
m_projectRoot = NormalizePathString(projectRoot);
|
||||||
m_assetsRoot = NormalizePathString(fs::path(m_projectRoot.CStr()) / "Assets");
|
m_assetsRoot = NormalizePathString(fs::path(m_projectRoot.CStr()) / "Assets");
|
||||||
m_libraryRoot = NormalizePathString(fs::path(m_projectRoot.CStr()) / "Library");
|
m_libraryRoot = NormalizePathString(fs::path(m_projectRoot.CStr()) / "Library");
|
||||||
m_sourceDbPath = NormalizePathString(fs::path(m_libraryRoot.CStr()) / "SourceAssetDB" / "assets.db");
|
m_sourceDbPath =
|
||||||
m_artifactDbPath = NormalizePathString(fs::path(m_libraryRoot.CStr()) / "ArtifactDB" / "artifacts.db");
|
NormalizePathString(fs::path(m_libraryRoot.CStr()) / kSourceAssetDbFileName);
|
||||||
|
m_artifactDbPath =
|
||||||
|
NormalizePathString(fs::path(m_libraryRoot.CStr()) / kArtifactDbFileName);
|
||||||
|
|
||||||
EnsureProjectLayout();
|
EnsureProjectLayout();
|
||||||
|
TryMigrateLegacyLibraryDatabaseFile(
|
||||||
|
fs::path(m_libraryRoot.CStr()),
|
||||||
|
kLegacySourceAssetDbDirectory,
|
||||||
|
kSourceAssetDbFileName);
|
||||||
|
TryMigrateLegacyLibraryDatabaseFile(
|
||||||
|
fs::path(m_libraryRoot.CStr()),
|
||||||
|
kLegacyArtifactDbDirectory,
|
||||||
|
kArtifactDbFileName);
|
||||||
LoadSourceAssetDB();
|
LoadSourceAssetDB();
|
||||||
LoadArtifactDB();
|
LoadArtifactDB();
|
||||||
}
|
}
|
||||||
@@ -1441,9 +1633,7 @@ void AssetDatabase::EnsureProjectLayout() {
|
|||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
fs::create_directories(fs::path(m_assetsRoot.CStr()), ec);
|
fs::create_directories(fs::path(m_assetsRoot.CStr()), ec);
|
||||||
ec.clear();
|
ec.clear();
|
||||||
fs::create_directories(fs::path(m_libraryRoot.CStr()) / "SourceAssetDB", ec);
|
fs::create_directories(fs::path(m_libraryRoot.CStr()), ec);
|
||||||
ec.clear();
|
|
||||||
fs::create_directories(fs::path(m_libraryRoot.CStr()) / "ArtifactDB", ec);
|
|
||||||
ec.clear();
|
ec.clear();
|
||||||
fs::create_directories(fs::path(m_libraryRoot.CStr()) / "Artifacts", ec);
|
fs::create_directories(fs::path(m_libraryRoot.CStr()) / "Artifacts", ec);
|
||||||
}
|
}
|
||||||
@@ -1666,12 +1856,15 @@ Core::uint32 AssetDatabase::CleanupOrphanedArtifacts() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::unordered_set<std::string> retainedArtifactPathKeys;
|
std::unordered_set<std::string> retainedArtifactPathKeys;
|
||||||
retainedArtifactPathKeys.reserve(m_artifactsByGuid.size());
|
retainedArtifactPathKeys.reserve(m_artifactsByGuid.size() * 2u);
|
||||||
for (const auto& [guid, record] : m_artifactsByGuid) {
|
for (const auto& [guid, record] : m_artifactsByGuid) {
|
||||||
(void)guid;
|
(void)guid;
|
||||||
if (!record.artifactDirectory.Empty()) {
|
if (!record.artifactDirectory.Empty()) {
|
||||||
retainedArtifactPathKeys.insert(ToStdString(MakeKey(record.artifactDirectory)));
|
retainedArtifactPathKeys.insert(ToStdString(MakeKey(record.artifactDirectory)));
|
||||||
}
|
}
|
||||||
|
if (!record.mainArtifactPath.Empty()) {
|
||||||
|
retainedArtifactPathKeys.insert(ToStdString(MakeKey(record.mainArtifactPath)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Core::uint32 removedArtifactCount = 0;
|
Core::uint32 removedArtifactCount = 0;
|
||||||
@@ -1743,13 +1936,13 @@ bool AssetDatabase::EnsureMetaForPath(const fs::path& sourcePath,
|
|||||||
outRecord = SourceAssetRecord();
|
outRecord = SourceAssetRecord();
|
||||||
outRecord.relativePath = relativePath;
|
outRecord.relativePath = relativePath;
|
||||||
outRecord.importerName = GetImporterNameForPath(relativePath, isFolder);
|
outRecord.importerName = GetImporterNameForPath(relativePath, isFolder);
|
||||||
outRecord.importerVersion = kCurrentImporterVersion;
|
outRecord.importerVersion = GetCurrentImporterVersion(outRecord.importerName);
|
||||||
}
|
}
|
||||||
|
|
||||||
outRecord.relativePath = relativePath;
|
outRecord.relativePath = relativePath;
|
||||||
outRecord.isFolder = isFolder;
|
outRecord.isFolder = isFolder;
|
||||||
outRecord.importerName = GetImporterNameForPath(relativePath, isFolder);
|
outRecord.importerName = GetImporterNameForPath(relativePath, isFolder);
|
||||||
outRecord.importerVersion = kCurrentImporterVersion;
|
outRecord.importerVersion = GetCurrentImporterVersion(outRecord.importerName);
|
||||||
|
|
||||||
if (UsesExternalSyntheticSourceRecord(relativePath)) {
|
if (UsesExternalSyntheticSourceRecord(relativePath)) {
|
||||||
if (!outRecord.guid.IsValid()) {
|
if (!outRecord.guid.IsValid()) {
|
||||||
@@ -1795,6 +1988,7 @@ bool AssetDatabase::EnsureMetaForPath(const fs::path& sourcePath,
|
|||||||
const fs::path metaPath(sourcePath.string() + ".meta");
|
const fs::path metaPath(sourcePath.string() + ".meta");
|
||||||
outRecord.metaPath = NormalizeRelativePath(metaPath);
|
outRecord.metaPath = NormalizeRelativePath(metaPath);
|
||||||
const Containers::String expectedImporterName = GetImporterNameForPath(relativePath, isFolder);
|
const Containers::String expectedImporterName = GetImporterNameForPath(relativePath, isFolder);
|
||||||
|
const Core::uint32 expectedImporterVersion = GetCurrentImporterVersion(expectedImporterName);
|
||||||
|
|
||||||
bool shouldRewriteMeta = false;
|
bool shouldRewriteMeta = false;
|
||||||
if (!fs::exists(metaPath) || !ReadMetaFile(metaPath, outRecord) || !outRecord.guid.IsValid()) {
|
if (!fs::exists(metaPath) || !ReadMetaFile(metaPath, outRecord) || !outRecord.guid.IsValid()) {
|
||||||
@@ -1807,8 +2001,8 @@ bool AssetDatabase::EnsureMetaForPath(const fs::path& sourcePath,
|
|||||||
outRecord.importerName = expectedImporterName;
|
outRecord.importerName = expectedImporterName;
|
||||||
shouldRewriteMeta = true;
|
shouldRewriteMeta = true;
|
||||||
}
|
}
|
||||||
if (outRecord.importerVersion != kCurrentImporterVersion) {
|
if (outRecord.importerVersion != expectedImporterVersion) {
|
||||||
outRecord.importerVersion = kCurrentImporterVersion;
|
outRecord.importerVersion = expectedImporterVersion;
|
||||||
shouldRewriteMeta = true;
|
shouldRewriteMeta = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1879,7 +2073,7 @@ bool AssetDatabase::ReadMetaFile(const fs::path& metaPath,
|
|||||||
inOutRecord.importerName = GetImporterNameForPath(inOutRecord.relativePath, inOutRecord.isFolder);
|
inOutRecord.importerName = GetImporterNameForPath(inOutRecord.relativePath, inOutRecord.isFolder);
|
||||||
}
|
}
|
||||||
if (inOutRecord.importerVersion == 0) {
|
if (inOutRecord.importerVersion == 0) {
|
||||||
inOutRecord.importerVersion = kCurrentImporterVersion;
|
inOutRecord.importerVersion = GetCurrentImporterVersion(inOutRecord.importerName);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -1962,6 +2156,34 @@ Containers::String AssetDatabase::GetImporterNameForPath(const Containers::Strin
|
|||||||
return Containers::String("DefaultImporter");
|
return Containers::String("DefaultImporter");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Core::uint32 AssetDatabase::GetCurrentImporterVersion(const Containers::String& importerName) {
|
||||||
|
if (importerName == "TextureImporter") {
|
||||||
|
return 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (importerName == "MaterialImporter") {
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (importerName == "ShaderImporter") {
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (importerName == "ModelImporter") {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (importerName == "GaussianSplatImporter") {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (importerName == "VolumeFieldImporter") {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return kBaseImporterVersion;
|
||||||
|
}
|
||||||
|
|
||||||
ResourceType AssetDatabase::GetPrimaryResourceTypeForImporter(const Containers::String& importerName) {
|
ResourceType AssetDatabase::GetPrimaryResourceTypeForImporter(const Containers::String& importerName) {
|
||||||
if (importerName == "UIViewImporter") {
|
if (importerName == "UIViewImporter") {
|
||||||
return ResourceType::UIView;
|
return ResourceType::UIView;
|
||||||
@@ -2187,27 +2409,41 @@ bool AssetDatabase::EnsureArtifact(const Containers::String& requestPath,
|
|||||||
bool AssetDatabase::ImportTextureAsset(const SourceAssetRecord& sourceRecord,
|
bool AssetDatabase::ImportTextureAsset(const SourceAssetRecord& sourceRecord,
|
||||||
ArtifactRecord& outRecord) {
|
ArtifactRecord& outRecord) {
|
||||||
TextureLoader loader;
|
TextureLoader loader;
|
||||||
const Containers::String absolutePath = NormalizePathString(fs::path(m_projectRoot.CStr()) / sourceRecord.relativePath.CStr());
|
const Containers::String absolutePath =
|
||||||
LoadResult result = loader.Load(absolutePath);
|
NormalizePathString(fs::path(m_projectRoot.CStr()) / sourceRecord.relativePath.CStr());
|
||||||
|
const TextureImportSettings importSettings =
|
||||||
|
BuildTextureImportSettingsFromIdentifier(sourceRecord.relativePath);
|
||||||
|
LoadResult result = loader.Load(absolutePath, &importSettings);
|
||||||
if (!result || result.resource == nullptr) {
|
if (!result || result.resource == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Texture* texture = static_cast<Texture*>(result.resource);
|
Texture* texture = static_cast<Texture*>(result.resource);
|
||||||
const Containers::String artifactKey = BuildArtifactKey(sourceRecord);
|
const Containers::String artifactKey = BuildArtifactKey(sourceRecord);
|
||||||
const Containers::String artifactDir = BuildArtifactDirectory(artifactKey);
|
const Containers::String legacyArtifactDir = BuildArtifactDirectory(artifactKey);
|
||||||
const Containers::String mainArtifactPath = NormalizePathString(fs::path(artifactDir.CStr()) / "main.xctex");
|
const Containers::String mainArtifactPath = BuildArtifactFilePath(artifactKey, ".xctex");
|
||||||
|
const Containers::String artifactDir =
|
||||||
|
NormalizePathString(fs::path(mainArtifactPath.CStr()).parent_path());
|
||||||
|
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
fs::remove_all(fs::path(m_projectRoot.CStr()) / artifactDir.CStr(), ec);
|
fs::remove_all(fs::path(m_projectRoot.CStr()) / legacyArtifactDir.CStr(), ec);
|
||||||
ec.clear();
|
ec.clear();
|
||||||
fs::create_directories(fs::path(m_projectRoot.CStr()) / artifactDir.CStr(), ec);
|
fs::create_directories(
|
||||||
|
(fs::path(m_projectRoot.CStr()) / mainArtifactPath.CStr()).parent_path(),
|
||||||
|
ec);
|
||||||
if (ec) {
|
if (ec) {
|
||||||
delete texture;
|
delete texture;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool writeOk = WriteTextureArtifactFile(fs::path(m_projectRoot.CStr()) / mainArtifactPath.CStr(), *texture);
|
Containers::Array<Core::uint8> payload;
|
||||||
|
const bool serialized = SerializeTextureArtifactPayload(*texture, payload);
|
||||||
|
const bool writeOk =
|
||||||
|
serialized &&
|
||||||
|
WriteSingleEntryArtifactContainerFile(
|
||||||
|
fs::path(m_projectRoot.CStr()) / mainArtifactPath.CStr(),
|
||||||
|
ResourceType::Texture,
|
||||||
|
payload);
|
||||||
delete texture;
|
delete texture;
|
||||||
if (!writeOk) {
|
if (!writeOk) {
|
||||||
return false;
|
return false;
|
||||||
@@ -2243,21 +2479,30 @@ bool AssetDatabase::ImportMaterialAsset(const SourceAssetRecord& sourceRecord,
|
|||||||
std::vector<ArtifactDependencyRecord> dependencies;
|
std::vector<ArtifactDependencyRecord> dependencies;
|
||||||
CollectMaterialDependencies(*material, dependencies);
|
CollectMaterialDependencies(*material, dependencies);
|
||||||
const Containers::String artifactKey = BuildArtifactKey(sourceRecord, dependencies);
|
const Containers::String artifactKey = BuildArtifactKey(sourceRecord, dependencies);
|
||||||
const Containers::String artifactDir = BuildArtifactDirectory(artifactKey);
|
const Containers::String legacyArtifactDir = BuildArtifactDirectory(artifactKey);
|
||||||
const Containers::String mainArtifactPath =
|
const Containers::String mainArtifactPath = BuildArtifactFilePath(artifactKey, ".xcmat");
|
||||||
NormalizePathString(fs::path(artifactDir.CStr()) / "main.xcmat");
|
const Containers::String artifactDir =
|
||||||
|
NormalizePathString(fs::path(mainArtifactPath.CStr()).parent_path());
|
||||||
|
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
fs::remove_all(fs::path(m_projectRoot.CStr()) / artifactDir.CStr(), ec);
|
fs::remove_all(fs::path(m_projectRoot.CStr()) / legacyArtifactDir.CStr(), ec);
|
||||||
ec.clear();
|
ec.clear();
|
||||||
fs::create_directories(fs::path(m_projectRoot.CStr()) / artifactDir.CStr(), ec);
|
fs::create_directories(
|
||||||
|
(fs::path(m_projectRoot.CStr()) / mainArtifactPath.CStr()).parent_path(),
|
||||||
|
ec);
|
||||||
if (ec) {
|
if (ec) {
|
||||||
delete material;
|
delete material;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Containers::Array<Core::uint8> payload;
|
||||||
|
const bool serialized = SerializeMaterialArtifactPayload(*material, payload, {}, {}, this);
|
||||||
const bool writeOk =
|
const bool writeOk =
|
||||||
WriteMaterialArtifactFile(fs::path(m_projectRoot.CStr()) / mainArtifactPath.CStr(), *material, {}, {}, this);
|
serialized &&
|
||||||
|
WriteSingleEntryArtifactContainerFile(
|
||||||
|
fs::path(m_projectRoot.CStr()) / mainArtifactPath.CStr(),
|
||||||
|
ResourceType::Material,
|
||||||
|
payload);
|
||||||
delete material;
|
delete material;
|
||||||
if (!writeOk) {
|
if (!writeOk) {
|
||||||
return false;
|
return false;
|
||||||
@@ -2285,6 +2530,11 @@ bool AssetDatabase::ImportModelAsset(const SourceAssetRecord& sourceRecord,
|
|||||||
NormalizePathString(fs::path(m_projectRoot.CStr()) / sourceRecord.relativePath.CStr());
|
NormalizePathString(fs::path(m_projectRoot.CStr()) / sourceRecord.relativePath.CStr());
|
||||||
|
|
||||||
MeshImportSettings importSettings;
|
MeshImportSettings importSettings;
|
||||||
|
const std::string extension = ToLowerCopy(fs::path(sourceRecord.relativePath.CStr()).extension().string());
|
||||||
|
if (extension == ".fbx") {
|
||||||
|
importSettings.AddImportFlag(MeshImportFlags::FlipUVs);
|
||||||
|
}
|
||||||
|
|
||||||
ImportedModelData importedModel;
|
ImportedModelData importedModel;
|
||||||
Containers::String importErrorMessage;
|
Containers::String importErrorMessage;
|
||||||
if (!ImportAssimpModel(absolutePath, importSettings, importedModel, &importErrorMessage)) {
|
if (!ImportAssimpModel(absolutePath, importSettings, importedModel, &importErrorMessage)) {
|
||||||
@@ -2460,21 +2710,30 @@ bool AssetDatabase::ImportShaderAsset(const SourceAssetRecord& sourceRecord,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const Containers::String artifactKey = BuildArtifactKey(sourceRecord, dependencies);
|
const Containers::String artifactKey = BuildArtifactKey(sourceRecord, dependencies);
|
||||||
const Containers::String artifactDir = BuildArtifactDirectory(artifactKey);
|
const Containers::String legacyArtifactDir = BuildArtifactDirectory(artifactKey);
|
||||||
const Containers::String mainArtifactPath =
|
const Containers::String mainArtifactPath = BuildArtifactFilePath(artifactKey, ".xcshader");
|
||||||
NormalizePathString(fs::path(artifactDir.CStr()) / "main.xcshader");
|
const Containers::String artifactDir =
|
||||||
|
NormalizePathString(fs::path(mainArtifactPath.CStr()).parent_path());
|
||||||
|
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
fs::remove_all(fs::path(m_projectRoot.CStr()) / artifactDir.CStr(), ec);
|
fs::remove_all(fs::path(m_projectRoot.CStr()) / legacyArtifactDir.CStr(), ec);
|
||||||
ec.clear();
|
ec.clear();
|
||||||
fs::create_directories(fs::path(m_projectRoot.CStr()) / artifactDir.CStr(), ec);
|
fs::create_directories(
|
||||||
|
(fs::path(m_projectRoot.CStr()) / mainArtifactPath.CStr()).parent_path(),
|
||||||
|
ec);
|
||||||
if (ec) {
|
if (ec) {
|
||||||
delete shader;
|
delete shader;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Containers::Array<Core::uint8> payload;
|
||||||
|
const bool serialized = SerializeShaderArtifactPayload(*shader, payload);
|
||||||
const bool writeOk =
|
const bool writeOk =
|
||||||
WriteShaderArtifactFile(fs::path(m_projectRoot.CStr()) / mainArtifactPath.CStr(), *shader);
|
serialized &&
|
||||||
|
WriteSingleEntryArtifactContainerFile(
|
||||||
|
fs::path(m_projectRoot.CStr()) / mainArtifactPath.CStr(),
|
||||||
|
ResourceType::Shader,
|
||||||
|
payload);
|
||||||
delete shader;
|
delete shader;
|
||||||
if (!writeOk) {
|
if (!writeOk) {
|
||||||
return false;
|
return false;
|
||||||
@@ -2511,26 +2770,32 @@ bool AssetDatabase::ImportGaussianSplatAsset(const SourceAssetRecord& sourceReco
|
|||||||
|
|
||||||
GaussianSplat* gaussianSplat = static_cast<GaussianSplat*>(result.resource);
|
GaussianSplat* gaussianSplat = static_cast<GaussianSplat*>(result.resource);
|
||||||
const Containers::String artifactKey = BuildArtifactKey(sourceRecord);
|
const Containers::String artifactKey = BuildArtifactKey(sourceRecord);
|
||||||
const Containers::String artifactDir = BuildArtifactDirectory(artifactKey);
|
const Containers::String legacyArtifactDir = BuildArtifactDirectory(artifactKey);
|
||||||
const Containers::String mainArtifactPath =
|
const Containers::String mainArtifactPath = BuildArtifactFilePath(artifactKey, ".xcgsplat");
|
||||||
NormalizePathString(fs::path(artifactDir.CStr()) / "main.xcgsplat");
|
const Containers::String artifactDir =
|
||||||
|
NormalizePathString(fs::path(mainArtifactPath.CStr()).parent_path());
|
||||||
|
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
fs::remove_all(fs::path(m_projectRoot.CStr()) / artifactDir.CStr(), ec);
|
fs::remove_all(fs::path(m_projectRoot.CStr()) / legacyArtifactDir.CStr(), ec);
|
||||||
ec.clear();
|
ec.clear();
|
||||||
fs::create_directories(fs::path(m_projectRoot.CStr()) / artifactDir.CStr(), ec);
|
fs::create_directories(
|
||||||
|
(fs::path(m_projectRoot.CStr()) / mainArtifactPath.CStr()).parent_path(),
|
||||||
|
ec);
|
||||||
if (ec) {
|
if (ec) {
|
||||||
delete gaussianSplat;
|
delete gaussianSplat;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Containers::String writeErrorMessage;
|
Containers::String writeErrorMessage;
|
||||||
const Containers::String gaussianSplatArtifactWritePath =
|
Containers::Array<Core::uint8> payload;
|
||||||
NormalizePathString(fs::path(m_projectRoot.CStr()) / mainArtifactPath.CStr());
|
const bool serialized =
|
||||||
const bool writeOk = WriteGaussianSplatArtifactFile(
|
SerializeGaussianSplatArtifactPayload(*gaussianSplat, payload, &writeErrorMessage);
|
||||||
gaussianSplatArtifactWritePath,
|
const bool writeOk =
|
||||||
*gaussianSplat,
|
serialized &&
|
||||||
&writeErrorMessage);
|
WriteSingleEntryArtifactContainerFile(
|
||||||
|
fs::path(m_projectRoot.CStr()) / mainArtifactPath.CStr(),
|
||||||
|
ResourceType::GaussianSplat,
|
||||||
|
payload);
|
||||||
delete gaussianSplat;
|
delete gaussianSplat;
|
||||||
if (!writeOk) {
|
if (!writeOk) {
|
||||||
if (!writeErrorMessage.Empty()) {
|
if (!writeErrorMessage.Empty()) {
|
||||||
@@ -2567,21 +2832,52 @@ bool AssetDatabase::ImportVolumeFieldAsset(const SourceAssetRecord& sourceRecord
|
|||||||
|
|
||||||
VolumeField* volumeField = static_cast<VolumeField*>(result.resource);
|
VolumeField* volumeField = static_cast<VolumeField*>(result.resource);
|
||||||
const Containers::String artifactKey = BuildArtifactKey(sourceRecord);
|
const Containers::String artifactKey = BuildArtifactKey(sourceRecord);
|
||||||
const Containers::String artifactDir = BuildArtifactDirectory(artifactKey);
|
const Containers::String legacyArtifactDir = BuildArtifactDirectory(artifactKey);
|
||||||
const Containers::String mainArtifactPath =
|
const Containers::String mainArtifactPath = BuildArtifactFilePath(artifactKey, ".xcvol");
|
||||||
NormalizePathString(fs::path(artifactDir.CStr()) / "main.xcvol");
|
const Containers::String artifactDir =
|
||||||
|
NormalizePathString(fs::path(mainArtifactPath.CStr()).parent_path());
|
||||||
|
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
fs::remove_all(fs::path(m_projectRoot.CStr()) / artifactDir.CStr(), ec);
|
fs::remove_all(fs::path(m_projectRoot.CStr()) / legacyArtifactDir.CStr(), ec);
|
||||||
ec.clear();
|
ec.clear();
|
||||||
fs::create_directories(fs::path(m_projectRoot.CStr()) / artifactDir.CStr(), ec);
|
fs::create_directories(
|
||||||
|
(fs::path(m_projectRoot.CStr()) / mainArtifactPath.CStr()).parent_path(),
|
||||||
|
ec);
|
||||||
if (ec) {
|
if (ec) {
|
||||||
delete volumeField;
|
delete volumeField;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Containers::Array<Core::uint8> payload;
|
||||||
|
VolumeFieldArtifactHeader header;
|
||||||
|
header.storageKind = static_cast<Core::uint32>(volumeField->GetStorageKind());
|
||||||
|
header.boundsMin = volumeField->GetBounds().GetMin();
|
||||||
|
header.boundsMax = volumeField->GetBounds().GetMax();
|
||||||
|
header.voxelSize = volumeField->GetVoxelSize();
|
||||||
|
header.indexBoundsMin[0] = volumeField->GetIndexBounds().minX;
|
||||||
|
header.indexBoundsMin[1] = volumeField->GetIndexBounds().minY;
|
||||||
|
header.indexBoundsMin[2] = volumeField->GetIndexBounds().minZ;
|
||||||
|
header.indexBoundsMax[0] = volumeField->GetIndexBounds().maxX;
|
||||||
|
header.indexBoundsMax[1] = volumeField->GetIndexBounds().maxY;
|
||||||
|
header.indexBoundsMax[2] = volumeField->GetIndexBounds().maxZ;
|
||||||
|
header.gridType = volumeField->GetGridType();
|
||||||
|
header.gridClass = volumeField->GetGridClass();
|
||||||
|
header.payloadSize = static_cast<Core::uint64>(volumeField->GetPayloadSize());
|
||||||
|
|
||||||
|
payload.Resize(sizeof(header) + volumeField->GetPayloadSize());
|
||||||
|
std::memcpy(payload.Data(), &header, sizeof(header));
|
||||||
|
if (volumeField->GetPayloadSize() > 0) {
|
||||||
|
std::memcpy(
|
||||||
|
payload.Data() + sizeof(header),
|
||||||
|
volumeField->GetPayloadData(),
|
||||||
|
volumeField->GetPayloadSize());
|
||||||
|
}
|
||||||
|
|
||||||
const bool writeOk =
|
const bool writeOk =
|
||||||
WriteVolumeFieldArtifactFile(fs::path(m_projectRoot.CStr()) / mainArtifactPath.CStr(), *volumeField);
|
WriteSingleEntryArtifactContainerFile(
|
||||||
|
fs::path(m_projectRoot.CStr()) / mainArtifactPath.CStr(),
|
||||||
|
ResourceType::VolumeField,
|
||||||
|
payload);
|
||||||
delete volumeField;
|
delete volumeField;
|
||||||
if (!writeOk) {
|
if (!writeOk) {
|
||||||
return false;
|
return false;
|
||||||
@@ -2922,6 +3218,21 @@ Containers::String AssetDatabase::BuildArtifactDirectory(const Containers::Strin
|
|||||||
return Containers::String("Library/Artifacts/") + shard + "/" + artifactKey;
|
return Containers::String("Library/Artifacts/") + shard + "/" + artifactKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Containers::String AssetDatabase::BuildArtifactFilePath(const Containers::String& artifactKey,
|
||||||
|
const char* extension) const {
|
||||||
|
if (artifactKey.Length() < 2) {
|
||||||
|
return Containers::String("Library/Artifacts/00/invalid") +
|
||||||
|
Containers::String(extension == nullptr ? "" : extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Containers::String shard = artifactKey.Substring(0, 2);
|
||||||
|
return Containers::String("Library/Artifacts/") +
|
||||||
|
shard +
|
||||||
|
"/" +
|
||||||
|
artifactKey +
|
||||||
|
Containers::String(extension == nullptr ? "" : extension);
|
||||||
|
}
|
||||||
|
|
||||||
Containers::String AssetDatabase::ReadWholeFileText(const fs::path& path) {
|
Containers::String AssetDatabase::ReadWholeFileText(const fs::path& path) {
|
||||||
std::ifstream input(path, std::ios::binary);
|
std::ifstream input(path, std::ios::binary);
|
||||||
if (!input.is_open()) {
|
if (!input.is_open()) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <XCEngine/Resources/Mesh/MeshImportSettings.h>
|
#include <XCEngine/Resources/Mesh/MeshImportSettings.h>
|
||||||
#include <XCEngine/Resources/Material/Material.h>
|
#include <XCEngine/Resources/Material/Material.h>
|
||||||
#include <XCEngine/Resources/Texture/Texture.h>
|
#include <XCEngine/Resources/Texture/Texture.h>
|
||||||
|
#include <XCEngine/Resources/Texture/TextureImportHeuristics.h>
|
||||||
#include <XCEngine/Resources/Texture/TextureLoader.h>
|
#include <XCEngine/Resources/Texture/TextureLoader.h>
|
||||||
#include <XCEngine/Resources/Texture/TextureImportSettings.h>
|
#include <XCEngine/Resources/Texture/TextureImportSettings.h>
|
||||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||||
@@ -93,7 +94,7 @@ Core::uint32 BuildPostProcessFlags(const MeshImportSettings& settings) {
|
|||||||
|
|
||||||
if (settings.GetAxisConversion()) {
|
if (settings.GetAxisConversion()) {
|
||||||
flags |= aiProcess_MakeLeftHanded;
|
flags |= aiProcess_MakeLeftHanded;
|
||||||
flags |= aiProcess_FlipWindingOrder;
|
// The engine treats CCW as front-facing, so keep the imported winding unchanged.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.HasImportFlag(MeshImportFlags::FlipUVs)) {
|
if (settings.HasImportFlag(MeshImportFlags::FlipUVs)) {
|
||||||
@@ -174,11 +175,7 @@ Containers::String BuildSubResourcePath(const Containers::String& sourcePath,
|
|||||||
}
|
}
|
||||||
|
|
||||||
TextureImportSettings BuildMaterialTextureImportSettings(const char* propertyName) {
|
TextureImportSettings BuildMaterialTextureImportSettings(const char* propertyName) {
|
||||||
TextureImportSettings settings;
|
return BuildTextureImportSettingsForMaterialProperty(propertyName);
|
||||||
(void)propertyName;
|
|
||||||
settings.SetSRGB(false);
|
|
||||||
settings.SetTargetFormat(TextureFormat::RGBA8_UNORM);
|
|
||||||
return settings;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string BuildTextureCacheKey(const std::string& pathKey, const TextureImportSettings& settings) {
|
std::string BuildTextureCacheKey(const std::string& pathKey, const TextureImportSettings& settings) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <XCEngine/Resources/Model/Model.h>
|
#include <XCEngine/Resources/Model/Model.h>
|
||||||
#include <XCEngine/Resources/Shader/Shader.h>
|
#include <XCEngine/Resources/Shader/Shader.h>
|
||||||
#include <XCEngine/Resources/Texture/Texture.h>
|
#include <XCEngine/Resources/Texture/Texture.h>
|
||||||
|
#include <XCEngine/Resources/Texture/TextureImportHeuristics.h>
|
||||||
#include <XCEngine/Resources/Texture/TextureImportSettings.h>
|
#include <XCEngine/Resources/Texture/TextureImportSettings.h>
|
||||||
#include <XCEngine/Resources/Texture/TextureLoader.h>
|
#include <XCEngine/Resources/Texture/TextureLoader.h>
|
||||||
|
|
||||||
@@ -185,7 +186,7 @@ Core::uint32 BuildPostProcessFlags(const MeshImportSettings& settings) {
|
|||||||
|
|
||||||
if (settings.GetAxisConversion()) {
|
if (settings.GetAxisConversion()) {
|
||||||
flags |= aiProcess_MakeLeftHanded;
|
flags |= aiProcess_MakeLeftHanded;
|
||||||
flags |= aiProcess_FlipWindingOrder;
|
// The engine treats CCW as front-facing, so keep the imported winding unchanged.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.HasImportFlag(MeshImportFlags::FlipUVs)) {
|
if (settings.HasImportFlag(MeshImportFlags::FlipUVs)) {
|
||||||
@@ -214,11 +215,7 @@ Core::uint32 BuildPostProcessFlags(const MeshImportSettings& settings) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TextureImportSettings BuildMaterialTextureImportSettings(const char* propertyName) {
|
TextureImportSettings BuildMaterialTextureImportSettings(const char* propertyName) {
|
||||||
TextureImportSettings settings;
|
return BuildTextureImportSettingsForMaterialProperty(propertyName);
|
||||||
(void)propertyName;
|
|
||||||
settings.SetSRGB(false);
|
|
||||||
settings.SetTargetFormat(TextureFormat::RGBA8_UNORM);
|
|
||||||
return settings;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string BuildTextureCacheKey(const std::string& pathKey, const TextureImportSettings& settings) {
|
std::string BuildTextureCacheKey(const std::string& pathKey, const TextureImportSettings& settings) {
|
||||||
|
|||||||
736
tests/Rendering/integration/nahida_preview_scene/main.cpp
Normal file
736
tests/Rendering/integration/nahida_preview_scene/main.cpp
Normal file
@@ -0,0 +1,736 @@
|
|||||||
|
#define NOMINMAX
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "../RenderingIntegrationImageAssert.h"
|
||||||
|
#include "../RenderingIntegrationMain.h"
|
||||||
|
|
||||||
|
#include <XCEngine/Components/MeshFilterComponent.h>
|
||||||
|
#include <XCEngine/Components/MeshRendererComponent.h>
|
||||||
|
#include <XCEngine/Components/LightComponent.h>
|
||||||
|
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||||
|
#include <XCEngine/Rendering/Extraction/RenderSceneExtractor.h>
|
||||||
|
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
|
||||||
|
#include <XCEngine/Rendering/RenderContext.h>
|
||||||
|
#include <XCEngine/Rendering/RenderSurface.h>
|
||||||
|
#include <XCEngine/Resources/Material/Material.h>
|
||||||
|
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||||
|
#include <XCEngine/RHI/RHITexture.h>
|
||||||
|
#include <XCEngine/Scene/Scene.h>
|
||||||
|
|
||||||
|
#include "../../../RHI/integration/fixtures/RHIIntegrationFixture.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <memory>
|
||||||
|
#include <limits>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace XCEngine::Components;
|
||||||
|
using namespace XCEngine::Rendering;
|
||||||
|
using namespace XCEngine::Resources;
|
||||||
|
using namespace XCEngine::RHI;
|
||||||
|
using namespace XCEngine::RHI::Integration;
|
||||||
|
using namespace RenderingIntegrationTestUtils;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr const char* kD3D12Screenshot = "nahida_preview_scene_d3d12.ppm";
|
||||||
|
constexpr uint32_t kFrameWidth = 1280;
|
||||||
|
constexpr uint32_t kFrameHeight = 720;
|
||||||
|
constexpr int kWarmupFrames = 45;
|
||||||
|
|
||||||
|
enum class DiagnosticMode {
|
||||||
|
Original,
|
||||||
|
NoShadows,
|
||||||
|
ForwardLit,
|
||||||
|
Unlit
|
||||||
|
};
|
||||||
|
|
||||||
|
DiagnosticMode GetDiagnosticMode() {
|
||||||
|
const char* value = std::getenv("XC_NAHIDA_DIAG_MODE");
|
||||||
|
if (value == nullptr) {
|
||||||
|
return DiagnosticMode::Original;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string mode(value);
|
||||||
|
if (mode == "no_shadows") {
|
||||||
|
return DiagnosticMode::NoShadows;
|
||||||
|
}
|
||||||
|
if (mode == "forward_lit") {
|
||||||
|
return DiagnosticMode::ForwardLit;
|
||||||
|
}
|
||||||
|
if (mode == "unlit") {
|
||||||
|
return DiagnosticMode::Unlit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DiagnosticMode::Original;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* GetDiagnosticModeName(DiagnosticMode mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case DiagnosticMode::Original: return "original";
|
||||||
|
case DiagnosticMode::NoShadows: return "no_shadows";
|
||||||
|
case DiagnosticMode::ForwardLit: return "forward_lit";
|
||||||
|
case DiagnosticMode::Unlit: return "unlit";
|
||||||
|
default: return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_set<std::string> GetIsolationObjectNames() {
|
||||||
|
std::unordered_set<std::string> result;
|
||||||
|
const char* value = std::getenv("XC_NAHIDA_DIAG_ONLY");
|
||||||
|
if (value == nullptr) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::stringstream stream(value);
|
||||||
|
std::string token;
|
||||||
|
while (std::getline(stream, token, ',')) {
|
||||||
|
const size_t first = token.find_first_not_of(" \t\r\n");
|
||||||
|
if (first == std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t last = token.find_last_not_of(" \t\r\n");
|
||||||
|
result.emplace(token.substr(first, last - first + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string DescribeVertexAttributes(VertexAttribute attributes) {
|
||||||
|
std::string result;
|
||||||
|
const auto appendFlag = [&result](const char* name) {
|
||||||
|
if (!result.empty()) {
|
||||||
|
result += "|";
|
||||||
|
}
|
||||||
|
result += name;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (HasVertexAttribute(attributes, VertexAttribute::Position)) {
|
||||||
|
appendFlag("Position");
|
||||||
|
}
|
||||||
|
if (HasVertexAttribute(attributes, VertexAttribute::Normal)) {
|
||||||
|
appendFlag("Normal");
|
||||||
|
}
|
||||||
|
if (HasVertexAttribute(attributes, VertexAttribute::Tangent)) {
|
||||||
|
appendFlag("Tangent");
|
||||||
|
}
|
||||||
|
if (HasVertexAttribute(attributes, VertexAttribute::Bitangent)) {
|
||||||
|
appendFlag("Bitangent");
|
||||||
|
}
|
||||||
|
if (HasVertexAttribute(attributes, VertexAttribute::Color)) {
|
||||||
|
appendFlag("Color");
|
||||||
|
}
|
||||||
|
if (HasVertexAttribute(attributes, VertexAttribute::UV0)) {
|
||||||
|
appendFlag("UV0");
|
||||||
|
}
|
||||||
|
if (HasVertexAttribute(attributes, VertexAttribute::UV1)) {
|
||||||
|
appendFlag("UV1");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DumpMeshDiagnostics(const char* label, const Mesh* mesh) {
|
||||||
|
if (mesh == nullptr) {
|
||||||
|
std::printf("[NahidaDiag] %s mesh=null\n", label);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const XCEngine::Math::Bounds& bounds = mesh->GetBounds();
|
||||||
|
std::printf(
|
||||||
|
"[NahidaDiag] %s meshPath=%s vertices=%u indices=%u stride=%u attrs=%s center=(%.4f, %.4f, %.4f) extents=(%.4f, %.4f, %.4f) sections=%zu\n",
|
||||||
|
label,
|
||||||
|
mesh->GetPath().CStr(),
|
||||||
|
mesh->GetVertexCount(),
|
||||||
|
mesh->GetIndexCount(),
|
||||||
|
mesh->GetVertexStride(),
|
||||||
|
DescribeVertexAttributes(mesh->GetVertexAttributes()).c_str(),
|
||||||
|
bounds.center.x,
|
||||||
|
bounds.center.y,
|
||||||
|
bounds.center.z,
|
||||||
|
bounds.extents.x,
|
||||||
|
bounds.extents.y,
|
||||||
|
bounds.extents.z,
|
||||||
|
mesh->GetSections().Size());
|
||||||
|
|
||||||
|
if (mesh->GetVertexStride() != sizeof(StaticMeshVertex) || mesh->GetVertexCount() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto* vertices = static_cast<const StaticMeshVertex*>(mesh->GetVertexData());
|
||||||
|
XCEngine::Math::Vector2 uvMin(
|
||||||
|
std::numeric_limits<float>::max(),
|
||||||
|
std::numeric_limits<float>::max());
|
||||||
|
XCEngine::Math::Vector2 uvMax(
|
||||||
|
std::numeric_limits<float>::lowest(),
|
||||||
|
std::numeric_limits<float>::lowest());
|
||||||
|
for (uint32_t vertexIndex = 0; vertexIndex < mesh->GetVertexCount(); ++vertexIndex) {
|
||||||
|
uvMin.x = std::min(uvMin.x, vertices[vertexIndex].uv0.x);
|
||||||
|
uvMin.y = std::min(uvMin.y, vertices[vertexIndex].uv0.y);
|
||||||
|
uvMax.x = std::max(uvMax.x, vertices[vertexIndex].uv0.x);
|
||||||
|
uvMax.y = std::max(uvMax.y, vertices[vertexIndex].uv0.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
const StaticMeshVertex& firstVertex = vertices[0];
|
||||||
|
std::printf(
|
||||||
|
"[NahidaDiag] %s firstVertex pos=(%.4f, %.4f, %.4f) normal=(%.4f, %.4f, %.4f) tangent=(%.4f, %.4f, %.4f) bitangent=(%.4f, %.4f, %.4f) uv=(%.4f, %.4f)\n",
|
||||||
|
label,
|
||||||
|
firstVertex.position.x,
|
||||||
|
firstVertex.position.y,
|
||||||
|
firstVertex.position.z,
|
||||||
|
firstVertex.normal.x,
|
||||||
|
firstVertex.normal.y,
|
||||||
|
firstVertex.normal.z,
|
||||||
|
firstVertex.tangent.x,
|
||||||
|
firstVertex.tangent.y,
|
||||||
|
firstVertex.tangent.z,
|
||||||
|
firstVertex.bitangent.x,
|
||||||
|
firstVertex.bitangent.y,
|
||||||
|
firstVertex.bitangent.z,
|
||||||
|
firstVertex.uv0.x,
|
||||||
|
firstVertex.uv0.y);
|
||||||
|
std::printf(
|
||||||
|
"[NahidaDiag] %s uvRange min=(%.4f, %.4f) max=(%.4f, %.4f)\n",
|
||||||
|
label,
|
||||||
|
uvMin.x,
|
||||||
|
uvMin.y,
|
||||||
|
uvMax.x,
|
||||||
|
uvMax.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DumpMaterialDiagnostics(const char* label, Material* material) {
|
||||||
|
if (material == nullptr) {
|
||||||
|
std::printf("[NahidaDiag] %s material=null\n", label);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::printf(
|
||||||
|
"[NahidaDiag] %s materialPath=%s shader=%s constants=%zu textures=%u keywords=%u renderQueue=%d\n",
|
||||||
|
label,
|
||||||
|
material->GetPath().CStr(),
|
||||||
|
material->GetShader() != nullptr ? material->GetShader()->GetPath().CStr() : "<null>",
|
||||||
|
material->GetConstantLayout().Size(),
|
||||||
|
material->GetTextureBindingCount(),
|
||||||
|
material->GetKeywordCount(),
|
||||||
|
material->GetRenderQueue());
|
||||||
|
|
||||||
|
std::printf(
|
||||||
|
"[NahidaDiag] %s props _BaseColor=(%.3f, %.3f, %.3f, %.3f) _ShadowColor=(%.3f, %.3f, %.3f, %.3f) _UseNormalMap=%.3f _UseSpecular=%.3f _UseEmission=%.3f _UseRim=%.3f _IsFace=%.3f _UseCustomMaterialType=%.3f\n",
|
||||||
|
label,
|
||||||
|
material->GetFloat4("_BaseColor").x,
|
||||||
|
material->GetFloat4("_BaseColor").y,
|
||||||
|
material->GetFloat4("_BaseColor").z,
|
||||||
|
material->GetFloat4("_BaseColor").w,
|
||||||
|
material->GetFloat4("_ShadowColor").x,
|
||||||
|
material->GetFloat4("_ShadowColor").y,
|
||||||
|
material->GetFloat4("_ShadowColor").z,
|
||||||
|
material->GetFloat4("_ShadowColor").w,
|
||||||
|
material->GetFloat("_UseNormalMap"),
|
||||||
|
material->GetFloat("_UseSpecular"),
|
||||||
|
material->GetFloat("_UseEmission"),
|
||||||
|
material->GetFloat("_UseRim"),
|
||||||
|
material->GetFloat("_IsFace"),
|
||||||
|
material->GetFloat("_UseCustomMaterialType"));
|
||||||
|
|
||||||
|
for (uint32_t keywordIndex = 0; keywordIndex < material->GetKeywordCount(); ++keywordIndex) {
|
||||||
|
std::printf("[NahidaDiag] %s keyword[%u]=%s\n", label, keywordIndex, material->GetKeyword(keywordIndex).CStr());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t bindingIndex = 0; bindingIndex < material->GetTextureBindingCount(); ++bindingIndex) {
|
||||||
|
const ResourceHandle<Texture> textureHandle = material->GetTextureBindingTexture(bindingIndex);
|
||||||
|
Texture* texture = textureHandle.Get();
|
||||||
|
std::printf(
|
||||||
|
"[NahidaDiag] %s texture[%u] name=%s path=%s loaded=%d resolved=%s\n",
|
||||||
|
label,
|
||||||
|
bindingIndex,
|
||||||
|
material->GetTextureBindingName(bindingIndex).CStr(),
|
||||||
|
material->GetTextureBindingPath(bindingIndex).CStr(),
|
||||||
|
texture != nullptr ? 1 : 0,
|
||||||
|
texture != nullptr ? texture->GetPath().CStr() : "<null>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DumpSceneDiagnostics(Scene& scene) {
|
||||||
|
RenderSceneExtractor extractor;
|
||||||
|
RenderSceneData sceneData = extractor.Extract(scene, nullptr, kFrameWidth, kFrameHeight);
|
||||||
|
std::printf(
|
||||||
|
"[NahidaDiag] extracted cameraPos=(%.3f, %.3f, %.3f) visibleItems=%zu additionalLights=%u\n",
|
||||||
|
sceneData.cameraData.worldPosition.x,
|
||||||
|
sceneData.cameraData.worldPosition.y,
|
||||||
|
sceneData.cameraData.worldPosition.z,
|
||||||
|
sceneData.visibleItems.size(),
|
||||||
|
sceneData.lighting.additionalLightCount);
|
||||||
|
|
||||||
|
for (size_t visibleIndex = 0; visibleIndex < sceneData.visibleItems.size(); ++visibleIndex) {
|
||||||
|
const VisibleRenderItem& item = sceneData.visibleItems[visibleIndex];
|
||||||
|
const char* objectName = item.gameObject != nullptr ? item.gameObject->GetName().c_str() : "<null>";
|
||||||
|
const char* meshPath = item.mesh != nullptr ? item.mesh->GetPath().CStr() : "<null>";
|
||||||
|
const char* materialPath = item.material != nullptr ? item.material->GetPath().CStr() : "<null>";
|
||||||
|
std::printf(
|
||||||
|
"[NahidaDiag] visible[%zu] object=%s section=%u hasSection=%d materialIndex=%u queue=%d mesh=%s material=%s\n",
|
||||||
|
visibleIndex,
|
||||||
|
objectName,
|
||||||
|
item.sectionIndex,
|
||||||
|
item.hasSection ? 1 : 0,
|
||||||
|
item.materialIndex,
|
||||||
|
item.renderQueue,
|
||||||
|
meshPath,
|
||||||
|
materialPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path GetRepositoryRoot() {
|
||||||
|
return std::filesystem::path(__FILE__).parent_path().parent_path().parent_path().parent_path().parent_path();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path GetProjectRoot() {
|
||||||
|
return GetRepositoryRoot() / "project";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path GetScenePath() {
|
||||||
|
return GetProjectRoot() / "Assets" / "Scenes" / "NahidaPreview.xc";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path GetAssimpDllPath() {
|
||||||
|
return GetRepositoryRoot() / "engine" / "third_party" / "assimp" / "bin" / "assimp-vc143-mt.dll";
|
||||||
|
}
|
||||||
|
|
||||||
|
class NahidaPreviewSceneTest : public RHIIntegrationFixture {
|
||||||
|
protected:
|
||||||
|
void SetUp() override;
|
||||||
|
void TearDown() override;
|
||||||
|
void RenderFrame() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void InitializeProjectResources();
|
||||||
|
void PreloadSceneResources();
|
||||||
|
void PumpSceneLoads(std::chrono::milliseconds timeout);
|
||||||
|
void TouchSceneMaterialsAndTextures();
|
||||||
|
void ApplyIsolationFilter();
|
||||||
|
void ApplyDiagnosticOverrides();
|
||||||
|
void DumpTargetDiagnostics();
|
||||||
|
RHIResourceView* GetCurrentBackBufferView();
|
||||||
|
Material* CreateOverrideMaterial(Material* sourceMaterial, const char* shaderPath);
|
||||||
|
|
||||||
|
HMODULE m_assimpModule = nullptr;
|
||||||
|
std::unique_ptr<Scene> m_scene;
|
||||||
|
std::unique_ptr<SceneRenderer> m_sceneRenderer;
|
||||||
|
std::vector<std::unique_ptr<Material>> m_overrideMaterials;
|
||||||
|
std::vector<RHIResourceView*> m_backBufferViews;
|
||||||
|
RHITexture* m_depthTexture = nullptr;
|
||||||
|
RHIResourceView* m_depthView = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
void NahidaPreviewSceneTest::SetUp() {
|
||||||
|
RHIIntegrationFixture::SetUp();
|
||||||
|
|
||||||
|
const std::filesystem::path assimpDllPath = GetAssimpDllPath();
|
||||||
|
ASSERT_TRUE(std::filesystem::exists(assimpDllPath)) << assimpDllPath.string();
|
||||||
|
m_assimpModule = LoadLibraryW(assimpDllPath.wstring().c_str());
|
||||||
|
ASSERT_NE(m_assimpModule, nullptr);
|
||||||
|
|
||||||
|
InitializeProjectResources();
|
||||||
|
|
||||||
|
const std::filesystem::path scenePath = GetScenePath();
|
||||||
|
ASSERT_TRUE(std::filesystem::exists(scenePath)) << scenePath.string();
|
||||||
|
|
||||||
|
m_sceneRenderer = std::make_unique<SceneRenderer>();
|
||||||
|
m_scene = std::make_unique<Scene>("NahidaPreview");
|
||||||
|
m_scene->Load(scenePath.string());
|
||||||
|
|
||||||
|
ASSERT_NE(m_scene->Find("Preview Camera"), nullptr);
|
||||||
|
ASSERT_NE(m_scene->Find("NahidaUnityModel"), nullptr);
|
||||||
|
ASSERT_NE(m_scene->Find("Body_Mesh0"), nullptr);
|
||||||
|
ASSERT_NE(m_scene->Find("Face"), nullptr);
|
||||||
|
|
||||||
|
PreloadSceneResources();
|
||||||
|
ApplyIsolationFilter();
|
||||||
|
ApplyDiagnosticOverrides();
|
||||||
|
DumpTargetDiagnostics();
|
||||||
|
|
||||||
|
TextureDesc depthDesc = {};
|
||||||
|
depthDesc.width = kFrameWidth;
|
||||||
|
depthDesc.height = kFrameHeight;
|
||||||
|
depthDesc.depth = 1;
|
||||||
|
depthDesc.mipLevels = 1;
|
||||||
|
depthDesc.arraySize = 1;
|
||||||
|
depthDesc.format = static_cast<uint32_t>(Format::D24_UNorm_S8_UInt);
|
||||||
|
depthDesc.textureType = static_cast<uint32_t>(XCEngine::RHI::TextureType::Texture2D);
|
||||||
|
depthDesc.sampleCount = 1;
|
||||||
|
depthDesc.sampleQuality = 0;
|
||||||
|
depthDesc.flags = 0;
|
||||||
|
m_depthTexture = GetDevice()->CreateTexture(depthDesc);
|
||||||
|
ASSERT_NE(m_depthTexture, nullptr);
|
||||||
|
|
||||||
|
ResourceViewDesc depthViewDesc = {};
|
||||||
|
depthViewDesc.format = static_cast<uint32_t>(Format::D24_UNorm_S8_UInt);
|
||||||
|
depthViewDesc.dimension = ResourceViewDimension::Texture2D;
|
||||||
|
depthViewDesc.mipLevel = 0;
|
||||||
|
m_depthView = GetDevice()->CreateDepthStencilView(m_depthTexture, depthViewDesc);
|
||||||
|
ASSERT_NE(m_depthView, nullptr);
|
||||||
|
|
||||||
|
m_backBufferViews.resize(2, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NahidaPreviewSceneTest::TearDown() {
|
||||||
|
m_sceneRenderer.reset();
|
||||||
|
|
||||||
|
if (m_depthView != nullptr) {
|
||||||
|
m_depthView->Shutdown();
|
||||||
|
delete m_depthView;
|
||||||
|
m_depthView = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_depthTexture != nullptr) {
|
||||||
|
m_depthTexture->Shutdown();
|
||||||
|
delete m_depthTexture;
|
||||||
|
m_depthTexture = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (RHIResourceView*& backBufferView : m_backBufferViews) {
|
||||||
|
if (backBufferView != nullptr) {
|
||||||
|
backBufferView->Shutdown();
|
||||||
|
delete backBufferView;
|
||||||
|
backBufferView = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_backBufferViews.clear();
|
||||||
|
|
||||||
|
m_scene.reset();
|
||||||
|
|
||||||
|
ResourceManager& manager = ResourceManager::Get();
|
||||||
|
manager.UnloadAll();
|
||||||
|
manager.SetResourceRoot("");
|
||||||
|
manager.Shutdown();
|
||||||
|
|
||||||
|
if (m_assimpModule != nullptr) {
|
||||||
|
FreeLibrary(m_assimpModule);
|
||||||
|
m_assimpModule = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHIIntegrationFixture::TearDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NahidaPreviewSceneTest::InitializeProjectResources() {
|
||||||
|
ResourceManager& manager = ResourceManager::Get();
|
||||||
|
manager.Initialize();
|
||||||
|
manager.SetResourceRoot(GetProjectRoot().string().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void NahidaPreviewSceneTest::PreloadSceneResources() {
|
||||||
|
ASSERT_NE(m_scene, nullptr);
|
||||||
|
PumpSceneLoads(std::chrono::milliseconds(4000));
|
||||||
|
}
|
||||||
|
|
||||||
|
void NahidaPreviewSceneTest::PumpSceneLoads(std::chrono::milliseconds timeout) {
|
||||||
|
ResourceManager& manager = ResourceManager::Get();
|
||||||
|
const auto deadline = std::chrono::steady_clock::now() + timeout;
|
||||||
|
|
||||||
|
do {
|
||||||
|
TouchSceneMaterialsAndTextures();
|
||||||
|
manager.UpdateAsyncLoads();
|
||||||
|
|
||||||
|
if (!manager.IsAsyncLoading()) {
|
||||||
|
TouchSceneMaterialsAndTextures();
|
||||||
|
manager.UpdateAsyncLoads();
|
||||||
|
if (!manager.IsAsyncLoading()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||||
|
} while (std::chrono::steady_clock::now() < deadline);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NahidaPreviewSceneTest::TouchSceneMaterialsAndTextures() {
|
||||||
|
ASSERT_NE(m_scene, nullptr);
|
||||||
|
|
||||||
|
const std::vector<MeshFilterComponent*> meshFilters = m_scene->FindObjectsOfType<MeshFilterComponent>();
|
||||||
|
for (MeshFilterComponent* meshFilter : meshFilters) {
|
||||||
|
if (meshFilter == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
meshFilter->GetMesh();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<MeshRendererComponent*> meshRenderers = m_scene->FindObjectsOfType<MeshRendererComponent>();
|
||||||
|
for (MeshRendererComponent* meshRenderer : meshRenderers) {
|
||||||
|
if (meshRenderer == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t materialIndex = 0; materialIndex < meshRenderer->GetMaterialCount(); ++materialIndex) {
|
||||||
|
Material* material = meshRenderer->GetMaterial(materialIndex);
|
||||||
|
if (material == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t bindingIndex = 0; bindingIndex < material->GetTextureBindingCount(); ++bindingIndex) {
|
||||||
|
material->GetTextureBindingTexture(bindingIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NahidaPreviewSceneTest::ApplyIsolationFilter() {
|
||||||
|
ASSERT_NE(m_scene, nullptr);
|
||||||
|
|
||||||
|
const std::unordered_set<std::string> isolatedObjects = GetIsolationObjectNames();
|
||||||
|
if (isolatedObjects.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::printf("[NahidaDiag] isolation=");
|
||||||
|
bool first = true;
|
||||||
|
for (const std::string& name : isolatedObjects) {
|
||||||
|
std::printf("%s%s", first ? "" : ",", name.c_str());
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
std::printf("\n");
|
||||||
|
|
||||||
|
const std::vector<MeshRendererComponent*> meshRenderers = m_scene->FindObjectsOfType<MeshRendererComponent>();
|
||||||
|
for (MeshRendererComponent* meshRenderer : meshRenderers) {
|
||||||
|
if (meshRenderer == nullptr || meshRenderer->GetGameObject() == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameObject* gameObject = meshRenderer->GetGameObject();
|
||||||
|
const bool keep = isolatedObjects.find(gameObject->GetName()) != isolatedObjects.end();
|
||||||
|
gameObject->SetActive(keep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Material* NahidaPreviewSceneTest::CreateOverrideMaterial(Material* sourceMaterial, const char* shaderPath) {
|
||||||
|
if (sourceMaterial == nullptr || shaderPath == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto material = std::make_unique<Material>();
|
||||||
|
IResource::ConstructParams params = {};
|
||||||
|
params.name = XCEngine::Containers::String("NahidaDiagnosticMaterial");
|
||||||
|
params.path = XCEngine::Containers::String(("memory://nahida/" + std::string(shaderPath)).c_str());
|
||||||
|
params.guid = ResourceGUID::Generate(params.path);
|
||||||
|
material->Initialize(params);
|
||||||
|
material->SetShader(ResourceManager::Get().Load<Shader>(shaderPath));
|
||||||
|
|
||||||
|
const ResourceHandle<Texture> baseMap = sourceMaterial->GetTexture("_BaseMap");
|
||||||
|
if (baseMap.Get() != nullptr) {
|
||||||
|
material->SetTexture("_MainTex", baseMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::string(shaderPath) == "builtin://shaders/forward-lit") {
|
||||||
|
material->EnableKeyword("XC_ALPHA_TEST");
|
||||||
|
material->SetFloat("_Cutoff", sourceMaterial->GetFloat("_Cutoff"));
|
||||||
|
}
|
||||||
|
|
||||||
|
material->SetFloat4("_BaseColor", sourceMaterial->GetFloat4("_BaseColor"));
|
||||||
|
|
||||||
|
m_overrideMaterials.push_back(std::move(material));
|
||||||
|
return m_overrideMaterials.back().get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NahidaPreviewSceneTest::ApplyDiagnosticOverrides() {
|
||||||
|
ASSERT_NE(m_scene, nullptr);
|
||||||
|
|
||||||
|
const DiagnosticMode mode = GetDiagnosticMode();
|
||||||
|
std::printf("[NahidaDiag] diagnosticMode=%s\n", GetDiagnosticModeName(mode));
|
||||||
|
|
||||||
|
if (mode == DiagnosticMode::Original) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode == DiagnosticMode::NoShadows) {
|
||||||
|
const std::vector<LightComponent*> lights = m_scene->FindObjectsOfType<LightComponent>();
|
||||||
|
for (LightComponent* light : lights) {
|
||||||
|
if (light != nullptr) {
|
||||||
|
light->SetCastsShadows(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* shaderPath = mode == DiagnosticMode::ForwardLit
|
||||||
|
? "builtin://shaders/forward-lit"
|
||||||
|
: "builtin://shaders/unlit";
|
||||||
|
|
||||||
|
std::unordered_map<Material*, Material*> overrideBySource;
|
||||||
|
const std::vector<MeshRendererComponent*> meshRenderers = m_scene->FindObjectsOfType<MeshRendererComponent>();
|
||||||
|
for (MeshRendererComponent* meshRenderer : meshRenderers) {
|
||||||
|
if (meshRenderer == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t materialIndex = 0; materialIndex < meshRenderer->GetMaterialCount(); ++materialIndex) {
|
||||||
|
Material* sourceMaterial = meshRenderer->GetMaterial(materialIndex);
|
||||||
|
if (sourceMaterial == nullptr || sourceMaterial->GetShader() == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::string(sourceMaterial->GetShader()->GetPath().CStr()) != "Assets/Shaders/XCCharacterToon.shader") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Material* overrideMaterial = nullptr;
|
||||||
|
const auto found = overrideBySource.find(sourceMaterial);
|
||||||
|
if (found != overrideBySource.end()) {
|
||||||
|
overrideMaterial = found->second;
|
||||||
|
} else {
|
||||||
|
overrideMaterial = CreateOverrideMaterial(sourceMaterial, shaderPath);
|
||||||
|
overrideBySource.emplace(sourceMaterial, overrideMaterial);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overrideMaterial != nullptr) {
|
||||||
|
meshRenderer->SetMaterial(materialIndex, overrideMaterial);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NahidaPreviewSceneTest::DumpTargetDiagnostics() {
|
||||||
|
ASSERT_NE(m_scene, nullptr);
|
||||||
|
|
||||||
|
DumpSceneDiagnostics(*m_scene);
|
||||||
|
|
||||||
|
const char* const targetObjects[] = {
|
||||||
|
"Body_Mesh0",
|
||||||
|
"Brow",
|
||||||
|
"EyeStar",
|
||||||
|
"Dress_Mesh0",
|
||||||
|
"Hair_Mesh0",
|
||||||
|
"Face",
|
||||||
|
"Face_Eye"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const char* objectName : targetObjects) {
|
||||||
|
GameObject* gameObject = m_scene->Find(objectName);
|
||||||
|
if (gameObject == nullptr) {
|
||||||
|
std::printf("[NahidaDiag] object=%s missing\n", objectName);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* meshFilter = gameObject->GetComponent<MeshFilterComponent>();
|
||||||
|
auto* meshRenderer = gameObject->GetComponent<MeshRendererComponent>();
|
||||||
|
std::printf(
|
||||||
|
"[NahidaDiag] object=%s materialCount=%zu active=%d\n",
|
||||||
|
objectName,
|
||||||
|
meshRenderer != nullptr ? meshRenderer->GetMaterialCount() : 0u,
|
||||||
|
gameObject->IsActiveInHierarchy() ? 1 : 0);
|
||||||
|
|
||||||
|
DumpMeshDiagnostics(objectName, meshFilter != nullptr ? meshFilter->GetMesh() : nullptr);
|
||||||
|
|
||||||
|
if (meshRenderer == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t materialIndex = 0; materialIndex < meshRenderer->GetMaterialCount(); ++materialIndex) {
|
||||||
|
std::string label = std::string(objectName) + "#mat" + std::to_string(materialIndex);
|
||||||
|
DumpMaterialDiagnostics(label.c_str(), meshRenderer->GetMaterial(materialIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RHIResourceView* NahidaPreviewSceneTest::GetCurrentBackBufferView() {
|
||||||
|
const int backBufferIndex = GetCurrentBackBufferIndex();
|
||||||
|
if (backBufferIndex < 0) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (static_cast<size_t>(backBufferIndex) >= m_backBufferViews.size()) {
|
||||||
|
m_backBufferViews.resize(static_cast<size_t>(backBufferIndex) + 1, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_backBufferViews[backBufferIndex] == nullptr) {
|
||||||
|
ResourceViewDesc viewDesc = {};
|
||||||
|
viewDesc.format = static_cast<uint32_t>(Format::R8G8B8A8_UNorm);
|
||||||
|
viewDesc.dimension = ResourceViewDimension::Texture2D;
|
||||||
|
viewDesc.mipLevel = 0;
|
||||||
|
m_backBufferViews[backBufferIndex] = GetDevice()->CreateRenderTargetView(GetCurrentBackBuffer(), viewDesc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_backBufferViews[backBufferIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
void NahidaPreviewSceneTest::RenderFrame() {
|
||||||
|
ASSERT_NE(m_scene, nullptr);
|
||||||
|
ASSERT_NE(m_sceneRenderer, nullptr);
|
||||||
|
|
||||||
|
TouchSceneMaterialsAndTextures();
|
||||||
|
ResourceManager::Get().UpdateAsyncLoads();
|
||||||
|
|
||||||
|
RHICommandList* commandList = GetCommandList();
|
||||||
|
ASSERT_NE(commandList, nullptr);
|
||||||
|
|
||||||
|
commandList->Reset();
|
||||||
|
|
||||||
|
RenderSurface surface(kFrameWidth, kFrameHeight);
|
||||||
|
surface.SetColorAttachment(GetCurrentBackBufferView());
|
||||||
|
surface.SetDepthAttachment(m_depthView);
|
||||||
|
|
||||||
|
RenderContext renderContext = {};
|
||||||
|
renderContext.device = GetDevice();
|
||||||
|
renderContext.commandList = commandList;
|
||||||
|
renderContext.commandQueue = GetCommandQueue();
|
||||||
|
renderContext.backendType = GetBackendType();
|
||||||
|
|
||||||
|
ASSERT_TRUE(m_sceneRenderer->Render(*m_scene, nullptr, renderContext, surface));
|
||||||
|
|
||||||
|
commandList->Close();
|
||||||
|
void* commandLists[] = { commandList };
|
||||||
|
GetCommandQueue()->ExecuteCommandLists(1, commandLists);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(NahidaPreviewSceneTest, RenderNahidaPreviewScene) {
|
||||||
|
RHICommandQueue* commandQueue = GetCommandQueue();
|
||||||
|
RHISwapChain* swapChain = GetSwapChain();
|
||||||
|
ASSERT_NE(commandQueue, nullptr);
|
||||||
|
ASSERT_NE(swapChain, nullptr);
|
||||||
|
|
||||||
|
for (int frameIndex = 0; frameIndex <= kWarmupFrames; ++frameIndex) {
|
||||||
|
if (frameIndex > 0) {
|
||||||
|
commandQueue->WaitForPreviousFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
BeginRender();
|
||||||
|
RenderFrame();
|
||||||
|
|
||||||
|
if (frameIndex >= kWarmupFrames) {
|
||||||
|
commandQueue->WaitForIdle();
|
||||||
|
ASSERT_TRUE(TakeScreenshot(kD3D12Screenshot));
|
||||||
|
|
||||||
|
const PpmImage image = LoadPpmImage(kD3D12Screenshot);
|
||||||
|
ASSERT_EQ(image.width, kFrameWidth);
|
||||||
|
ASSERT_EQ(image.height, kFrameHeight);
|
||||||
|
|
||||||
|
const std::filesystem::path gtPath = ResolveRuntimePath("GT.ppm");
|
||||||
|
if (!std::filesystem::exists(gtPath)) {
|
||||||
|
GTEST_SKIP() << "GT.ppm missing, screenshot captured for manual review: " << kD3D12Screenshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_TRUE(CompareWithGoldenTemplate(kD3D12Screenshot, "GT.ppm", 10.0f));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
swapChain->Present(0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
INSTANTIATE_TEST_SUITE_P(D3D12, NahidaPreviewSceneTest, ::testing::Values(RHIType::D3D12));
|
||||||
|
|
||||||
|
GTEST_API_ int main(int argc, char** argv) {
|
||||||
|
return RunRenderingIntegrationTestMain(argc, argv);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user