chore: checkpoint current workspace changes

This commit is contained in:
2026-04-11 22:14:02 +08:00
parent 3e55f8c204
commit 8848cfd958
227 changed files with 34027 additions and 6711 deletions

View File

@@ -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(),

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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) +

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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(&sectionRecord, 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*>(&sectionRecord), 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*>(&sectionRecord), sizeof(sectionRecord));
if (!input) {
if (!readBytes(&sectionRecord, 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;

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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) {

View 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

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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=" +

View File

@@ -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) {