chore: checkpoint current workspace changes
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
#include "Components/VolumeRendererComponent.h"
|
||||
|
||||
#include "Core/Asset/ResourceManager.h"
|
||||
#include "Debug/Logger.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Components {
|
||||
@@ -47,10 +50,24 @@ std::string MaterialPathFromHandle(const Resources::ResourceHandle<Resources::Ma
|
||||
return material.Get() != nullptr ? ToStdString(material->GetPath()) : std::string();
|
||||
}
|
||||
|
||||
uint64_t GetVolumeTraceSteadyMs() {
|
||||
using Clock = std::chrono::steady_clock;
|
||||
static const Clock::time_point s_start = Clock::now();
|
||||
return static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
Clock::now() - s_start).count());
|
||||
}
|
||||
|
||||
void LogVolumeTraceFileSystem(const std::string& message) {
|
||||
Containers::String entry("[VolumeTrace] ");
|
||||
entry += message.c_str();
|
||||
Debug::Logger::Get().Info(Debug::LogCategory::FileSystem, entry);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct VolumeRendererComponent::PendingVolumeLoadState {
|
||||
Resources::LoadResult result;
|
||||
uint64_t requestedAtMs = 0u;
|
||||
bool completed = false;
|
||||
};
|
||||
|
||||
@@ -311,12 +328,30 @@ void VolumeRendererComponent::BeginAsyncVolumeLoad(const std::string& volumeFiel
|
||||
m_asyncVolumeLoadRequested = true;
|
||||
m_volumeField.Reset();
|
||||
m_pendingVolumeLoad = std::make_shared<PendingVolumeLoadState>();
|
||||
m_pendingVolumeLoad->requestedAtMs = GetVolumeTraceSteadyMs();
|
||||
LogVolumeTraceFileSystem(
|
||||
"BeginAsyncVolumeLoad path=" + volumeFieldPath +
|
||||
" steady_ms=" + std::to_string(m_pendingVolumeLoad->requestedAtMs));
|
||||
std::weak_ptr<PendingVolumeLoadState> weakState = m_pendingVolumeLoad;
|
||||
Resources::ResourceManager::Get().LoadAsync(
|
||||
volumeFieldPath.c_str(),
|
||||
Resources::ResourceType::VolumeField,
|
||||
[weakState](Resources::LoadResult result) {
|
||||
[weakState, volumeFieldPath](Resources::LoadResult result) {
|
||||
if (std::shared_ptr<PendingVolumeLoadState> state = weakState.lock()) {
|
||||
const uint64_t completedAtMs = GetVolumeTraceSteadyMs();
|
||||
const bool succeeded = static_cast<bool>(result) && result.resource != nullptr;
|
||||
size_t payloadBytes = 0u;
|
||||
if (succeeded) {
|
||||
if (auto* volumeField = static_cast<Resources::VolumeField*>(result.resource)) {
|
||||
payloadBytes = volumeField->GetPayloadSize();
|
||||
}
|
||||
}
|
||||
LogVolumeTraceFileSystem(
|
||||
"AsyncVolumeLoadCompleted path=" + volumeFieldPath +
|
||||
" steady_ms=" + std::to_string(completedAtMs) +
|
||||
" elapsed_ms=" + std::to_string(completedAtMs - state->requestedAtMs) +
|
||||
" success=" + std::to_string(succeeded ? 1 : 0) +
|
||||
" payload_bytes=" + std::to_string(payloadBytes));
|
||||
state->result = std::move(result);
|
||||
state->completed = true;
|
||||
}
|
||||
@@ -349,6 +384,13 @@ void VolumeRendererComponent::ResolvePendingVolumeField() {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint64_t resolvedAtMs = GetVolumeTraceSteadyMs();
|
||||
LogVolumeTraceFileSystem(
|
||||
"ResolvePendingVolumeField path=" + VolumeFieldPathFromHandle(m_volumeField) +
|
||||
" steady_ms=" + std::to_string(resolvedAtMs) +
|
||||
" elapsed_ms=" + std::to_string(resolvedAtMs - completedLoad->requestedAtMs) +
|
||||
" payload_bytes=" + std::to_string(m_volumeField->GetPayloadSize()));
|
||||
|
||||
m_volumeFieldPath = VolumeFieldPathFromHandle(m_volumeField);
|
||||
if (!Resources::ResourceManager::Get().TryGetAssetRef(
|
||||
m_volumeFieldPath.c_str(),
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "Rendering/Internal/ShaderVariantUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <d3dcompiler.h>
|
||||
@@ -37,6 +38,10 @@
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <bcrypt.h>
|
||||
#endif
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Resources {
|
||||
|
||||
@@ -44,6 +49,158 @@ namespace fs = std::filesystem;
|
||||
|
||||
namespace {
|
||||
|
||||
class IncrementalGuidHasher {
|
||||
public:
|
||||
void Append(const void* data, size_t size) {
|
||||
if (data == nullptr || size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_hasBytes = true;
|
||||
const auto* bytes = static_cast<const Core::uint8*>(data);
|
||||
for (size_t index = 0; index < size; ++index) {
|
||||
m_high ^= static_cast<Core::uint64>(bytes[index]);
|
||||
m_high *= 1099511628211ULL;
|
||||
|
||||
m_low ^= static_cast<Core::uint64>(bytes[index]);
|
||||
m_low *= 1099511628211ULL;
|
||||
}
|
||||
}
|
||||
|
||||
AssetGUID Finish() const {
|
||||
if (!m_hasBytes) {
|
||||
return AssetGUID();
|
||||
}
|
||||
|
||||
return AssetGUID(m_high, m_low);
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_hasBytes = false;
|
||||
Core::uint64 m_high = 14695981039346656037ULL;
|
||||
Core::uint64 m_low = 1099511628211ULL ^ 0x9e3779b97f4a7c15ULL;
|
||||
};
|
||||
|
||||
#if defined(_WIN32)
|
||||
bool TryComputeFastFileHash(const fs::path& path, Containers::String& outHash) {
|
||||
outHash.Clear();
|
||||
|
||||
std::ifstream input(path, std::ios::binary);
|
||||
if (!input.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BCRYPT_ALG_HANDLE algorithm = nullptr;
|
||||
BCRYPT_HASH_HANDLE hash = nullptr;
|
||||
std::vector<UCHAR> hashObject;
|
||||
std::vector<UCHAR> digest;
|
||||
|
||||
auto cleanup = [&]() {
|
||||
if (hash != nullptr) {
|
||||
BCryptDestroyHash(hash);
|
||||
hash = nullptr;
|
||||
}
|
||||
if (algorithm != nullptr) {
|
||||
BCryptCloseAlgorithmProvider(algorithm, 0);
|
||||
algorithm = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
NTSTATUS status = BCryptOpenAlgorithmProvider(
|
||||
&algorithm,
|
||||
BCRYPT_SHA256_ALGORITHM,
|
||||
nullptr,
|
||||
0);
|
||||
if (!BCRYPT_SUCCESS(status)) {
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD hashObjectSize = 0;
|
||||
DWORD bytesCopied = 0;
|
||||
status = BCryptGetProperty(
|
||||
algorithm,
|
||||
BCRYPT_OBJECT_LENGTH,
|
||||
reinterpret_cast<PUCHAR>(&hashObjectSize),
|
||||
sizeof(hashObjectSize),
|
||||
&bytesCopied,
|
||||
0);
|
||||
if (!BCRYPT_SUCCESS(status) || hashObjectSize == 0) {
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD digestSize = 0;
|
||||
status = BCryptGetProperty(
|
||||
algorithm,
|
||||
BCRYPT_HASH_LENGTH,
|
||||
reinterpret_cast<PUCHAR>(&digestSize),
|
||||
sizeof(digestSize),
|
||||
&bytesCopied,
|
||||
0);
|
||||
if (!BCRYPT_SUCCESS(status) || digestSize == 0) {
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
hashObject.resize(hashObjectSize);
|
||||
digest.resize(digestSize);
|
||||
status = BCryptCreateHash(
|
||||
algorithm,
|
||||
&hash,
|
||||
hashObject.data(),
|
||||
static_cast<ULONG>(hashObject.size()),
|
||||
nullptr,
|
||||
0,
|
||||
0);
|
||||
if (!BCRYPT_SUCCESS(status)) {
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::array<char, 256 * 1024> buffer = {};
|
||||
bool hasBytes = false;
|
||||
while (input) {
|
||||
input.read(buffer.data(), static_cast<std::streamsize>(buffer.size()));
|
||||
const std::streamsize bytesRead = input.gcount();
|
||||
if (bytesRead <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
hasBytes = true;
|
||||
status = BCryptHashData(
|
||||
hash,
|
||||
reinterpret_cast<PUCHAR>(buffer.data()),
|
||||
static_cast<ULONG>(bytesRead),
|
||||
0);
|
||||
if (!BCRYPT_SUCCESS(status)) {
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasBytes) {
|
||||
cleanup();
|
||||
outHash = HashBytesToAssetGUID(nullptr, 0).ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
status = BCryptFinishHash(
|
||||
hash,
|
||||
digest.data(),
|
||||
static_cast<ULONG>(digest.size()),
|
||||
0);
|
||||
if (!BCRYPT_SUCCESS(status)) {
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
cleanup();
|
||||
outHash = HashBytesToAssetGUID(digest.data(), digest.size()).ToString();
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string ToStdString(const Containers::String& value) {
|
||||
return std::string(value.CStr());
|
||||
}
|
||||
@@ -86,6 +243,34 @@ constexpr const char* kSourceAssetDbFileName = "assets.db";
|
||||
constexpr const char* kArtifactDbFileName = "artifacts.db";
|
||||
constexpr const char* kLegacySourceAssetDbDirectory = "SourceAssetDB";
|
||||
constexpr const char* kLegacyArtifactDbDirectory = "ArtifactDB";
|
||||
constexpr Core::uint32 kArtifactDbSchemaVersion = 2;
|
||||
|
||||
ArtifactStorageKind InferArtifactStorageKind(const Containers::String& projectRoot,
|
||||
const AssetDatabase::ArtifactRecord& record) {
|
||||
if (!record.mainArtifactPath.Empty()) {
|
||||
const Containers::String absoluteMainArtifactPath =
|
||||
NormalizeArtifactPathString(fs::path(projectRoot.CStr()) / record.mainArtifactPath.CStr());
|
||||
if (IsArtifactContainerFile(absoluteMainArtifactPath)) {
|
||||
return ArtifactStorageKind::SingleFileContainer;
|
||||
}
|
||||
}
|
||||
|
||||
return record.artifactDirectory.Empty()
|
||||
? ArtifactStorageKind::Unknown
|
||||
: ArtifactStorageKind::LegacyDirectory;
|
||||
}
|
||||
|
||||
Containers::String BuildArtifactMainEntryLoadPath(const Containers::String& artifactMainPath,
|
||||
ArtifactStorageKind storageKind,
|
||||
const Containers::String& mainEntryName) {
|
||||
if (storageKind == ArtifactStorageKind::SingleFileContainer &&
|
||||
!artifactMainPath.Empty() &&
|
||||
!mainEntryName.Empty()) {
|
||||
return BuildArtifactContainerEntryPath(artifactMainPath, mainEntryName);
|
||||
}
|
||||
|
||||
return artifactMainPath;
|
||||
}
|
||||
|
||||
struct ModelSubAssetManifestEntry {
|
||||
LocalID localID = kInvalidLocalID;
|
||||
@@ -107,10 +292,18 @@ void PopulateResolvedAssetResult(const Containers::String& projectRoot,
|
||||
outAsset.relativePath = sourceRecord.relativePath;
|
||||
outAsset.assetGuid = sourceRecord.guid;
|
||||
outAsset.resourceType = artifactRecord.resourceType;
|
||||
outAsset.artifactDirectory =
|
||||
ToContainersString((fs::path(projectRoot.CStr()) / artifactRecord.artifactDirectory.CStr()).lexically_normal().generic_string());
|
||||
outAsset.artifactMainPath =
|
||||
ToContainersString((fs::path(projectRoot.CStr()) / artifactRecord.mainArtifactPath.CStr()).lexically_normal().generic_string());
|
||||
outAsset.artifactStorageKind = artifactRecord.storageKind;
|
||||
outAsset.mainEntryName = artifactRecord.mainEntryName;
|
||||
if (!artifactRecord.artifactDirectory.Empty()) {
|
||||
outAsset.artifactDirectory = ToContainersString(
|
||||
(fs::path(projectRoot.CStr()) / artifactRecord.artifactDirectory.CStr()).lexically_normal().generic_string());
|
||||
}
|
||||
outAsset.artifactMainPath = ToContainersString(
|
||||
(fs::path(projectRoot.CStr()) / artifactRecord.mainArtifactPath.CStr()).lexically_normal().generic_string());
|
||||
outAsset.artifactMainEntryPath = BuildArtifactMainEntryLoadPath(
|
||||
outAsset.artifactMainPath,
|
||||
artifactRecord.storageKind,
|
||||
artifactRecord.mainEntryName);
|
||||
outAsset.mainLocalID = artifactRecord.mainLocalID;
|
||||
}
|
||||
|
||||
@@ -1753,8 +1946,25 @@ void AssetDatabase::LoadArtifactDB() {
|
||||
}
|
||||
|
||||
std::string line;
|
||||
Core::uint32 schemaVersion = 1;
|
||||
while (std::getline(input, line)) {
|
||||
if (line.empty() || line[0] == '#') {
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line[0] == '#') {
|
||||
const std::string schemaToken = "schema=";
|
||||
const size_t schemaTokenPosition = line.find(schemaToken);
|
||||
if (schemaTokenPosition != std::string::npos) {
|
||||
const size_t valueStart = schemaTokenPosition + schemaToken.length();
|
||||
const std::string valueText = line.substr(valueStart);
|
||||
if (!valueText.empty()) {
|
||||
schemaVersion = static_cast<Core::uint32>(std::stoul(valueText));
|
||||
}
|
||||
} else if (line.find("storageKind") != std::string::npos &&
|
||||
line.find("mainEntryName") != std::string::npos) {
|
||||
schemaVersion = std::max(schemaVersion, kArtifactDbSchemaVersion);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1776,7 +1986,26 @@ void AssetDatabase::LoadArtifactDB() {
|
||||
record.sourceFileSize = static_cast<Core::uint64>(std::stoull(fields[9]));
|
||||
record.sourceWriteTime = fields.size() > 10 ? static_cast<Core::uint64>(std::stoull(fields[10])) : 0;
|
||||
record.mainLocalID = fields.size() > 11 ? static_cast<LocalID>(std::stoull(fields[11])) : kMainAssetLocalID;
|
||||
for (size_t index = 12; index + 3 < fields.size(); index += 4) {
|
||||
size_t dependencyFieldStart = 12;
|
||||
if (schemaVersion >= kArtifactDbSchemaVersion && fields.size() > 13) {
|
||||
record.storageKind = static_cast<ArtifactStorageKind>(std::stoul(fields[12]));
|
||||
record.mainEntryName = ToContainersString(fields[13]);
|
||||
dependencyFieldStart = 14;
|
||||
}
|
||||
|
||||
if (record.storageKind == ArtifactStorageKind::Unknown) {
|
||||
record.storageKind = InferArtifactStorageKind(m_projectRoot, record);
|
||||
}
|
||||
if (record.mainEntryName.Empty() &&
|
||||
record.storageKind == ArtifactStorageKind::SingleFileContainer) {
|
||||
record.mainEntryName = "main";
|
||||
}
|
||||
if (record.artifactDirectory.Empty() && !record.mainArtifactPath.Empty()) {
|
||||
record.artifactDirectory =
|
||||
NormalizePathString(fs::path(record.mainArtifactPath.CStr()).parent_path());
|
||||
}
|
||||
|
||||
for (size_t index = dependencyFieldStart; index + 3 < fields.size(); index += 4) {
|
||||
ArtifactDependencyRecord dependency;
|
||||
dependency.path = ToContainersString(fields[index + 0]);
|
||||
dependency.hash = ToContainersString(fields[index + 1]);
|
||||
@@ -1801,7 +2030,8 @@ void AssetDatabase::SaveArtifactDB() const {
|
||||
return;
|
||||
}
|
||||
|
||||
output << "# artifactKey\tassetGuid\timporter\tversion\ttype\tartifactDir\tmainArtifact\tsourceHash\tmetaHash\tsize\twriteTime\tmainLocalID\t(depPath\tdepHash\tdepSize\tdepWriteTime)...\n";
|
||||
output << "# schema=" << kArtifactDbSchemaVersion << "\n";
|
||||
output << "# artifactKey\tassetGuid\timporter\tversion\ttype\tartifactDir\tmainArtifact\tsourceHash\tmetaHash\tsize\twriteTime\tmainLocalID\tstorageKind\tmainEntryName\t(depPath\tdepHash\tdepSize\tdepWriteTime)...\n";
|
||||
for (const auto& [guid, record] : m_artifactsByGuid) {
|
||||
output << EscapeField(ToStdString(record.artifactKey)) << '\t'
|
||||
<< EscapeField(ToStdString(record.assetGuid.ToString())) << '\t'
|
||||
@@ -1814,7 +2044,9 @@ void AssetDatabase::SaveArtifactDB() const {
|
||||
<< EscapeField(ToStdString(record.metaHash)) << '\t'
|
||||
<< record.sourceFileSize << '\t'
|
||||
<< record.sourceWriteTime << '\t'
|
||||
<< record.mainLocalID;
|
||||
<< record.mainLocalID << '\t'
|
||||
<< static_cast<Core::uint32>(record.storageKind) << '\t'
|
||||
<< EscapeField(ToStdString(record.mainEntryName));
|
||||
for (const ArtifactDependencyRecord& dependency : record.dependencies) {
|
||||
output << '\t' << EscapeField(ToStdString(dependency.path))
|
||||
<< '\t' << EscapeField(ToStdString(dependency.hash))
|
||||
@@ -2494,8 +2726,10 @@ bool AssetDatabase::ImportTextureAsset(const SourceAssetRecord& sourceRecord,
|
||||
outRecord.importerName = sourceRecord.importerName;
|
||||
outRecord.importerVersion = sourceRecord.importerVersion;
|
||||
outRecord.resourceType = ResourceType::Texture;
|
||||
outRecord.storageKind = ArtifactStorageKind::SingleFileContainer;
|
||||
outRecord.artifactDirectory = artifactDir;
|
||||
outRecord.mainArtifactPath = mainArtifactPath;
|
||||
outRecord.mainEntryName = "main";
|
||||
outRecord.sourceHash = sourceRecord.sourceHash;
|
||||
outRecord.metaHash = sourceRecord.metaHash;
|
||||
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
|
||||
@@ -2553,8 +2787,10 @@ bool AssetDatabase::ImportMaterialAsset(const SourceAssetRecord& sourceRecord,
|
||||
outRecord.importerName = sourceRecord.importerName;
|
||||
outRecord.importerVersion = sourceRecord.importerVersion;
|
||||
outRecord.resourceType = ResourceType::Material;
|
||||
outRecord.storageKind = ArtifactStorageKind::SingleFileContainer;
|
||||
outRecord.artifactDirectory = artifactDir;
|
||||
outRecord.mainArtifactPath = mainArtifactPath;
|
||||
outRecord.mainEntryName = "main";
|
||||
outRecord.sourceHash = sourceRecord.sourceHash;
|
||||
outRecord.metaHash = sourceRecord.metaHash;
|
||||
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
|
||||
@@ -2743,8 +2979,10 @@ bool AssetDatabase::ImportModelAsset(const SourceAssetRecord& sourceRecord,
|
||||
outRecord.importerName = sourceRecord.importerName;
|
||||
outRecord.importerVersion = sourceRecord.importerVersion;
|
||||
outRecord.resourceType = ResourceType::Model;
|
||||
outRecord.storageKind = ArtifactStorageKind::SingleFileContainer;
|
||||
outRecord.artifactDirectory = artifactDir;
|
||||
outRecord.mainArtifactPath = mainArtifactPath;
|
||||
outRecord.mainEntryName = "main";
|
||||
outRecord.sourceHash = sourceRecord.sourceHash;
|
||||
outRecord.metaHash = sourceRecord.metaHash;
|
||||
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
|
||||
@@ -2809,8 +3047,10 @@ bool AssetDatabase::ImportShaderAsset(const SourceAssetRecord& sourceRecord,
|
||||
outRecord.importerName = sourceRecord.importerName;
|
||||
outRecord.importerVersion = sourceRecord.importerVersion;
|
||||
outRecord.resourceType = ResourceType::Shader;
|
||||
outRecord.storageKind = ArtifactStorageKind::SingleFileContainer;
|
||||
outRecord.artifactDirectory = artifactDir;
|
||||
outRecord.mainArtifactPath = mainArtifactPath;
|
||||
outRecord.mainEntryName = "main";
|
||||
outRecord.sourceHash = sourceRecord.sourceHash;
|
||||
outRecord.metaHash = sourceRecord.metaHash;
|
||||
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
|
||||
@@ -2874,8 +3114,10 @@ bool AssetDatabase::ImportGaussianSplatAsset(const SourceAssetRecord& sourceReco
|
||||
outRecord.importerName = sourceRecord.importerName;
|
||||
outRecord.importerVersion = sourceRecord.importerVersion;
|
||||
outRecord.resourceType = ResourceType::GaussianSplat;
|
||||
outRecord.storageKind = ArtifactStorageKind::SingleFileContainer;
|
||||
outRecord.artifactDirectory = artifactDir;
|
||||
outRecord.mainArtifactPath = mainArtifactPath;
|
||||
outRecord.mainEntryName = "main";
|
||||
outRecord.sourceHash = sourceRecord.sourceHash;
|
||||
outRecord.metaHash = sourceRecord.metaHash;
|
||||
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
|
||||
@@ -2953,8 +3195,10 @@ bool AssetDatabase::ImportVolumeFieldAsset(const SourceAssetRecord& sourceRecord
|
||||
outRecord.importerName = sourceRecord.importerName;
|
||||
outRecord.importerVersion = sourceRecord.importerVersion;
|
||||
outRecord.resourceType = ResourceType::VolumeField;
|
||||
outRecord.storageKind = ArtifactStorageKind::SingleFileContainer;
|
||||
outRecord.artifactDirectory = artifactDir;
|
||||
outRecord.mainArtifactPath = mainArtifactPath;
|
||||
outRecord.mainEntryName = "main";
|
||||
outRecord.sourceHash = sourceRecord.sourceHash;
|
||||
outRecord.metaHash = sourceRecord.metaHash;
|
||||
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
|
||||
@@ -3040,8 +3284,10 @@ bool AssetDatabase::ImportUIDocumentAsset(const SourceAssetRecord& sourceRecord,
|
||||
outRecord.importerName = sourceRecord.importerName;
|
||||
outRecord.importerVersion = sourceRecord.importerVersion;
|
||||
outRecord.resourceType = resourceType;
|
||||
outRecord.storageKind = ArtifactStorageKind::SingleFileContainer;
|
||||
outRecord.artifactDirectory = artifactDir;
|
||||
outRecord.mainArtifactPath = mainArtifactPath;
|
||||
outRecord.mainEntryName = "main";
|
||||
outRecord.sourceHash = sourceRecord.sourceHash;
|
||||
outRecord.metaHash = sourceRecord.metaHash;
|
||||
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
|
||||
@@ -3315,19 +3561,31 @@ Containers::String AssetDatabase::ReadWholeFileText(const fs::path& path) {
|
||||
}
|
||||
|
||||
Containers::String AssetDatabase::ComputeFileHash(const fs::path& path) {
|
||||
#if defined(_WIN32)
|
||||
Containers::String fastHash;
|
||||
if (TryComputeFastFileHash(path, fastHash)) {
|
||||
return fastHash;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::ifstream input(path, std::ios::binary);
|
||||
if (!input.is_open()) {
|
||||
return Containers::String();
|
||||
}
|
||||
|
||||
std::vector<Core::uint8> bytes(
|
||||
(std::istreambuf_iterator<char>(input)),
|
||||
std::istreambuf_iterator<char>());
|
||||
if (bytes.empty()) {
|
||||
return HashBytesToAssetGUID(nullptr, 0).ToString();
|
||||
IncrementalGuidHasher hasher;
|
||||
std::array<char, 64 * 1024> buffer = {};
|
||||
while (input) {
|
||||
input.read(buffer.data(), static_cast<std::streamsize>(buffer.size()));
|
||||
const std::streamsize bytesRead = input.gcount();
|
||||
if (bytesRead <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
hasher.Append(buffer.data(), static_cast<size_t>(bytesRead));
|
||||
}
|
||||
|
||||
return HashBytesToAssetGUID(bytes.data(), bytes.size()).ToString();
|
||||
return hasher.Finish().ToString();
|
||||
}
|
||||
|
||||
Core::uint64 AssetDatabase::GetFileSizeValue(const fs::path& path) {
|
||||
|
||||
@@ -341,9 +341,17 @@ AssetImportService::ImportedAsset AssetImportService::ConvertResolvedAsset(
|
||||
importedAsset.relativePath = resolvedAsset.relativePath;
|
||||
importedAsset.assetGuid = resolvedAsset.assetGuid;
|
||||
importedAsset.resourceType = resolvedAsset.resourceType;
|
||||
importedAsset.artifactMainPath = resolvedAsset.artifactMainPath;
|
||||
importedAsset.artifactMainEntryPath = resolvedAsset.artifactMainEntryPath;
|
||||
importedAsset.runtimeLoadPath =
|
||||
resolvedAsset.artifactReady ? resolvedAsset.artifactMainPath : resolvedAsset.absolutePath;
|
||||
resolvedAsset.artifactReady
|
||||
? (!resolvedAsset.artifactMainEntryPath.Empty()
|
||||
? resolvedAsset.artifactMainEntryPath
|
||||
: resolvedAsset.artifactMainPath)
|
||||
: resolvedAsset.absolutePath;
|
||||
importedAsset.artifactDirectory = resolvedAsset.artifactDirectory;
|
||||
importedAsset.artifactStorageKind = resolvedAsset.artifactStorageKind;
|
||||
importedAsset.mainEntryName = resolvedAsset.mainEntryName;
|
||||
importedAsset.mainLocalID = resolvedAsset.mainLocalID;
|
||||
return importedAsset;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <XCEngine/Core/IO/IResourceLoader.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactContainer.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
@@ -53,16 +54,23 @@ Containers::Array<Core::uint8> IResourceLoader::ReadFileData(const Containers::S
|
||||
}
|
||||
|
||||
Containers::String IResourceLoader::GetExtension(const Containers::String& path) {
|
||||
Containers::String normalizedPath = path;
|
||||
Containers::String containerPath;
|
||||
Containers::String entryName;
|
||||
if (TryParseArtifactContainerEntryPath(path, containerPath, entryName)) {
|
||||
normalizedPath = containerPath;
|
||||
}
|
||||
|
||||
Containers::String ext;
|
||||
size_t dotPos = Containers::String::npos;
|
||||
for (size_t i = path.Length(); i > 0; --i) {
|
||||
if (path[i - 1] == '.') {
|
||||
for (size_t i = normalizedPath.Length(); i > 0; --i) {
|
||||
if (normalizedPath[i - 1] == '.') {
|
||||
dotPos = i - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dotPos != Containers::String::npos) {
|
||||
ext = path.Substring(dotPos + 1);
|
||||
ext = normalizedPath.Substring(dotPos + 1);
|
||||
}
|
||||
return ext;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#include "XCEngine/RHI/D3D12/D3D12Buffer.h"
|
||||
|
||||
#include "Debug/Logger.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace RHI {
|
||||
|
||||
@@ -46,6 +50,28 @@ bool D3D12Buffer::Initialize(
|
||||
);
|
||||
|
||||
if (FAILED(hResult)) {
|
||||
char message[256];
|
||||
std::snprintf(
|
||||
message,
|
||||
sizeof(message),
|
||||
"D3D12Buffer::Initialize CreateCommittedResource failed: hr=0x%08X size=%llu heapType=%u flags=0x%X initialState=0x%X",
|
||||
static_cast<unsigned int>(hResult),
|
||||
static_cast<unsigned long long>(size),
|
||||
static_cast<unsigned int>(heapType),
|
||||
static_cast<unsigned int>(resourceFlags),
|
||||
static_cast<unsigned int>(initialState));
|
||||
Debug::Logger::Get().Error(Debug::LogCategory::Rendering, message);
|
||||
|
||||
const HRESULT removedReason = device->GetDeviceRemovedReason();
|
||||
if (FAILED(removedReason)) {
|
||||
char removedMessage[128];
|
||||
std::snprintf(
|
||||
removedMessage,
|
||||
sizeof(removedMessage),
|
||||
"D3D12Buffer::Initialize device removed reason: 0x%08X",
|
||||
static_cast<unsigned int>(removedReason));
|
||||
Debug::Logger::Get().Error(Debug::LogCategory::Rendering, removedMessage);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -170,13 +170,13 @@ void D3D12CommandList::SetPipelineLayout(D3D12PipelineLayout* layout) {
|
||||
}
|
||||
|
||||
void D3D12CommandList::TransitionBarrier(RHIResourceView* resource, ResourceStates stateBefore, ResourceStates stateAfter) {
|
||||
if (!resource || !resource->IsValid()) return;
|
||||
if (stateBefore == stateAfter || !resource || !resource->IsValid()) return;
|
||||
D3D12ResourceView* d3d12View = static_cast<D3D12ResourceView*>(resource);
|
||||
TransitionBarrierInternal(d3d12View->GetResource(), stateBefore, stateAfter);
|
||||
}
|
||||
|
||||
void D3D12CommandList::TransitionBarrier(ID3D12Resource* resource, ResourceStates stateBefore, ResourceStates stateAfter) {
|
||||
if (!resource) return;
|
||||
if (stateBefore == stateAfter || !resource) return;
|
||||
TransitionBarrierInternal(resource, stateBefore, stateAfter);
|
||||
}
|
||||
|
||||
|
||||
@@ -624,6 +624,10 @@ void OpenGLCommandList::SetUniformMat4(const char* name, const float* value) {
|
||||
}
|
||||
|
||||
void OpenGLCommandList::TransitionBarrier(RHIResourceView* resource, ResourceStates stateBefore, ResourceStates stateAfter) {
|
||||
if (stateBefore == stateAfter) {
|
||||
return;
|
||||
}
|
||||
|
||||
(void)stateBefore;
|
||||
if (resource != nullptr && resource->IsValid()) {
|
||||
OpenGLResourceView* view = static_cast<OpenGLResourceView*>(resource);
|
||||
|
||||
@@ -677,7 +677,17 @@ std::vector<std::filesystem::path> CollectShaderIncludeDirectories(const ShaderC
|
||||
return directories;
|
||||
}
|
||||
|
||||
std::wstring BuildIncludeDirectoryArguments(const ShaderCompileDesc& desc) {
|
||||
std::wstring BuildGlslangIncludeDirectoryArguments(const ShaderCompileDesc& desc) {
|
||||
std::wstring arguments;
|
||||
const std::vector<std::filesystem::path> includeDirectories = CollectShaderIncludeDirectories(desc);
|
||||
for (const std::filesystem::path& includeDirectory : includeDirectories) {
|
||||
arguments += L" -I\"" + includeDirectory.wstring() + L"\"";
|
||||
}
|
||||
|
||||
return arguments;
|
||||
}
|
||||
|
||||
std::wstring BuildDxcIncludeDirectoryArguments(const ShaderCompileDesc& desc) {
|
||||
std::wstring arguments;
|
||||
const std::vector<std::filesystem::path> includeDirectories = CollectShaderIncludeDirectories(desc);
|
||||
for (const std::filesystem::path& includeDirectory : includeDirectories) {
|
||||
@@ -795,7 +805,7 @@ bool CompileGlslToSpirv(const ShaderCompileDesc& desc,
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::wstring includeArguments = BuildIncludeDirectoryArguments(desc);
|
||||
const std::wstring includeArguments = BuildGlslangIncludeDirectoryArguments(desc);
|
||||
const std::wstring workingDirectory = ResolveCompilerWorkingDirectory(desc);
|
||||
const std::wstring arguments =
|
||||
SpirvTargetArguments(targetEnvironment) + L" -S " + ShaderStageArgument(type) +
|
||||
@@ -888,7 +898,7 @@ bool CompileHlslToSpirv(const ShaderCompileDesc& desc,
|
||||
const std::string profile = NarrowAscii(desc.profile);
|
||||
const std::wstring targetProfile =
|
||||
WidenAscii(profile.empty() ? "ps_6_0" : profile.c_str());
|
||||
const std::wstring includeArguments = BuildIncludeDirectoryArguments(desc);
|
||||
const std::wstring includeArguments = BuildDxcIncludeDirectoryArguments(desc);
|
||||
const std::wstring workingDirectory = ResolveCompilerWorkingDirectory(desc);
|
||||
|
||||
const std::wstring arguments =
|
||||
@@ -971,7 +981,7 @@ bool CompileHlslToSpirv(const ShaderCompileDesc& desc,
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::wstring includeArguments = BuildIncludeDirectoryArguments(desc);
|
||||
const std::wstring includeArguments = BuildGlslangIncludeDirectoryArguments(desc);
|
||||
const std::wstring workingDirectory = ResolveCompilerWorkingDirectory(desc);
|
||||
const std::wstring arguments =
|
||||
L"-D --auto-map-bindings " + SpirvTargetArguments(targetEnvironment) +
|
||||
|
||||
@@ -365,6 +365,10 @@ void VulkanCommandList::TransitionTexture(VulkanTexture* texture, ResourceStates
|
||||
}
|
||||
|
||||
void VulkanCommandList::TransitionBarrier(RHIResourceView* resource, ResourceStates stateBefore, ResourceStates stateAfter) {
|
||||
if (stateBefore == stateAfter) {
|
||||
return;
|
||||
}
|
||||
|
||||
(void)stateBefore;
|
||||
auto* view = static_cast<VulkanResourceView*>(resource);
|
||||
if (view != nullptr && view->GetTexture() != nullptr) {
|
||||
|
||||
@@ -80,6 +80,7 @@ bool DirectionalShadowSurfaceCache::EnsureSurface(
|
||||
m_depthShaderView = depthShaderView;
|
||||
m_surface = RenderSurface(plan.mapWidth, plan.mapHeight);
|
||||
m_surface.SetDepthAttachment(depthView);
|
||||
m_surface.SetSampleDesc(1u, 0u);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -103,6 +103,7 @@ public:
|
||||
|
||||
entry.surface = RenderSurface(width, height);
|
||||
entry.surface.SetColorAttachment(entry.renderTargetView);
|
||||
entry.surface.SetSampleDesc(1u, 0u);
|
||||
entry.surface.SetAutoTransitionEnabled(true);
|
||||
entry.surface.SetColorStateBefore(RHI::ResourceStates::Common);
|
||||
entry.surface.SetColorStateAfter(RHI::ResourceStates::PixelShaderResource);
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace Rendering {
|
||||
namespace {
|
||||
|
||||
constexpr size_t kVolumeWordStride = sizeof(uint32_t);
|
||||
constexpr uint32_t kGaussianSplatPackedPositionStride = sizeof(float) * 4u;
|
||||
|
||||
uint64_t GetVolumeTraceSteadyMs() {
|
||||
using Clock = std::chrono::steady_clock;
|
||||
@@ -236,6 +237,15 @@ bool ValidateGaussianSplatSectionLayout(
|
||||
return expectedDataSize == section.dataSize;
|
||||
}
|
||||
|
||||
bool ShouldPadGaussianSplatSectionToAlignedFloat4(
|
||||
Resources::GaussianSplatSectionType sectionType,
|
||||
Resources::GaussianSplatSectionFormat format,
|
||||
uint32_t stride) {
|
||||
return sectionType == Resources::GaussianSplatSectionType::Positions &&
|
||||
format == Resources::GaussianSplatSectionFormat::VectorFloat32 &&
|
||||
stride == sizeof(Resources::GaussianSplatPositionRecord);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RenderResourceCache::~RenderResourceCache() {
|
||||
@@ -709,29 +719,54 @@ bool RenderResourceCache::UploadGaussianSplatSection(
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<Core::uint8> alignedUploadData;
|
||||
const void* uploadData = sectionData;
|
||||
uint64_t uploadSize = section->dataSize;
|
||||
uint32_t uploadStride = resolvedStride;
|
||||
|
||||
if (ShouldPadGaussianSplatSectionToAlignedFloat4(sectionType, section->format, resolvedStride)) {
|
||||
alignedUploadData.resize(
|
||||
static_cast<size_t>(section->elementCount) * static_cast<size_t>(kGaussianSplatPackedPositionStride),
|
||||
static_cast<Core::uint8>(0u));
|
||||
const auto* sourcePositions = static_cast<const Resources::GaussianSplatPositionRecord*>(sectionData);
|
||||
auto* destinationWords = reinterpret_cast<float*>(alignedUploadData.data());
|
||||
for (uint32_t elementIndex = 0u; elementIndex < section->elementCount; ++elementIndex) {
|
||||
const Resources::GaussianSplatPositionRecord& source = sourcePositions[elementIndex];
|
||||
const size_t destinationIndex = static_cast<size_t>(elementIndex) * 4u;
|
||||
destinationWords[destinationIndex + 0u] = source.position.x;
|
||||
destinationWords[destinationIndex + 1u] = source.position.y;
|
||||
destinationWords[destinationIndex + 2u] = source.position.z;
|
||||
destinationWords[destinationIndex + 3u] = 0.0f;
|
||||
}
|
||||
|
||||
uploadData = alignedUploadData.data();
|
||||
uploadSize = static_cast<uint64_t>(alignedUploadData.size());
|
||||
uploadStride = kGaussianSplatPackedPositionStride;
|
||||
}
|
||||
|
||||
RHI::BufferDesc bufferDesc = {};
|
||||
bufferDesc.size = section->dataSize;
|
||||
bufferDesc.stride = resolvedStride;
|
||||
bufferDesc.size = uploadSize;
|
||||
bufferDesc.stride = uploadStride;
|
||||
bufferDesc.bufferType = static_cast<uint32_t>(RHI::BufferType::Storage);
|
||||
bufferDesc.flags = 0u;
|
||||
|
||||
cachedSection.buffer = device->CreateBuffer(
|
||||
bufferDesc,
|
||||
sectionData,
|
||||
static_cast<size_t>(section->dataSize),
|
||||
uploadData,
|
||||
static_cast<size_t>(uploadSize),
|
||||
RHI::ResourceStates::GenericRead);
|
||||
if (cachedSection.buffer == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
cachedSection.buffer->SetStride(resolvedStride);
|
||||
cachedSection.buffer->SetStride(uploadStride);
|
||||
cachedSection.buffer->SetBufferType(RHI::BufferType::Storage);
|
||||
|
||||
RHI::ResourceViewDesc viewDesc = {};
|
||||
viewDesc.dimension = RHI::ResourceViewDimension::StructuredBuffer;
|
||||
viewDesc.firstElement = 0u;
|
||||
viewDesc.elementCount = section->elementCount;
|
||||
viewDesc.structureByteStride = resolvedStride;
|
||||
viewDesc.structureByteStride = uploadStride;
|
||||
|
||||
cachedSection.shaderResourceView = device->CreateShaderResourceView(cachedSection.buffer, viewDesc);
|
||||
if (cachedSection.shaderResourceView == nullptr) {
|
||||
@@ -740,9 +775,9 @@ bool RenderResourceCache::UploadGaussianSplatSection(
|
||||
|
||||
cachedSection.type = sectionType;
|
||||
cachedSection.format = section->format;
|
||||
cachedSection.elementStride = resolvedStride;
|
||||
cachedSection.elementStride = uploadStride;
|
||||
cachedSection.elementCount = section->elementCount;
|
||||
cachedSection.payloadSize = section->dataSize;
|
||||
cachedSection.payloadSize = uploadSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,438 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Core/Containers/String.h>
|
||||
#include <XCEngine/RHI/RHIEnums.h>
|
||||
#include <XCEngine/RHI/RHIPipelineState.h>
|
||||
#include <XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
#include <XCEngine/Resources/Shader/Shader.h>
|
||||
|
||||
#include <regex>
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
namespace Detail {
|
||||
|
||||
inline Resources::ShaderBackend ToShaderBackend(RHI::RHIType backendType) {
|
||||
switch (backendType) {
|
||||
case RHI::RHIType::D3D12:
|
||||
return Resources::ShaderBackend::D3D12;
|
||||
case RHI::RHIType::Vulkan:
|
||||
return Resources::ShaderBackend::Vulkan;
|
||||
case RHI::RHIType::OpenGL:
|
||||
default:
|
||||
return Resources::ShaderBackend::OpenGL;
|
||||
}
|
||||
}
|
||||
|
||||
inline RHI::ShaderLanguage ToRHIShaderLanguage(Resources::ShaderLanguage language) {
|
||||
switch (language) {
|
||||
case Resources::ShaderLanguage::HLSL:
|
||||
return RHI::ShaderLanguage::HLSL;
|
||||
case Resources::ShaderLanguage::SPIRV:
|
||||
return RHI::ShaderLanguage::SPIRV;
|
||||
case Resources::ShaderLanguage::GLSL:
|
||||
default:
|
||||
return RHI::ShaderLanguage::GLSL;
|
||||
}
|
||||
}
|
||||
|
||||
inline std::wstring ToWideAscii(const Containers::String& value) {
|
||||
std::wstring wide;
|
||||
wide.reserve(value.Length());
|
||||
for (size_t index = 0; index < value.Length(); ++index) {
|
||||
wide.push_back(static_cast<wchar_t>(value[index]));
|
||||
}
|
||||
return wide;
|
||||
}
|
||||
|
||||
inline std::string ToStdString(const Containers::String& value) {
|
||||
return std::string(value.CStr(), value.Length());
|
||||
}
|
||||
|
||||
inline std::string EscapeRegexLiteral(const Containers::String& value) {
|
||||
std::string escaped;
|
||||
escaped.reserve(value.Length() * 2u);
|
||||
for (size_t index = 0; index < value.Length(); ++index) {
|
||||
const char ch = value[index];
|
||||
switch (ch) {
|
||||
case '\\':
|
||||
case '^':
|
||||
case '$':
|
||||
case '.':
|
||||
case '|':
|
||||
case '?':
|
||||
case '*':
|
||||
case '+':
|
||||
case '(':
|
||||
case ')':
|
||||
case '[':
|
||||
case ']':
|
||||
case '{':
|
||||
case '}':
|
||||
escaped.push_back('\\');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
escaped.push_back(ch);
|
||||
}
|
||||
|
||||
return escaped;
|
||||
}
|
||||
|
||||
inline bool TryCollectShaderPassResourceBindings(
|
||||
const Resources::ShaderPass& pass,
|
||||
Containers::Array<Resources::ShaderResourceBindingDesc>& outBindings) {
|
||||
outBindings.Clear();
|
||||
|
||||
if (pass.resources.Empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outBindings.Reserve(pass.resources.Size());
|
||||
for (const Resources::ShaderResourceBindingDesc& binding : pass.resources) {
|
||||
outBindings.PushBack(binding);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool TryRewriteHlslRegisterBindingWithName(
|
||||
std::string& sourceText,
|
||||
const Containers::String& declarationName,
|
||||
const char* registerPrefix,
|
||||
Core::uint32 bindingIndex,
|
||||
Core::uint32 setIndex,
|
||||
bool includeRegisterSpace,
|
||||
Resources::ShaderResourceType resourceType) {
|
||||
if (declarationName.Empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string registerClause =
|
||||
includeRegisterSpace
|
||||
? std::string("register(") + registerPrefix +
|
||||
std::to_string(bindingIndex) +
|
||||
", space" +
|
||||
std::to_string(setIndex) +
|
||||
")"
|
||||
: std::string("register(") + registerPrefix +
|
||||
std::to_string(bindingIndex) +
|
||||
")";
|
||||
const std::string escapedName = EscapeRegexLiteral(declarationName);
|
||||
|
||||
if (resourceType == Resources::ShaderResourceType::ConstantBuffer) {
|
||||
const std::regex pattern(
|
||||
"(cbuffer\\s+" + escapedName + "\\s*)(:\\s*register\\s*\\([^\\)]*\\))?(\\s*\\{)",
|
||||
std::regex::ECMAScript);
|
||||
const std::string rewritten =
|
||||
std::regex_replace(sourceText, pattern, "$1: " + registerClause + "$3");
|
||||
if (rewritten != sourceText) {
|
||||
sourceText = rewritten;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (resourceType == Resources::ShaderResourceType::StructuredBuffer ||
|
||||
resourceType == Resources::ShaderResourceType::RWStructuredBuffer) {
|
||||
const std::regex pattern(
|
||||
"((?:globallycoherent\\s+)?(?:StructuredBuffer|RWStructuredBuffer)\\s*<[^;\\r\\n>]+>\\s+" +
|
||||
escapedName + "\\s*)(:\\s*register\\s*\\([^\\)]*\\))?(\\s*;)",
|
||||
std::regex::ECMAScript);
|
||||
const std::string rewritten =
|
||||
std::regex_replace(sourceText, pattern, "$1: " + registerClause + "$3");
|
||||
if (rewritten != sourceText) {
|
||||
sourceText = rewritten;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (resourceType == Resources::ShaderResourceType::RawBuffer ||
|
||||
resourceType == Resources::ShaderResourceType::RWRawBuffer) {
|
||||
const std::regex pattern(
|
||||
"((?:globallycoherent\\s+)?(?:ByteAddressBuffer|RWByteAddressBuffer)\\s+" + escapedName +
|
||||
"\\s*)(:\\s*register\\s*\\([^\\)]*\\))?(\\s*;)",
|
||||
std::regex::ECMAScript);
|
||||
const std::string rewritten =
|
||||
std::regex_replace(sourceText, pattern, "$1: " + registerClause + "$3");
|
||||
if (rewritten != sourceText) {
|
||||
sourceText = rewritten;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::regex pattern(
|
||||
"((?:Texture2D|TextureCube|SamplerState|SamplerComparisonState)\\s+" + escapedName +
|
||||
"\\s*)(:\\s*register\\s*\\([^\\)]*\\))?(\\s*;)",
|
||||
std::regex::ECMAScript);
|
||||
const std::string rewritten =
|
||||
std::regex_replace(sourceText, pattern, "$1: " + registerClause + "$3");
|
||||
if (rewritten != sourceText) {
|
||||
sourceText = rewritten;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline const char* TryGetHlslRegisterPrefix(Resources::ShaderResourceType type) {
|
||||
switch (type) {
|
||||
case Resources::ShaderResourceType::ConstantBuffer:
|
||||
return "b";
|
||||
case Resources::ShaderResourceType::Texture2D:
|
||||
case Resources::ShaderResourceType::TextureCube:
|
||||
case Resources::ShaderResourceType::StructuredBuffer:
|
||||
case Resources::ShaderResourceType::RawBuffer:
|
||||
return "t";
|
||||
case Resources::ShaderResourceType::Sampler:
|
||||
return "s";
|
||||
case Resources::ShaderResourceType::RWStructuredBuffer:
|
||||
case Resources::ShaderResourceType::RWRawBuffer:
|
||||
return "u";
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool TryRewriteHlslRegisterBinding(
|
||||
std::string& sourceText,
|
||||
const Resources::ShaderResourceBindingDesc& binding,
|
||||
bool includeRegisterSpace) {
|
||||
const char* registerPrefix = TryGetHlslRegisterPrefix(binding.type);
|
||||
if (registerPrefix == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TryRewriteHlslRegisterBindingWithName(
|
||||
sourceText,
|
||||
binding.name,
|
||||
registerPrefix,
|
||||
binding.binding,
|
||||
binding.set,
|
||||
includeRegisterSpace,
|
||||
binding.type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool TryBuildRuntimeShaderBindings(
|
||||
const Resources::ShaderPass& pass,
|
||||
Resources::ShaderBackend backend,
|
||||
Containers::Array<Resources::ShaderResourceBindingDesc>& outBindings,
|
||||
bool& outIncludeRegisterSpace) {
|
||||
outBindings.Clear();
|
||||
outIncludeRegisterSpace = false;
|
||||
|
||||
if (!TryCollectShaderPassResourceBindings(pass, outBindings)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (backend == Resources::ShaderBackend::Vulkan) {
|
||||
outIncludeRegisterSpace = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (backend != Resources::ShaderBackend::D3D12 &&
|
||||
backend != Resources::ShaderBackend::OpenGL) {
|
||||
outBindings.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
Core::uint32 nextConstantBufferRegister = 0;
|
||||
Core::uint32 nextTextureRegister = 0;
|
||||
Core::uint32 nextSamplerRegister = 0;
|
||||
Core::uint32 nextUnorderedAccessRegister = 0;
|
||||
for (Resources::ShaderResourceBindingDesc& binding : outBindings) {
|
||||
binding.set = 0;
|
||||
switch (binding.type) {
|
||||
case Resources::ShaderResourceType::ConstantBuffer:
|
||||
binding.binding = nextConstantBufferRegister++;
|
||||
break;
|
||||
case Resources::ShaderResourceType::Texture2D:
|
||||
case Resources::ShaderResourceType::TextureCube:
|
||||
case Resources::ShaderResourceType::StructuredBuffer:
|
||||
case Resources::ShaderResourceType::RawBuffer:
|
||||
binding.binding = nextTextureRegister++;
|
||||
break;
|
||||
case Resources::ShaderResourceType::Sampler:
|
||||
binding.binding = nextSamplerRegister++;
|
||||
break;
|
||||
case Resources::ShaderResourceType::RWStructuredBuffer:
|
||||
case Resources::ShaderResourceType::RWRawBuffer:
|
||||
default:
|
||||
binding.binding = nextUnorderedAccessRegister++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline std::string BuildRuntimeShaderSource(
|
||||
const Resources::ShaderPass& pass,
|
||||
Resources::ShaderBackend backend,
|
||||
const Resources::ShaderStageVariant& variant) {
|
||||
std::string sourceText = ToStdString(variant.sourceCode);
|
||||
|
||||
if (variant.language != Resources::ShaderLanguage::HLSL ||
|
||||
backend == Resources::ShaderBackend::Generic) {
|
||||
return sourceText;
|
||||
}
|
||||
|
||||
Containers::Array<Resources::ShaderResourceBindingDesc> bindings;
|
||||
bool includeRegisterSpace = false;
|
||||
if (!TryBuildRuntimeShaderBindings(pass, backend, bindings, includeRegisterSpace)) {
|
||||
return sourceText;
|
||||
}
|
||||
|
||||
for (const Resources::ShaderResourceBindingDesc& binding : bindings) {
|
||||
TryRewriteHlslRegisterBinding(sourceText, binding, includeRegisterSpace);
|
||||
}
|
||||
|
||||
return sourceText;
|
||||
}
|
||||
|
||||
inline void AddShaderCompileMacro(
|
||||
RHI::ShaderCompileDesc& compileDesc,
|
||||
const wchar_t* name,
|
||||
const wchar_t* definition = L"1") {
|
||||
if (name == nullptr || *name == L'\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const RHI::ShaderCompileMacro& existingMacro : compileDesc.macros) {
|
||||
if (existingMacro.name == name) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
RHI::ShaderCompileMacro macro = {};
|
||||
macro.name = name;
|
||||
macro.definition = definition != nullptr ? definition : L"";
|
||||
compileDesc.macros.push_back(std::move(macro));
|
||||
}
|
||||
|
||||
inline void InjectShaderBackendMacros(
|
||||
Resources::ShaderBackend backend,
|
||||
RHI::ShaderCompileDesc& compileDesc) {
|
||||
switch (backend) {
|
||||
case Resources::ShaderBackend::OpenGL:
|
||||
AddShaderCompileMacro(compileDesc, L"SHADER_API_GLCORE");
|
||||
AddShaderCompileMacro(compileDesc, L"UNITY_UV_STARTS_AT_TOP", L"0");
|
||||
AddShaderCompileMacro(compileDesc, L"UNITY_NEAR_CLIP_VALUE", L"-1");
|
||||
break;
|
||||
case Resources::ShaderBackend::Vulkan:
|
||||
AddShaderCompileMacro(compileDesc, L"SHADER_API_VULKAN");
|
||||
AddShaderCompileMacro(compileDesc, L"UNITY_UV_STARTS_AT_TOP", L"1");
|
||||
AddShaderCompileMacro(compileDesc, L"UNITY_NEAR_CLIP_VALUE", L"0");
|
||||
break;
|
||||
case Resources::ShaderBackend::D3D12:
|
||||
AddShaderCompileMacro(compileDesc, L"SHADER_API_D3D12");
|
||||
AddShaderCompileMacro(compileDesc, L"UNITY_UV_STARTS_AT_TOP", L"1");
|
||||
AddShaderCompileMacro(compileDesc, L"UNITY_NEAR_CLIP_VALUE", L"0");
|
||||
break;
|
||||
case Resources::ShaderBackend::Generic:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
inline void ApplyShaderStageVariant(
|
||||
const Resources::ShaderStageVariant& variant,
|
||||
RHI::ShaderCompileDesc& compileDesc) {
|
||||
compileDesc.source.assign(
|
||||
variant.sourceCode.CStr(),
|
||||
variant.sourceCode.CStr() + variant.sourceCode.Length());
|
||||
compileDesc.sourceLanguage = ToRHIShaderLanguage(variant.language);
|
||||
compileDesc.entryPoint = ToWideAscii(variant.entryPoint);
|
||||
compileDesc.profile = ToWideAscii(variant.profile);
|
||||
}
|
||||
|
||||
inline std::wstring ResolveRuntimeShaderSourcePath(const Containers::String& shaderPath) {
|
||||
Containers::String resolvedPath = shaderPath;
|
||||
if (resolvedPath.Empty()) {
|
||||
return std::wstring();
|
||||
}
|
||||
|
||||
if (Resources::IsBuiltinShaderPath(resolvedPath)) {
|
||||
Containers::String builtinAssetPath;
|
||||
if (!Resources::TryResolveBuiltinShaderAssetPath(resolvedPath, builtinAssetPath)) {
|
||||
return std::wstring();
|
||||
}
|
||||
resolvedPath = builtinAssetPath;
|
||||
}
|
||||
|
||||
return ToWideAscii(resolvedPath);
|
||||
}
|
||||
|
||||
inline void ApplyShaderStageVariant(
|
||||
const Containers::String& shaderPath,
|
||||
const Resources::ShaderPass& pass,
|
||||
Resources::ShaderBackend backend,
|
||||
const Resources::ShaderStageVariant& variant,
|
||||
RHI::ShaderCompileDesc& compileDesc) {
|
||||
const std::string sourceText = BuildRuntimeShaderSource(pass, backend, variant);
|
||||
compileDesc.source.assign(sourceText.begin(), sourceText.end());
|
||||
compileDesc.fileName = ResolveRuntimeShaderSourcePath(shaderPath);
|
||||
compileDesc.sourceLanguage = ToRHIShaderLanguage(variant.language);
|
||||
compileDesc.entryPoint = ToWideAscii(variant.entryPoint);
|
||||
compileDesc.profile = ToWideAscii(variant.profile);
|
||||
InjectShaderBackendMacros(backend, compileDesc);
|
||||
}
|
||||
|
||||
inline void ApplyShaderStageVariant(
|
||||
const Resources::ShaderPass& pass,
|
||||
Resources::ShaderBackend backend,
|
||||
const Resources::ShaderStageVariant& variant,
|
||||
RHI::ShaderCompileDesc& compileDesc) {
|
||||
ApplyShaderStageVariant(Containers::String(), pass, backend, variant, compileDesc);
|
||||
}
|
||||
|
||||
inline Containers::String BuildShaderKeywordSignature(
|
||||
const Resources::ShaderKeywordSet& keywordSet) {
|
||||
Resources::ShaderKeywordSet normalizedKeywords = keywordSet;
|
||||
Resources::NormalizeShaderKeywordSetInPlace(normalizedKeywords);
|
||||
|
||||
Containers::String signature;
|
||||
for (size_t keywordIndex = 0; keywordIndex < normalizedKeywords.enabledKeywords.Size(); ++keywordIndex) {
|
||||
if (keywordIndex > 0) {
|
||||
signature += ";";
|
||||
}
|
||||
|
||||
signature += normalizedKeywords.enabledKeywords[keywordIndex];
|
||||
}
|
||||
|
||||
return signature;
|
||||
}
|
||||
|
||||
inline bool ShaderPassHasGraphicsVariants(
|
||||
const Resources::Shader& shader,
|
||||
const Containers::String& passName,
|
||||
Resources::ShaderBackend backend,
|
||||
const Resources::ShaderKeywordSet& enabledKeywords = Resources::ShaderKeywordSet()) {
|
||||
return shader.FindVariant(
|
||||
passName,
|
||||
Resources::ShaderType::Vertex,
|
||||
backend,
|
||||
enabledKeywords) != nullptr &&
|
||||
shader.FindVariant(
|
||||
passName,
|
||||
Resources::ShaderType::Fragment,
|
||||
backend,
|
||||
enabledKeywords) != nullptr;
|
||||
}
|
||||
|
||||
} // namespace Detail
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -100,7 +100,9 @@ bool ExecuteScenePassRequest(
|
||||
context,
|
||||
request.surface,
|
||||
sceneData,
|
||||
nullptr
|
||||
nullptr,
|
||||
nullptr,
|
||||
RHI::ResourceStates::Common
|
||||
};
|
||||
return pass->Execute(passContext);
|
||||
}
|
||||
@@ -122,7 +124,9 @@ bool ExecuteStandalonePass(
|
||||
context,
|
||||
surface,
|
||||
sceneData,
|
||||
nullptr
|
||||
nullptr,
|
||||
nullptr,
|
||||
RHI::ResourceStates::Common
|
||||
};
|
||||
return pass->Execute(passContext);
|
||||
}
|
||||
@@ -242,6 +246,7 @@ bool ExecuteFullscreenPassSequenceStage(
|
||||
|
||||
const RenderSurface* currentSourceSurface = passContext.sourceSurface;
|
||||
RHI::RHIResourceView* currentSourceColorView = passContext.sourceColorView;
|
||||
RHI::ResourceStates currentSourceColorState = passContext.sourceColorState;
|
||||
|
||||
for (size_t passIndex = 0; passIndex < sequence->GetPassCount(); ++passIndex) {
|
||||
const bool isLastPass = (passIndex + 1u) == sequence->GetPassCount();
|
||||
@@ -264,7 +269,8 @@ bool ExecuteFullscreenPassSequenceStage(
|
||||
*outputSurface,
|
||||
passContext.sceneData,
|
||||
currentSourceSurface,
|
||||
currentSourceColorView
|
||||
currentSourceColorView,
|
||||
currentSourceColorState
|
||||
};
|
||||
if (!sequence->ExecutePass(passIndex, chainedContext)) {
|
||||
return false;
|
||||
@@ -274,6 +280,7 @@ bool ExecuteFullscreenPassSequenceStage(
|
||||
intermediateEntry->currentColorState = RHI::ResourceStates::PixelShaderResource;
|
||||
currentSourceSurface = &intermediateEntry->surface;
|
||||
currentSourceColorView = intermediateEntry->shaderResourceView;
|
||||
currentSourceColorState = intermediateEntry->currentColorState;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,7 +297,8 @@ RenderPassContext BuildFrameStagePassContext(
|
||||
outputSurface != nullptr ? *outputSurface : request.surface,
|
||||
sceneData,
|
||||
request.GetSourceSurface(stage),
|
||||
request.GetSourceColorView(stage)
|
||||
request.GetSourceColorView(stage),
|
||||
request.GetSourceColorState(stage)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -373,11 +381,21 @@ RenderDirectionalShadowData BuildDirectionalShadowData(
|
||||
shadowData.enabled = true;
|
||||
shadowData.viewProjection = plan.cameraData.viewProjection;
|
||||
shadowData.shadowMap = shadowMapView;
|
||||
const float texelWorldSize = plan.texelWorldSize > Math::EPSILON
|
||||
? plan.texelWorldSize
|
||||
: (plan.orthographicHalfExtent > Math::EPSILON && plan.mapWidth > 0u
|
||||
? (plan.orthographicHalfExtent * 2.0f) / static_cast<float>(plan.mapWidth)
|
||||
: 0.0f);
|
||||
shadowData.shadowParams = Math::Vector4(
|
||||
0.0015f,
|
||||
1.0f / static_cast<float>(plan.mapWidth),
|
||||
1.0f / static_cast<float>(plan.mapHeight),
|
||||
0.85f);
|
||||
shadowData.shadowOptions = Math::Vector4(
|
||||
1.0f,
|
||||
texelWorldSize,
|
||||
1.5f,
|
||||
0.0f);
|
||||
return shadowData;
|
||||
}
|
||||
|
||||
@@ -619,43 +637,74 @@ bool CameraRenderer::ExecuteRenderPlan(
|
||||
bool CameraRenderer::Render(
|
||||
const CameraRenderRequest& request) {
|
||||
if (!request.IsValid() || m_pipeline == nullptr) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"CameraRenderer::Render failed: request invalid or pipeline missing");
|
||||
return false;
|
||||
}
|
||||
|
||||
const RenderSurface& mainSceneSurface = request.GetMainSceneSurface();
|
||||
if (mainSceneSurface.GetRenderAreaWidth() == 0 ||
|
||||
mainSceneSurface.GetRenderAreaHeight() == 0) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"CameraRenderer::Render failed: main scene surface render area is empty");
|
||||
return false;
|
||||
}
|
||||
if (request.depthOnly.IsRequested() &&
|
||||
!request.depthOnly.IsValid()) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"CameraRenderer::Render failed: depth-only request invalid");
|
||||
return false;
|
||||
}
|
||||
if (request.postProcess.IsRequested() &&
|
||||
!request.postProcess.IsValid()) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"CameraRenderer::Render failed: post-process request invalid");
|
||||
return false;
|
||||
}
|
||||
if (request.finalOutput.IsRequested() &&
|
||||
!request.finalOutput.IsValid()) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"CameraRenderer::Render failed: final-output request invalid");
|
||||
return false;
|
||||
}
|
||||
if (request.objectId.IsRequested() &&
|
||||
!request.objectId.IsValid()) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"CameraRenderer::Render failed: object-id request invalid");
|
||||
return false;
|
||||
}
|
||||
|
||||
ShadowCasterRenderRequest resolvedShadowCaster = {};
|
||||
RHI::RHIResourceView* shadowMapView = nullptr;
|
||||
if (!ResolveShadowCasterRequest(request, resolvedShadowCaster, shadowMapView)) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"CameraRenderer::Render failed: ResolveShadowCasterRequest returned false");
|
||||
return false;
|
||||
}
|
||||
|
||||
RenderSceneData sceneData = {};
|
||||
if (!BuildSceneDataForRequest(request, shadowMapView, sceneData)) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"CameraRenderer::Render failed: BuildSceneDataForRequest returned false");
|
||||
return false;
|
||||
}
|
||||
|
||||
return ExecuteRenderPlan(request, resolvedShadowCaster, sceneData);
|
||||
if (!ExecuteRenderPlan(request, resolvedShadowCaster, sceneData)) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"CameraRenderer::Render failed: ExecuteRenderPlan returned false");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Rendering
|
||||
|
||||
137
engine/src/Rendering/Internal/RenderSurfacePipelineUtils.h
Normal file
137
engine/src/Rendering/Internal/RenderSurfacePipelineUtils.h
Normal file
@@ -0,0 +1,137 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Rendering/RenderSurface.h>
|
||||
#include <XCEngine/RHI/RHIResourceView.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
namespace Internal {
|
||||
|
||||
inline constexpr uint32_t kMaxPipelineColorAttachmentCount = 8u;
|
||||
|
||||
inline uint32_t ResolveSurfaceColorAttachmentCount(const RenderSurface& surface) {
|
||||
uint32_t count = 0;
|
||||
for (RHI::RHIResourceView* attachment : surface.GetColorAttachments()) {
|
||||
if (attachment == nullptr || count >= kMaxPipelineColorAttachmentCount) {
|
||||
break;
|
||||
}
|
||||
|
||||
++count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
inline bool HasSingleColorAttachment(const RenderSurface& surface) {
|
||||
return ResolveSurfaceColorAttachmentCount(surface) == 1u;
|
||||
}
|
||||
|
||||
inline bool HasZeroOrSingleColorAttachment(const RenderSurface& surface) {
|
||||
return ResolveSurfaceColorAttachmentCount(surface) <= 1u;
|
||||
}
|
||||
|
||||
inline RHI::Format ResolveSurfaceColorFormat(
|
||||
const RenderSurface& surface,
|
||||
uint32_t attachmentIndex = 0u) {
|
||||
const std::vector<RHI::RHIResourceView*>& colorAttachments = surface.GetColorAttachments();
|
||||
if (attachmentIndex >= colorAttachments.size() || colorAttachments[attachmentIndex] == nullptr) {
|
||||
return RHI::Format::Unknown;
|
||||
}
|
||||
|
||||
return colorAttachments[attachmentIndex]->GetFormat();
|
||||
}
|
||||
|
||||
inline bool TryResolveSingleColorAttachmentFormat(
|
||||
const RenderSurface& surface,
|
||||
RHI::Format& outFormat) {
|
||||
if (!HasSingleColorAttachment(surface)) {
|
||||
outFormat = RHI::Format::Unknown;
|
||||
return false;
|
||||
}
|
||||
|
||||
outFormat = ResolveSurfaceColorFormat(surface, 0u);
|
||||
return outFormat != RHI::Format::Unknown;
|
||||
}
|
||||
|
||||
inline bool HasKnownSingleColorAttachmentFormat(const RenderSurface& surface) {
|
||||
return !HasSingleColorAttachment(surface) ||
|
||||
ResolveSurfaceColorFormat(surface, 0u) != RHI::Format::Unknown;
|
||||
}
|
||||
|
||||
inline std::array<uint32_t, kMaxPipelineColorAttachmentCount> ResolveSurfaceColorFormats(
|
||||
const RenderSurface& surface) {
|
||||
std::array<uint32_t, kMaxPipelineColorAttachmentCount> formats = {};
|
||||
const uint32_t colorAttachmentCount = ResolveSurfaceColorAttachmentCount(surface);
|
||||
for (uint32_t attachmentIndex = 0; attachmentIndex < colorAttachmentCount; ++attachmentIndex) {
|
||||
formats[attachmentIndex] = static_cast<uint32_t>(ResolveSurfaceColorFormat(surface, attachmentIndex));
|
||||
}
|
||||
|
||||
return formats;
|
||||
}
|
||||
|
||||
inline RHI::Format ResolveSurfaceDepthFormat(const RenderSurface& surface) {
|
||||
if (RHI::RHIResourceView* depthAttachment = surface.GetDepthAttachment();
|
||||
depthAttachment != nullptr) {
|
||||
return depthAttachment->GetFormat();
|
||||
}
|
||||
|
||||
return RHI::Format::Unknown;
|
||||
}
|
||||
|
||||
inline bool HasKnownDepthAttachmentFormat(const RenderSurface& surface) {
|
||||
return ResolveSurfaceDepthFormat(surface) != RHI::Format::Unknown;
|
||||
}
|
||||
|
||||
inline uint32_t ResolveSurfaceSampleCount(const RenderSurface& surface) {
|
||||
return surface.GetSampleCount();
|
||||
}
|
||||
|
||||
inline uint32_t ResolveSurfaceSampleQuality(const RenderSurface& surface) {
|
||||
return surface.GetSampleQuality();
|
||||
}
|
||||
|
||||
inline bool HasValidPipelineSampleDescription(const RenderSurface& surface) {
|
||||
const uint32_t sampleCount = ResolveSurfaceSampleCount(surface);
|
||||
const uint32_t sampleQuality = ResolveSurfaceSampleQuality(surface);
|
||||
return sampleCount > 0u &&
|
||||
(sampleCount > 1u || sampleQuality == 0u);
|
||||
}
|
||||
|
||||
inline bool IsDepthStyleCompatibleSurface(const RenderSurface& surface) {
|
||||
return HasZeroOrSingleColorAttachment(surface) &&
|
||||
HasKnownSingleColorAttachmentFormat(surface) &&
|
||||
HasKnownDepthAttachmentFormat(surface) &&
|
||||
HasValidPipelineSampleDescription(surface);
|
||||
}
|
||||
|
||||
inline void ApplySurfacePropertiesToGraphicsPipelineDesc(
|
||||
const RenderSurface& surface,
|
||||
RHI::GraphicsPipelineDesc& pipelineDesc) {
|
||||
const std::array<uint32_t, kMaxPipelineColorAttachmentCount> renderTargetFormats =
|
||||
ResolveSurfaceColorFormats(surface);
|
||||
pipelineDesc.renderTargetCount = ResolveSurfaceColorAttachmentCount(surface);
|
||||
for (uint32_t attachmentIndex = 0; attachmentIndex < pipelineDesc.renderTargetCount; ++attachmentIndex) {
|
||||
pipelineDesc.renderTargetFormats[attachmentIndex] = renderTargetFormats[attachmentIndex];
|
||||
}
|
||||
|
||||
pipelineDesc.depthStencilFormat = static_cast<uint32_t>(ResolveSurfaceDepthFormat(surface));
|
||||
pipelineDesc.sampleCount = ResolveSurfaceSampleCount(surface);
|
||||
pipelineDesc.sampleQuality = ResolveSurfaceSampleQuality(surface);
|
||||
}
|
||||
|
||||
inline void ApplySingleColorAttachmentPropertiesToGraphicsPipelineDesc(
|
||||
const RenderSurface& surface,
|
||||
RHI::GraphicsPipelineDesc& pipelineDesc) {
|
||||
pipelineDesc.renderTargetCount = HasSingleColorAttachment(surface) ? 1u : 0u;
|
||||
pipelineDesc.renderTargetFormats[0] =
|
||||
static_cast<uint32_t>(ResolveSurfaceColorFormat(surface, 0u));
|
||||
pipelineDesc.sampleCount = ResolveSurfaceSampleCount(surface);
|
||||
pipelineDesc.sampleQuality = ResolveSurfaceSampleQuality(surface);
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
@@ -53,6 +53,18 @@ inline RHI::ShaderBinaryBackend ToRHIShaderBinaryBackend(Resources::ShaderBacken
|
||||
}
|
||||
}
|
||||
|
||||
inline RHI::ShaderBinaryBackend ResolveCompiledBinaryBackend(
|
||||
Resources::ShaderBackend backend,
|
||||
const Resources::ShaderStageVariant& variant) {
|
||||
if (backend == Resources::ShaderBackend::OpenGL &&
|
||||
variant.language == Resources::ShaderLanguage::HLSL) {
|
||||
// OpenGL runtime consumes HLSL-authored precompiled SPIR-V through the Vulkan SPIR-V path.
|
||||
return RHI::ShaderBinaryBackend::Vulkan;
|
||||
}
|
||||
|
||||
return ToRHIShaderBinaryBackend(backend);
|
||||
}
|
||||
|
||||
inline std::wstring ToWideAscii(const Containers::String& value) {
|
||||
std::wstring wide;
|
||||
wide.reserve(value.Length());
|
||||
@@ -385,7 +397,7 @@ inline void ApplyShaderStageVariant(
|
||||
compileDesc.sourceLanguage = ToRHIShaderLanguage(variant.language);
|
||||
compileDesc.entryPoint = ToWideAscii(variant.entryPoint);
|
||||
compileDesc.profile = ToWideAscii(variant.profile);
|
||||
compileDesc.compiledBinaryBackend = ToRHIShaderBinaryBackend(variant.backend);
|
||||
compileDesc.compiledBinaryBackend = ResolveCompiledBinaryBackend(variant.backend, variant);
|
||||
compileDesc.compiledBinary.clear();
|
||||
if (!variant.compiledBinary.Empty()) {
|
||||
compileDesc.compiledBinary.assign(
|
||||
@@ -433,7 +445,7 @@ inline void ApplyShaderStageVariant(
|
||||
compileDesc.compiledBinaryBackend = RHI::ShaderBinaryBackend::Unknown;
|
||||
compileDesc.compiledBinary.clear();
|
||||
if (const Containers::Array<Core::uint8>* binary = variant.GetCompiledBinaryForBackend(backend)) {
|
||||
compileDesc.compiledBinaryBackend = ToRHIShaderBinaryBackend(backend);
|
||||
compileDesc.compiledBinaryBackend = ResolveCompiledBinaryBackend(backend, variant);
|
||||
compileDesc.compiledBinary.assign(binary->Data(), binary->Data() + binary->Size());
|
||||
}
|
||||
InjectShaderBackendMacros(backend, compileDesc);
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
#include "Core/Asset/ResourceManager.h"
|
||||
#include "Debug/Logger.h"
|
||||
#include "Rendering/Detail/ShaderVariantUtils.h"
|
||||
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
|
||||
#include "Rendering/Internal/ShaderVariantUtils.h"
|
||||
#include "Rendering/RenderSurface.h"
|
||||
#include "Resources/BuiltinResources.h"
|
||||
#include "RHI/RHICommandList.h"
|
||||
@@ -31,12 +32,12 @@ const Resources::ShaderPass* FindCompatiblePass(
|
||||
Resources::ShaderBackend backend) {
|
||||
const Resources::ShaderPass* colorScalePass = shader.FindPass("ColorScale");
|
||||
if (colorScalePass != nullptr &&
|
||||
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, colorScalePass->name, backend)) {
|
||||
::XCEngine::Rendering::Internal::ShaderPassHasGraphicsVariants(shader, colorScalePass->name, backend)) {
|
||||
return colorScalePass;
|
||||
}
|
||||
|
||||
if (shader.GetPassCount() > 0 &&
|
||||
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, shader.GetPasses()[0].name, backend)) {
|
||||
::XCEngine::Rendering::Internal::ShaderPassHasGraphicsVariants(shader, shader.GetPasses()[0].name, backend)) {
|
||||
return &shader.GetPasses()[0];
|
||||
}
|
||||
|
||||
@@ -48,14 +49,14 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
||||
RHI::RHIPipelineLayout* pipelineLayout,
|
||||
const Resources::Shader& shader,
|
||||
const Containers::String& passName,
|
||||
RHI::Format renderTargetFormat) {
|
||||
const RenderSurface& surface) {
|
||||
RHI::GraphicsPipelineDesc pipelineDesc = {};
|
||||
pipelineDesc.pipelineLayout = pipelineLayout;
|
||||
pipelineDesc.topologyType = static_cast<uint32_t>(RHI::PrimitiveTopologyType::Triangle);
|
||||
pipelineDesc.renderTargetCount = 1;
|
||||
pipelineDesc.renderTargetFormats[0] = static_cast<uint32_t>(renderTargetFormat);
|
||||
::XCEngine::Rendering::Internal::ApplySingleColorAttachmentPropertiesToGraphicsPipelineDesc(
|
||||
surface,
|
||||
pipelineDesc);
|
||||
pipelineDesc.depthStencilFormat = static_cast<uint32_t>(RHI::Format::Unknown);
|
||||
pipelineDesc.sampleCount = 1;
|
||||
|
||||
pipelineDesc.rasterizerState.fillMode = static_cast<uint32_t>(RHI::FillMode::Solid);
|
||||
pipelineDesc.rasterizerState.cullMode = static_cast<uint32_t>(RHI::CullMode::None);
|
||||
@@ -76,11 +77,11 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
||||
pipelineDesc.depthStencilState.depthFunc = static_cast<uint32_t>(RHI::ComparisonFunc::Always);
|
||||
|
||||
const Resources::ShaderPass* shaderPass = shader.FindPass(passName);
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType);
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(backendType);
|
||||
if (const Resources::ShaderStageVariant* vertexVariant =
|
||||
shader.FindVariant(passName, Resources::ShaderType::Vertex, backend)) {
|
||||
if (shaderPass != nullptr) {
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
shader.GetPath(),
|
||||
*shaderPass,
|
||||
backend,
|
||||
@@ -91,7 +92,7 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
||||
if (const Resources::ShaderStageVariant* fragmentVariant =
|
||||
shader.FindVariant(passName, Resources::ShaderType::Fragment, backend)) {
|
||||
if (shaderPass != nullptr) {
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
shader.GetPath(),
|
||||
*shaderPass,
|
||||
backend,
|
||||
@@ -124,12 +125,19 @@ const char* BuiltinColorScalePostProcessPass::GetName() const {
|
||||
}
|
||||
|
||||
bool BuiltinColorScalePostProcessPass::Execute(const RenderPassContext& context) {
|
||||
if (!context.renderContext.IsValid() || context.sourceColorView == nullptr) {
|
||||
if (!context.renderContext.IsValid() ||
|
||||
context.sourceSurface == nullptr ||
|
||||
context.sourceColorView == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (!::XCEngine::Rendering::Internal::HasSingleColorAttachment(*context.sourceSurface)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::vector<RHI::RHIResourceView*>& colorAttachments = context.surface.GetColorAttachments();
|
||||
if (colorAttachments.empty() || colorAttachments[0] == nullptr) {
|
||||
if (!::XCEngine::Rendering::Internal::HasSingleColorAttachment(context.surface) ||
|
||||
colorAttachments.empty() ||
|
||||
colorAttachments[0] == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -138,8 +146,7 @@ bool BuiltinColorScalePostProcessPass::Execute(const RenderPassContext& context)
|
||||
return false;
|
||||
}
|
||||
|
||||
const RHI::Format renderTargetFormat = colorAttachments[0]->GetFormat();
|
||||
if (!EnsureInitialized(context.renderContext, renderTargetFormat)) {
|
||||
if (!EnsureInitialized(context.renderContext, context.surface)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -154,6 +161,7 @@ bool BuiltinColorScalePostProcessPass::Execute(const RenderPassContext& context)
|
||||
|
||||
RHI::RHICommandList* commandList = context.renderContext.commandList;
|
||||
RHI::RHIResourceView* renderTarget = colorAttachments[0];
|
||||
const bool autoTransitionSource = context.sourceSurface->IsAutoTransitionEnabled();
|
||||
|
||||
if (context.surface.IsAutoTransitionEnabled()) {
|
||||
commandList->TransitionBarrier(
|
||||
@@ -161,6 +169,14 @@ bool BuiltinColorScalePostProcessPass::Execute(const RenderPassContext& context)
|
||||
context.surface.GetColorStateBefore(),
|
||||
RHI::ResourceStates::RenderTarget);
|
||||
}
|
||||
if (autoTransitionSource) {
|
||||
commandList->TransitionBarrier(
|
||||
context.sourceColorView,
|
||||
context.sourceColorState,
|
||||
RHI::ResourceStates::PixelShaderResource);
|
||||
} else if (context.sourceColorState != RHI::ResourceStates::PixelShaderResource) {
|
||||
return false;
|
||||
}
|
||||
|
||||
commandList->SetRenderTargets(1, &renderTarget, nullptr);
|
||||
|
||||
@@ -199,6 +215,12 @@ bool BuiltinColorScalePostProcessPass::Execute(const RenderPassContext& context)
|
||||
RHI::ResourceStates::RenderTarget,
|
||||
context.surface.GetColorStateAfter());
|
||||
}
|
||||
if (autoTransitionSource) {
|
||||
commandList->TransitionBarrier(
|
||||
context.sourceColorView,
|
||||
RHI::ResourceStates::PixelShaderResource,
|
||||
context.sourceColorState);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -230,10 +252,18 @@ const Containers::String& BuiltinColorScalePostProcessPass::GetShaderPath() cons
|
||||
|
||||
bool BuiltinColorScalePostProcessPass::EnsureInitialized(
|
||||
const RenderContext& renderContext,
|
||||
RHI::Format renderTargetFormat) {
|
||||
const RenderSurface& surface) {
|
||||
const RHI::Format renderTargetFormat =
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceColorFormat(surface, 0u);
|
||||
const uint32_t renderTargetSampleCount =
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceSampleCount(surface);
|
||||
const uint32_t renderTargetSampleQuality =
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceSampleQuality(surface);
|
||||
if (m_device == renderContext.device &&
|
||||
m_backendType == renderContext.backendType &&
|
||||
m_renderTargetFormat == renderTargetFormat &&
|
||||
m_renderTargetSampleCount == renderTargetSampleCount &&
|
||||
m_renderTargetSampleQuality == renderTargetSampleQuality &&
|
||||
m_pipelineLayout != nullptr &&
|
||||
m_pipelineState != nullptr &&
|
||||
m_sampler != nullptr &&
|
||||
@@ -244,16 +274,24 @@ bool BuiltinColorScalePostProcessPass::EnsureInitialized(
|
||||
}
|
||||
|
||||
DestroyResources();
|
||||
return CreateResources(renderContext, renderTargetFormat);
|
||||
return CreateResources(renderContext, surface);
|
||||
}
|
||||
|
||||
bool BuiltinColorScalePostProcessPass::CreateResources(
|
||||
const RenderContext& renderContext,
|
||||
RHI::Format renderTargetFormat) {
|
||||
const RenderSurface& surface) {
|
||||
if (!renderContext.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RHI::Format renderTargetFormat = RHI::Format::Unknown;
|
||||
if (!::XCEngine::Rendering::Internal::TryResolveSingleColorAttachmentFormat(surface, renderTargetFormat)) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinColorScalePostProcessPass requires exactly one valid destination color attachment");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_shaderPath.Empty()) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
@@ -270,7 +308,7 @@ bool BuiltinColorScalePostProcessPass::CreateResources(
|
||||
return false;
|
||||
}
|
||||
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(renderContext.backendType);
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(renderContext.backendType);
|
||||
const Resources::ShaderPass* colorScalePass = FindCompatiblePass(*m_shader.Get(), backend);
|
||||
if (colorScalePass == nullptr) {
|
||||
Debug::Logger::Get().Error(
|
||||
@@ -283,6 +321,8 @@ bool BuiltinColorScalePostProcessPass::CreateResources(
|
||||
m_device = renderContext.device;
|
||||
m_backendType = renderContext.backendType;
|
||||
m_renderTargetFormat = renderTargetFormat;
|
||||
m_renderTargetSampleCount = ::XCEngine::Rendering::Internal::ResolveSurfaceSampleCount(surface);
|
||||
m_renderTargetSampleQuality = ::XCEngine::Rendering::Internal::ResolveSurfaceSampleQuality(surface);
|
||||
|
||||
RHI::DescriptorSetLayoutBinding constantBinding = {};
|
||||
constantBinding.binding = 0;
|
||||
@@ -395,7 +435,7 @@ bool BuiltinColorScalePostProcessPass::CreateResources(
|
||||
m_pipelineLayout,
|
||||
*m_shader.Get(),
|
||||
colorScalePass->name,
|
||||
m_renderTargetFormat));
|
||||
surface));
|
||||
if (m_pipelineState == nullptr || !m_pipelineState->IsValid()) {
|
||||
DestroyResources();
|
||||
return false;
|
||||
@@ -433,6 +473,8 @@ void BuiltinColorScalePostProcessPass::DestroyResources() {
|
||||
m_device = nullptr;
|
||||
m_backendType = RHI::RHIType::D3D12;
|
||||
m_renderTargetFormat = RHI::Format::Unknown;
|
||||
m_renderTargetSampleCount = 1u;
|
||||
m_renderTargetSampleQuality = 0u;
|
||||
}
|
||||
|
||||
void BuiltinColorScalePostProcessPass::DestroyOwnedDescriptorSet(OwnedDescriptorSet& descriptorSet) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "RHI/RHICommandList.h"
|
||||
#include "Rendering/Extraction/RenderSceneExtractor.h"
|
||||
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
|
||||
#include "Rendering/RenderSurface.h"
|
||||
#include "Resources/Mesh/Mesh.h"
|
||||
|
||||
@@ -66,14 +67,17 @@ bool BuiltinDepthStylePassBase::Execute(const RenderPassContext& context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::vector<RHI::RHIResourceView*>& colorAttachments = context.surface.GetColorAttachments();
|
||||
RHI::RHIResourceView* depthAttachment = context.surface.GetDepthAttachment();
|
||||
RHI::RHIResourceView* renderTarget =
|
||||
(!colorAttachments.empty() ? colorAttachments[0] : nullptr);
|
||||
if (depthAttachment == nullptr) {
|
||||
if (!::XCEngine::Rendering::Internal::IsDepthStyleCompatibleSurface(context.surface)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::vector<RHI::RHIResourceView*>& colorAttachments = context.surface.GetColorAttachments();
|
||||
const uint32_t colorAttachmentCount =
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceColorAttachmentCount(context.surface);
|
||||
RHI::RHIResourceView* depthAttachment = context.surface.GetDepthAttachment();
|
||||
RHI::RHIResourceView* renderTarget =
|
||||
(colorAttachmentCount > 0u ? colorAttachments[0] : nullptr);
|
||||
|
||||
const Math::RectInt renderArea = context.surface.GetRenderArea();
|
||||
if (renderArea.width <= 0 || renderArea.height <= 0) {
|
||||
return false;
|
||||
@@ -91,6 +95,12 @@ bool BuiltinDepthStylePassBase::Execute(const RenderPassContext& context) {
|
||||
context.surface.GetColorStateBefore(),
|
||||
RHI::ResourceStates::RenderTarget);
|
||||
}
|
||||
if (context.surface.IsAutoTransitionEnabled() && depthAttachment != nullptr) {
|
||||
commandList->TransitionBarrier(
|
||||
depthAttachment,
|
||||
context.surface.GetDepthStateBefore(),
|
||||
RHI::ResourceStates::DepthWrite);
|
||||
}
|
||||
|
||||
RHI::RHIResourceView* renderTargets[] = { renderTarget };
|
||||
const uint32_t renderTargetCount = renderTarget != nullptr ? 1u : 0u;
|
||||
@@ -145,6 +155,12 @@ bool BuiltinDepthStylePassBase::Execute(const RenderPassContext& context) {
|
||||
RHI::ResourceStates::RenderTarget,
|
||||
context.surface.GetColorStateAfter());
|
||||
}
|
||||
if (context.surface.IsAutoTransitionEnabled() && depthAttachment != nullptr) {
|
||||
commandList->TransitionBarrier(
|
||||
depthAttachment,
|
||||
RHI::ResourceStates::DepthWrite,
|
||||
context.surface.GetDepthStateAfter());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
#include "Debug/Logger.h"
|
||||
#include "RHI/RHICommandList.h"
|
||||
#include "Rendering/Builtin/BuiltinPassLayoutUtils.h"
|
||||
#include "Rendering/Detail/ShaderVariantUtils.h"
|
||||
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
|
||||
#include "Rendering/Internal/ShaderVariantUtils.h"
|
||||
#include "Rendering/Materials/RenderMaterialResolve.h"
|
||||
#include "Rendering/Extraction/RenderSceneExtractor.h"
|
||||
#include "Rendering/RenderSurface.h"
|
||||
@@ -100,29 +101,6 @@ bool IsSupportedAlphaTestBindingPlan(const BuiltinPassResourceBindingPlan& bindi
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t ResolveSurfaceColorAttachmentCount(const RenderSurface& surface) {
|
||||
const std::vector<RHI::RHIResourceView*>& colorAttachments = surface.GetColorAttachments();
|
||||
return (!colorAttachments.empty() && colorAttachments[0] != nullptr) ? 1u : 0u;
|
||||
}
|
||||
|
||||
RHI::Format ResolveSurfaceColorFormat(const RenderSurface& surface) {
|
||||
const std::vector<RHI::RHIResourceView*>& colorAttachments = surface.GetColorAttachments();
|
||||
if (colorAttachments.empty() || colorAttachments[0] == nullptr) {
|
||||
return RHI::Format::Unknown;
|
||||
}
|
||||
|
||||
return colorAttachments[0]->GetFormat();
|
||||
}
|
||||
|
||||
RHI::Format ResolveSurfaceDepthFormat(const RenderSurface& surface) {
|
||||
if (RHI::RHIResourceView* depthAttachment = surface.GetDepthAttachment();
|
||||
depthAttachment != nullptr) {
|
||||
return depthAttachment->GetFormat();
|
||||
}
|
||||
|
||||
return RHI::Format::Unknown;
|
||||
}
|
||||
|
||||
RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
||||
RHI::RHIType backendType,
|
||||
RHI::RHIPipelineLayout* pipelineLayout,
|
||||
@@ -136,14 +114,7 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
||||
RHI::GraphicsPipelineDesc pipelineDesc = {};
|
||||
pipelineDesc.pipelineLayout = pipelineLayout;
|
||||
pipelineDesc.topologyType = static_cast<uint32_t>(RHI::PrimitiveTopologyType::Triangle);
|
||||
pipelineDesc.renderTargetCount = ResolveSurfaceColorAttachmentCount(surface);
|
||||
if (pipelineDesc.renderTargetCount > 0) {
|
||||
pipelineDesc.renderTargetFormats[0] =
|
||||
static_cast<uint32_t>(ResolveSurfaceColorFormat(surface));
|
||||
}
|
||||
pipelineDesc.depthStencilFormat =
|
||||
static_cast<uint32_t>(ResolveSurfaceDepthFormat(surface));
|
||||
pipelineDesc.sampleCount = 1;
|
||||
::XCEngine::Rendering::Internal::ApplySurfacePropertiesToGraphicsPipelineDesc(surface, pipelineDesc);
|
||||
pipelineDesc.inputLayout = inputLayout;
|
||||
ApplyResolvedRenderState(&shaderPass, material, pipelineDesc);
|
||||
|
||||
@@ -157,10 +128,10 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
||||
pipelineDesc.blendState.colorWriteMask = 0;
|
||||
}
|
||||
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType);
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(backendType);
|
||||
if (const Resources::ShaderStageVariant* vertexVariant =
|
||||
shader.FindVariant(passName, Resources::ShaderType::Vertex, backend, keywordSet)) {
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
shader.GetPath(),
|
||||
shaderPass,
|
||||
backend,
|
||||
@@ -169,7 +140,7 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
||||
}
|
||||
if (const Resources::ShaderStageVariant* fragmentVariant =
|
||||
shader.FindVariant(passName, Resources::ShaderType::Fragment, backend, keywordSet)) {
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
shader.GetPath(),
|
||||
shaderPass,
|
||||
backend,
|
||||
@@ -310,7 +281,7 @@ BuiltinDepthStylePassBase::ResolvedShaderPass BuiltinDepthStylePassBase::Resolve
|
||||
const RenderSceneData& sceneData,
|
||||
const Resources::Material* material) const {
|
||||
ResolvedShaderPass resolved = {};
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(m_backendType);
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(m_backendType);
|
||||
|
||||
auto tryResolveFromShader =
|
||||
[this, backend, &resolved, &sceneData](
|
||||
@@ -338,7 +309,7 @@ BuiltinDepthStylePassBase::ResolvedShaderPass BuiltinDepthStylePassBase::Resolve
|
||||
|
||||
for (const Resources::ShaderPass& shaderPass : shader->GetPasses()) {
|
||||
if (!ShaderPassMatchesBuiltinPass(shaderPass, m_passType) ||
|
||||
!::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(
|
||||
!::XCEngine::Rendering::Internal::ShaderPassHasGraphicsVariants(
|
||||
*shader,
|
||||
shaderPass.name,
|
||||
backend,
|
||||
@@ -477,11 +448,17 @@ RHI::RHIPipelineState* BuiltinDepthStylePassBase::GetOrCreatePipelineState(
|
||||
const RenderSurface& surface,
|
||||
const RenderSceneData& sceneData,
|
||||
const Resources::Material* material) {
|
||||
if (!::XCEngine::Rendering::Internal::IsDepthStyleCompatibleSurface(surface)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const Resources::ShaderKeywordSet keywordSet = ResolvePassKeywordSet(sceneData, material);
|
||||
const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(sceneData, material);
|
||||
if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
const uint32_t renderTargetCount =
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceColorAttachmentCount(surface);
|
||||
|
||||
PassResourceLayout* passLayout = GetOrCreatePassResourceLayout(context, resolvedShaderPass);
|
||||
if (passLayout == nullptr || passLayout->pipelineLayout == nullptr) {
|
||||
@@ -493,10 +470,16 @@ RHI::RHIPipelineState* BuiltinDepthStylePassBase::GetOrCreatePipelineState(
|
||||
BuildStaticPipelineRenderStateKey(ResolveEffectiveRenderState(resolvedShaderPass.pass, material));
|
||||
pipelineKey.shader = resolvedShaderPass.shader;
|
||||
pipelineKey.passName = resolvedShaderPass.passName;
|
||||
pipelineKey.keywordSignature = ::XCEngine::Rendering::Detail::BuildShaderKeywordSignature(keywordSet);
|
||||
pipelineKey.renderTargetCount = ResolveSurfaceColorAttachmentCount(surface);
|
||||
pipelineKey.renderTargetFormat = static_cast<uint32_t>(ResolveSurfaceColorFormat(surface));
|
||||
pipelineKey.depthStencilFormat = static_cast<uint32_t>(ResolveSurfaceDepthFormat(surface));
|
||||
pipelineKey.keywordSignature = ::XCEngine::Rendering::Internal::BuildShaderKeywordSignature(keywordSet);
|
||||
pipelineKey.renderTargetCount = renderTargetCount;
|
||||
pipelineKey.renderTargetFormat =
|
||||
static_cast<uint32_t>(::XCEngine::Rendering::Internal::ResolveSurfaceColorFormat(surface));
|
||||
pipelineKey.depthStencilFormat =
|
||||
static_cast<uint32_t>(::XCEngine::Rendering::Internal::ResolveSurfaceDepthFormat(surface));
|
||||
pipelineKey.sampleCount =
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceSampleCount(surface);
|
||||
pipelineKey.sampleQuality =
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceSampleQuality(surface);
|
||||
|
||||
const auto existing = m_pipelineStates.find(pipelineKey);
|
||||
if (existing != m_pipelineStates.end()) {
|
||||
@@ -810,7 +793,17 @@ bool BuiltinDepthStylePassBase::DrawVisibleItem(
|
||||
visibleItem.localToWorld.Transpose()
|
||||
};
|
||||
|
||||
MaterialConstantPayloadView materialConstants = ResolveSchemaMaterialConstantPayload(material);
|
||||
MaterialConstantPayloadView materialConstants = {};
|
||||
BuiltinDepthStyleMaterialConstants builtinFallbackConstants = {};
|
||||
Resources::MaterialConstantFieldDesc builtinFallbackLayout[2] = {};
|
||||
if (resolvedShaderPass.shader == m_builtinShader.Get()) {
|
||||
materialConstants = ResolveBuiltinDepthStyleMaterialConstantPayload(
|
||||
material,
|
||||
builtinFallbackConstants,
|
||||
builtinFallbackLayout);
|
||||
} else {
|
||||
materialConstants = ResolveSchemaMaterialConstantPayload(material);
|
||||
}
|
||||
if (passLayout->material.IsValid() && !materialConstants.IsValid()) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
#include "Core/Asset/ResourceManager.h"
|
||||
#include "Debug/Logger.h"
|
||||
#include "Rendering/Detail/ShaderVariantUtils.h"
|
||||
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
|
||||
#include "Rendering/Internal/ShaderVariantUtils.h"
|
||||
#include "Rendering/RenderSurface.h"
|
||||
#include "Resources/BuiltinResources.h"
|
||||
#include "RHI/RHICommandList.h"
|
||||
@@ -33,12 +34,12 @@ const Resources::ShaderPass* FindCompatiblePass(
|
||||
Resources::ShaderBackend backend) {
|
||||
const Resources::ShaderPass* finalColorPass = shader.FindPass("FinalColor");
|
||||
if (finalColorPass != nullptr &&
|
||||
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, finalColorPass->name, backend)) {
|
||||
::XCEngine::Rendering::Internal::ShaderPassHasGraphicsVariants(shader, finalColorPass->name, backend)) {
|
||||
return finalColorPass;
|
||||
}
|
||||
|
||||
if (shader.GetPassCount() > 0 &&
|
||||
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, shader.GetPasses()[0].name, backend)) {
|
||||
::XCEngine::Rendering::Internal::ShaderPassHasGraphicsVariants(shader, shader.GetPasses()[0].name, backend)) {
|
||||
return &shader.GetPasses()[0];
|
||||
}
|
||||
|
||||
@@ -50,14 +51,14 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
||||
RHI::RHIPipelineLayout* pipelineLayout,
|
||||
const Resources::Shader& shader,
|
||||
const Containers::String& passName,
|
||||
RHI::Format renderTargetFormat) {
|
||||
const RenderSurface& surface) {
|
||||
RHI::GraphicsPipelineDesc pipelineDesc = {};
|
||||
pipelineDesc.pipelineLayout = pipelineLayout;
|
||||
pipelineDesc.topologyType = static_cast<uint32_t>(RHI::PrimitiveTopologyType::Triangle);
|
||||
pipelineDesc.renderTargetCount = 1;
|
||||
pipelineDesc.renderTargetFormats[0] = static_cast<uint32_t>(renderTargetFormat);
|
||||
::XCEngine::Rendering::Internal::ApplySingleColorAttachmentPropertiesToGraphicsPipelineDesc(
|
||||
surface,
|
||||
pipelineDesc);
|
||||
pipelineDesc.depthStencilFormat = static_cast<uint32_t>(RHI::Format::Unknown);
|
||||
pipelineDesc.sampleCount = 1;
|
||||
|
||||
pipelineDesc.rasterizerState.fillMode = static_cast<uint32_t>(RHI::FillMode::Solid);
|
||||
pipelineDesc.rasterizerState.cullMode = static_cast<uint32_t>(RHI::CullMode::None);
|
||||
@@ -78,11 +79,11 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
||||
pipelineDesc.depthStencilState.depthFunc = static_cast<uint32_t>(RHI::ComparisonFunc::Always);
|
||||
|
||||
const Resources::ShaderPass* shaderPass = shader.FindPass(passName);
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType);
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(backendType);
|
||||
if (const Resources::ShaderStageVariant* vertexVariant =
|
||||
shader.FindVariant(passName, Resources::ShaderType::Vertex, backend)) {
|
||||
if (shaderPass != nullptr) {
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
shader.GetPath(),
|
||||
*shaderPass,
|
||||
backend,
|
||||
@@ -93,7 +94,7 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
||||
if (const Resources::ShaderStageVariant* fragmentVariant =
|
||||
shader.FindVariant(passName, Resources::ShaderType::Fragment, backend)) {
|
||||
if (shaderPass != nullptr) {
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
shader.GetPath(),
|
||||
*shaderPass,
|
||||
backend,
|
||||
@@ -132,18 +133,28 @@ const char* BuiltinFinalColorPass::GetName() const {
|
||||
}
|
||||
|
||||
bool BuiltinFinalColorPass::Execute(const RenderPassContext& context) {
|
||||
if (!context.renderContext.IsValid() || context.sourceColorView == nullptr) {
|
||||
if (!context.renderContext.IsValid() ||
|
||||
context.sourceSurface == nullptr ||
|
||||
context.sourceColorView == nullptr) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinFinalColorPass requires a valid render context and source color view");
|
||||
"BuiltinFinalColorPass requires a valid render context, source surface, and source color view");
|
||||
return false;
|
||||
}
|
||||
if (!::XCEngine::Rendering::Internal::HasSingleColorAttachment(*context.sourceSurface)) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinFinalColorPass requires a single-color source surface");
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::vector<RHI::RHIResourceView*>& colorAttachments = context.surface.GetColorAttachments();
|
||||
if (colorAttachments.empty() || colorAttachments[0] == nullptr) {
|
||||
if (!::XCEngine::Rendering::Internal::HasSingleColorAttachment(context.surface) ||
|
||||
colorAttachments.empty() ||
|
||||
colorAttachments[0] == nullptr) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinFinalColorPass requires a valid destination color attachment");
|
||||
"BuiltinFinalColorPass requires exactly one valid destination color attachment");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -155,8 +166,7 @@ bool BuiltinFinalColorPass::Execute(const RenderPassContext& context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const RHI::Format renderTargetFormat = colorAttachments[0]->GetFormat();
|
||||
if (!EnsureInitialized(context.renderContext, renderTargetFormat)) {
|
||||
if (!EnsureInitialized(context.renderContext, context.surface)) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinFinalColorPass failed to initialize GPU resources");
|
||||
@@ -179,6 +189,7 @@ bool BuiltinFinalColorPass::Execute(const RenderPassContext& context) {
|
||||
|
||||
RHI::RHICommandList* commandList = context.renderContext.commandList;
|
||||
RHI::RHIResourceView* renderTarget = colorAttachments[0];
|
||||
const bool autoTransitionSource = context.sourceSurface->IsAutoTransitionEnabled();
|
||||
|
||||
if (context.surface.IsAutoTransitionEnabled()) {
|
||||
commandList->TransitionBarrier(
|
||||
@@ -186,6 +197,17 @@ bool BuiltinFinalColorPass::Execute(const RenderPassContext& context) {
|
||||
context.surface.GetColorStateBefore(),
|
||||
RHI::ResourceStates::RenderTarget);
|
||||
}
|
||||
if (autoTransitionSource) {
|
||||
commandList->TransitionBarrier(
|
||||
context.sourceColorView,
|
||||
context.sourceColorState,
|
||||
RHI::ResourceStates::PixelShaderResource);
|
||||
} else if (context.sourceColorState != RHI::ResourceStates::PixelShaderResource) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinFinalColorPass requires a PixelShaderResource source when source auto-transition is disabled");
|
||||
return false;
|
||||
}
|
||||
|
||||
commandList->SetRenderTargets(1, &renderTarget, nullptr);
|
||||
|
||||
@@ -224,6 +246,12 @@ bool BuiltinFinalColorPass::Execute(const RenderPassContext& context) {
|
||||
RHI::ResourceStates::RenderTarget,
|
||||
context.surface.GetColorStateAfter());
|
||||
}
|
||||
if (autoTransitionSource) {
|
||||
commandList->TransitionBarrier(
|
||||
context.sourceColorView,
|
||||
RHI::ResourceStates::PixelShaderResource,
|
||||
context.sourceColorState);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -255,10 +283,18 @@ const Containers::String& BuiltinFinalColorPass::GetShaderPath() const {
|
||||
|
||||
bool BuiltinFinalColorPass::EnsureInitialized(
|
||||
const RenderContext& renderContext,
|
||||
RHI::Format renderTargetFormat) {
|
||||
const RenderSurface& surface) {
|
||||
const RHI::Format renderTargetFormat =
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceColorFormat(surface, 0u);
|
||||
const uint32_t renderTargetSampleCount =
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceSampleCount(surface);
|
||||
const uint32_t renderTargetSampleQuality =
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceSampleQuality(surface);
|
||||
if (m_device == renderContext.device &&
|
||||
m_backendType == renderContext.backendType &&
|
||||
m_renderTargetFormat == renderTargetFormat &&
|
||||
m_renderTargetSampleCount == renderTargetSampleCount &&
|
||||
m_renderTargetSampleQuality == renderTargetSampleQuality &&
|
||||
m_pipelineLayout != nullptr &&
|
||||
m_pipelineState != nullptr &&
|
||||
m_sampler != nullptr &&
|
||||
@@ -269,12 +305,12 @@ bool BuiltinFinalColorPass::EnsureInitialized(
|
||||
}
|
||||
|
||||
DestroyResources();
|
||||
return CreateResources(renderContext, renderTargetFormat);
|
||||
return CreateResources(renderContext, surface);
|
||||
}
|
||||
|
||||
bool BuiltinFinalColorPass::CreateResources(
|
||||
const RenderContext& renderContext,
|
||||
RHI::Format renderTargetFormat) {
|
||||
const RenderSurface& surface) {
|
||||
if (!renderContext.IsValid()) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
@@ -282,6 +318,14 @@ bool BuiltinFinalColorPass::CreateResources(
|
||||
return false;
|
||||
}
|
||||
|
||||
RHI::Format renderTargetFormat = RHI::Format::Unknown;
|
||||
if (!::XCEngine::Rendering::Internal::TryResolveSingleColorAttachmentFormat(surface, renderTargetFormat)) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinFinalColorPass requires exactly one valid destination color attachment");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_shaderPath.Empty()) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
@@ -298,7 +342,7 @@ bool BuiltinFinalColorPass::CreateResources(
|
||||
return false;
|
||||
}
|
||||
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(renderContext.backendType);
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(renderContext.backendType);
|
||||
const Resources::ShaderPass* finalColorPass = FindCompatiblePass(*m_shader.Get(), backend);
|
||||
if (finalColorPass == nullptr) {
|
||||
Debug::Logger::Get().Error(
|
||||
@@ -311,6 +355,8 @@ bool BuiltinFinalColorPass::CreateResources(
|
||||
m_device = renderContext.device;
|
||||
m_backendType = renderContext.backendType;
|
||||
m_renderTargetFormat = renderTargetFormat;
|
||||
m_renderTargetSampleCount = ::XCEngine::Rendering::Internal::ResolveSurfaceSampleCount(surface);
|
||||
m_renderTargetSampleQuality = ::XCEngine::Rendering::Internal::ResolveSurfaceSampleQuality(surface);
|
||||
|
||||
RHI::DescriptorSetLayoutBinding constantBinding = {};
|
||||
constantBinding.binding = 0;
|
||||
@@ -432,7 +478,7 @@ bool BuiltinFinalColorPass::CreateResources(
|
||||
m_pipelineLayout,
|
||||
*m_shader.Get(),
|
||||
finalColorPass->name,
|
||||
m_renderTargetFormat));
|
||||
surface));
|
||||
if (m_pipelineState == nullptr || !m_pipelineState->IsValid()) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
@@ -473,6 +519,8 @@ void BuiltinFinalColorPass::DestroyResources() {
|
||||
m_device = nullptr;
|
||||
m_backendType = RHI::RHIType::D3D12;
|
||||
m_renderTargetFormat = RHI::Format::Unknown;
|
||||
m_renderTargetSampleCount = 1u;
|
||||
m_renderTargetSampleQuality = 0u;
|
||||
}
|
||||
|
||||
void BuiltinFinalColorPass::DestroyOwnedDescriptorSet(OwnedDescriptorSet& descriptorSet) {
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
#include <XCEngine/RHI/RHICommandList.h>
|
||||
#include <XCEngine/RHI/RHIDevice.h>
|
||||
|
||||
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
|
||||
#include "Rendering/Materials/RenderMaterialStateUtils.h"
|
||||
#include "Rendering/Detail/ShaderVariantUtils.h"
|
||||
#include "Rendering/Internal/ShaderVariantUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
@@ -40,18 +41,84 @@ const Resources::ShaderPass* FindInfiniteGridCompatiblePass(
|
||||
Resources::ShaderBackend backend) {
|
||||
const Resources::ShaderPass* gridPass = shader.FindPass("InfiniteGrid");
|
||||
if (gridPass != nullptr &&
|
||||
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, gridPass->name, backend)) {
|
||||
::XCEngine::Rendering::Internal::ShaderPassHasGraphicsVariants(shader, gridPass->name, backend)) {
|
||||
return gridPass;
|
||||
}
|
||||
|
||||
if (shader.GetPassCount() > 0 &&
|
||||
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, shader.GetPasses()[0].name, backend)) {
|
||||
::XCEngine::Rendering::Internal::ShaderPassHasGraphicsVariants(shader, shader.GetPasses()[0].name, backend)) {
|
||||
return &shader.GetPasses()[0];
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
||||
RHI::RHIType backendType,
|
||||
RHI::RHIPipelineLayout* pipelineLayout,
|
||||
const Resources::Shader& shader,
|
||||
const Containers::String& passName,
|
||||
const RenderSurface& surface) {
|
||||
RHI::GraphicsPipelineDesc pipelineDesc = {};
|
||||
pipelineDesc.pipelineLayout = pipelineLayout;
|
||||
pipelineDesc.topologyType = static_cast<uint32_t>(RHI::PrimitiveTopologyType::Triangle);
|
||||
::XCEngine::Rendering::Internal::ApplySingleColorAttachmentPropertiesToGraphicsPipelineDesc(
|
||||
surface,
|
||||
pipelineDesc);
|
||||
pipelineDesc.depthStencilFormat =
|
||||
static_cast<uint32_t>(::XCEngine::Rendering::Internal::ResolveSurfaceDepthFormat(surface));
|
||||
|
||||
const Resources::ShaderPass* shaderPass = shader.FindPass(passName);
|
||||
if (shaderPass != nullptr && shaderPass->hasFixedFunctionState) {
|
||||
::XCEngine::Rendering::ApplyRenderState(shaderPass->fixedFunctionState, pipelineDesc);
|
||||
} else {
|
||||
Resources::MaterialRenderState fallbackState = {};
|
||||
fallbackState.cullMode = Resources::MaterialCullMode::None;
|
||||
fallbackState.depthWriteEnable = false;
|
||||
fallbackState.depthTestEnable = true;
|
||||
fallbackState.depthFunc = Resources::MaterialComparisonFunc::LessEqual;
|
||||
fallbackState.blendEnable = true;
|
||||
fallbackState.srcBlend = Resources::MaterialBlendFactor::SrcAlpha;
|
||||
fallbackState.dstBlend = Resources::MaterialBlendFactor::InvSrcAlpha;
|
||||
fallbackState.srcBlendAlpha = Resources::MaterialBlendFactor::One;
|
||||
fallbackState.dstBlendAlpha = Resources::MaterialBlendFactor::InvSrcAlpha;
|
||||
fallbackState.blendOp = Resources::MaterialBlendOp::Add;
|
||||
fallbackState.blendOpAlpha = Resources::MaterialBlendOp::Add;
|
||||
fallbackState.colorWriteMask = static_cast<uint8_t>(RHI::ColorWriteMask::All);
|
||||
::XCEngine::Rendering::ApplyRenderState(fallbackState, pipelineDesc);
|
||||
}
|
||||
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(backendType);
|
||||
if (const Resources::ShaderStageVariant* vertexVariant = shader.FindVariant(
|
||||
passName,
|
||||
Resources::ShaderType::Vertex,
|
||||
backend)) {
|
||||
if (shaderPass != nullptr) {
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
shader.GetPath(),
|
||||
*shaderPass,
|
||||
backend,
|
||||
*vertexVariant,
|
||||
pipelineDesc.vertexShader);
|
||||
}
|
||||
}
|
||||
if (const Resources::ShaderStageVariant* fragmentVariant = shader.FindVariant(
|
||||
passName,
|
||||
Resources::ShaderType::Fragment,
|
||||
backend)) {
|
||||
if (shaderPass != nullptr) {
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
shader.GetPath(),
|
||||
*shaderPass,
|
||||
backend,
|
||||
*fragmentVariant,
|
||||
pipelineDesc.fragmentShader);
|
||||
}
|
||||
}
|
||||
|
||||
return pipelineDesc;
|
||||
}
|
||||
|
||||
float SnapGridSpacing(float targetSpacing) {
|
||||
const float clampedTarget = (std::max)(targetSpacing, 0.02f);
|
||||
const float exponent = std::floor(std::log10(clampedTarget));
|
||||
@@ -166,16 +233,24 @@ bool BuiltinInfiniteGridPass::Render(
|
||||
const RenderContext& renderContext,
|
||||
const RenderSurface& surface,
|
||||
const InfiniteGridPassData& data) {
|
||||
if (!data.valid || !renderContext.IsValid() || renderContext.backendType != RHI::RHIType::D3D12) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EnsureInitialized(renderContext)) {
|
||||
if (!data.valid || !renderContext.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::vector<RHI::RHIResourceView*>& colorAttachments = surface.GetColorAttachments();
|
||||
if (colorAttachments.empty() || colorAttachments[0] == nullptr || surface.GetDepthAttachment() == nullptr) {
|
||||
if (!::XCEngine::Rendering::Internal::HasSingleColorAttachment(surface) ||
|
||||
colorAttachments.empty() ||
|
||||
colorAttachments[0] == nullptr ||
|
||||
surface.GetDepthAttachment() == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Math::RectInt renderArea = surface.GetRenderArea();
|
||||
if (renderArea.width <= 0 || renderArea.height <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EnsureInitialized(renderContext, surface)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -187,12 +262,12 @@ bool BuiltinInfiniteGridPass::Render(
|
||||
const Math::Matrix4x4 viewProjection =
|
||||
BuildInfiniteGridProjectionMatrix(
|
||||
data,
|
||||
static_cast<float>(surface.GetWidth()),
|
||||
static_cast<float>(surface.GetHeight())) *
|
||||
static_cast<float>(renderArea.width),
|
||||
static_cast<float>(renderArea.height)) *
|
||||
BuildInfiniteGridViewMatrix(data);
|
||||
|
||||
const float aspect = surface.GetHeight() > 0
|
||||
? static_cast<float>(surface.GetWidth()) / static_cast<float>(surface.GetHeight())
|
||||
const float aspect = renderArea.height > 0
|
||||
? static_cast<float>(renderArea.width) / static_cast<float>(renderArea.height)
|
||||
: 1.0f;
|
||||
|
||||
GridConstants constants = {};
|
||||
@@ -214,21 +289,33 @@ bool BuiltinInfiniteGridPass::Render(
|
||||
|
||||
RHI::RHICommandList* commandList = renderContext.commandList;
|
||||
RHI::RHIResourceView* renderTarget = colorAttachments[0];
|
||||
commandList->SetRenderTargets(1, &renderTarget, surface.GetDepthAttachment());
|
||||
RHI::RHIResourceView* depthAttachment = surface.GetDepthAttachment();
|
||||
if (surface.IsAutoTransitionEnabled()) {
|
||||
commandList->TransitionBarrier(
|
||||
renderTarget,
|
||||
surface.GetColorStateAfter(),
|
||||
RHI::ResourceStates::RenderTarget);
|
||||
commandList->TransitionBarrier(
|
||||
depthAttachment,
|
||||
surface.GetDepthStateAfter(),
|
||||
RHI::ResourceStates::DepthWrite);
|
||||
}
|
||||
|
||||
commandList->SetRenderTargets(1, &renderTarget, depthAttachment);
|
||||
|
||||
const RHI::Viewport viewport = {
|
||||
0.0f,
|
||||
0.0f,
|
||||
static_cast<float>(surface.GetWidth()),
|
||||
static_cast<float>(surface.GetHeight()),
|
||||
static_cast<float>(renderArea.x),
|
||||
static_cast<float>(renderArea.y),
|
||||
static_cast<float>(renderArea.width),
|
||||
static_cast<float>(renderArea.height),
|
||||
0.0f,
|
||||
1.0f
|
||||
};
|
||||
const RHI::Rect scissorRect = {
|
||||
0,
|
||||
0,
|
||||
static_cast<int32_t>(surface.GetWidth()),
|
||||
static_cast<int32_t>(surface.GetHeight())
|
||||
renderArea.x,
|
||||
renderArea.y,
|
||||
renderArea.x + renderArea.width,
|
||||
renderArea.y + renderArea.height
|
||||
};
|
||||
|
||||
commandList->SetViewport(viewport);
|
||||
@@ -239,25 +326,53 @@ bool BuiltinInfiniteGridPass::Render(
|
||||
RHI::RHIDescriptorSet* descriptorSets[] = { m_constantSet };
|
||||
commandList->SetGraphicsDescriptorSets(0, 1, descriptorSets, m_pipelineLayout);
|
||||
commandList->Draw(3, 1, 0, 0);
|
||||
commandList->EndRenderPass();
|
||||
|
||||
if (surface.IsAutoTransitionEnabled()) {
|
||||
commandList->TransitionBarrier(
|
||||
renderTarget,
|
||||
RHI::ResourceStates::RenderTarget,
|
||||
surface.GetColorStateAfter());
|
||||
commandList->TransitionBarrier(
|
||||
depthAttachment,
|
||||
RHI::ResourceStates::DepthWrite,
|
||||
surface.GetDepthStateAfter());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BuiltinInfiniteGridPass::EnsureInitialized(const RenderContext& renderContext) {
|
||||
bool BuiltinInfiniteGridPass::EnsureInitialized(const RenderContext& renderContext, const RenderSurface& surface) {
|
||||
const RHI::Format renderTargetFormat =
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceColorFormat(surface, 0u);
|
||||
const RHI::Format depthFormat =
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceDepthFormat(surface);
|
||||
const uint32_t renderTargetSampleCount =
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceSampleCount(surface);
|
||||
if (m_pipelineState != nullptr &&
|
||||
m_pipelineLayout != nullptr &&
|
||||
m_constantPool != nullptr &&
|
||||
m_constantSet != nullptr &&
|
||||
m_device == renderContext.device &&
|
||||
m_backendType == renderContext.backendType) {
|
||||
m_backendType == renderContext.backendType &&
|
||||
m_renderTargetFormat == renderTargetFormat &&
|
||||
m_depthStencilFormat == depthFormat &&
|
||||
m_renderTargetSampleCount == renderTargetSampleCount) {
|
||||
return true;
|
||||
}
|
||||
|
||||
DestroyResources();
|
||||
return CreateResources(renderContext);
|
||||
return CreateResources(renderContext, surface);
|
||||
}
|
||||
|
||||
bool BuiltinInfiniteGridPass::CreateResources(const RenderContext& renderContext) {
|
||||
if (!renderContext.IsValid() || renderContext.backendType != RHI::RHIType::D3D12) {
|
||||
bool BuiltinInfiniteGridPass::CreateResources(const RenderContext& renderContext, const RenderSurface& surface) {
|
||||
if (!renderContext.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!::XCEngine::Rendering::Internal::HasSingleColorAttachment(surface) ||
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceColorFormat(surface, 0u) == RHI::Format::Unknown ||
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceDepthFormat(surface) == RHI::Format::Unknown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -280,7 +395,7 @@ bool BuiltinInfiniteGridPass::CreateResources(const RenderContext& renderContext
|
||||
return false;
|
||||
}
|
||||
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(m_backendType);
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(m_backendType);
|
||||
const Resources::ShaderPass* infiniteGridPass =
|
||||
FindInfiniteGridCompatiblePass(*m_builtinInfiniteGridShader.Get(), backend);
|
||||
if (infiniteGridPass == nullptr) {
|
||||
@@ -326,60 +441,12 @@ bool BuiltinInfiniteGridPass::CreateResources(const RenderContext& renderContext
|
||||
}
|
||||
|
||||
RHI::GraphicsPipelineDesc pipelineDesc = {};
|
||||
pipelineDesc.pipelineLayout = m_pipelineLayout;
|
||||
pipelineDesc.topologyType = static_cast<uint32_t>(RHI::PrimitiveTopologyType::Triangle);
|
||||
pipelineDesc.renderTargetCount = 1;
|
||||
pipelineDesc.renderTargetFormats[0] = static_cast<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
|
||||
pipelineDesc.depthStencilFormat = static_cast<uint32_t>(RHI::Format::D24_UNorm_S8_UInt);
|
||||
pipelineDesc.sampleCount = 1;
|
||||
|
||||
if (infiniteGridPass->hasFixedFunctionState) {
|
||||
::XCEngine::Rendering::ApplyRenderState(infiniteGridPass->fixedFunctionState, pipelineDesc);
|
||||
} else {
|
||||
Resources::MaterialRenderState fallbackState = {};
|
||||
fallbackState.cullMode = Resources::MaterialCullMode::None;
|
||||
fallbackState.depthWriteEnable = false;
|
||||
fallbackState.depthTestEnable = true;
|
||||
fallbackState.depthFunc = Resources::MaterialComparisonFunc::LessEqual;
|
||||
fallbackState.blendEnable = true;
|
||||
fallbackState.srcBlend = Resources::MaterialBlendFactor::SrcAlpha;
|
||||
fallbackState.dstBlend = Resources::MaterialBlendFactor::InvSrcAlpha;
|
||||
fallbackState.srcBlendAlpha = Resources::MaterialBlendFactor::One;
|
||||
fallbackState.dstBlendAlpha = Resources::MaterialBlendFactor::InvSrcAlpha;
|
||||
fallbackState.blendOp = Resources::MaterialBlendOp::Add;
|
||||
fallbackState.blendOpAlpha = Resources::MaterialBlendOp::Add;
|
||||
fallbackState.colorWriteMask = static_cast<uint8_t>(RHI::ColorWriteMask::All);
|
||||
::XCEngine::Rendering::ApplyRenderState(fallbackState, pipelineDesc);
|
||||
}
|
||||
|
||||
if (const Resources::ShaderStageVariant* vertexVariant = m_builtinInfiniteGridShader->FindVariant(
|
||||
infiniteGridPass->name,
|
||||
Resources::ShaderType::Vertex,
|
||||
backend)) {
|
||||
const Resources::ShaderPass* shaderPass = m_builtinInfiniteGridShader->FindPass(infiniteGridPass->name);
|
||||
if (shaderPass != nullptr) {
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
m_builtinInfiniteGridShader->GetPath(),
|
||||
*shaderPass,
|
||||
backend,
|
||||
*vertexVariant,
|
||||
pipelineDesc.vertexShader);
|
||||
}
|
||||
}
|
||||
if (const Resources::ShaderStageVariant* fragmentVariant = m_builtinInfiniteGridShader->FindVariant(
|
||||
infiniteGridPass->name,
|
||||
Resources::ShaderType::Fragment,
|
||||
backend)) {
|
||||
const Resources::ShaderPass* shaderPass = m_builtinInfiniteGridShader->FindPass(infiniteGridPass->name);
|
||||
if (shaderPass != nullptr) {
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
m_builtinInfiniteGridShader->GetPath(),
|
||||
*shaderPass,
|
||||
backend,
|
||||
*fragmentVariant,
|
||||
pipelineDesc.fragmentShader);
|
||||
}
|
||||
}
|
||||
pipelineDesc = CreatePipelineDesc(
|
||||
m_backendType,
|
||||
m_pipelineLayout,
|
||||
*m_builtinInfiniteGridShader.Get(),
|
||||
infiniteGridPass->name,
|
||||
surface);
|
||||
|
||||
m_pipelineState = m_device->CreatePipelineState(pipelineDesc);
|
||||
if (m_pipelineState == nullptr || !m_pipelineState->IsValid()) {
|
||||
@@ -387,6 +454,10 @@ bool BuiltinInfiniteGridPass::CreateResources(const RenderContext& renderContext
|
||||
return false;
|
||||
}
|
||||
|
||||
m_renderTargetFormat = ::XCEngine::Rendering::Internal::ResolveSurfaceColorFormat(surface, 0u);
|
||||
m_depthStencilFormat = ::XCEngine::Rendering::Internal::ResolveSurfaceDepthFormat(surface);
|
||||
m_renderTargetSampleCount = ::XCEngine::Rendering::Internal::ResolveSurfaceSampleCount(surface);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -418,6 +489,9 @@ void BuiltinInfiniteGridPass::DestroyResources() {
|
||||
m_device = nullptr;
|
||||
m_backendType = RHI::RHIType::D3D12;
|
||||
m_builtinInfiniteGridShader.Reset();
|
||||
m_renderTargetFormat = RHI::Format::Unknown;
|
||||
m_depthStencilFormat = RHI::Format::Unknown;
|
||||
m_renderTargetSampleCount = 1u;
|
||||
}
|
||||
|
||||
} // namespace Passes
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "Core/Asset/ResourceManager.h"
|
||||
#include "RHI/RHICommandList.h"
|
||||
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
|
||||
#include "Rendering/Extraction/RenderSceneExtractor.h"
|
||||
#include "Rendering/RenderSurface.h"
|
||||
#include "Resources/Mesh/Mesh.h"
|
||||
@@ -52,7 +53,7 @@ RHI::InputLayoutDesc BuiltinObjectIdPass::BuildInputLayout() {
|
||||
}
|
||||
|
||||
bool BuiltinObjectIdPass::Initialize(const RenderContext& context) {
|
||||
return EnsureInitialized(context);
|
||||
return context.IsValid();
|
||||
}
|
||||
|
||||
bool BuiltinObjectIdPass::Execute(const RenderPassContext& context) {
|
||||
@@ -61,7 +62,8 @@ bool BuiltinObjectIdPass::Execute(const RenderPassContext& context) {
|
||||
}
|
||||
|
||||
const std::vector<RHI::RHIResourceView*>& colorAttachments = context.surface.GetColorAttachments();
|
||||
if (colorAttachments.empty() ||
|
||||
if (!::XCEngine::Rendering::Internal::HasSingleColorAttachment(context.surface) ||
|
||||
colorAttachments.empty() ||
|
||||
colorAttachments[0] == nullptr ||
|
||||
context.surface.GetDepthAttachment() == nullptr) {
|
||||
return false;
|
||||
@@ -72,21 +74,26 @@ bool BuiltinObjectIdPass::Execute(const RenderPassContext& context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EnsureInitialized(context.renderContext)) {
|
||||
if (!EnsureInitialized(context.renderContext, context.surface)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RHI::RHICommandList* commandList = context.renderContext.commandList;
|
||||
RHI::RHIResourceView* renderTarget = colorAttachments[0];
|
||||
RHI::RHIResourceView* depthAttachment = context.surface.GetDepthAttachment();
|
||||
|
||||
if (context.surface.IsAutoTransitionEnabled()) {
|
||||
commandList->TransitionBarrier(
|
||||
renderTarget,
|
||||
context.surface.GetColorStateBefore(),
|
||||
RHI::ResourceStates::RenderTarget);
|
||||
commandList->TransitionBarrier(
|
||||
depthAttachment,
|
||||
context.surface.GetDepthStateBefore(),
|
||||
RHI::ResourceStates::DepthWrite);
|
||||
}
|
||||
|
||||
commandList->SetRenderTargets(1, &renderTarget, context.surface.GetDepthAttachment());
|
||||
commandList->SetRenderTargets(1, &renderTarget, depthAttachment);
|
||||
|
||||
const RHI::Viewport viewport = {
|
||||
static_cast<float>(renderArea.x),
|
||||
@@ -108,7 +115,7 @@ bool BuiltinObjectIdPass::Execute(const RenderPassContext& context) {
|
||||
commandList->SetViewport(viewport);
|
||||
commandList->SetScissorRect(scissorRect);
|
||||
commandList->ClearRenderTarget(renderTarget, clearColor, 1, clearRects);
|
||||
commandList->ClearDepthStencil(context.surface.GetDepthAttachment(), 1.0f, 0, 1, clearRects);
|
||||
commandList->ClearDepthStencil(depthAttachment, 1.0f, 0, 1, clearRects);
|
||||
commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
|
||||
commandList->SetPipelineState(m_pipelineState);
|
||||
|
||||
@@ -123,6 +130,10 @@ bool BuiltinObjectIdPass::Execute(const RenderPassContext& context) {
|
||||
renderTarget,
|
||||
RHI::ResourceStates::RenderTarget,
|
||||
context.surface.GetColorStateAfter());
|
||||
commandList->TransitionBarrier(
|
||||
depthAttachment,
|
||||
RHI::ResourceStates::DepthWrite,
|
||||
context.surface.GetDepthStateAfter());
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
#include "Core/Asset/ResourceManager.h"
|
||||
#include "Debug/Logger.h"
|
||||
#include "Rendering/Detail/ShaderVariantUtils.h"
|
||||
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
|
||||
#include "Rendering/Internal/ShaderVariantUtils.h"
|
||||
#include "Rendering/Materials/RenderMaterialStateUtils.h"
|
||||
#include "RHI/RHICommandList.h"
|
||||
#include "RHI/RHIDevice.h"
|
||||
@@ -22,13 +23,13 @@ const Resources::ShaderPass* FindSelectionOutlineCompatiblePass(
|
||||
Resources::ShaderBackend backend) {
|
||||
const Resources::ShaderPass* outlinePass = shader.FindPass("SelectionOutline");
|
||||
if (outlinePass != nullptr &&
|
||||
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, outlinePass->name, backend)) {
|
||||
::XCEngine::Rendering::Internal::ShaderPassHasGraphicsVariants(shader, outlinePass->name, backend)) {
|
||||
return outlinePass;
|
||||
}
|
||||
|
||||
const Resources::ShaderPass* editorOutlinePass = shader.FindPass("EditorSelectionOutline");
|
||||
if (editorOutlinePass != nullptr &&
|
||||
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(
|
||||
::XCEngine::Rendering::Internal::ShaderPassHasGraphicsVariants(
|
||||
shader,
|
||||
editorOutlinePass->name,
|
||||
backend)) {
|
||||
@@ -36,7 +37,7 @@ const Resources::ShaderPass* FindSelectionOutlineCompatiblePass(
|
||||
}
|
||||
|
||||
if (shader.GetPassCount() > 0 &&
|
||||
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(
|
||||
::XCEngine::Rendering::Internal::ShaderPassHasGraphicsVariants(
|
||||
shader,
|
||||
shader.GetPasses()[0].name,
|
||||
backend)) {
|
||||
@@ -50,14 +51,15 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
||||
RHI::RHIType backendType,
|
||||
RHI::RHIPipelineLayout* pipelineLayout,
|
||||
const Resources::Shader& shader,
|
||||
const Containers::String& passName) {
|
||||
const Containers::String& passName,
|
||||
const RenderSurface& surface) {
|
||||
RHI::GraphicsPipelineDesc pipelineDesc = {};
|
||||
pipelineDesc.pipelineLayout = pipelineLayout;
|
||||
pipelineDesc.topologyType = static_cast<uint32_t>(RHI::PrimitiveTopologyType::Triangle);
|
||||
pipelineDesc.renderTargetCount = 1;
|
||||
pipelineDesc.renderTargetFormats[0] = static_cast<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
|
||||
::XCEngine::Rendering::Internal::ApplySingleColorAttachmentPropertiesToGraphicsPipelineDesc(
|
||||
surface,
|
||||
pipelineDesc);
|
||||
pipelineDesc.depthStencilFormat = static_cast<uint32_t>(RHI::Format::Unknown);
|
||||
pipelineDesc.sampleCount = 1;
|
||||
|
||||
const Resources::ShaderPass* shaderPass = shader.FindPass(passName);
|
||||
if (shaderPass != nullptr && shaderPass->hasFixedFunctionState) {
|
||||
@@ -79,11 +81,11 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
||||
::XCEngine::Rendering::ApplyRenderState(fallbackState, pipelineDesc);
|
||||
}
|
||||
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType);
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(backendType);
|
||||
if (const Resources::ShaderStageVariant* vertexVariant =
|
||||
shader.FindVariant(passName, Resources::ShaderType::Vertex, backend)) {
|
||||
if (shaderPass != nullptr) {
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
shader.GetPath(),
|
||||
*shaderPass,
|
||||
backend,
|
||||
@@ -94,7 +96,7 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
||||
if (const Resources::ShaderStageVariant* fragmentVariant =
|
||||
shader.FindVariant(passName, Resources::ShaderType::Fragment, backend)) {
|
||||
if (shaderPass != nullptr) {
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
shader.GetPath(),
|
||||
*shaderPass,
|
||||
backend,
|
||||
@@ -133,22 +135,27 @@ void BuiltinSelectionOutlinePass::Shutdown() {
|
||||
bool BuiltinSelectionOutlinePass::Render(
|
||||
const RenderContext& renderContext,
|
||||
const RenderSurface& surface,
|
||||
RHI::RHIResourceView* selectionMaskTextureView,
|
||||
RHI::RHIResourceView* depthTextureView,
|
||||
const SelectionOutlinePassInputs& inputs,
|
||||
const SelectionOutlineStyle& style) {
|
||||
if (!renderContext.IsValid() ||
|
||||
renderContext.backendType != RHI::RHIType::D3D12 ||
|
||||
selectionMaskTextureView == nullptr ||
|
||||
depthTextureView == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EnsureInitialized(renderContext)) {
|
||||
inputs.selectionMaskTextureView == nullptr ||
|
||||
inputs.depthTextureView == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::vector<RHI::RHIResourceView*>& colorAttachments = surface.GetColorAttachments();
|
||||
if (colorAttachments.empty() || colorAttachments[0] == nullptr) {
|
||||
if (!::XCEngine::Rendering::Internal::HasSingleColorAttachment(surface) ||
|
||||
colorAttachments.empty() ||
|
||||
colorAttachments[0] == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Math::RectInt renderArea = surface.GetRenderArea();
|
||||
if (renderArea.width <= 0 || renderArea.height <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EnsureInitialized(renderContext, surface)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -171,22 +178,27 @@ bool BuiltinSelectionOutlinePass::Render(
|
||||
constants.depthParams = Math::Vector4(1.0e-5f, 0.0f, 0.0f, 0.0f);
|
||||
|
||||
m_constantSet->WriteConstant(0, &constants, sizeof(constants));
|
||||
m_textureSet->Update(0, selectionMaskTextureView);
|
||||
m_textureSet->Update(1, depthTextureView);
|
||||
m_textureSet->Update(0, inputs.selectionMaskTextureView);
|
||||
m_textureSet->Update(1, inputs.depthTextureView);
|
||||
|
||||
RHI::RHICommandList* commandList = renderContext.commandList;
|
||||
RHI::RHIResourceView* renderTarget = colorAttachments[0];
|
||||
commandList->TransitionBarrier(
|
||||
renderTarget,
|
||||
surface.GetColorStateAfter(),
|
||||
RHI::ResourceStates::RenderTarget);
|
||||
commandList->TransitionBarrier(
|
||||
depthTextureView,
|
||||
RHI::ResourceStates::DepthWrite,
|
||||
RHI::ResourceStates::PixelShaderResource);
|
||||
if (surface.IsAutoTransitionEnabled()) {
|
||||
commandList->TransitionBarrier(
|
||||
renderTarget,
|
||||
surface.GetColorStateAfter(),
|
||||
RHI::ResourceStates::RenderTarget);
|
||||
commandList->TransitionBarrier(
|
||||
inputs.selectionMaskTextureView,
|
||||
inputs.selectionMaskState,
|
||||
RHI::ResourceStates::PixelShaderResource);
|
||||
commandList->TransitionBarrier(
|
||||
inputs.depthTextureView,
|
||||
inputs.depthTextureState,
|
||||
RHI::ResourceStates::PixelShaderResource);
|
||||
}
|
||||
commandList->SetRenderTargets(1, &renderTarget, nullptr);
|
||||
|
||||
const Math::RectInt renderArea = surface.GetRenderArea();
|
||||
const RHI::Viewport viewport = {
|
||||
static_cast<float>(renderArea.x),
|
||||
static_cast<float>(renderArea.y),
|
||||
@@ -210,19 +222,30 @@ bool BuiltinSelectionOutlinePass::Render(
|
||||
RHI::RHIDescriptorSet* descriptorSets[] = { m_constantSet, m_textureSet };
|
||||
commandList->SetGraphicsDescriptorSets(0, 2, descriptorSets, m_pipelineLayout);
|
||||
commandList->Draw(3, 1, 0, 0);
|
||||
commandList->EndRenderPass();
|
||||
|
||||
commandList->TransitionBarrier(
|
||||
renderTarget,
|
||||
RHI::ResourceStates::RenderTarget,
|
||||
surface.GetColorStateAfter());
|
||||
commandList->TransitionBarrier(
|
||||
depthTextureView,
|
||||
RHI::ResourceStates::PixelShaderResource,
|
||||
RHI::ResourceStates::DepthWrite);
|
||||
if (surface.IsAutoTransitionEnabled()) {
|
||||
commandList->TransitionBarrier(
|
||||
renderTarget,
|
||||
RHI::ResourceStates::RenderTarget,
|
||||
surface.GetColorStateAfter());
|
||||
commandList->TransitionBarrier(
|
||||
inputs.selectionMaskTextureView,
|
||||
RHI::ResourceStates::PixelShaderResource,
|
||||
inputs.selectionMaskState);
|
||||
commandList->TransitionBarrier(
|
||||
inputs.depthTextureView,
|
||||
RHI::ResourceStates::PixelShaderResource,
|
||||
inputs.depthTextureState);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BuiltinSelectionOutlinePass::EnsureInitialized(const RenderContext& renderContext) {
|
||||
bool BuiltinSelectionOutlinePass::EnsureInitialized(const RenderContext& renderContext, const RenderSurface& surface) {
|
||||
const RHI::Format renderTargetFormat =
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceColorFormat(surface, 0u);
|
||||
const uint32_t renderTargetSampleCount =
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceSampleCount(surface);
|
||||
if (m_pipelineLayout != nullptr &&
|
||||
m_pipelineState != nullptr &&
|
||||
m_constantPool != nullptr &&
|
||||
@@ -230,18 +253,25 @@ bool BuiltinSelectionOutlinePass::EnsureInitialized(const RenderContext& renderC
|
||||
m_texturePool != nullptr &&
|
||||
m_textureSet != nullptr &&
|
||||
m_device == renderContext.device &&
|
||||
m_backendType == renderContext.backendType) {
|
||||
m_backendType == renderContext.backendType &&
|
||||
m_renderTargetFormat == renderTargetFormat &&
|
||||
m_renderTargetSampleCount == renderTargetSampleCount) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (HasCreatedResources()) {
|
||||
DestroyResources();
|
||||
}
|
||||
return CreateResources(renderContext);
|
||||
return CreateResources(renderContext, surface);
|
||||
}
|
||||
|
||||
bool BuiltinSelectionOutlinePass::CreateResources(const RenderContext& renderContext) {
|
||||
if (!renderContext.IsValid() || renderContext.backendType != RHI::RHIType::D3D12) {
|
||||
bool BuiltinSelectionOutlinePass::CreateResources(const RenderContext& renderContext, const RenderSurface& surface) {
|
||||
if (!renderContext.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!::XCEngine::Rendering::Internal::HasSingleColorAttachment(surface) ||
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceColorFormat(surface, 0u) == RHI::Format::Unknown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -266,7 +296,7 @@ bool BuiltinSelectionOutlinePass::CreateResources(const RenderContext& renderCon
|
||||
m_backendType = renderContext.backendType;
|
||||
m_builtinSelectionOutlineShader.emplace(std::move(shader));
|
||||
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(m_backendType);
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(m_backendType);
|
||||
const Resources::ShaderPass* outlinePass =
|
||||
FindSelectionOutlineCompatiblePass(*m_builtinSelectionOutlineShader->Get(), backend);
|
||||
if (outlinePass == nullptr) {
|
||||
@@ -348,12 +378,16 @@ bool BuiltinSelectionOutlinePass::CreateResources(const RenderContext& renderCon
|
||||
m_backendType,
|
||||
m_pipelineLayout,
|
||||
*m_builtinSelectionOutlineShader->Get(),
|
||||
outlinePass->name));
|
||||
outlinePass->name,
|
||||
surface));
|
||||
if (m_pipelineState == nullptr || !m_pipelineState->IsValid()) {
|
||||
DestroyResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_renderTargetFormat = ::XCEngine::Rendering::Internal::ResolveSurfaceColorFormat(surface, 0u);
|
||||
m_renderTargetSampleCount = ::XCEngine::Rendering::Internal::ResolveSurfaceSampleCount(surface);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -422,6 +456,8 @@ void BuiltinSelectionOutlinePass::ResetState() {
|
||||
m_texturePool = nullptr;
|
||||
m_textureSet = nullptr;
|
||||
m_builtinSelectionOutlineShader.reset();
|
||||
m_renderTargetFormat = RHI::Format::Unknown;
|
||||
m_renderTargetSampleCount = 1u;
|
||||
}
|
||||
|
||||
} // namespace Passes
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
#include "RHI/RHICommandList.h"
|
||||
#include "RHI/RHIDevice.h"
|
||||
#include "Rendering/Builtin/BuiltinPassLayoutUtils.h"
|
||||
#include "Rendering/Detail/ShaderVariantUtils.h"
|
||||
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
|
||||
#include "Rendering/Internal/ShaderVariantUtils.h"
|
||||
#include "Rendering/FrameData/RenderSceneData.h"
|
||||
#include "Rendering/FrameData/VisibleVolumeItem.h"
|
||||
#include "Rendering/RenderSurface.h"
|
||||
@@ -16,7 +17,9 @@
|
||||
#include "Resources/Volume/VolumeField.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
@@ -24,6 +27,19 @@ namespace Passes {
|
||||
|
||||
namespace {
|
||||
|
||||
uint64_t GetVolumeTraceSteadyMs() {
|
||||
using Clock = std::chrono::steady_clock;
|
||||
static const Clock::time_point s_start = Clock::now();
|
||||
return static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
Clock::now() - s_start).count());
|
||||
}
|
||||
|
||||
void LogVolumeTraceRendering(const std::string& message) {
|
||||
Containers::String entry("[VolumeTrace] ");
|
||||
entry += message.c_str();
|
||||
Debug::Logger::Get().Info(Debug::LogCategory::Rendering, entry);
|
||||
}
|
||||
|
||||
bool IsDepthFormat(RHI::Format format) {
|
||||
return format == RHI::Format::D24_UNorm_S8_UInt ||
|
||||
format == RHI::Format::D32_Float;
|
||||
@@ -45,7 +61,7 @@ const Resources::ShaderPass* FindCompatibleVolumePass(
|
||||
const Resources::ShaderKeywordSet keywordSet = ResolvePassKeywordSet(sceneData, material);
|
||||
for (const Resources::ShaderPass& shaderPass : shader.GetPasses()) {
|
||||
if (ShaderPassMatchesBuiltinPass(shaderPass, BuiltinMaterialPass::Volumetric) &&
|
||||
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(
|
||||
::XCEngine::Rendering::Internal::ShaderPassHasGraphicsVariants(
|
||||
shader,
|
||||
shaderPass.name,
|
||||
backend,
|
||||
@@ -65,22 +81,22 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
||||
const Containers::String& passName,
|
||||
const Resources::ShaderKeywordSet& keywordSet,
|
||||
const Resources::Material* material,
|
||||
RHI::Format renderTargetFormat,
|
||||
RHI::Format depthStencilFormat) {
|
||||
const RenderSurface& surface) {
|
||||
RHI::GraphicsPipelineDesc pipelineDesc = {};
|
||||
pipelineDesc.pipelineLayout = pipelineLayout;
|
||||
pipelineDesc.topologyType = static_cast<uint32_t>(RHI::PrimitiveTopologyType::Triangle);
|
||||
pipelineDesc.renderTargetCount = 1;
|
||||
pipelineDesc.renderTargetFormats[0] = static_cast<uint32_t>(renderTargetFormat);
|
||||
pipelineDesc.depthStencilFormat = static_cast<uint32_t>(depthStencilFormat);
|
||||
pipelineDesc.sampleCount = 1;
|
||||
::XCEngine::Rendering::Internal::ApplySingleColorAttachmentPropertiesToGraphicsPipelineDesc(
|
||||
surface,
|
||||
pipelineDesc);
|
||||
pipelineDesc.depthStencilFormat =
|
||||
static_cast<uint32_t>(::XCEngine::Rendering::Internal::ResolveSurfaceDepthFormat(surface));
|
||||
pipelineDesc.inputLayout = BuiltinVolumetricPass::BuildInputLayout();
|
||||
ApplyResolvedRenderState(&shaderPass, material, pipelineDesc);
|
||||
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType);
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(backendType);
|
||||
if (const Resources::ShaderStageVariant* vertexVariant =
|
||||
shader.FindVariant(passName, Resources::ShaderType::Vertex, backend, keywordSet)) {
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
shader.GetPath(),
|
||||
shaderPass,
|
||||
backend,
|
||||
@@ -89,7 +105,7 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
||||
}
|
||||
if (const Resources::ShaderStageVariant* fragmentVariant =
|
||||
shader.FindVariant(passName, Resources::ShaderType::Fragment, backend, keywordSet)) {
|
||||
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(
|
||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
||||
shader.GetPath(),
|
||||
shaderPass,
|
||||
backend,
|
||||
@@ -194,6 +210,38 @@ bool BuiltinVolumetricPass::Initialize(const RenderContext& context) {
|
||||
return EnsureInitialized(context);
|
||||
}
|
||||
|
||||
bool BuiltinVolumetricPass::PrepareVolumeResources(
|
||||
const RenderContext& context,
|
||||
const RenderSceneData& sceneData) {
|
||||
if (!EnsureInitialized(context)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sceneData.visibleVolumes.empty()) {
|
||||
const RenderResourceCache::CachedMesh* cachedMesh =
|
||||
m_resourceCache.GetOrCreateMesh(m_device, m_builtinCubeMesh.Get());
|
||||
if (cachedMesh == nullptr || cachedMesh->vertexBufferView == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const VisibleVolumeItem& visibleVolume : sceneData.visibleVolumes) {
|
||||
if (visibleVolume.volumeField == nullptr ||
|
||||
visibleVolume.material == nullptr ||
|
||||
visibleVolume.volumeField->GetStorageKind() != Resources::VolumeStorageKind::NanoVDB) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const RenderResourceCache::CachedVolumeField* cachedVolume =
|
||||
m_resourceCache.GetOrCreateVolumeField(m_device, visibleVolume.volumeField);
|
||||
if (cachedVolume == nullptr || cachedVolume->shaderResourceView == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BuiltinVolumetricPass::Execute(const RenderPassContext& context) {
|
||||
if (!context.renderContext.IsValid()) {
|
||||
return false;
|
||||
@@ -204,7 +252,10 @@ bool BuiltinVolumetricPass::Execute(const RenderPassContext& context) {
|
||||
}
|
||||
|
||||
const std::vector<RHI::RHIResourceView*>& colorAttachments = context.surface.GetColorAttachments();
|
||||
if (colorAttachments.empty() || colorAttachments[0] == nullptr || context.surface.GetDepthAttachment() == nullptr) {
|
||||
if (!::XCEngine::Rendering::Internal::HasSingleColorAttachment(context.surface) ||
|
||||
colorAttachments.empty() ||
|
||||
colorAttachments[0] == nullptr ||
|
||||
context.surface.GetDepthAttachment() == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -213,7 +264,7 @@ bool BuiltinVolumetricPass::Execute(const RenderPassContext& context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EnsureInitialized(context.renderContext)) {
|
||||
if (!PrepareVolumeResources(context.renderContext, context.sceneData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -316,7 +367,7 @@ BuiltinVolumetricPass::ResolvedShaderPass BuiltinVolumetricPass::ResolveVolumeSh
|
||||
}
|
||||
|
||||
const Resources::Shader* shader = material->GetShader();
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(m_backendType);
|
||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(m_backendType);
|
||||
if (const Resources::ShaderPass* shaderPass =
|
||||
FindCompatibleVolumePass(*shader, sceneData, material, backend)) {
|
||||
resolved.shader = shader;
|
||||
@@ -343,6 +394,12 @@ BuiltinVolumetricPass::PassResourceLayout* BuiltinVolumetricPass::GetOrCreatePas
|
||||
return &existing->second;
|
||||
}
|
||||
|
||||
const uint64_t layoutStartMs = GetVolumeTraceSteadyMs();
|
||||
LogVolumeTraceRendering(
|
||||
"VolumetricPass layout create begin steady_ms=" + std::to_string(layoutStartMs) +
|
||||
" shader=" + std::string(resolvedShaderPass.shader->GetPath().CStr()) +
|
||||
" pass=" + std::string(resolvedShaderPass.passName.CStr()));
|
||||
|
||||
PassResourceLayout passLayout = {};
|
||||
auto failLayout = [this, &passLayout](const char* message) -> PassResourceLayout* {
|
||||
Debug::Logger::Get().Error(Debug::LogCategory::Rendering, message);
|
||||
@@ -397,6 +454,12 @@ BuiltinVolumetricPass::PassResourceLayout* BuiltinVolumetricPass::GetOrCreatePas
|
||||
const auto result = m_passResourceLayouts.emplace(passLayoutKey, passLayout);
|
||||
PassResourceLayout& storedPassLayout = result.first->second;
|
||||
RefreshBuiltinPassSetLayouts(storedPassLayout.setLayouts);
|
||||
const uint64_t layoutEndMs = GetVolumeTraceSteadyMs();
|
||||
LogVolumeTraceRendering(
|
||||
"VolumetricPass layout create end steady_ms=" + std::to_string(layoutEndMs) +
|
||||
" total_ms=" + std::to_string(layoutEndMs - layoutStartMs) +
|
||||
" shader=" + std::string(resolvedShaderPass.shader->GetPath().CStr()) +
|
||||
" pass=" + std::string(resolvedShaderPass.passName.CStr()));
|
||||
return &storedPassLayout;
|
||||
}
|
||||
|
||||
@@ -416,31 +479,35 @@ RHI::RHIPipelineState* BuiltinVolumetricPass::GetOrCreatePipelineState(
|
||||
}
|
||||
|
||||
const Resources::ShaderKeywordSet keywordSet = ResolvePassKeywordSet(sceneData, material);
|
||||
const std::vector<RHI::RHIResourceView*>& colorAttachments = surface.GetColorAttachments();
|
||||
const RHI::Format renderTargetFormat =
|
||||
(!colorAttachments.empty() && colorAttachments[0] != nullptr)
|
||||
? colorAttachments[0]->GetFormat()
|
||||
: RHI::Format::Unknown;
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceColorFormat(surface, 0u);
|
||||
const RHI::Format depthStencilFormat =
|
||||
surface.GetDepthAttachment() != nullptr
|
||||
? surface.GetDepthAttachment()->GetFormat()
|
||||
: RHI::Format::Unknown;
|
||||
::XCEngine::Rendering::Internal::ResolveSurfaceDepthFormat(surface);
|
||||
|
||||
PipelineStateKey pipelineKey = {};
|
||||
pipelineKey.renderState =
|
||||
BuildStaticPipelineRenderStateKey(ResolveEffectiveRenderState(resolvedShaderPass.pass, material));
|
||||
pipelineKey.shader = resolvedShaderPass.shader;
|
||||
pipelineKey.passName = resolvedShaderPass.passName;
|
||||
pipelineKey.keywordSignature = ::XCEngine::Rendering::Detail::BuildShaderKeywordSignature(keywordSet);
|
||||
pipelineKey.renderTargetCount = renderTargetFormat != RHI::Format::Unknown ? 1u : 0u;
|
||||
pipelineKey.keywordSignature = ::XCEngine::Rendering::Internal::BuildShaderKeywordSignature(keywordSet);
|
||||
pipelineKey.renderTargetCount =
|
||||
::XCEngine::Rendering::Internal::HasSingleColorAttachment(surface) ? 1u : 0u;
|
||||
pipelineKey.renderTargetFormat = static_cast<uint32_t>(renderTargetFormat);
|
||||
pipelineKey.depthStencilFormat = static_cast<uint32_t>(depthStencilFormat);
|
||||
pipelineKey.sampleCount = ::XCEngine::Rendering::Internal::ResolveSurfaceSampleCount(surface);
|
||||
pipelineKey.sampleQuality = ::XCEngine::Rendering::Internal::ResolveSurfaceSampleQuality(surface);
|
||||
|
||||
const auto existing = m_pipelineStates.find(pipelineKey);
|
||||
if (existing != m_pipelineStates.end()) {
|
||||
return existing->second;
|
||||
}
|
||||
|
||||
const uint64_t pipelineStartMs = GetVolumeTraceSteadyMs();
|
||||
LogVolumeTraceRendering(
|
||||
"VolumetricPass pipeline create begin steady_ms=" + std::to_string(pipelineStartMs) +
|
||||
" shader=" + std::string(resolvedShaderPass.shader->GetPath().CStr()) +
|
||||
" pass=" + std::string(resolvedShaderPass.passName.CStr()));
|
||||
|
||||
const RHI::GraphicsPipelineDesc pipelineDesc =
|
||||
CreatePipelineDesc(
|
||||
context.backendType,
|
||||
@@ -450,18 +517,27 @@ RHI::RHIPipelineState* BuiltinVolumetricPass::GetOrCreatePipelineState(
|
||||
resolvedShaderPass.passName,
|
||||
keywordSet,
|
||||
material,
|
||||
renderTargetFormat,
|
||||
depthStencilFormat);
|
||||
surface);
|
||||
RHI::RHIPipelineState* pipelineState = context.device->CreatePipelineState(pipelineDesc);
|
||||
if (pipelineState == nullptr || !pipelineState->IsValid()) {
|
||||
if (pipelineState != nullptr) {
|
||||
pipelineState->Shutdown();
|
||||
delete pipelineState;
|
||||
}
|
||||
LogVolumeTraceRendering(
|
||||
"VolumetricPass pipeline create failed steady_ms=" + std::to_string(GetVolumeTraceSteadyMs()) +
|
||||
" shader=" + std::string(resolvedShaderPass.shader->GetPath().CStr()) +
|
||||
" pass=" + std::string(resolvedShaderPass.passName.CStr()));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
m_pipelineStates.emplace(pipelineKey, pipelineState);
|
||||
const uint64_t pipelineEndMs = GetVolumeTraceSteadyMs();
|
||||
LogVolumeTraceRendering(
|
||||
"VolumetricPass pipeline create end steady_ms=" + std::to_string(pipelineEndMs) +
|
||||
" total_ms=" + std::to_string(pipelineEndMs - pipelineStartMs) +
|
||||
" shader=" + std::string(resolvedShaderPass.shader->GetPath().CStr()) +
|
||||
" pass=" + std::string(resolvedShaderPass.passName.CStr()));
|
||||
return pipelineState;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
|
||||
namespace XCEngine {
|
||||
@@ -132,8 +134,12 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
|
||||
const Components::LightComponent& light,
|
||||
const DirectionalShadowPlanningSettings& shadowSettings,
|
||||
float viewportAspect) {
|
||||
std::fprintf(stderr, "[shadow-debug] enter BuildDirectionalShadowRenderPlan\n");
|
||||
std::fflush(stderr);
|
||||
DirectionalShadowRenderPlan plan = {};
|
||||
if (!light.GetCastsShadows()) {
|
||||
std::fprintf(stderr, "[shadow-debug] early out: light shadows disabled\n");
|
||||
std::fflush(stderr);
|
||||
return plan;
|
||||
}
|
||||
|
||||
@@ -167,6 +173,8 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
|
||||
shadowSettings.maxFocusDistance);
|
||||
const float sliceNear = std::max(camera.GetNearClipPlane(), 0.1f);
|
||||
const float sliceFar = std::max(sliceNear + 0.1f, shadowDistance);
|
||||
std::fprintf(stderr, "[shadow-debug] built slice distances\n");
|
||||
std::fflush(stderr);
|
||||
|
||||
std::array<Math::Vector3, 8> frustumCorners = {};
|
||||
if (camera.GetProjectionType() == Components::CameraProjectionType::Perspective) {
|
||||
@@ -208,6 +216,8 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
|
||||
focusPoint += corner;
|
||||
}
|
||||
focusPoint /= static_cast<float>(frustumCorners.size());
|
||||
std::fprintf(stderr, "[shadow-debug] built frustum corners\n");
|
||||
std::fflush(stderr);
|
||||
|
||||
Math::Bounds frustumWorldBounds(frustumCorners[0], Math::Vector3::Zero());
|
||||
for (size_t index = 1; index < frustumCorners.size(); ++index) {
|
||||
@@ -231,6 +241,8 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
|
||||
shadowWorldPosition,
|
||||
shadowRotation,
|
||||
Math::Vector3::One()).Inverse();
|
||||
std::fprintf(stderr, "[shadow-debug] built light view matrix\n");
|
||||
std::fflush(stderr);
|
||||
|
||||
float minX = std::numeric_limits<float>::max();
|
||||
float maxX = std::numeric_limits<float>::lowest();
|
||||
@@ -239,10 +251,14 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
|
||||
float minZ = std::numeric_limits<float>::max();
|
||||
float maxZ = std::numeric_limits<float>::lowest();
|
||||
ExpandLightSpaceBounds(view, frustumCorners, minX, maxX, minY, maxY, minZ, maxZ);
|
||||
std::fprintf(stderr, "[shadow-debug] expanded frustum bounds\n");
|
||||
std::fflush(stderr);
|
||||
|
||||
const uint32_t cullingMask = camera.GetCullingMask();
|
||||
const std::vector<Components::MeshFilterComponent*> meshFilters =
|
||||
scene.FindObjectsOfType<Components::MeshFilterComponent>();
|
||||
std::fprintf(stderr, "[shadow-debug] mesh filter count=%zu\n", meshFilters.size());
|
||||
std::fflush(stderr);
|
||||
for (Components::MeshFilterComponent* meshFilter : meshFilters) {
|
||||
if (meshFilter == nullptr ||
|
||||
!meshFilter->IsEnabled() ||
|
||||
@@ -284,6 +300,8 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
|
||||
|
||||
ExpandLightSpaceBounds(view, worldCorners, minX, maxX, minY, maxY, minZ, maxZ);
|
||||
}
|
||||
std::fprintf(stderr, "[shadow-debug] finished mesh bounds expansion\n");
|
||||
std::fflush(stderr);
|
||||
|
||||
minX -= shadowSettings.boundsPadding;
|
||||
maxX += shadowSettings.boundsPadding;
|
||||
@@ -303,6 +321,27 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
|
||||
maxZ = centerZ + halfDepthRange;
|
||||
}
|
||||
|
||||
const float shadowWidth = std::max(maxX - minX, Math::EPSILON);
|
||||
const float shadowHeight = std::max(maxY - minY, Math::EPSILON);
|
||||
const float shadowTexelSizeX = shadowWidth / static_cast<float>(shadowSettings.mapDimension);
|
||||
const float shadowTexelSizeY = shadowHeight / static_cast<float>(shadowSettings.mapDimension);
|
||||
if (shadowTexelSizeX > Math::EPSILON) {
|
||||
const float centerX = (minX + maxX) * 0.5f;
|
||||
const float halfWidth = shadowWidth * 0.5f;
|
||||
const float snappedCenterX =
|
||||
std::floor(centerX / shadowTexelSizeX + 0.5f) * shadowTexelSizeX;
|
||||
minX = snappedCenterX - halfWidth;
|
||||
maxX = snappedCenterX + halfWidth;
|
||||
}
|
||||
if (shadowTexelSizeY > Math::EPSILON) {
|
||||
const float centerY = (minY + maxY) * 0.5f;
|
||||
const float halfHeight = shadowHeight * 0.5f;
|
||||
const float snappedCenterY =
|
||||
std::floor(centerY / shadowTexelSizeY + 0.5f) * shadowTexelSizeY;
|
||||
minY = snappedCenterY - halfHeight;
|
||||
maxY = snappedCenterY + halfHeight;
|
||||
}
|
||||
|
||||
const Math::Matrix4x4 projection = Math::Matrix4x4::Orthographic(
|
||||
minX,
|
||||
maxX,
|
||||
@@ -310,11 +349,14 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
|
||||
maxY,
|
||||
minZ,
|
||||
maxZ);
|
||||
std::fprintf(stderr, "[shadow-debug] built orthographic projection\n");
|
||||
std::fflush(stderr);
|
||||
|
||||
plan.enabled = true;
|
||||
plan.lightDirection = lightDirection;
|
||||
plan.focusPoint = focusPoint;
|
||||
plan.orthographicHalfExtent = shadowHalfExtent;
|
||||
plan.texelWorldSize = std::max(shadowTexelSizeX, shadowTexelSizeY);
|
||||
plan.nearClipPlane = minZ;
|
||||
plan.farClipPlane = maxZ;
|
||||
plan.mapWidth = shadowSettings.mapDimension;
|
||||
@@ -327,6 +369,8 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
|
||||
plan.cameraData.clearFlags = RenderClearFlags::Depth;
|
||||
plan.cameraData.viewportWidth = plan.mapWidth;
|
||||
plan.cameraData.viewportHeight = plan.mapHeight;
|
||||
std::fprintf(stderr, "[shadow-debug] leave BuildDirectionalShadowRenderPlan\n");
|
||||
std::fflush(stderr);
|
||||
return plan;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,10 @@ namespace Rendering {
|
||||
|
||||
namespace {
|
||||
|
||||
uint32_t NormalizeSampleCount(uint32_t sampleCount) {
|
||||
return sampleCount > 0 ? sampleCount : 1u;
|
||||
}
|
||||
|
||||
Math::RectInt ClampRenderArea(
|
||||
const Math::RectInt& renderArea,
|
||||
uint32_t surfaceWidth,
|
||||
@@ -93,5 +97,10 @@ void RenderSurface::ClearClearColorOverride() {
|
||||
m_hasClearColorOverride = false;
|
||||
}
|
||||
|
||||
void RenderSurface::SetSampleDesc(uint32_t sampleCount, uint32_t sampleQuality) {
|
||||
m_sampleCount = NormalizeSampleCount(sampleCount);
|
||||
m_sampleQuality = m_sampleCount > 1u ? sampleQuality : 0u;
|
||||
}
|
||||
|
||||
} // namespace Rendering
|
||||
} // namespace XCEngine
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <XCEngine/Resources/GaussianSplat/GaussianSplatArtifactIO.h>
|
||||
|
||||
#include <XCEngine/Core/Asset/ArtifactContainer.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactFormats.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Resources/GaussianSplat/GaussianSplat.h>
|
||||
@@ -35,6 +36,45 @@ std::filesystem::path ResolveArtifactPath(const Containers::String& path) {
|
||||
return resolvedPath.lexically_normal();
|
||||
}
|
||||
|
||||
bool ReadArtifactFileData(const Containers::String& path,
|
||||
ResourceType resourceType,
|
||||
Containers::Array<Core::uint8>& outData) {
|
||||
outData.Clear();
|
||||
|
||||
const std::filesystem::path resolvedPath = ResolveArtifactPath(path);
|
||||
Containers::String containerError;
|
||||
if (ReadArtifactContainerPayloadByPath(
|
||||
Containers::String(resolvedPath.generic_string().c_str()),
|
||||
resourceType,
|
||||
outData,
|
||||
&containerError)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::ifstream input(resolvedPath, std::ios::binary | std::ios::ate);
|
||||
if (!input.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::streamsize size = input.tellg();
|
||||
if (size < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
input.seekg(0, std::ios::beg);
|
||||
outData.Resize(static_cast<size_t>(size));
|
||||
if (size == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!input.read(reinterpret_cast<char*>(outData.Data()), size)) {
|
||||
outData.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
LoadResult CreateOwnedGaussianSplatResource(const Containers::String& path,
|
||||
const GaussianSplatMetadata& metadata,
|
||||
Containers::Array<GaussianSplatSection>&& sections,
|
||||
@@ -58,6 +98,67 @@ LoadResult CreateOwnedGaussianSplatResource(const Containers::String& path,
|
||||
|
||||
} // namespace
|
||||
|
||||
bool SerializeGaussianSplatArtifactPayload(const GaussianSplat& gaussianSplat,
|
||||
Containers::Array<Core::uint8>& outPayload,
|
||||
Containers::String* outErrorMessage) {
|
||||
(void)outErrorMessage;
|
||||
|
||||
GaussianSplatArtifactFileHeader fileHeader;
|
||||
|
||||
const GaussianSplatMetadata& metadata = gaussianSplat.GetMetadata();
|
||||
GaussianSplatArtifactHeader header;
|
||||
header.contentVersion = metadata.contentVersion;
|
||||
header.splatCount = metadata.splatCount;
|
||||
header.chunkCount = metadata.chunkCount;
|
||||
header.cameraCount = metadata.cameraCount;
|
||||
header.boundsMin = metadata.bounds.GetMin();
|
||||
header.boundsMax = metadata.bounds.GetMax();
|
||||
header.positionFormat = static_cast<Core::uint32>(metadata.positionFormat);
|
||||
header.otherFormat = static_cast<Core::uint32>(metadata.otherFormat);
|
||||
header.colorFormat = static_cast<Core::uint32>(metadata.colorFormat);
|
||||
header.shFormat = static_cast<Core::uint32>(metadata.shFormat);
|
||||
header.chunkFormat = static_cast<Core::uint32>(metadata.chunkFormat);
|
||||
header.cameraFormat = static_cast<Core::uint32>(metadata.cameraFormat);
|
||||
header.sectionCount = static_cast<Core::uint32>(gaussianSplat.GetSections().Size());
|
||||
header.payloadSize = static_cast<Core::uint64>(gaussianSplat.GetPayloadSize());
|
||||
|
||||
const size_t sectionTableSize =
|
||||
gaussianSplat.GetSections().Size() * sizeof(GaussianSplatArtifactSectionRecord);
|
||||
const size_t totalSize =
|
||||
sizeof(fileHeader) +
|
||||
sizeof(header) +
|
||||
sectionTableSize +
|
||||
gaussianSplat.GetPayloadSize();
|
||||
|
||||
outPayload.Resize(totalSize);
|
||||
size_t writeOffset = 0;
|
||||
auto appendBytes = [&outPayload, &writeOffset](const void* source, size_t byteCount) {
|
||||
if (byteCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::memcpy(outPayload.Data() + writeOffset, source, byteCount);
|
||||
writeOffset += byteCount;
|
||||
};
|
||||
|
||||
appendBytes(&fileHeader, sizeof(fileHeader));
|
||||
appendBytes(&header, sizeof(header));
|
||||
|
||||
for (const GaussianSplatSection& section : gaussianSplat.GetSections()) {
|
||||
GaussianSplatArtifactSectionRecord sectionRecord;
|
||||
sectionRecord.sectionType = static_cast<Core::uint32>(section.type);
|
||||
sectionRecord.format = static_cast<Core::uint32>(section.format);
|
||||
sectionRecord.payloadOffset = section.dataOffset;
|
||||
sectionRecord.dataSize = section.dataSize;
|
||||
sectionRecord.elementCount = section.elementCount;
|
||||
sectionRecord.elementStride = section.elementStride;
|
||||
appendBytes(§ionRecord, sizeof(sectionRecord));
|
||||
}
|
||||
|
||||
appendBytes(gaussianSplat.GetPayloadData(), gaussianSplat.GetPayloadSize());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteGaussianSplatArtifactFile(const Containers::String& artifactPath,
|
||||
const GaussianSplat& gaussianSplat,
|
||||
Containers::String* outErrorMessage) {
|
||||
@@ -76,6 +177,11 @@ bool WriteGaussianSplatArtifactFile(const Containers::String& artifactPath,
|
||||
}
|
||||
}
|
||||
|
||||
Containers::Array<Core::uint8> payload;
|
||||
if (!SerializeGaussianSplatArtifactPayload(gaussianSplat, payload, outErrorMessage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream output(resolvedPath, std::ios::binary | std::ios::trunc);
|
||||
if (!output.is_open()) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
@@ -84,40 +190,8 @@ bool WriteGaussianSplatArtifactFile(const Containers::String& artifactPath,
|
||||
return false;
|
||||
}
|
||||
|
||||
GaussianSplatArtifactFileHeader fileHeader;
|
||||
output.write(reinterpret_cast<const char*>(&fileHeader), sizeof(fileHeader));
|
||||
|
||||
const GaussianSplatMetadata& metadata = gaussianSplat.GetMetadata();
|
||||
GaussianSplatArtifactHeader header;
|
||||
header.contentVersion = metadata.contentVersion;
|
||||
header.splatCount = metadata.splatCount;
|
||||
header.chunkCount = metadata.chunkCount;
|
||||
header.cameraCount = metadata.cameraCount;
|
||||
header.boundsMin = metadata.bounds.GetMin();
|
||||
header.boundsMax = metadata.bounds.GetMax();
|
||||
header.positionFormat = static_cast<Core::uint32>(metadata.positionFormat);
|
||||
header.otherFormat = static_cast<Core::uint32>(metadata.otherFormat);
|
||||
header.colorFormat = static_cast<Core::uint32>(metadata.colorFormat);
|
||||
header.shFormat = static_cast<Core::uint32>(metadata.shFormat);
|
||||
header.chunkFormat = static_cast<Core::uint32>(metadata.chunkFormat);
|
||||
header.cameraFormat = static_cast<Core::uint32>(metadata.cameraFormat);
|
||||
header.sectionCount = static_cast<Core::uint32>(gaussianSplat.GetSections().Size());
|
||||
header.payloadSize = static_cast<Core::uint64>(gaussianSplat.GetPayloadSize());
|
||||
output.write(reinterpret_cast<const char*>(&header), sizeof(header));
|
||||
|
||||
for (const GaussianSplatSection& section : gaussianSplat.GetSections()) {
|
||||
GaussianSplatArtifactSectionRecord sectionRecord;
|
||||
sectionRecord.sectionType = static_cast<Core::uint32>(section.type);
|
||||
sectionRecord.format = static_cast<Core::uint32>(section.format);
|
||||
sectionRecord.payloadOffset = section.dataOffset;
|
||||
sectionRecord.dataSize = section.dataSize;
|
||||
sectionRecord.elementCount = section.elementCount;
|
||||
sectionRecord.elementStride = section.elementStride;
|
||||
output.write(reinterpret_cast<const char*>(§ionRecord), sizeof(sectionRecord));
|
||||
}
|
||||
|
||||
if (gaussianSplat.GetPayloadSize() > 0) {
|
||||
output.write(reinterpret_cast<const char*>(gaussianSplat.GetPayloadData()), gaussianSplat.GetPayloadSize());
|
||||
if (!payload.Empty()) {
|
||||
output.write(reinterpret_cast<const char*>(payload.Data()), static_cast<std::streamsize>(payload.Size()));
|
||||
}
|
||||
|
||||
if (!output && outErrorMessage != nullptr) {
|
||||
@@ -128,16 +202,26 @@ bool WriteGaussianSplatArtifactFile(const Containers::String& artifactPath,
|
||||
}
|
||||
|
||||
LoadResult LoadGaussianSplatArtifact(const Containers::String& path) {
|
||||
const std::filesystem::path resolvedPath = ResolveArtifactPath(path);
|
||||
|
||||
std::ifstream input(resolvedPath, std::ios::binary);
|
||||
if (!input.is_open()) {
|
||||
Containers::Array<Core::uint8> data;
|
||||
if (!ReadArtifactFileData(path, ResourceType::GaussianSplat, data)) {
|
||||
return LoadResult(Containers::String("Failed to read GaussianSplat artifact: ") + path);
|
||||
}
|
||||
|
||||
size_t offset = 0;
|
||||
auto readBytes = [&data, &offset](void* destination, size_t byteCount) -> bool {
|
||||
if (offset + byteCount > data.Size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (byteCount > 0) {
|
||||
std::memcpy(destination, data.Data() + offset, byteCount);
|
||||
offset += byteCount;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
GaussianSplatArtifactFileHeader fileHeader;
|
||||
input.read(reinterpret_cast<char*>(&fileHeader), sizeof(fileHeader));
|
||||
if (!input) {
|
||||
if (!readBytes(&fileHeader, sizeof(fileHeader))) {
|
||||
return LoadResult(Containers::String("Failed to parse GaussianSplat artifact file header: ") + path);
|
||||
}
|
||||
|
||||
@@ -149,8 +233,7 @@ LoadResult LoadGaussianSplatArtifact(const Containers::String& path) {
|
||||
}
|
||||
|
||||
GaussianSplatArtifactHeader header;
|
||||
input.read(reinterpret_cast<char*>(&header), sizeof(header));
|
||||
if (!input) {
|
||||
if (!readBytes(&header, sizeof(header))) {
|
||||
return LoadResult(Containers::String("Failed to parse GaussianSplat artifact header: ") + path);
|
||||
}
|
||||
|
||||
@@ -158,8 +241,7 @@ LoadResult LoadGaussianSplatArtifact(const Containers::String& path) {
|
||||
sections.Reserve(header.sectionCount);
|
||||
for (Core::uint32 index = 0; index < header.sectionCount; ++index) {
|
||||
GaussianSplatArtifactSectionRecord sectionRecord;
|
||||
input.read(reinterpret_cast<char*>(§ionRecord), sizeof(sectionRecord));
|
||||
if (!input) {
|
||||
if (!readBytes(§ionRecord, sizeof(sectionRecord))) {
|
||||
return LoadResult(Containers::String("Failed to read GaussianSplat artifact section table: ") + path);
|
||||
}
|
||||
|
||||
@@ -175,11 +257,13 @@ LoadResult LoadGaussianSplatArtifact(const Containers::String& path) {
|
||||
|
||||
Containers::Array<Core::uint8> payload;
|
||||
payload.Resize(static_cast<size_t>(header.payloadSize));
|
||||
if (header.payloadSize > 0) {
|
||||
input.read(reinterpret_cast<char*>(payload.Data()), static_cast<std::streamsize>(header.payloadSize));
|
||||
if (!input) {
|
||||
return LoadResult(Containers::String("Failed to read GaussianSplat artifact payload: ") + path);
|
||||
}
|
||||
const size_t remainingBytes = data.Size() - offset;
|
||||
if (header.payloadSize > static_cast<Core::uint64>(remainingBytes)) {
|
||||
return LoadResult(Containers::String("Failed to read GaussianSplat artifact payload: ") + path);
|
||||
}
|
||||
if (header.payloadSize > 0 &&
|
||||
!readBytes(payload.Data(), static_cast<size_t>(header.payloadSize))) {
|
||||
return LoadResult(Containers::String("Failed to read GaussianSplat artifact payload: ") + path);
|
||||
}
|
||||
|
||||
GaussianSplatMetadata metadata;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <XCEngine/Resources/Material/MaterialLoader.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactContainer.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactFormats.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
@@ -19,6 +20,32 @@ namespace Resources {
|
||||
|
||||
namespace {
|
||||
|
||||
Containers::String GetPathExtensionString(const Containers::String& path) {
|
||||
const std::filesystem::path fsPath(path.CStr());
|
||||
const std::string extension = fsPath.has_extension()
|
||||
? fsPath.extension().string()
|
||||
: std::string();
|
||||
if (!extension.empty() && extension[0] == '.') {
|
||||
return Containers::String(extension.substr(1).c_str());
|
||||
}
|
||||
|
||||
return Containers::String(extension.c_str());
|
||||
}
|
||||
|
||||
Containers::String GetMaterialPathExtension(const Containers::String& path) {
|
||||
Containers::String containerPath;
|
||||
Containers::String entryName;
|
||||
if (TryParseArtifactContainerEntryPath(path, containerPath, entryName)) {
|
||||
const Containers::String entryExtension = GetPathExtensionString(entryName);
|
||||
if (!entryExtension.Empty()) {
|
||||
return entryExtension;
|
||||
}
|
||||
return GetPathExtensionString(containerPath);
|
||||
}
|
||||
|
||||
return GetPathExtensionString(path);
|
||||
}
|
||||
|
||||
std::string ToStdString(const Containers::Array<Core::uint8>& data) {
|
||||
return std::string(reinterpret_cast<const char*>(data.Data()), data.Size());
|
||||
}
|
||||
@@ -27,6 +54,17 @@ Containers::Array<Core::uint8> ReadMaterialArtifactFileData(const Containers::St
|
||||
Containers::Array<Core::uint8> data;
|
||||
|
||||
auto tryRead = [&data](const std::filesystem::path& filePath, bool& opened) {
|
||||
opened = false;
|
||||
Containers::String containerError;
|
||||
if (ReadArtifactContainerPayloadByPath(
|
||||
Containers::String(filePath.generic_string().c_str()),
|
||||
ResourceType::Material,
|
||||
data,
|
||||
&containerError)) {
|
||||
opened = true;
|
||||
return;
|
||||
}
|
||||
|
||||
std::ifstream file(filePath, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
opened = false;
|
||||
@@ -143,12 +181,27 @@ Containers::String ResolveArtifactDependencyPath(const Containers::String& depen
|
||||
}
|
||||
|
||||
std::filesystem::path dependencyFsPath(dependencyPath.CStr());
|
||||
Containers::String containerPathText;
|
||||
Containers::String entryName;
|
||||
const bool isContainerEntryPath =
|
||||
TryParseArtifactContainerEntryPath(dependencyPath, containerPathText, entryName);
|
||||
if (isContainerEntryPath) {
|
||||
dependencyFsPath = std::filesystem::path(containerPathText.CStr());
|
||||
}
|
||||
|
||||
auto rebuildResolvedPath = [&entryName, isContainerEntryPath](const std::filesystem::path& resolvedPath) {
|
||||
const Containers::String normalizedPath = NormalizePathString(resolvedPath);
|
||||
return isContainerEntryPath
|
||||
? BuildArtifactContainerEntryPath(normalizedPath, entryName)
|
||||
: normalizedPath;
|
||||
};
|
||||
|
||||
if (dependencyFsPath.is_absolute() && std::filesystem::exists(dependencyFsPath)) {
|
||||
return NormalizePathString(dependencyFsPath);
|
||||
return rebuildResolvedPath(dependencyFsPath);
|
||||
}
|
||||
|
||||
if (std::filesystem::exists(dependencyFsPath)) {
|
||||
return NormalizePathString(dependencyFsPath);
|
||||
return rebuildResolvedPath(dependencyFsPath);
|
||||
}
|
||||
|
||||
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
|
||||
@@ -156,7 +209,7 @@ Containers::String ResolveArtifactDependencyPath(const Containers::String& depen
|
||||
const std::filesystem::path projectRelativeCandidate =
|
||||
std::filesystem::path(resourceRoot.CStr()) / dependencyFsPath;
|
||||
if (std::filesystem::exists(projectRelativeCandidate)) {
|
||||
return NormalizePathString(projectRelativeCandidate);
|
||||
return rebuildResolvedPath(projectRelativeCandidate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +221,7 @@ Containers::String ResolveArtifactDependencyPath(const Containers::String& depen
|
||||
const std::filesystem::path ownerRelativeCandidate =
|
||||
ownerArtifactFsPath.parent_path() / dependencyFsPath;
|
||||
if (std::filesystem::exists(ownerRelativeCandidate)) {
|
||||
return NormalizePathString(ownerRelativeCandidate);
|
||||
return rebuildResolvedPath(ownerRelativeCandidate);
|
||||
}
|
||||
|
||||
std::filesystem::path current = ownerArtifactFsPath.parent_path();
|
||||
@@ -178,7 +231,7 @@ Containers::String ResolveArtifactDependencyPath(const Containers::String& depen
|
||||
if (!projectRoot.empty()) {
|
||||
const std::filesystem::path projectRelativeCandidate = projectRoot / dependencyFsPath;
|
||||
if (std::filesystem::exists(projectRelativeCandidate)) {
|
||||
return NormalizePathString(projectRelativeCandidate);
|
||||
return rebuildResolvedPath(projectRelativeCandidate);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -1800,7 +1853,7 @@ bool MaterialLoader::CanLoad(const Containers::String& path) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
Containers::String ext = GetExtension(path).ToLower();
|
||||
const Containers::String ext = GetMaterialPathExtension(path).ToLower();
|
||||
return ext == "mat" || ext == "material" || ext == "json" || ext == "xcmat";
|
||||
}
|
||||
|
||||
@@ -1811,7 +1864,7 @@ LoadResult MaterialLoader::Load(const Containers::String& path, const ImportSett
|
||||
return CreateBuiltinMaterialResource(path);
|
||||
}
|
||||
|
||||
const Containers::String ext = GetExtension(path).ToLower();
|
||||
const Containers::String ext = GetMaterialPathExtension(path).ToLower();
|
||||
if (ext == "xcmat") {
|
||||
return LoadMaterialArtifact(path);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,32 @@ namespace Resources {
|
||||
|
||||
namespace {
|
||||
|
||||
Containers::String GetPathExtensionString(const Containers::String& path) {
|
||||
const std::filesystem::path fsPath(path.CStr());
|
||||
const std::string extension = fsPath.has_extension()
|
||||
? fsPath.extension().string()
|
||||
: std::string();
|
||||
if (!extension.empty() && extension[0] == '.') {
|
||||
return Containers::String(extension.substr(1).c_str());
|
||||
}
|
||||
|
||||
return Containers::String(extension.c_str());
|
||||
}
|
||||
|
||||
Containers::String GetMeshPathExtension(const Containers::String& path) {
|
||||
Containers::String containerPath;
|
||||
Containers::String entryName;
|
||||
if (TryParseArtifactContainerEntryPath(path, containerPath, entryName)) {
|
||||
const Containers::String entryExtension = GetPathExtensionString(entryName);
|
||||
if (!entryExtension.Empty()) {
|
||||
return entryExtension;
|
||||
}
|
||||
return GetPathExtensionString(containerPath);
|
||||
}
|
||||
|
||||
return GetPathExtensionString(path);
|
||||
}
|
||||
|
||||
struct ImportedMeshData {
|
||||
std::vector<StaticMeshVertex> vertices;
|
||||
std::vector<Core::uint32> indices;
|
||||
@@ -1074,7 +1100,7 @@ bool MeshLoader::CanLoad(const Containers::String& path) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
Containers::String ext = GetExtension(path).ToLower();
|
||||
const Containers::String ext = GetMeshPathExtension(path).ToLower();
|
||||
|
||||
return ext == "fbx" || ext == "obj" || ext == "gltf" ||
|
||||
ext == "glb" || ext == "dae" || ext == "stl" ||
|
||||
@@ -1086,7 +1112,7 @@ LoadResult MeshLoader::Load(const Containers::String& path, const ImportSettings
|
||||
return CreateBuiltinMeshResource(path);
|
||||
}
|
||||
|
||||
const Containers::String ext = GetExtension(path).ToLower();
|
||||
const Containers::String ext = GetMeshPathExtension(path).ToLower();
|
||||
|
||||
if (!CanLoad(path)) {
|
||||
return LoadResult(Containers::String("Unsupported mesh format: ") + ext);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <XCEngine/Resources/Model/ModelArtifactIO.h>
|
||||
|
||||
#include <XCEngine/Core/Asset/ArtifactContainer.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactFormats.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Resources/Model/Model.h>
|
||||
@@ -7,6 +8,7 @@
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
@@ -35,7 +37,46 @@ std::filesystem::path ResolveArtifactPath(const Containers::String& path) {
|
||||
return resolvedPath.lexically_normal();
|
||||
}
|
||||
|
||||
void WriteString(std::ofstream& stream, const Containers::String& value) {
|
||||
bool ReadArtifactFileData(const Containers::String& path,
|
||||
ResourceType resourceType,
|
||||
Containers::Array<Core::uint8>& outData) {
|
||||
outData.Clear();
|
||||
|
||||
const std::filesystem::path resolvedPath = ResolveArtifactPath(path);
|
||||
Containers::String containerError;
|
||||
if (ReadArtifactContainerPayloadByPath(
|
||||
Containers::String(resolvedPath.generic_string().c_str()),
|
||||
resourceType,
|
||||
outData,
|
||||
&containerError)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::ifstream input(resolvedPath, std::ios::binary | std::ios::ate);
|
||||
if (!input.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::streamsize size = input.tellg();
|
||||
if (size < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
input.seekg(0, std::ios::beg);
|
||||
outData.Resize(static_cast<size_t>(size));
|
||||
if (size == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!input.read(reinterpret_cast<char*>(outData.Data()), size)) {
|
||||
outData.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WriteString(std::ostream& stream, const Containers::String& value) {
|
||||
const Core::uint32 length = static_cast<Core::uint32>(value.Length());
|
||||
stream.write(reinterpret_cast<const char*>(&length), sizeof(length));
|
||||
if (length > 0) {
|
||||
@@ -43,20 +84,65 @@ void WriteString(std::ofstream& stream, const Containers::String& value) {
|
||||
}
|
||||
}
|
||||
|
||||
Containers::String ReadString(std::ifstream& stream) {
|
||||
bool ReadString(const Containers::Array<Core::uint8>& data,
|
||||
size_t& offset,
|
||||
Containers::String& outValue) {
|
||||
Core::uint32 length = 0;
|
||||
stream.read(reinterpret_cast<char*>(&length), sizeof(length));
|
||||
if (!stream || length == 0) {
|
||||
return Containers::String();
|
||||
if (offset + sizeof(length) > data.Size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string buffer(length, '\0');
|
||||
stream.read(buffer.data(), length);
|
||||
if (!stream) {
|
||||
return Containers::String();
|
||||
std::memcpy(&length, data.Data() + offset, sizeof(length));
|
||||
offset += sizeof(length);
|
||||
if (length == 0) {
|
||||
outValue.Clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
return Containers::String(buffer.c_str());
|
||||
if (offset + length > data.Size()) {
|
||||
outValue.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue = Containers::String(reinterpret_cast<const char*>(data.Data() + offset), length);
|
||||
offset += length;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool ReadValue(const Containers::Array<Core::uint8>& data, size_t& offset, T& outValue) {
|
||||
if (offset + sizeof(T) > data.Size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::memcpy(&outValue, data.Data() + offset, sizeof(T));
|
||||
offset += sizeof(T);
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ModelArtifactHeader BuildModelArtifactHeader(const Model& model) {
|
||||
ModelArtifactHeader header;
|
||||
header.nodeCount = static_cast<Core::uint32>(model.GetNodes().Size());
|
||||
header.meshBindingCount = static_cast<Core::uint32>(model.GetMeshBindings().Size());
|
||||
header.materialBindingCount = static_cast<Core::uint32>(model.GetMaterialBindings().Size());
|
||||
header.rootNodeIndex = model.GetRootNodeIndex();
|
||||
return header;
|
||||
}
|
||||
|
||||
bool ValidateModelArtifactHeader(const ModelArtifactHeader& header) {
|
||||
return header.rootNodeIndex == kInvalidModelNodeIndex ||
|
||||
header.rootNodeIndex < header.nodeCount;
|
||||
}
|
||||
|
||||
LoadResult CreateOwnedModelResource(const Containers::String& path,
|
||||
@@ -94,40 +180,17 @@ LoadResult CreateOwnedModelResource(const Containers::String& path,
|
||||
|
||||
} // namespace
|
||||
|
||||
bool WriteModelArtifactFile(const Containers::String& artifactPath,
|
||||
const Model& model,
|
||||
Containers::String* outErrorMessage) {
|
||||
const std::filesystem::path resolvedPath = ResolveArtifactPath(artifactPath);
|
||||
std::error_code ec;
|
||||
const std::filesystem::path parentPath = resolvedPath.parent_path();
|
||||
if (!parentPath.empty()) {
|
||||
std::filesystem::create_directories(parentPath, ec);
|
||||
if (ec) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage =
|
||||
Containers::String("Failed to create model artifact directory: ") +
|
||||
Containers::String(parentPath.generic_string().c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool SerializeModelArtifactPayload(const Model& model,
|
||||
Containers::Array<Core::uint8>& outPayload,
|
||||
Containers::String* outErrorMessage) {
|
||||
(void)outErrorMessage;
|
||||
|
||||
std::ofstream output(resolvedPath, std::ios::binary | std::ios::trunc);
|
||||
if (!output.is_open()) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = Containers::String("Failed to open model artifact for write: ") + artifactPath;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
std::ostringstream output(std::ios::binary | std::ios::out);
|
||||
|
||||
ModelArtifactFileHeader fileHeader;
|
||||
output.write(reinterpret_cast<const char*>(&fileHeader), sizeof(fileHeader));
|
||||
|
||||
ModelArtifactHeader header;
|
||||
header.nodeCount = static_cast<Core::uint32>(model.GetNodes().Size());
|
||||
header.meshBindingCount = static_cast<Core::uint32>(model.GetMeshBindings().Size());
|
||||
header.materialBindingCount = static_cast<Core::uint32>(model.GetMaterialBindings().Size());
|
||||
header.rootNodeIndex = model.GetRootNodeIndex();
|
||||
const ModelArtifactHeader header = BuildModelArtifactHeader(model);
|
||||
output.write(reinterpret_cast<const char*>(&header), sizeof(header));
|
||||
|
||||
for (const ModelNode& node : model.GetNodes()) {
|
||||
@@ -158,27 +221,25 @@ bool WriteModelArtifactFile(const Containers::String& artifactPath,
|
||||
output.write(reinterpret_cast<const char*>(&bindingArtifact), sizeof(bindingArtifact));
|
||||
}
|
||||
|
||||
if (!output && outErrorMessage != nullptr) {
|
||||
*outErrorMessage = Containers::String("Failed to write model artifact: ") + artifactPath;
|
||||
if (!output) {
|
||||
outPayload.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
return static_cast<bool>(output);
|
||||
outPayload = ToByteArray(output.str());
|
||||
return true;
|
||||
}
|
||||
|
||||
LoadResult LoadModelArtifact(const Containers::String& path) {
|
||||
const std::filesystem::path resolvedPath = ResolveArtifactPath(path);
|
||||
namespace {
|
||||
|
||||
std::ifstream input(resolvedPath, std::ios::binary);
|
||||
if (!input.is_open()) {
|
||||
return LoadResult(Containers::String("Failed to read model artifact: ") + path);
|
||||
}
|
||||
LoadResult ParseModelArtifactPayload(const Containers::String& path,
|
||||
const Containers::Array<Core::uint8>& data) {
|
||||
size_t offset = 0;
|
||||
|
||||
ModelArtifactFileHeader fileHeader;
|
||||
input.read(reinterpret_cast<char*>(&fileHeader), sizeof(fileHeader));
|
||||
if (!input) {
|
||||
if (!ReadValue(data, offset, fileHeader)) {
|
||||
return LoadResult(Containers::String("Failed to parse model artifact file header: ") + path);
|
||||
}
|
||||
|
||||
const bool validFileHeader =
|
||||
std::memcmp(fileHeader.magic, "XCMOD01", 7) == 0 &&
|
||||
fileHeader.schemaVersion == kModelArtifactSchemaVersion;
|
||||
@@ -187,13 +248,11 @@ LoadResult LoadModelArtifact(const Containers::String& path) {
|
||||
}
|
||||
|
||||
ModelArtifactHeader header;
|
||||
input.read(reinterpret_cast<char*>(&header), sizeof(header));
|
||||
if (!input) {
|
||||
if (!ReadValue(data, offset, header)) {
|
||||
return LoadResult(Containers::String("Failed to parse model artifact header: ") + path);
|
||||
}
|
||||
|
||||
if (header.rootNodeIndex != kInvalidModelNodeIndex &&
|
||||
header.rootNodeIndex >= header.nodeCount) {
|
||||
if (!ValidateModelArtifactHeader(header)) {
|
||||
return LoadResult(Containers::String("Invalid model artifact root node index: ") + path);
|
||||
}
|
||||
|
||||
@@ -201,11 +260,12 @@ LoadResult LoadModelArtifact(const Containers::String& path) {
|
||||
nodes.Reserve(header.nodeCount);
|
||||
for (Core::uint32 index = 0; index < header.nodeCount; ++index) {
|
||||
ModelNode node;
|
||||
node.name = ReadString(input);
|
||||
if (!ReadString(data, offset, node.name)) {
|
||||
return LoadResult(Containers::String("Failed to read model node artifact name: ") + path);
|
||||
}
|
||||
|
||||
ModelNodeArtifactHeader nodeHeader;
|
||||
input.read(reinterpret_cast<char*>(&nodeHeader), sizeof(nodeHeader));
|
||||
if (!input) {
|
||||
if (!ReadValue(data, offset, nodeHeader)) {
|
||||
return LoadResult(Containers::String("Failed to read model node artifact: ") + path);
|
||||
}
|
||||
|
||||
@@ -222,8 +282,7 @@ LoadResult LoadModelArtifact(const Containers::String& path) {
|
||||
meshBindings.Reserve(header.meshBindingCount);
|
||||
for (Core::uint32 index = 0; index < header.meshBindingCount; ++index) {
|
||||
ModelMeshBindingArtifact bindingArtifact;
|
||||
input.read(reinterpret_cast<char*>(&bindingArtifact), sizeof(bindingArtifact));
|
||||
if (!input) {
|
||||
if (!ReadValue(data, offset, bindingArtifact)) {
|
||||
return LoadResult(Containers::String("Failed to read model mesh binding artifact: ") + path);
|
||||
}
|
||||
|
||||
@@ -238,8 +297,7 @@ LoadResult LoadModelArtifact(const Containers::String& path) {
|
||||
materialBindings.Reserve(header.materialBindingCount);
|
||||
for (Core::uint32 index = 0; index < header.materialBindingCount; ++index) {
|
||||
ModelMaterialBindingArtifact bindingArtifact;
|
||||
input.read(reinterpret_cast<char*>(&bindingArtifact), sizeof(bindingArtifact));
|
||||
if (!input) {
|
||||
if (!ReadValue(data, offset, bindingArtifact)) {
|
||||
return LoadResult(Containers::String("Failed to read model material binding artifact: ") + path);
|
||||
}
|
||||
|
||||
@@ -257,5 +315,57 @@ LoadResult LoadModelArtifact(const Containers::String& path) {
|
||||
std::move(materialBindings));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool WriteModelArtifactFile(const Containers::String& artifactPath,
|
||||
const Model& model,
|
||||
Containers::String* outErrorMessage) {
|
||||
const std::filesystem::path resolvedPath = ResolveArtifactPath(artifactPath);
|
||||
std::error_code ec;
|
||||
const std::filesystem::path parentPath = resolvedPath.parent_path();
|
||||
if (!parentPath.empty()) {
|
||||
std::filesystem::create_directories(parentPath, ec);
|
||||
if (ec) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage =
|
||||
Containers::String("Failed to create model artifact directory: ") +
|
||||
Containers::String(parentPath.generic_string().c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Containers::Array<Core::uint8> payload;
|
||||
if (!SerializeModelArtifactPayload(model, payload, outErrorMessage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream output(resolvedPath, std::ios::binary | std::ios::trunc);
|
||||
if (!output.is_open()) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = Containers::String("Failed to open model artifact for write: ") + artifactPath;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!payload.Empty()) {
|
||||
output.write(reinterpret_cast<const char*>(payload.Data()), static_cast<std::streamsize>(payload.Size()));
|
||||
}
|
||||
|
||||
if (!output && outErrorMessage != nullptr) {
|
||||
*outErrorMessage = Containers::String("Failed to write model artifact: ") + artifactPath;
|
||||
}
|
||||
return static_cast<bool>(output);
|
||||
}
|
||||
|
||||
LoadResult LoadModelArtifact(const Containers::String& path) {
|
||||
Containers::Array<Core::uint8> data;
|
||||
if (!ReadArtifactFileData(path, ResourceType::Model, data)) {
|
||||
return LoadResult(Containers::String("Failed to read model artifact: ") + path);
|
||||
}
|
||||
|
||||
return ParseModelArtifactPayload(path, data);
|
||||
}
|
||||
|
||||
} // namespace Resources
|
||||
} // namespace XCEngine
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "ShaderFileUtils.h"
|
||||
|
||||
#include <XCEngine/Core/Asset/ArtifactContainer.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
|
||||
#include <filesystem>
|
||||
@@ -15,6 +16,16 @@ Containers::Array<Core::uint8> TryReadFileData(
|
||||
bool& opened) {
|
||||
Containers::Array<Core::uint8> data;
|
||||
|
||||
Containers::String payloadError;
|
||||
if (ReadArtifactContainerPayloadByPath(
|
||||
Containers::String(filePath.generic_string().c_str()),
|
||||
ResourceType::Shader,
|
||||
data,
|
||||
&payloadError)) {
|
||||
opened = true;
|
||||
return data;
|
||||
}
|
||||
|
||||
std::ifstream file(filePath, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
opened = false;
|
||||
@@ -69,9 +80,16 @@ bool ReadShaderTextFile(const Containers::String& path, Containers::String& outT
|
||||
}
|
||||
|
||||
Containers::String GetShaderPathExtension(const Containers::String& path) {
|
||||
Containers::String normalizedPath = path;
|
||||
Containers::String containerPath;
|
||||
Containers::String entryName;
|
||||
if (TryParseArtifactContainerEntryPath(path, containerPath, entryName)) {
|
||||
normalizedPath = containerPath;
|
||||
}
|
||||
|
||||
size_t dotPos = Containers::String::npos;
|
||||
for (size_t i = path.Length(); i > 0; --i) {
|
||||
if (path[i - 1] == '.') {
|
||||
for (size_t i = normalizedPath.Length(); i > 0; --i) {
|
||||
if (normalizedPath[i - 1] == '.') {
|
||||
dotPos = i - 1;
|
||||
break;
|
||||
}
|
||||
@@ -81,7 +99,7 @@ Containers::String GetShaderPathExtension(const Containers::String& path) {
|
||||
return Containers::String();
|
||||
}
|
||||
|
||||
return path.Substring(dotPos + 1);
|
||||
return normalizedPath.Substring(dotPos + 1);
|
||||
}
|
||||
|
||||
ShaderType DetectShaderTypeFromPath(const Containers::String& path) {
|
||||
|
||||
388
engine/src/Resources/Shader/ShaderCompilationCache.cpp
Normal file
388
engine/src/Resources/Shader/ShaderCompilationCache.cpp
Normal file
@@ -0,0 +1,388 @@
|
||||
#include <XCEngine/Resources/Shader/ShaderCompilationCache.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Resources {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace {
|
||||
|
||||
struct ShaderCompilationCacheFileHeader {
|
||||
char magic[8] = { 'X', 'C', 'B', 'C', '0', '1', '\0', '\0' };
|
||||
Core::uint32 schemaVersion = kShaderCompilationCacheSchemaVersion;
|
||||
Core::uint32 backend = 0;
|
||||
Core::uint32 format = 0;
|
||||
Core::uint32 reserved = 0;
|
||||
Core::uint64 payloadSize = 0;
|
||||
Core::uint64 compileKeyHigh = 0;
|
||||
Core::uint64 compileKeyLow = 0;
|
||||
};
|
||||
|
||||
Containers::String MakeError(const char* message) {
|
||||
return Containers::String(message == nullptr ? "" : message);
|
||||
}
|
||||
|
||||
std::string ToStdString(const Containers::String& text) {
|
||||
return std::string(text.CStr(), text.Length());
|
||||
}
|
||||
|
||||
Containers::String ToContainersString(const std::string& text) {
|
||||
return Containers::String(text.c_str(), text.size());
|
||||
}
|
||||
|
||||
std::vector<std::string> SplitFields(const std::string& line) {
|
||||
std::vector<std::string> fields;
|
||||
std::stringstream stream(line);
|
||||
std::string field;
|
||||
while (std::getline(stream, field, '\t')) {
|
||||
fields.push_back(field);
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
bool IsExpectedMagic(const ShaderCompilationCacheFileHeader& header) {
|
||||
return std::memcmp(header.magic, "XCBC01", 6) == 0 &&
|
||||
header.schemaVersion == kShaderCompilationCacheSchemaVersion;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ShaderCompileKey::Normalize() {
|
||||
shaderPath = shaderPath.Trim();
|
||||
sourceHash = sourceHash.Trim();
|
||||
dependencyHash = dependencyHash.Trim();
|
||||
passName = passName.Trim();
|
||||
entryPoint = entryPoint.Trim();
|
||||
profile = profile.Trim();
|
||||
compilerName = compilerName.Trim();
|
||||
compilerVersion = compilerVersion.Trim();
|
||||
optionsSignature = optionsSignature.Trim();
|
||||
|
||||
std::vector<std::string> normalizedKeywords;
|
||||
normalizedKeywords.reserve(keywords.Size());
|
||||
for (const Containers::String& keyword : keywords) {
|
||||
const Containers::String trimmedKeyword = keyword.Trim();
|
||||
if (!trimmedKeyword.Empty()) {
|
||||
normalizedKeywords.push_back(ToStdString(trimmedKeyword));
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(normalizedKeywords.begin(), normalizedKeywords.end());
|
||||
normalizedKeywords.erase(
|
||||
std::unique(normalizedKeywords.begin(), normalizedKeywords.end()),
|
||||
normalizedKeywords.end());
|
||||
|
||||
Containers::Array<Containers::String> canonicalKeywords;
|
||||
canonicalKeywords.Reserve(normalizedKeywords.size());
|
||||
for (const std::string& keyword : normalizedKeywords) {
|
||||
canonicalKeywords.PushBack(ToContainersString(keyword));
|
||||
}
|
||||
keywords = std::move(canonicalKeywords);
|
||||
}
|
||||
|
||||
Containers::String ShaderCompileKey::BuildSignature() const {
|
||||
ShaderCompileKey normalizedKey = *this;
|
||||
normalizedKey.Normalize();
|
||||
|
||||
Containers::String signature;
|
||||
signature += "shader=";
|
||||
signature += normalizedKey.shaderPath;
|
||||
signature += "\nsourceHash=";
|
||||
signature += normalizedKey.sourceHash;
|
||||
signature += "\ndependencyHash=";
|
||||
signature += normalizedKey.dependencyHash;
|
||||
signature += "\npass=";
|
||||
signature += normalizedKey.passName;
|
||||
signature += "\nentry=";
|
||||
signature += normalizedKey.entryPoint;
|
||||
signature += "\nprofile=";
|
||||
signature += normalizedKey.profile;
|
||||
signature += "\ncompiler=";
|
||||
signature += normalizedKey.compilerName;
|
||||
signature += "\ncompilerVersion=";
|
||||
signature += normalizedKey.compilerVersion;
|
||||
signature += "\noptions=";
|
||||
signature += normalizedKey.optionsSignature;
|
||||
signature += "\nstage=";
|
||||
signature += ToContainersString(std::to_string(static_cast<Core::uint32>(normalizedKey.stage)));
|
||||
signature += "\nsourceLanguage=";
|
||||
signature += ToContainersString(std::to_string(static_cast<Core::uint32>(normalizedKey.sourceLanguage)));
|
||||
signature += "\nbackend=";
|
||||
signature += ToContainersString(std::to_string(static_cast<Core::uint32>(normalizedKey.backend)));
|
||||
|
||||
for (const Containers::String& keyword : normalizedKey.keywords) {
|
||||
signature += "\nkeyword=";
|
||||
signature += keyword;
|
||||
}
|
||||
|
||||
return signature;
|
||||
}
|
||||
|
||||
Containers::String ShaderCompileKey::BuildCacheKey() const {
|
||||
return HashStringToAssetGUID(BuildSignature()).ToString();
|
||||
}
|
||||
|
||||
void ShaderCompilationCache::Initialize(const Containers::String& libraryRoot) {
|
||||
m_libraryRoot = libraryRoot.Trim();
|
||||
m_databasePath = m_libraryRoot + "/shadercache.db";
|
||||
LoadDatabase();
|
||||
}
|
||||
|
||||
void ShaderCompilationCache::Shutdown() {
|
||||
m_libraryRoot.Clear();
|
||||
m_databasePath.Clear();
|
||||
m_records.clear();
|
||||
}
|
||||
|
||||
Containers::String ShaderCompilationCache::BuildCacheKey(const ShaderCompileKey& key) const {
|
||||
return key.BuildCacheKey();
|
||||
}
|
||||
|
||||
Containers::String ShaderCompilationCache::BuildBackendDirectoryName(ShaderBackend backend) {
|
||||
switch (backend) {
|
||||
case ShaderBackend::D3D12:
|
||||
return Containers::String("D3D12");
|
||||
case ShaderBackend::OpenGL:
|
||||
return Containers::String("OpenGL");
|
||||
case ShaderBackend::Vulkan:
|
||||
return Containers::String("Vulkan");
|
||||
case ShaderBackend::Generic:
|
||||
default:
|
||||
return Containers::String("Generic");
|
||||
}
|
||||
}
|
||||
|
||||
AssetGUID ShaderCompilationCache::ComputeKeyGuid(const Containers::String& cacheKey) {
|
||||
AssetGUID guid;
|
||||
if (AssetGUID::TryParse(cacheKey, guid)) {
|
||||
return guid;
|
||||
}
|
||||
return HashStringToAssetGUID(cacheKey);
|
||||
}
|
||||
|
||||
Containers::String ShaderCompilationCache::BuildCacheRelativePath(const ShaderCompileKey& key) const {
|
||||
const Containers::String cacheKey = BuildCacheKey(key);
|
||||
const Containers::String shard =
|
||||
cacheKey.Length() >= 2 ? cacheKey.Substring(0, 2) : Containers::String("00");
|
||||
return Containers::String("ShaderCache/") +
|
||||
BuildBackendDirectoryName(key.backend) +
|
||||
"/" +
|
||||
shard +
|
||||
"/" +
|
||||
cacheKey +
|
||||
".xcbc";
|
||||
}
|
||||
|
||||
Containers::String ShaderCompilationCache::BuildCacheAbsolutePath(const ShaderCompileKey& key) const {
|
||||
if (m_libraryRoot.Empty()) {
|
||||
return Containers::String();
|
||||
}
|
||||
|
||||
return m_libraryRoot + "/" + BuildCacheRelativePath(key);
|
||||
}
|
||||
|
||||
void ShaderCompilationCache::LoadDatabase() {
|
||||
m_records.clear();
|
||||
|
||||
std::ifstream input(m_databasePath.CStr());
|
||||
if (!input.is_open()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string line;
|
||||
while (std::getline(input, line)) {
|
||||
if (line.empty() || line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::vector<std::string> fields = SplitFields(line);
|
||||
if (fields.size() < 5) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ShaderCacheRecord record;
|
||||
record.backend = static_cast<ShaderBackend>(std::stoul(fields[1]));
|
||||
record.relativePath = ToContainersString(fields[2]);
|
||||
record.format = static_cast<ShaderBytecodeFormat>(std::stoul(fields[3]));
|
||||
record.payloadSize = static_cast<Core::uint64>(std::stoull(fields[4]));
|
||||
m_records[fields[0]] = record;
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderCompilationCache::SaveDatabase() const {
|
||||
if (m_databasePath.Empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fs::path dbPath(m_databasePath.CStr());
|
||||
std::error_code ec;
|
||||
if (!dbPath.parent_path().empty()) {
|
||||
fs::create_directories(dbPath.parent_path(), ec);
|
||||
if (ec) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::ofstream output(dbPath, std::ios::out | std::ios::trunc);
|
||||
if (!output.is_open()) {
|
||||
return;
|
||||
}
|
||||
|
||||
output << "# compileKey\tbackend\trelativePath\tformat\tpayloadSize\n";
|
||||
for (const auto& [compileKey, record] : m_records) {
|
||||
output << compileKey << '\t'
|
||||
<< static_cast<Core::uint32>(record.backend) << '\t'
|
||||
<< ToStdString(record.relativePath) << '\t'
|
||||
<< static_cast<Core::uint32>(record.format) << '\t'
|
||||
<< record.payloadSize << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
bool ShaderCompilationCache::Store(const ShaderCacheEntry& entry,
|
||||
Containers::String* outErrorMessage) {
|
||||
if (!IsInitialized()) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = MakeError("ShaderCompilationCache is not initialized.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const Containers::String cacheKey = BuildCacheKey(entry.key);
|
||||
const Containers::String relativePath = BuildCacheRelativePath(entry.key);
|
||||
const Containers::String absolutePath = BuildCacheAbsolutePath(entry.key);
|
||||
if (absolutePath.Empty()) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = MakeError("ShaderCompilationCache absolute path is empty.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
const fs::path cachePath(absolutePath.CStr());
|
||||
fs::create_directories(cachePath.parent_path(), ec);
|
||||
if (ec) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = MakeError("Failed to create ShaderCompilationCache directory.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream output(cachePath, std::ios::binary | std::ios::trunc);
|
||||
if (!output.is_open()) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = MakeError("Failed to open ShaderCompilationCache output file.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const AssetGUID keyGuid = ComputeKeyGuid(cacheKey);
|
||||
ShaderCompilationCacheFileHeader fileHeader = {};
|
||||
fileHeader.backend = static_cast<Core::uint32>(entry.key.backend);
|
||||
fileHeader.format = static_cast<Core::uint32>(entry.format);
|
||||
fileHeader.payloadSize = static_cast<Core::uint64>(entry.payload.Size());
|
||||
fileHeader.compileKeyHigh = keyGuid.high;
|
||||
fileHeader.compileKeyLow = keyGuid.low;
|
||||
output.write(reinterpret_cast<const char*>(&fileHeader), sizeof(fileHeader));
|
||||
if (!output) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = MakeError("Failed to write ShaderCompilationCache header.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!entry.payload.Empty()) {
|
||||
output.write(reinterpret_cast<const char*>(entry.payload.Data()),
|
||||
static_cast<std::streamsize>(entry.payload.Size()));
|
||||
if (!output) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = MakeError("Failed to write ShaderCompilationCache payload.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ShaderCacheRecord record;
|
||||
record.backend = entry.key.backend;
|
||||
record.format = entry.format;
|
||||
record.relativePath = relativePath;
|
||||
record.payloadSize = static_cast<Core::uint64>(entry.payload.Size());
|
||||
m_records[ToStdString(cacheKey)] = record;
|
||||
SaveDatabase();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShaderCompilationCache::TryLoad(const ShaderCompileKey& key,
|
||||
ShaderCacheEntry& outEntry,
|
||||
Containers::String* outErrorMessage) const {
|
||||
outEntry = {};
|
||||
if (!IsInitialized()) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = MakeError("ShaderCompilationCache is not initialized.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const Containers::String cacheKey = BuildCacheKey(key);
|
||||
Containers::String relativePath = BuildCacheRelativePath(key);
|
||||
const auto recordIt = m_records.find(ToStdString(cacheKey));
|
||||
if (recordIt != m_records.end() && !recordIt->second.relativePath.Empty()) {
|
||||
relativePath = recordIt->second.relativePath;
|
||||
}
|
||||
|
||||
const Containers::String absolutePath = m_libraryRoot + "/" + relativePath;
|
||||
std::ifstream input(absolutePath.CStr(), std::ios::binary);
|
||||
if (!input.is_open()) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = MakeError("ShaderCompilationCache entry does not exist.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ShaderCompilationCacheFileHeader fileHeader = {};
|
||||
input.read(reinterpret_cast<char*>(&fileHeader), sizeof(fileHeader));
|
||||
if (!input || !IsExpectedMagic(fileHeader)) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = MakeError("ShaderCompilationCache header is invalid.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const AssetGUID expectedKeyGuid = ComputeKeyGuid(cacheKey);
|
||||
const AssetGUID actualKeyGuid(fileHeader.compileKeyHigh, fileHeader.compileKeyLow);
|
||||
if (expectedKeyGuid != actualKeyGuid ||
|
||||
fileHeader.backend != static_cast<Core::uint32>(key.backend)) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = MakeError("ShaderCompilationCache entry does not match the requested key.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Containers::Array<Core::uint8> payload;
|
||||
payload.ResizeUninitialized(static_cast<size_t>(fileHeader.payloadSize));
|
||||
if (fileHeader.payloadSize > 0) {
|
||||
input.read(reinterpret_cast<char*>(payload.Data()),
|
||||
static_cast<std::streamsize>(fileHeader.payloadSize));
|
||||
if (!input) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = MakeError("Failed to read ShaderCompilationCache payload.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
outEntry.key = key;
|
||||
outEntry.format = static_cast<ShaderBytecodeFormat>(fileHeader.format);
|
||||
outEntry.payload = std::move(payload);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Resources
|
||||
} // namespace XCEngine
|
||||
@@ -1,9 +1,11 @@
|
||||
#include <XCEngine/Resources/Texture/TextureLoader.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactContainer.h>
|
||||
#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 <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
@@ -12,6 +14,32 @@ namespace Resources {
|
||||
|
||||
namespace {
|
||||
|
||||
Containers::String GetPathExtensionString(const Containers::String& path) {
|
||||
const std::filesystem::path fsPath(path.CStr());
|
||||
const std::string extension = fsPath.has_extension()
|
||||
? fsPath.extension().string()
|
||||
: std::string();
|
||||
if (!extension.empty() && extension[0] == '.') {
|
||||
return Containers::String(extension.substr(1).c_str());
|
||||
}
|
||||
|
||||
return Containers::String(extension.c_str());
|
||||
}
|
||||
|
||||
Containers::String GetTexturePathExtension(const Containers::String& path) {
|
||||
Containers::String containerPath;
|
||||
Containers::String entryName;
|
||||
if (TryParseArtifactContainerEntryPath(path, containerPath, entryName)) {
|
||||
const Containers::String entryExtension = GetPathExtensionString(entryName);
|
||||
if (!entryExtension.Empty()) {
|
||||
return entryExtension;
|
||||
}
|
||||
return GetPathExtensionString(containerPath);
|
||||
}
|
||||
|
||||
return GetPathExtensionString(path);
|
||||
}
|
||||
|
||||
Containers::String GetResourceNameFromPath(const Containers::String& path) {
|
||||
const std::filesystem::path filePath(path.CStr());
|
||||
const std::string fileName = filePath.filename().string();
|
||||
@@ -76,37 +104,76 @@ TextureFormat ResolveDecodedTextureFormat(const ImportSettings* settings, bool i
|
||||
}
|
||||
|
||||
LoadResult LoadTextureArtifact(const Containers::String& path) {
|
||||
auto readFileData = [](const std::filesystem::path& filePath,
|
||||
Containers::Array<Core::uint8>& outData,
|
||||
bool& opened) {
|
||||
opened = false;
|
||||
outData.Clear();
|
||||
|
||||
Containers::String payloadError;
|
||||
if (ReadArtifactContainerPayloadByPath(
|
||||
Containers::String(filePath.generic_string().c_str()),
|
||||
ResourceType::Texture,
|
||||
outData,
|
||||
&payloadError)) {
|
||||
opened = true;
|
||||
return;
|
||||
}
|
||||
|
||||
std::ifstream input(filePath, std::ios::binary | std::ios::ate);
|
||||
if (!input.is_open()) {
|
||||
return;
|
||||
}
|
||||
|
||||
opened = true;
|
||||
const std::streamsize size = input.tellg();
|
||||
if (size <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
input.seekg(0, std::ios::beg);
|
||||
outData.Resize(static_cast<size_t>(size));
|
||||
if (!input.read(reinterpret_cast<char*>(outData.Data()), size)) {
|
||||
outData.Clear();
|
||||
}
|
||||
};
|
||||
|
||||
Containers::Array<Core::uint8> data;
|
||||
bool opened = false;
|
||||
std::filesystem::path resolvedPath(path.CStr());
|
||||
if (!resolvedPath.is_absolute() && !std::filesystem::exists(resolvedPath)) {
|
||||
readFileData(resolvedPath, data, opened);
|
||||
if (!opened && !resolvedPath.is_absolute()) {
|
||||
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
|
||||
if (!resourceRoot.Empty()) {
|
||||
resolvedPath = std::filesystem::path(resourceRoot.CStr()) / resolvedPath;
|
||||
readFileData(resolvedPath, data, opened);
|
||||
}
|
||||
}
|
||||
|
||||
std::ifstream input(resolvedPath, std::ios::binary);
|
||||
if (!input.is_open()) {
|
||||
if (!opened || data.Size() < sizeof(TextureArtifactHeader)) {
|
||||
return LoadResult(Containers::String("Failed to read texture artifact: ") + path);
|
||||
}
|
||||
|
||||
TextureArtifactHeader header;
|
||||
input.read(reinterpret_cast<char*>(&header), sizeof(header));
|
||||
if (!input) {
|
||||
return LoadResult(Containers::String("Failed to parse texture artifact header: ") + path);
|
||||
}
|
||||
std::memcpy(&header, data.Data(), sizeof(header));
|
||||
|
||||
const std::string magic(header.magic, header.magic + 7);
|
||||
if (magic != "XCTEX01") {
|
||||
return LoadResult(Containers::String("Invalid texture artifact magic: ") + path);
|
||||
}
|
||||
|
||||
const size_t payloadOffset = sizeof(TextureArtifactHeader);
|
||||
if (header.pixelDataSize > static_cast<Core::uint64>(data.Size() - payloadOffset)) {
|
||||
return LoadResult(Containers::String("Failed to read texture artifact payload: ") + path);
|
||||
}
|
||||
|
||||
Containers::Array<Core::uint8> pixelData;
|
||||
pixelData.Resize(static_cast<size_t>(header.pixelDataSize));
|
||||
if (header.pixelDataSize > 0) {
|
||||
input.read(reinterpret_cast<char*>(pixelData.Data()), static_cast<std::streamsize>(header.pixelDataSize));
|
||||
if (!input) {
|
||||
return LoadResult(Containers::String("Failed to read texture artifact payload: ") + path);
|
||||
}
|
||||
std::memcpy(
|
||||
pixelData.Data(),
|
||||
data.Data() + payloadOffset,
|
||||
static_cast<size_t>(header.pixelDataSize));
|
||||
}
|
||||
|
||||
return CreateTextureResource(path,
|
||||
@@ -150,7 +217,7 @@ bool TextureLoader::CanLoad(const Containers::String& path) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
Containers::String ext = GetExtension(path).ToLower();
|
||||
Containers::String ext = GetTexturePathExtension(path).ToLower();
|
||||
|
||||
return ext == "png" || ext == "jpg" || ext == "jpeg" ||
|
||||
ext == "tga" || ext == "bmp" || ext == "gif" ||
|
||||
@@ -162,7 +229,7 @@ LoadResult TextureLoader::Load(const Containers::String& path, const ImportSetti
|
||||
return CreateBuiltinTextureResource(path);
|
||||
}
|
||||
|
||||
Containers::String ext = GetExtension(path).ToLower();
|
||||
Containers::String ext = GetTexturePathExtension(path).ToLower();
|
||||
|
||||
if (!CanLoad(path)) {
|
||||
return LoadResult(Containers::String("Unsupported texture format: ") + ext);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <XCEngine/Resources/UI/UIDocumentCompiler.h>
|
||||
|
||||
#include <XCEngine/Core/Asset/ArtifactContainer.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactFormats.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
|
||||
@@ -53,7 +54,15 @@ Containers::String FormatDiagnosticMessage(const Containers::String& path,
|
||||
message;
|
||||
}
|
||||
|
||||
void WriteString(std::ofstream& stream, const Containers::String& value) {
|
||||
bool BuildSchemaDefinition(
|
||||
const Containers::String& sourcePath,
|
||||
const UIDocumentNode& rootNode,
|
||||
const Containers::String& displayName,
|
||||
UISchemaDefinition& outDefinition,
|
||||
Containers::Array<UIDocumentDiagnostic>& diagnostics,
|
||||
Containers::String& outErrorMessage);
|
||||
|
||||
void WriteString(std::ostream& stream, const Containers::String& value) {
|
||||
const Core::uint32 length = static_cast<Core::uint32>(value.Length());
|
||||
stream.write(reinterpret_cast<const char*>(&length), sizeof(length));
|
||||
if (length > 0) {
|
||||
@@ -61,7 +70,7 @@ void WriteString(std::ofstream& stream, const Containers::String& value) {
|
||||
}
|
||||
}
|
||||
|
||||
bool ReadString(std::ifstream& stream, Containers::String& outValue) {
|
||||
bool ReadString(std::istream& stream, Containers::String& outValue) {
|
||||
Core::uint32 length = 0;
|
||||
stream.read(reinterpret_cast<char*>(&length), sizeof(length));
|
||||
if (!stream) {
|
||||
@@ -83,7 +92,7 @@ bool ReadString(std::ifstream& stream, Containers::String& outValue) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteNode(std::ofstream& stream, const UIDocumentNode& node) {
|
||||
bool WriteNode(std::ostream& stream, const UIDocumentNode& node) {
|
||||
WriteString(stream, node.tagName);
|
||||
|
||||
UIDocumentArtifactNodeHeader header;
|
||||
@@ -114,7 +123,7 @@ bool WriteNode(std::ofstream& stream, const UIDocumentNode& node) {
|
||||
return static_cast<bool>(stream);
|
||||
}
|
||||
|
||||
bool ReadNode(std::ifstream& stream, UIDocumentNode& outNode) {
|
||||
bool ReadNode(std::istream& stream, UIDocumentNode& outNode) {
|
||||
if (!ReadString(stream, outNode.tagName)) {
|
||||
return false;
|
||||
}
|
||||
@@ -178,7 +187,7 @@ struct UIDocumentArtifactSchemaAttributeHeader {
|
||||
Core::uint32 restrictDocumentKind = 0;
|
||||
};
|
||||
|
||||
bool WriteSchemaAttribute(std::ofstream& stream, const UISchemaAttributeDefinition& attribute) {
|
||||
bool WriteSchemaAttribute(std::ostream& stream, const UISchemaAttributeDefinition& attribute) {
|
||||
WriteString(stream, attribute.name);
|
||||
|
||||
UIDocumentArtifactSchemaAttributeHeader header;
|
||||
@@ -204,7 +213,7 @@ bool WriteSchemaAttribute(std::ofstream& stream, const UISchemaAttributeDefiniti
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadSchemaAttribute(std::ifstream& stream, UISchemaAttributeDefinition& outAttribute) {
|
||||
bool ReadSchemaAttribute(std::istream& stream, UISchemaAttributeDefinition& outAttribute) {
|
||||
if (!ReadString(stream, outAttribute.name)) {
|
||||
return false;
|
||||
}
|
||||
@@ -234,7 +243,7 @@ bool ReadSchemaAttribute(std::ifstream& stream, UISchemaAttributeDefinition& out
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteSchemaElement(std::ofstream& stream, const UISchemaElementDefinition& element) {
|
||||
bool WriteSchemaElement(std::ostream& stream, const UISchemaElementDefinition& element) {
|
||||
WriteString(stream, element.tagName);
|
||||
|
||||
UIDocumentArtifactSchemaElementHeader header;
|
||||
@@ -264,7 +273,7 @@ bool WriteSchemaElement(std::ofstream& stream, const UISchemaElementDefinition&
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadSchemaElement(std::ifstream& stream, UISchemaElementDefinition& outElement) {
|
||||
bool ReadSchemaElement(std::istream& stream, UISchemaElementDefinition& outElement) {
|
||||
if (!ReadString(stream, outElement.tagName)) {
|
||||
return false;
|
||||
}
|
||||
@@ -302,7 +311,7 @@ bool ReadSchemaElement(std::ifstream& stream, UISchemaElementDefinition& outElem
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteSchemaDefinition(std::ofstream& stream, const UISchemaDefinition& schemaDefinition) {
|
||||
bool WriteSchemaDefinition(std::ostream& stream, const UISchemaDefinition& schemaDefinition) {
|
||||
WriteString(stream, schemaDefinition.name);
|
||||
|
||||
UIDocumentArtifactSchemaDefinitionHeader header;
|
||||
@@ -322,7 +331,7 @@ bool WriteSchemaDefinition(std::ofstream& stream, const UISchemaDefinition& sche
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadSchemaDefinition(std::ifstream& stream, UISchemaDefinition& outSchemaDefinition) {
|
||||
bool ReadSchemaDefinition(std::istream& stream, UISchemaDefinition& outSchemaDefinition) {
|
||||
if (!ReadString(stream, outSchemaDefinition.name)) {
|
||||
return false;
|
||||
}
|
||||
@@ -347,6 +356,193 @@ bool ReadSchemaDefinition(std::ifstream& stream, UISchemaDefinition& outSchemaDe
|
||||
return true;
|
||||
}
|
||||
|
||||
ResourceType GetUIDocumentResourceType(UIDocumentKind kind) {
|
||||
switch (kind) {
|
||||
case UIDocumentKind::View:
|
||||
return ResourceType::UIView;
|
||||
case UIDocumentKind::Theme:
|
||||
return ResourceType::UITheme;
|
||||
case UIDocumentKind::Schema:
|
||||
return ResourceType::UISchema;
|
||||
default:
|
||||
return ResourceType::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
bool SerializeUIDocumentArtifactPayload(const UIDocumentCompileResult& compileResult,
|
||||
Containers::Array<Core::uint8>& outPayload,
|
||||
Containers::String* outErrorMessage) {
|
||||
outPayload.Clear();
|
||||
|
||||
if (!compileResult.succeeded || !compileResult.document.valid) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = "UI document compile result is invalid.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ostringstream output(std::ios::binary | std::ios::out);
|
||||
|
||||
UIDocumentArtifactFileHeader header;
|
||||
header.kind = static_cast<Core::uint32>(compileResult.document.kind);
|
||||
header.dependencyCount = static_cast<Core::uint32>(compileResult.document.dependencies.Size());
|
||||
header.diagnosticCount = static_cast<Core::uint32>(compileResult.document.diagnostics.Size());
|
||||
output.write(reinterpret_cast<const char*>(&header), sizeof(header));
|
||||
if (!output) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = "Failed to serialize UI document artifact header.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
WriteString(output, compileResult.document.sourcePath);
|
||||
WriteString(output, compileResult.document.displayName);
|
||||
if (!WriteNode(output, compileResult.document.rootNode)) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = "Failed to serialize UI document node tree.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!WriteSchemaDefinition(output, compileResult.document.schemaDefinition)) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = "Failed to serialize UI document schema definition.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const Containers::String& dependency : compileResult.document.dependencies) {
|
||||
WriteString(output, dependency);
|
||||
if (!output) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = "Failed to serialize UI document dependencies.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const UIDocumentDiagnostic& diagnostic : compileResult.document.diagnostics) {
|
||||
UIDocumentArtifactDiagnosticHeader diagnosticHeader;
|
||||
diagnosticHeader.severity = static_cast<Core::uint32>(diagnostic.severity);
|
||||
diagnosticHeader.line = diagnostic.location.line;
|
||||
diagnosticHeader.column = diagnostic.location.column;
|
||||
output.write(reinterpret_cast<const char*>(&diagnosticHeader), sizeof(diagnosticHeader));
|
||||
WriteString(output, diagnostic.message);
|
||||
if (!output) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = "Failed to serialize UI document diagnostics.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string serialized = output.str();
|
||||
outPayload.Resize(serialized.size());
|
||||
if (!serialized.empty()) {
|
||||
std::memcpy(outPayload.Data(), serialized.data(), serialized.size());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseUIDocumentArtifactStream(std::istream& input,
|
||||
const Containers::String& artifactPath,
|
||||
UIDocumentKind expectedKind,
|
||||
UIDocumentCompileResult& outResult) {
|
||||
UIDocumentArtifactFileHeader header;
|
||||
input.read(reinterpret_cast<char*>(&header), sizeof(header));
|
||||
if (!input) {
|
||||
outResult.errorMessage = Containers::String("Failed to read UI document artifact header: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
const UIDocumentArtifactFileHeader expectedHeader;
|
||||
if (std::memcmp(header.magic, expectedHeader.magic, sizeof(header.magic)) != 0) {
|
||||
outResult.errorMessage = Containers::String("Invalid UI document artifact magic: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.schemaVersion != 1u &&
|
||||
header.schemaVersion != kUIDocumentArtifactSchemaVersion) {
|
||||
outResult.errorMessage = Containers::String("Unsupported UI document artifact schema version: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.kind != static_cast<Core::uint32>(expectedKind)) {
|
||||
outResult.errorMessage = Containers::String("UI document artifact kind mismatch: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
outResult.document.kind = expectedKind;
|
||||
if (!ReadString(input, outResult.document.sourcePath) ||
|
||||
!ReadString(input, outResult.document.displayName) ||
|
||||
!ReadNode(input, outResult.document.rootNode)) {
|
||||
outResult.errorMessage = Containers::String("Failed to read UI document artifact body: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
outResult.document.schemaDefinition.Clear();
|
||||
if (header.schemaVersion >= 2u) {
|
||||
if (!ReadSchemaDefinition(input, outResult.document.schemaDefinition)) {
|
||||
outResult.errorMessage = Containers::String("Failed to read UI document artifact schema definition: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
outResult.document.dependencies.Clear();
|
||||
outResult.document.dependencies.Reserve(header.dependencyCount);
|
||||
for (Core::uint32 index = 0; index < header.dependencyCount; ++index) {
|
||||
Containers::String dependency;
|
||||
if (!ReadString(input, dependency)) {
|
||||
outResult.errorMessage = Containers::String("Failed to read UI document artifact dependencies: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
outResult.document.dependencies.PushBack(std::move(dependency));
|
||||
}
|
||||
|
||||
outResult.document.diagnostics.Clear();
|
||||
outResult.document.diagnostics.Reserve(header.diagnosticCount);
|
||||
for (Core::uint32 index = 0; index < header.diagnosticCount; ++index) {
|
||||
UIDocumentArtifactDiagnosticHeader diagnosticHeader;
|
||||
input.read(reinterpret_cast<char*>(&diagnosticHeader), sizeof(diagnosticHeader));
|
||||
if (!input) {
|
||||
outResult.errorMessage = Containers::String("Failed to read UI document artifact diagnostic header: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
UIDocumentDiagnostic diagnostic;
|
||||
diagnostic.severity = static_cast<UIDocumentDiagnosticSeverity>(diagnosticHeader.severity);
|
||||
diagnostic.location.line = diagnosticHeader.line;
|
||||
diagnostic.location.column = diagnosticHeader.column;
|
||||
if (!ReadString(input, diagnostic.message)) {
|
||||
outResult.errorMessage = Containers::String("Failed to read UI document artifact diagnostic message: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
outResult.document.diagnostics.PushBack(std::move(diagnostic));
|
||||
}
|
||||
|
||||
if (expectedKind == UIDocumentKind::Schema) {
|
||||
const Containers::String sourcePath =
|
||||
outResult.document.sourcePath.Empty()
|
||||
? artifactPath
|
||||
: outResult.document.sourcePath;
|
||||
if (!outResult.document.schemaDefinition.valid &&
|
||||
!BuildSchemaDefinition(
|
||||
sourcePath,
|
||||
outResult.document.rootNode,
|
||||
outResult.document.displayName,
|
||||
outResult.document.schemaDefinition,
|
||||
outResult.document.diagnostics,
|
||||
outResult.errorMessage)) {
|
||||
outResult.succeeded = false;
|
||||
outResult.document.valid = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
outResult.document.valid = true;
|
||||
outResult.succeeded = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsNameStartChar(char ch) {
|
||||
return std::isalpha(static_cast<unsigned char>(ch)) != 0 ||
|
||||
ch == '_' ||
|
||||
@@ -1548,74 +1744,27 @@ bool CompileUIDocument(const UIDocumentCompileRequest& request,
|
||||
bool WriteUIDocumentArtifact(const Containers::String& artifactPath,
|
||||
const UIDocumentCompileResult& compileResult,
|
||||
Containers::String* outErrorMessage) {
|
||||
if (!compileResult.succeeded || !compileResult.document.valid) {
|
||||
Containers::Array<Core::uint8> payload;
|
||||
if (!SerializeUIDocumentArtifactPayload(compileResult, payload, outErrorMessage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ResourceType resourceType = GetUIDocumentResourceType(compileResult.document.kind);
|
||||
if (resourceType == ResourceType::Unknown) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = "UI document compile result is invalid.";
|
||||
*outErrorMessage = "UI document kind is not importable.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream output(artifactPath.CStr(), std::ios::binary | std::ios::trunc);
|
||||
if (!output.is_open()) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = Containers::String("Unable to write UI document artifact: ") + artifactPath;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
UIDocumentArtifactFileHeader header;
|
||||
header.kind = static_cast<Core::uint32>(compileResult.document.kind);
|
||||
header.dependencyCount = static_cast<Core::uint32>(compileResult.document.dependencies.Size());
|
||||
header.diagnosticCount = static_cast<Core::uint32>(compileResult.document.diagnostics.Size());
|
||||
output.write(reinterpret_cast<const char*>(&header), sizeof(header));
|
||||
if (!output) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = Containers::String("Failed to write UI document artifact header: ") + artifactPath;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
WriteString(output, compileResult.document.sourcePath);
|
||||
WriteString(output, compileResult.document.displayName);
|
||||
if (!WriteNode(output, compileResult.document.rootNode)) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = Containers::String("Failed to write UI document node tree: ") + artifactPath;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!WriteSchemaDefinition(output, compileResult.document.schemaDefinition)) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = Containers::String("Failed to write UI document schema definition: ") + artifactPath;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const Containers::String& dependency : compileResult.document.dependencies) {
|
||||
WriteString(output, dependency);
|
||||
if (!output) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = Containers::String("Failed to write UI document dependencies: ") + artifactPath;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const UIDocumentDiagnostic& diagnostic : compileResult.document.diagnostics) {
|
||||
UIDocumentArtifactDiagnosticHeader diagnosticHeader;
|
||||
diagnosticHeader.severity = static_cast<Core::uint32>(diagnostic.severity);
|
||||
diagnosticHeader.line = diagnostic.location.line;
|
||||
diagnosticHeader.column = diagnostic.location.column;
|
||||
output.write(reinterpret_cast<const char*>(&diagnosticHeader), sizeof(diagnosticHeader));
|
||||
WriteString(output, diagnostic.message);
|
||||
if (!output) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = Containers::String("Failed to write UI document diagnostics: ") + artifactPath;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
ArtifactContainerWriter writer;
|
||||
ArtifactContainerEntry mainEntry;
|
||||
mainEntry.name = "main";
|
||||
mainEntry.resourceType = resourceType;
|
||||
mainEntry.localID = kMainAssetLocalID;
|
||||
mainEntry.payload = std::move(payload);
|
||||
writer.AddEntry(std::move(mainEntry));
|
||||
return writer.WriteToFile(artifactPath, outErrorMessage);
|
||||
}
|
||||
|
||||
bool LoadUIDocumentArtifact(const Containers::String& artifactPath,
|
||||
@@ -1623,106 +1772,30 @@ bool LoadUIDocumentArtifact(const Containers::String& artifactPath,
|
||||
UIDocumentCompileResult& outResult) {
|
||||
outResult = UIDocumentCompileResult();
|
||||
|
||||
Containers::Array<Core::uint8> payload;
|
||||
Containers::String containerError;
|
||||
if (ReadArtifactContainerPayloadByPath(
|
||||
artifactPath,
|
||||
GetUIDocumentResourceType(expectedKind),
|
||||
payload,
|
||||
&containerError)) {
|
||||
std::string inputBytes;
|
||||
inputBytes.resize(payload.Size());
|
||||
if (!payload.Empty()) {
|
||||
std::memcpy(inputBytes.data(), payload.Data(), payload.Size());
|
||||
}
|
||||
|
||||
std::istringstream input(inputBytes, std::ios::binary | std::ios::in);
|
||||
return ParseUIDocumentArtifactStream(input, artifactPath, expectedKind, outResult);
|
||||
}
|
||||
|
||||
std::ifstream input(artifactPath.CStr(), std::ios::binary);
|
||||
if (!input.is_open()) {
|
||||
outResult.errorMessage = Containers::String("Unable to open UI document artifact: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
UIDocumentArtifactFileHeader header;
|
||||
input.read(reinterpret_cast<char*>(&header), sizeof(header));
|
||||
if (!input) {
|
||||
outResult.errorMessage = Containers::String("Failed to read UI document artifact header: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
const UIDocumentArtifactFileHeader expectedHeader;
|
||||
if (std::memcmp(header.magic, expectedHeader.magic, sizeof(header.magic)) != 0) {
|
||||
outResult.errorMessage = Containers::String("Invalid UI document artifact magic: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.schemaVersion != 1u &&
|
||||
header.schemaVersion != kUIDocumentArtifactSchemaVersion) {
|
||||
outResult.errorMessage = Containers::String("Unsupported UI document artifact schema version: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.kind != static_cast<Core::uint32>(expectedKind)) {
|
||||
outResult.errorMessage = Containers::String("UI document artifact kind mismatch: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
outResult.document.kind = expectedKind;
|
||||
if (!ReadString(input, outResult.document.sourcePath) ||
|
||||
!ReadString(input, outResult.document.displayName) ||
|
||||
!ReadNode(input, outResult.document.rootNode)) {
|
||||
outResult.errorMessage = Containers::String("Failed to read UI document artifact body: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
outResult.document.schemaDefinition.Clear();
|
||||
if (header.schemaVersion >= 2u) {
|
||||
if (!ReadSchemaDefinition(input, outResult.document.schemaDefinition)) {
|
||||
outResult.errorMessage = Containers::String("Failed to read UI document artifact schema definition: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
outResult.document.dependencies.Clear();
|
||||
outResult.document.dependencies.Reserve(header.dependencyCount);
|
||||
for (Core::uint32 index = 0; index < header.dependencyCount; ++index) {
|
||||
Containers::String dependency;
|
||||
if (!ReadString(input, dependency)) {
|
||||
outResult.errorMessage = Containers::String("Failed to read UI document artifact dependencies: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
outResult.document.dependencies.PushBack(std::move(dependency));
|
||||
}
|
||||
|
||||
outResult.document.diagnostics.Clear();
|
||||
outResult.document.diagnostics.Reserve(header.diagnosticCount);
|
||||
for (Core::uint32 index = 0; index < header.diagnosticCount; ++index) {
|
||||
UIDocumentArtifactDiagnosticHeader diagnosticHeader;
|
||||
input.read(reinterpret_cast<char*>(&diagnosticHeader), sizeof(diagnosticHeader));
|
||||
if (!input) {
|
||||
outResult.errorMessage = Containers::String("Failed to read UI document artifact diagnostic header: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
UIDocumentDiagnostic diagnostic;
|
||||
diagnostic.severity = static_cast<UIDocumentDiagnosticSeverity>(diagnosticHeader.severity);
|
||||
diagnostic.location.line = diagnosticHeader.line;
|
||||
diagnostic.location.column = diagnosticHeader.column;
|
||||
if (!ReadString(input, diagnostic.message)) {
|
||||
outResult.errorMessage = Containers::String("Failed to read UI document artifact diagnostic message: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
outResult.document.diagnostics.PushBack(std::move(diagnostic));
|
||||
}
|
||||
|
||||
if (expectedKind == UIDocumentKind::Schema) {
|
||||
const Containers::String sourcePath =
|
||||
outResult.document.sourcePath.Empty()
|
||||
? artifactPath
|
||||
: outResult.document.sourcePath;
|
||||
if (!outResult.document.schemaDefinition.valid &&
|
||||
!BuildSchemaDefinition(
|
||||
sourcePath,
|
||||
outResult.document.rootNode,
|
||||
outResult.document.displayName,
|
||||
outResult.document.schemaDefinition,
|
||||
outResult.document.diagnostics,
|
||||
outResult.errorMessage)) {
|
||||
outResult.succeeded = false;
|
||||
outResult.document.valid = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
outResult.document.valid = true;
|
||||
outResult.succeeded = true;
|
||||
return true;
|
||||
return ParseUIDocumentArtifactStream(input, artifactPath, expectedKind, outResult);
|
||||
}
|
||||
|
||||
Containers::String GetUIDocumentDefaultRootTag(UIDocumentKind kind) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <XCEngine/Resources/UI/UIDocumentLoaders.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactContainer.h>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
@@ -8,7 +9,14 @@ namespace Resources {
|
||||
namespace {
|
||||
|
||||
Containers::String GetPathExtension(const Containers::String& path) {
|
||||
const std::filesystem::path fsPath(path.CStr());
|
||||
Containers::String normalizedPath = path;
|
||||
Containers::String containerPath;
|
||||
Containers::String entryName;
|
||||
if (TryParseArtifactContainerEntryPath(path, containerPath, entryName)) {
|
||||
normalizedPath = containerPath;
|
||||
}
|
||||
|
||||
const std::filesystem::path fsPath(normalizedPath.CStr());
|
||||
const std::string extension = fsPath.has_extension()
|
||||
? fsPath.extension().generic_string()
|
||||
: std::string();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <XCEngine/Resources/Volume/VolumeFieldLoader.h>
|
||||
|
||||
#include <XCEngine/Core/Asset/ArtifactContainer.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactFormats.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Debug/Logger.h>
|
||||
@@ -82,25 +83,66 @@ std::filesystem::path ResolveVolumeFieldPath(const Containers::String& path) {
|
||||
return resolvedPath.lexically_normal();
|
||||
}
|
||||
|
||||
LoadResult LoadVolumeFieldArtifact(const Containers::String& path) {
|
||||
const std::filesystem::path resolvedPath = ResolveVolumeFieldPath(path);
|
||||
const auto totalStart = std::chrono::steady_clock::now();
|
||||
bool ReadVolumeArtifactData(const Containers::String& path,
|
||||
Containers::Array<Core::uint8>& outData,
|
||||
bool& outReadFromContainer,
|
||||
std::filesystem::path& outResolvedPath) {
|
||||
outData.Clear();
|
||||
outReadFromContainer = false;
|
||||
outResolvedPath = ResolveVolumeFieldPath(path);
|
||||
|
||||
const auto openStart = std::chrono::steady_clock::now();
|
||||
std::ifstream input(resolvedPath, std::ios::binary);
|
||||
const auto openEnd = std::chrono::steady_clock::now();
|
||||
Containers::String containerError;
|
||||
if (ReadArtifactContainerPayloadByPath(
|
||||
Containers::String(outResolvedPath.generic_string().c_str()),
|
||||
ResourceType::VolumeField,
|
||||
outData,
|
||||
&containerError)) {
|
||||
outReadFromContainer = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::ifstream input(outResolvedPath, std::ios::binary | std::ios::ate);
|
||||
if (!input.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::streamsize size = input.tellg();
|
||||
if (size < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
input.seekg(0, std::ios::beg);
|
||||
outData.Resize(static_cast<size_t>(size));
|
||||
if (size == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!input.read(reinterpret_cast<char*>(outData.Data()), size)) {
|
||||
outData.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
LoadResult LoadVolumeFieldArtifact(const Containers::String& path) {
|
||||
const auto totalStart = std::chrono::steady_clock::now();
|
||||
Containers::Array<Core::uint8> data;
|
||||
bool readFromContainer = false;
|
||||
std::filesystem::path resolvedPath;
|
||||
const auto readStart = std::chrono::steady_clock::now();
|
||||
if (!ReadVolumeArtifactData(path, data, readFromContainer, resolvedPath)) {
|
||||
return LoadResult(Containers::String("Failed to read volume artifact: ") + path);
|
||||
}
|
||||
const auto readEnd = std::chrono::steady_clock::now();
|
||||
|
||||
const auto headerStart = std::chrono::steady_clock::now();
|
||||
VolumeFieldArtifactHeader header;
|
||||
input.read(reinterpret_cast<char*>(&header), sizeof(header));
|
||||
const auto headerEnd = std::chrono::steady_clock::now();
|
||||
if (!input) {
|
||||
if (data.Size() < sizeof(VolumeFieldArtifactHeader)) {
|
||||
return LoadResult(Containers::String("Failed to parse volume artifact header: ") + path);
|
||||
}
|
||||
|
||||
VolumeFieldArtifactHeader header;
|
||||
std::memcpy(&header, data.Data(), sizeof(header));
|
||||
|
||||
const bool validHeader =
|
||||
std::memcmp(header.magic, "XCVOL02", 7) == 0 &&
|
||||
header.schemaVersion == kVolumeFieldArtifactSchemaVersion &&
|
||||
@@ -110,23 +152,26 @@ LoadResult LoadVolumeFieldArtifact(const Containers::String& path) {
|
||||
}
|
||||
|
||||
Containers::Array<Core::uint8> payload;
|
||||
payload.ResizeUninitialized(static_cast<size_t>(header.payloadSize));
|
||||
const auto payloadReadStart = std::chrono::steady_clock::now();
|
||||
input.read(reinterpret_cast<char*>(payload.Data()), static_cast<std::streamsize>(header.payloadSize));
|
||||
const auto payloadReadEnd = std::chrono::steady_clock::now();
|
||||
if (!input) {
|
||||
const size_t payloadOffset = sizeof(VolumeFieldArtifactHeader);
|
||||
if (header.payloadSize > static_cast<Core::uint64>(data.Size() - payloadOffset)) {
|
||||
return LoadResult(Containers::String("Failed to read volume artifact payload: ") + path);
|
||||
}
|
||||
|
||||
payload.ResizeUninitialized(static_cast<size_t>(header.payloadSize));
|
||||
if (header.payloadSize > 0) {
|
||||
std::memcpy(
|
||||
payload.Data(),
|
||||
data.Data() + payloadOffset,
|
||||
static_cast<size_t>(header.payloadSize));
|
||||
}
|
||||
const auto totalEnd = std::chrono::steady_clock::now();
|
||||
LogVolumeTraceFileSystem(
|
||||
"VolumeFieldLoader Artifact path=" + std::string(path.CStr()) +
|
||||
" resolved=" + resolvedPath.generic_string() +
|
||||
" open_ms=" +
|
||||
std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(openEnd - openStart).count()) +
|
||||
" header_ms=" +
|
||||
std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(headerEnd - headerStart).count()) +
|
||||
" payload_read_ms=" +
|
||||
std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(payloadReadEnd - payloadReadStart).count()) +
|
||||
" mode=" +
|
||||
std::string(readFromContainer ? "container" : "raw") +
|
||||
" read_ms=" +
|
||||
std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(readEnd - readStart).count()) +
|
||||
" total_ms=" +
|
||||
std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(totalEnd - totalStart).count()) +
|
||||
" payload_bytes=" +
|
||||
|
||||
@@ -128,7 +128,19 @@ bool UISelectionModel::ToggleSelection(std::string selectionId) {
|
||||
return RemoveSelection(selectionId);
|
||||
}
|
||||
|
||||
return AddSelection(std::move(selectionId), true);
|
||||
return SetSelection(std::move(selectionId));
|
||||
}
|
||||
|
||||
bool UISelectionModel::ToggleSelectionMembership(std::string selectionId, bool makePrimary) {
|
||||
if (selectionId.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsSelected(selectionId)) {
|
||||
return RemoveSelection(selectionId);
|
||||
}
|
||||
|
||||
return AddSelection(std::move(selectionId), makePrimary);
|
||||
}
|
||||
|
||||
void UISelectionModel::NormalizeSelectionIds(std::vector<std::string>& selectionIds) {
|
||||
|
||||
Reference in New Issue
Block a user