refactor: route builtin outline pass through shader assets
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
#include <XCEngine/Core/Math/Color.h>
|
#include <XCEngine/Core/Math/Color.h>
|
||||||
#include <XCEngine/Core/Math/Vector3.h>
|
#include <XCEngine/Core/Math/Vector3.h>
|
||||||
#include <XCEngine/Core/Math/Vector4.h>
|
#include <XCEngine/Core/Math/Vector4.h>
|
||||||
|
#include <XCEngine/Core/Asset/ResourceHandle.h>
|
||||||
#include <XCEngine/Rendering/RenderContext.h>
|
#include <XCEngine/Rendering/RenderContext.h>
|
||||||
#include <XCEngine/Rendering/RenderSurface.h>
|
#include <XCEngine/Rendering/RenderSurface.h>
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@
|
|||||||
#include <XCEngine/RHI/RHIPipelineLayout.h>
|
#include <XCEngine/RHI/RHIPipelineLayout.h>
|
||||||
#include <XCEngine/RHI/RHIPipelineState.h>
|
#include <XCEngine/RHI/RHIPipelineState.h>
|
||||||
#include <XCEngine/RHI/RHIResourceView.h>
|
#include <XCEngine/RHI/RHIResourceView.h>
|
||||||
|
#include <XCEngine/Resources/Shader/Shader.h>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -62,6 +64,7 @@ private:
|
|||||||
RHI::RHIDescriptorSet* m_constantSet = nullptr;
|
RHI::RHIDescriptorSet* m_constantSet = nullptr;
|
||||||
RHI::RHIDescriptorPool* m_texturePool = nullptr;
|
RHI::RHIDescriptorPool* m_texturePool = nullptr;
|
||||||
RHI::RHIDescriptorSet* m_textureSet = nullptr;
|
RHI::RHIDescriptorSet* m_textureSet = nullptr;
|
||||||
|
Resources::ResourceHandle<Resources::Shader> m_builtinObjectIdOutlineShader;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Passes
|
} // namespace Passes
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ Containers::String GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType primitiveTyp
|
|||||||
Containers::String GetBuiltinDefaultPrimitiveMaterialPath();
|
Containers::String GetBuiltinDefaultPrimitiveMaterialPath();
|
||||||
Containers::String GetBuiltinForwardLitShaderPath();
|
Containers::String GetBuiltinForwardLitShaderPath();
|
||||||
Containers::String GetBuiltinObjectIdShaderPath();
|
Containers::String GetBuiltinObjectIdShaderPath();
|
||||||
|
Containers::String GetBuiltinObjectIdOutlineShaderPath();
|
||||||
Containers::String GetBuiltinInfiniteGridShaderPath();
|
Containers::String GetBuiltinInfiniteGridShaderPath();
|
||||||
Containers::String GetBuiltinDefaultPrimitiveTexturePath();
|
Containers::String GetBuiltinDefaultPrimitiveTexturePath();
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
@@ -48,6 +49,32 @@ Containers::String NormalizeArtifactPathString(const Containers::String& path) {
|
|||||||
return ToContainersString(fs::path(path.CStr()).lexically_normal().generic_string());
|
return ToContainersString(fs::path(path.CStr()).lexically_normal().generic_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsProjectRelativePath(const fs::path& path) {
|
||||||
|
const std::string generic = path.generic_string();
|
||||||
|
return !generic.empty() &&
|
||||||
|
generic != "." &&
|
||||||
|
generic != ".." &&
|
||||||
|
generic.rfind("../", 0) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddUniqueDependencyPath(const fs::path& path,
|
||||||
|
std::unordered_set<std::string>& seenPaths,
|
||||||
|
std::vector<fs::path>& outPaths) {
|
||||||
|
if (path.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fs::path normalizedPath = path.lexically_normal();
|
||||||
|
const std::string key = normalizedPath.generic_string();
|
||||||
|
if (key.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seenPaths.insert(key).second) {
|
||||||
|
outPaths.push_back(normalizedPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::string TrimCopy(const std::string& text) {
|
std::string TrimCopy(const std::string& text) {
|
||||||
const auto begin = std::find_if_not(text.begin(), text.end(), [](unsigned char ch) {
|
const auto begin = std::find_if_not(text.begin(), text.end(), [](unsigned char ch) {
|
||||||
return std::isspace(ch) != 0;
|
return std::isspace(ch) != 0;
|
||||||
@@ -69,6 +96,91 @@ std::string ToLowerCopy(std::string text) {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> SplitWhitespaceTokens(const std::string& text) {
|
||||||
|
std::vector<std::string> tokens;
|
||||||
|
std::istringstream stream(text);
|
||||||
|
std::string token;
|
||||||
|
while (stream >> token) {
|
||||||
|
tokens.push_back(token);
|
||||||
|
}
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<fs::path> CollectObjDeclaredDependencyPaths(const fs::path& sourcePath) {
|
||||||
|
std::vector<fs::path> dependencies;
|
||||||
|
std::unordered_set<std::string> seenPaths;
|
||||||
|
|
||||||
|
std::ifstream input(sourcePath);
|
||||||
|
if (!input.is_open()) {
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fs::path sourceDirectory = sourcePath.parent_path();
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(input, line)) {
|
||||||
|
const std::string trimmed = TrimCopy(line);
|
||||||
|
if (trimmed.empty() || trimmed[0] == '#') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string lowered = ToLowerCopy(trimmed);
|
||||||
|
if (lowered.rfind("mtllib", 0) != 0 ||
|
||||||
|
(trimmed.size() > 6 && std::isspace(static_cast<unsigned char>(trimmed[6])) == 0)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string remainder = TrimCopy(trimmed.substr(6));
|
||||||
|
for (const std::string& token : SplitWhitespaceTokens(remainder)) {
|
||||||
|
AddUniqueDependencyPath(sourceDirectory / token, seenPaths, dependencies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<fs::path> CollectMtlDeclaredDependencyPaths(const fs::path& mtlPath) {
|
||||||
|
std::vector<fs::path> dependencies;
|
||||||
|
std::unordered_set<std::string> seenPaths;
|
||||||
|
|
||||||
|
std::ifstream input(mtlPath);
|
||||||
|
if (!input.is_open()) {
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fs::path sourceDirectory = mtlPath.parent_path();
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(input, line)) {
|
||||||
|
const std::string trimmed = TrimCopy(line);
|
||||||
|
if (trimmed.empty() || trimmed[0] == '#') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<std::string> tokens = SplitWhitespaceTokens(trimmed);
|
||||||
|
if (tokens.size() < 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string keyword = ToLowerCopy(tokens.front());
|
||||||
|
const bool isTextureDirective =
|
||||||
|
keyword.rfind("map_", 0) == 0 ||
|
||||||
|
keyword == "bump" ||
|
||||||
|
keyword == "disp" ||
|
||||||
|
keyword == "decal" ||
|
||||||
|
keyword == "refl" ||
|
||||||
|
keyword == "norm";
|
||||||
|
if (!isTextureDirective) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& textureToken = tokens.back();
|
||||||
|
if (!textureToken.empty() && textureToken[0] != '-') {
|
||||||
|
AddUniqueDependencyPath(sourceDirectory / textureToken, seenPaths, dependencies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
std::string EscapeField(const std::string& value) {
|
std::string EscapeField(const std::string& value) {
|
||||||
std::string escaped;
|
std::string escaped;
|
||||||
escaped.reserve(value.size());
|
escaped.reserve(value.size());
|
||||||
@@ -589,6 +701,16 @@ void AssetDatabase::LoadArtifactDB() {
|
|||||||
record.sourceFileSize = static_cast<Core::uint64>(std::stoull(fields[9]));
|
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.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;
|
record.mainLocalID = fields.size() > 11 ? static_cast<LocalID>(std::stoull(fields[11])) : kMainAssetLocalID;
|
||||||
|
for (size_t index = 12; index + 3 < fields.size(); index += 4) {
|
||||||
|
ArtifactDependencyRecord dependency;
|
||||||
|
dependency.path = ToContainersString(fields[index + 0]);
|
||||||
|
dependency.hash = ToContainersString(fields[index + 1]);
|
||||||
|
dependency.fileSize = static_cast<Core::uint64>(std::stoull(fields[index + 2]));
|
||||||
|
dependency.writeTime = static_cast<Core::uint64>(std::stoull(fields[index + 3]));
|
||||||
|
if (!dependency.path.Empty()) {
|
||||||
|
record.dependencies.push_back(dependency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!record.assetGuid.IsValid() || record.artifactKey.Empty()) {
|
if (!record.assetGuid.IsValid() || record.artifactKey.Empty()) {
|
||||||
continue;
|
continue;
|
||||||
@@ -604,7 +726,7 @@ void AssetDatabase::SaveArtifactDB() const {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
output << "# artifactKey\tassetGuid\timporter\tversion\ttype\tartifactDir\tmainArtifact\tsourceHash\tmetaHash\tsize\twriteTime\tmainLocalID\n";
|
output << "# artifactKey\tassetGuid\timporter\tversion\ttype\tartifactDir\tmainArtifact\tsourceHash\tmetaHash\tsize\twriteTime\tmainLocalID\t(depPath\tdepHash\tdepSize\tdepWriteTime)...\n";
|
||||||
for (const auto& [guid, record] : m_artifactsByGuid) {
|
for (const auto& [guid, record] : m_artifactsByGuid) {
|
||||||
output << EscapeField(ToStdString(record.artifactKey)) << '\t'
|
output << EscapeField(ToStdString(record.artifactKey)) << '\t'
|
||||||
<< EscapeField(ToStdString(record.assetGuid.ToString())) << '\t'
|
<< EscapeField(ToStdString(record.assetGuid.ToString())) << '\t'
|
||||||
@@ -617,7 +739,14 @@ void AssetDatabase::SaveArtifactDB() const {
|
|||||||
<< EscapeField(ToStdString(record.metaHash)) << '\t'
|
<< EscapeField(ToStdString(record.metaHash)) << '\t'
|
||||||
<< record.sourceFileSize << '\t'
|
<< record.sourceFileSize << '\t'
|
||||||
<< record.sourceWriteTime << '\t'
|
<< record.sourceWriteTime << '\t'
|
||||||
<< record.mainLocalID << '\n';
|
<< record.mainLocalID;
|
||||||
|
for (const ArtifactDependencyRecord& dependency : record.dependencies) {
|
||||||
|
output << '\t' << EscapeField(ToStdString(dependency.path))
|
||||||
|
<< '\t' << EscapeField(ToStdString(dependency.hash))
|
||||||
|
<< '\t' << dependency.fileSize
|
||||||
|
<< '\t' << dependency.writeTime;
|
||||||
|
}
|
||||||
|
output << '\n';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -714,6 +843,10 @@ bool AssetDatabase::EnsureMetaForPath(const fs::path& sourcePath,
|
|||||||
}
|
}
|
||||||
shouldRewriteMeta = true;
|
shouldRewriteMeta = true;
|
||||||
}
|
}
|
||||||
|
if (outRecord.importerVersion != kCurrentImporterVersion) {
|
||||||
|
outRecord.importerVersion = kCurrentImporterVersion;
|
||||||
|
shouldRewriteMeta = true;
|
||||||
|
}
|
||||||
|
|
||||||
const auto duplicateGuidIt = m_sourcesByGuid.find(outRecord.guid);
|
const auto duplicateGuidIt = m_sourcesByGuid.find(outRecord.guid);
|
||||||
if (duplicateGuidIt != m_sourcesByGuid.end() &&
|
if (duplicateGuidIt != m_sourcesByGuid.end() &&
|
||||||
@@ -880,7 +1013,8 @@ bool AssetDatabase::ShouldReimport(const SourceAssetRecord& sourceRecord,
|
|||||||
artifactRecord->sourceHash != sourceRecord.sourceHash ||
|
artifactRecord->sourceHash != sourceRecord.sourceHash ||
|
||||||
artifactRecord->metaHash != sourceRecord.metaHash ||
|
artifactRecord->metaHash != sourceRecord.metaHash ||
|
||||||
artifactRecord->sourceFileSize != sourceRecord.sourceFileSize ||
|
artifactRecord->sourceFileSize != sourceRecord.sourceFileSize ||
|
||||||
artifactRecord->sourceWriteTime != sourceRecord.sourceWriteTime;
|
artifactRecord->sourceWriteTime != sourceRecord.sourceWriteTime ||
|
||||||
|
!AreDependenciesCurrent(artifactRecord->dependencies);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AssetDatabase::ImportAsset(const SourceAssetRecord& sourceRecord,
|
bool AssetDatabase::ImportAsset(const SourceAssetRecord& sourceRecord,
|
||||||
@@ -1058,6 +1192,7 @@ bool AssetDatabase::ImportTextureAsset(const SourceAssetRecord& sourceRecord,
|
|||||||
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
|
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
|
||||||
outRecord.sourceWriteTime = sourceRecord.sourceWriteTime;
|
outRecord.sourceWriteTime = sourceRecord.sourceWriteTime;
|
||||||
outRecord.mainLocalID = kMainAssetLocalID;
|
outRecord.mainLocalID = kMainAssetLocalID;
|
||||||
|
outRecord.dependencies.clear();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1072,7 +1207,9 @@ bool AssetDatabase::ImportMaterialAsset(const SourceAssetRecord& sourceRecord,
|
|||||||
}
|
}
|
||||||
|
|
||||||
Material* material = static_cast<Material*>(result.resource);
|
Material* material = static_cast<Material*>(result.resource);
|
||||||
const Containers::String artifactKey = BuildArtifactKey(sourceRecord);
|
std::vector<ArtifactDependencyRecord> dependencies;
|
||||||
|
CollectMaterialDependencies(*material, dependencies);
|
||||||
|
const Containers::String artifactKey = BuildArtifactKey(sourceRecord, dependencies);
|
||||||
const Containers::String artifactDir = BuildArtifactDirectory(artifactKey);
|
const Containers::String artifactDir = BuildArtifactDirectory(artifactKey);
|
||||||
const Containers::String mainArtifactPath =
|
const Containers::String mainArtifactPath =
|
||||||
NormalizePathString(fs::path(artifactDir.CStr()) / "main.xcmat");
|
NormalizePathString(fs::path(artifactDir.CStr()) / "main.xcmat");
|
||||||
@@ -1105,6 +1242,7 @@ bool AssetDatabase::ImportMaterialAsset(const SourceAssetRecord& sourceRecord,
|
|||||||
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
|
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
|
||||||
outRecord.sourceWriteTime = sourceRecord.sourceWriteTime;
|
outRecord.sourceWriteTime = sourceRecord.sourceWriteTime;
|
||||||
outRecord.mainLocalID = kMainAssetLocalID;
|
outRecord.mainLocalID = kMainAssetLocalID;
|
||||||
|
outRecord.dependencies = std::move(dependencies);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1118,7 +1256,9 @@ bool AssetDatabase::ImportModelAsset(const SourceAssetRecord& sourceRecord,
|
|||||||
}
|
}
|
||||||
|
|
||||||
Mesh* mesh = static_cast<Mesh*>(result.resource);
|
Mesh* mesh = static_cast<Mesh*>(result.resource);
|
||||||
const Containers::String artifactKey = BuildArtifactKey(sourceRecord);
|
std::vector<ArtifactDependencyRecord> dependencies;
|
||||||
|
CollectModelDependencies(sourceRecord, *mesh, dependencies);
|
||||||
|
const Containers::String artifactKey = BuildArtifactKey(sourceRecord, dependencies);
|
||||||
const Containers::String artifactDir = BuildArtifactDirectory(artifactKey);
|
const Containers::String artifactDir = BuildArtifactDirectory(artifactKey);
|
||||||
const Containers::String mainArtifactPath = NormalizePathString(fs::path(artifactDir.CStr()) / "main.xcmesh");
|
const Containers::String mainArtifactPath = NormalizePathString(fs::path(artifactDir.CStr()) / "main.xcmesh");
|
||||||
|
|
||||||
@@ -1196,10 +1336,13 @@ bool AssetDatabase::ImportModelAsset(const SourceAssetRecord& sourceRecord,
|
|||||||
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
|
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
|
||||||
outRecord.sourceWriteTime = sourceRecord.sourceWriteTime;
|
outRecord.sourceWriteTime = sourceRecord.sourceWriteTime;
|
||||||
outRecord.mainLocalID = kMainAssetLocalID;
|
outRecord.mainLocalID = kMainAssetLocalID;
|
||||||
|
outRecord.dependencies = std::move(dependencies);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Containers::String AssetDatabase::BuildArtifactKey(const SourceAssetRecord& sourceRecord) const {
|
Containers::String AssetDatabase::BuildArtifactKey(
|
||||||
|
const AssetDatabase::SourceAssetRecord& sourceRecord,
|
||||||
|
const std::vector<AssetDatabase::ArtifactDependencyRecord>& dependencies) const {
|
||||||
Containers::String signature = sourceRecord.guid.ToString();
|
Containers::String signature = sourceRecord.guid.ToString();
|
||||||
signature += ":";
|
signature += ":";
|
||||||
signature += sourceRecord.importerName;
|
signature += sourceRecord.importerName;
|
||||||
@@ -1213,9 +1356,176 @@ Containers::String AssetDatabase::BuildArtifactKey(const SourceAssetRecord& sour
|
|||||||
signature += Containers::String(std::to_string(sourceRecord.sourceFileSize).c_str());
|
signature += Containers::String(std::to_string(sourceRecord.sourceFileSize).c_str());
|
||||||
signature += ":";
|
signature += ":";
|
||||||
signature += Containers::String(std::to_string(sourceRecord.sourceWriteTime).c_str());
|
signature += Containers::String(std::to_string(sourceRecord.sourceWriteTime).c_str());
|
||||||
|
for (const ArtifactDependencyRecord& dependency : dependencies) {
|
||||||
|
signature += ":dep:";
|
||||||
|
signature += dependency.path;
|
||||||
|
signature += ":";
|
||||||
|
signature += dependency.hash;
|
||||||
|
signature += ":";
|
||||||
|
signature += Containers::String(std::to_string(dependency.fileSize).c_str());
|
||||||
|
signature += ":";
|
||||||
|
signature += Containers::String(std::to_string(dependency.writeTime).c_str());
|
||||||
|
}
|
||||||
return HashStringToAssetGUID(signature).ToString();
|
return HashStringToAssetGUID(signature).ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Containers::String AssetDatabase::NormalizeDependencyPath(const fs::path& path) const {
|
||||||
|
const fs::path normalizedPath = path.lexically_normal();
|
||||||
|
if (normalizedPath.empty()) {
|
||||||
|
return Containers::String();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizedPath.is_absolute() && !m_projectRoot.Empty()) {
|
||||||
|
std::error_code ec;
|
||||||
|
const fs::path relativePath = fs::relative(normalizedPath, fs::path(m_projectRoot.CStr()), ec);
|
||||||
|
if (!ec && IsProjectRelativePath(relativePath)) {
|
||||||
|
return ToContainersString(relativePath.generic_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ToContainersString(normalizedPath.generic_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path AssetDatabase::ResolveDependencyPath(const Containers::String& path) const {
|
||||||
|
if (path.Empty()) {
|
||||||
|
return fs::path();
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path dependencyPath(path.CStr());
|
||||||
|
if (dependencyPath.is_absolute()) {
|
||||||
|
return dependencyPath.lexically_normal();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (fs::path(m_projectRoot.CStr()) / dependencyPath).lexically_normal();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AssetDatabase::CaptureDependencyRecord(const fs::path& path,
|
||||||
|
AssetDatabase::ArtifactDependencyRecord& outRecord) const {
|
||||||
|
const Containers::String normalizedPath = NormalizeDependencyPath(path);
|
||||||
|
if (normalizedPath.Empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outRecord = ArtifactDependencyRecord();
|
||||||
|
outRecord.path = normalizedPath;
|
||||||
|
|
||||||
|
const fs::path resolvedPath = ResolveDependencyPath(normalizedPath);
|
||||||
|
if (!resolvedPath.empty() && fs::exists(resolvedPath)) {
|
||||||
|
outRecord.hash = ComputeFileHash(resolvedPath);
|
||||||
|
outRecord.fileSize = GetFileSizeValue(resolvedPath);
|
||||||
|
outRecord.writeTime = GetFileWriteTimeValue(resolvedPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AssetDatabase::AreDependenciesCurrent(
|
||||||
|
const std::vector<AssetDatabase::ArtifactDependencyRecord>& dependencies) const {
|
||||||
|
for (const AssetDatabase::ArtifactDependencyRecord& dependency : dependencies) {
|
||||||
|
if (dependency.path.Empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fs::path resolvedPath = ResolveDependencyPath(dependency.path);
|
||||||
|
if (resolvedPath.empty() || !fs::exists(resolvedPath)) {
|
||||||
|
if (!dependency.hash.Empty() ||
|
||||||
|
dependency.fileSize != 0 ||
|
||||||
|
dependency.writeTime != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Core::uint64 currentFileSize = GetFileSizeValue(resolvedPath);
|
||||||
|
const Core::uint64 currentWriteTime = GetFileWriteTimeValue(resolvedPath);
|
||||||
|
if (currentFileSize != dependency.fileSize ||
|
||||||
|
currentWriteTime != dependency.writeTime) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ComputeFileHash(resolvedPath) != dependency.hash) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AssetDatabase::CollectModelDependencies(const AssetDatabase::SourceAssetRecord& sourceRecord,
|
||||||
|
const Mesh& mesh,
|
||||||
|
std::vector<AssetDatabase::ArtifactDependencyRecord>& outDependencies) const {
|
||||||
|
outDependencies.clear();
|
||||||
|
|
||||||
|
std::unordered_set<std::string> seenDependencyPaths;
|
||||||
|
std::vector<fs::path> candidatePaths;
|
||||||
|
|
||||||
|
const fs::path sourcePath = fs::path(m_projectRoot.CStr()) / sourceRecord.relativePath.CStr();
|
||||||
|
const fs::path normalizedSourcePath = sourcePath.lexically_normal();
|
||||||
|
if (ToLowerCopy(normalizedSourcePath.extension().string()) == ".obj") {
|
||||||
|
const std::vector<fs::path> mtlPaths = CollectObjDeclaredDependencyPaths(normalizedSourcePath);
|
||||||
|
for (const fs::path& mtlPath : mtlPaths) {
|
||||||
|
AddUniqueDependencyPath(mtlPath, seenDependencyPaths, candidatePaths);
|
||||||
|
const std::vector<fs::path> texturePaths = CollectMtlDeclaredDependencyPaths(mtlPath);
|
||||||
|
for (const fs::path& texturePath : texturePaths) {
|
||||||
|
AddUniqueDependencyPath(texturePath, seenDependencyPaths, candidatePaths);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Texture* texture : mesh.GetTextures()) {
|
||||||
|
if (texture == nullptr || texture->GetPath().Empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string texturePath = ToStdString(texture->GetPath());
|
||||||
|
if (texturePath.find('#') != std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddUniqueDependencyPath(fs::path(texturePath), seenDependencyPaths, candidatePaths);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string sourcePathKey = normalizedSourcePath.generic_string();
|
||||||
|
for (const fs::path& candidatePath : candidatePaths) {
|
||||||
|
if (candidatePath.generic_string() == sourcePathKey) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArtifactDependencyRecord dependency;
|
||||||
|
if (CaptureDependencyRecord(candidatePath, dependency)) {
|
||||||
|
outDependencies.push_back(dependency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AssetDatabase::CollectMaterialDependencies(
|
||||||
|
const Material& material,
|
||||||
|
std::vector<AssetDatabase::ArtifactDependencyRecord>& outDependencies) const {
|
||||||
|
outDependencies.clear();
|
||||||
|
|
||||||
|
std::unordered_set<std::string> seenDependencyPaths;
|
||||||
|
for (Core::uint32 bindingIndex = 0; bindingIndex < material.GetTextureBindingCount(); ++bindingIndex) {
|
||||||
|
const Containers::String texturePath = material.GetTextureBindingPath(bindingIndex);
|
||||||
|
if (texturePath.Empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArtifactDependencyRecord dependency;
|
||||||
|
if (!CaptureDependencyRecord(ResolveDependencyPath(texturePath), dependency)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string dependencyKey = ToStdString(dependency.path);
|
||||||
|
if (seenDependencyPaths.insert(dependencyKey).second) {
|
||||||
|
outDependencies.push_back(dependency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Containers::String AssetDatabase::BuildArtifactDirectory(const Containers::String& artifactKey) const {
|
Containers::String AssetDatabase::BuildArtifactDirectory(const Containers::String& artifactKey) const {
|
||||||
if (artifactKey.Length() < 2) {
|
if (artifactKey.Length() < 2) {
|
||||||
return Containers::String("Library/Artifacts/00/invalid");
|
return Containers::String("Library/Artifacts/00/invalid");
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
#include "Rendering/Passes/BuiltinObjectIdOutlinePass.h"
|
#include "Rendering/Passes/BuiltinObjectIdOutlinePass.h"
|
||||||
|
|
||||||
|
#include "Core/Asset/ResourceManager.h"
|
||||||
|
#include "Debug/Logger.h"
|
||||||
#include "Rendering/ObjectIdEncoding.h"
|
#include "Rendering/ObjectIdEncoding.h"
|
||||||
|
#include "Rendering/Detail/ShaderVariantUtils.h"
|
||||||
#include "RHI/RHICommandList.h"
|
#include "RHI/RHICommandList.h"
|
||||||
#include "RHI/RHIDevice.h"
|
#include "RHI/RHIDevice.h"
|
||||||
|
#include "Resources/BuiltinResources.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Rendering {
|
namespace Rendering {
|
||||||
@@ -13,109 +16,73 @@ namespace Passes {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
const char kBuiltinObjectIdOutlineHlsl[] = R"(
|
const Resources::ShaderPass* FindObjectIdOutlineCompatiblePass(
|
||||||
cbuffer OutlineConstants : register(b0) {
|
const Resources::Shader& shader,
|
||||||
float4 gViewportSizeAndTexelSize;
|
Resources::ShaderBackend backend) {
|
||||||
float4 gOutlineColor;
|
const Resources::ShaderPass* outlinePass = shader.FindPass("ObjectIdOutline");
|
||||||
float4 gSelectedInfo;
|
if (outlinePass != nullptr &&
|
||||||
float4 gSelectedObjectColors[256];
|
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, outlinePass->name, backend)) {
|
||||||
};
|
return outlinePass;
|
||||||
|
}
|
||||||
|
|
||||||
Texture2D gObjectIdTexture : register(t0);
|
const Resources::ShaderPass* editorOutlinePass = shader.FindPass("EditorObjectIdOutline");
|
||||||
|
if (editorOutlinePass != nullptr &&
|
||||||
|
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, editorOutlinePass->name, backend)) {
|
||||||
|
return editorOutlinePass;
|
||||||
|
}
|
||||||
|
|
||||||
struct VSOutput {
|
if (shader.GetPassCount() > 0 &&
|
||||||
float4 position : SV_POSITION;
|
::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, shader.GetPasses()[0].name, backend)) {
|
||||||
};
|
return &shader.GetPasses()[0];
|
||||||
|
}
|
||||||
|
|
||||||
VSOutput MainVS(uint vertexId : SV_VertexID) {
|
return nullptr;
|
||||||
static const float2 positions[3] = {
|
|
||||||
float2(-1.0, -1.0),
|
|
||||||
float2(-1.0, 3.0),
|
|
||||||
float2( 3.0, -1.0)
|
|
||||||
};
|
|
||||||
|
|
||||||
VSOutput output;
|
|
||||||
output.position = float4(positions[vertexId], 0.0, 1.0);
|
|
||||||
return output;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int2 ClampPixelCoord(int2 pixelCoord) {
|
RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
||||||
const int2 maxCoord = int2(
|
RHI::RHIType backendType,
|
||||||
max((int)gViewportSizeAndTexelSize.x - 1, 0),
|
RHI::RHIPipelineLayout* pipelineLayout,
|
||||||
max((int)gViewportSizeAndTexelSize.y - 1, 0));
|
const Resources::Shader& shader,
|
||||||
return clamp(pixelCoord, int2(0, 0), maxCoord);
|
const Containers::String& passName) {
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
pipelineDesc.rasterizerState.frontFace = static_cast<uint32_t>(RHI::FrontFace::CounterClockwise);
|
||||||
|
pipelineDesc.rasterizerState.depthClipEnable = true;
|
||||||
|
|
||||||
|
pipelineDesc.blendState.blendEnable = true;
|
||||||
|
pipelineDesc.blendState.srcBlend = static_cast<uint32_t>(RHI::BlendFactor::SrcAlpha);
|
||||||
|
pipelineDesc.blendState.dstBlend = static_cast<uint32_t>(RHI::BlendFactor::InvSrcAlpha);
|
||||||
|
pipelineDesc.blendState.srcBlendAlpha = static_cast<uint32_t>(RHI::BlendFactor::One);
|
||||||
|
pipelineDesc.blendState.dstBlendAlpha = static_cast<uint32_t>(RHI::BlendFactor::InvSrcAlpha);
|
||||||
|
pipelineDesc.blendState.blendOp = static_cast<uint32_t>(RHI::BlendOp::Add);
|
||||||
|
pipelineDesc.blendState.blendOpAlpha = static_cast<uint32_t>(RHI::BlendOp::Add);
|
||||||
|
pipelineDesc.blendState.colorWriteMask = static_cast<uint8_t>(RHI::ColorWriteMask::All);
|
||||||
|
|
||||||
|
pipelineDesc.depthStencilState.depthTestEnable = false;
|
||||||
|
pipelineDesc.depthStencilState.depthWriteEnable = false;
|
||||||
|
pipelineDesc.depthStencilState.depthFunc = static_cast<uint32_t>(RHI::ComparisonFunc::Always);
|
||||||
|
|
||||||
|
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType);
|
||||||
|
if (const Resources::ShaderStageVariant* vertexVariant =
|
||||||
|
shader.FindVariant(passName, Resources::ShaderType::Vertex, backend)) {
|
||||||
|
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*vertexVariant, pipelineDesc.vertexShader);
|
||||||
|
}
|
||||||
|
if (const Resources::ShaderStageVariant* fragmentVariant =
|
||||||
|
shader.FindVariant(passName, Resources::ShaderType::Fragment, backend)) {
|
||||||
|
::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*fragmentVariant, pipelineDesc.fragmentShader);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pipelineDesc;
|
||||||
}
|
}
|
||||||
|
|
||||||
float4 LoadObjectId(int2 pixelCoord) {
|
|
||||||
return gObjectIdTexture.Load(int3(ClampPixelCoord(pixelCoord), 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsSelectedObject(float4 objectIdColor) {
|
|
||||||
if (objectIdColor.a <= 0.0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int selectedCount = min((int)gSelectedInfo.x, 256);
|
|
||||||
[loop]
|
|
||||||
for (int i = 0; i < selectedCount; ++i) {
|
|
||||||
const float4 selectedColor = gSelectedObjectColors[i];
|
|
||||||
if (all(abs(objectIdColor - selectedColor) <= float4(
|
|
||||||
0.0025,
|
|
||||||
0.0025,
|
|
||||||
0.0025,
|
|
||||||
0.0025))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 MainPS(VSOutput input) : SV_TARGET {
|
|
||||||
const int2 pixelCoord = int2(input.position.xy);
|
|
||||||
const bool debugSelectionMask = gSelectedInfo.y > 0.5;
|
|
||||||
const bool centerSelected = IsSelectedObject(LoadObjectId(pixelCoord));
|
|
||||||
|
|
||||||
if (debugSelectionMask) {
|
|
||||||
return centerSelected ? float4(1.0, 1.0, 1.0, 1.0) : float4(0.0, 0.0, 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (centerSelected) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int outlineWidth = max((int)gSelectedInfo.z, 1);
|
|
||||||
float outline = 0.0;
|
|
||||||
[loop]
|
|
||||||
for (int y = -2; y <= 2; ++y) {
|
|
||||||
[loop]
|
|
||||||
for (int x = -2; x <= 2; ++x) {
|
|
||||||
if (x == 0 && y == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const float distancePixels = length(float2((float)x, (float)y));
|
|
||||||
if (distancePixels > outlineWidth) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsSelectedObject(LoadObjectId(pixelCoord + int2(x, y)))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const float weight = saturate(1.0 - ((distancePixels - 1.0) / max((float)outlineWidth, 1.0)));
|
|
||||||
outline = max(outline, weight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (outline <= 0.001) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
|
|
||||||
return float4(gOutlineColor.rgb, gOutlineColor.a * outline);
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void BuiltinObjectIdOutlinePass::Shutdown() {
|
void BuiltinObjectIdOutlinePass::Shutdown() {
|
||||||
@@ -225,6 +192,26 @@ bool BuiltinObjectIdOutlinePass::CreateResources(const RenderContext& renderCont
|
|||||||
|
|
||||||
m_device = renderContext.device;
|
m_device = renderContext.device;
|
||||||
m_backendType = renderContext.backendType;
|
m_backendType = renderContext.backendType;
|
||||||
|
m_builtinObjectIdOutlineShader = Resources::ResourceManager::Get().Load<Resources::Shader>(
|
||||||
|
Resources::GetBuiltinObjectIdOutlineShaderPath());
|
||||||
|
if (!m_builtinObjectIdOutlineShader.IsValid()) {
|
||||||
|
Debug::Logger::Get().Error(
|
||||||
|
Debug::LogCategory::Rendering,
|
||||||
|
"BuiltinObjectIdOutlinePass failed to load builtin object-id-outline shader resource");
|
||||||
|
DestroyResources();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(m_backendType);
|
||||||
|
const Resources::ShaderPass* outlinePass =
|
||||||
|
FindObjectIdOutlineCompatiblePass(*m_builtinObjectIdOutlineShader.Get(), backend);
|
||||||
|
if (outlinePass == nullptr) {
|
||||||
|
Debug::Logger::Get().Error(
|
||||||
|
Debug::LogCategory::Rendering,
|
||||||
|
"BuiltinObjectIdOutlinePass could not resolve a valid ObjectIdOutline shader pass");
|
||||||
|
DestroyResources();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
RHI::DescriptorSetLayoutBinding setBindings[2] = {};
|
RHI::DescriptorSetLayoutBinding setBindings[2] = {};
|
||||||
setBindings[0].binding = 0;
|
setBindings[0].binding = 0;
|
||||||
@@ -287,47 +274,12 @@ bool BuiltinObjectIdOutlinePass::CreateResources(const RenderContext& renderCont
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
RHI::GraphicsPipelineDesc pipelineDesc = {};
|
m_pipelineState = m_device->CreatePipelineState(
|
||||||
pipelineDesc.pipelineLayout = m_pipelineLayout;
|
CreatePipelineDesc(
|
||||||
pipelineDesc.topologyType = static_cast<uint32_t>(RHI::PrimitiveTopologyType::Triangle);
|
m_backendType,
|
||||||
pipelineDesc.renderTargetCount = 1;
|
m_pipelineLayout,
|
||||||
pipelineDesc.renderTargetFormats[0] = static_cast<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
|
*m_builtinObjectIdOutlineShader.Get(),
|
||||||
pipelineDesc.depthStencilFormat = static_cast<uint32_t>(RHI::Format::Unknown);
|
outlinePass->name));
|
||||||
pipelineDesc.sampleCount = 1;
|
|
||||||
|
|
||||||
pipelineDesc.rasterizerState.fillMode = static_cast<uint32_t>(RHI::FillMode::Solid);
|
|
||||||
pipelineDesc.rasterizerState.cullMode = static_cast<uint32_t>(RHI::CullMode::None);
|
|
||||||
pipelineDesc.rasterizerState.frontFace = static_cast<uint32_t>(RHI::FrontFace::CounterClockwise);
|
|
||||||
pipelineDesc.rasterizerState.depthClipEnable = true;
|
|
||||||
|
|
||||||
pipelineDesc.blendState.blendEnable = true;
|
|
||||||
pipelineDesc.blendState.srcBlend = static_cast<uint32_t>(RHI::BlendFactor::SrcAlpha);
|
|
||||||
pipelineDesc.blendState.dstBlend = static_cast<uint32_t>(RHI::BlendFactor::InvSrcAlpha);
|
|
||||||
pipelineDesc.blendState.srcBlendAlpha = static_cast<uint32_t>(RHI::BlendFactor::One);
|
|
||||||
pipelineDesc.blendState.dstBlendAlpha = static_cast<uint32_t>(RHI::BlendFactor::InvSrcAlpha);
|
|
||||||
pipelineDesc.blendState.blendOp = static_cast<uint32_t>(RHI::BlendOp::Add);
|
|
||||||
pipelineDesc.blendState.blendOpAlpha = static_cast<uint32_t>(RHI::BlendOp::Add);
|
|
||||||
pipelineDesc.blendState.colorWriteMask = static_cast<uint8_t>(RHI::ColorWriteMask::All);
|
|
||||||
|
|
||||||
pipelineDesc.depthStencilState.depthTestEnable = false;
|
|
||||||
pipelineDesc.depthStencilState.depthWriteEnable = false;
|
|
||||||
pipelineDesc.depthStencilState.depthFunc = static_cast<uint32_t>(RHI::ComparisonFunc::Always);
|
|
||||||
|
|
||||||
pipelineDesc.vertexShader.source.assign(
|
|
||||||
kBuiltinObjectIdOutlineHlsl,
|
|
||||||
kBuiltinObjectIdOutlineHlsl + std::strlen(kBuiltinObjectIdOutlineHlsl));
|
|
||||||
pipelineDesc.vertexShader.sourceLanguage = RHI::ShaderLanguage::HLSL;
|
|
||||||
pipelineDesc.vertexShader.entryPoint = L"MainVS";
|
|
||||||
pipelineDesc.vertexShader.profile = L"vs_5_0";
|
|
||||||
|
|
||||||
pipelineDesc.fragmentShader.source.assign(
|
|
||||||
kBuiltinObjectIdOutlineHlsl,
|
|
||||||
kBuiltinObjectIdOutlineHlsl + std::strlen(kBuiltinObjectIdOutlineHlsl));
|
|
||||||
pipelineDesc.fragmentShader.sourceLanguage = RHI::ShaderLanguage::HLSL;
|
|
||||||
pipelineDesc.fragmentShader.entryPoint = L"MainPS";
|
|
||||||
pipelineDesc.fragmentShader.profile = L"ps_5_0";
|
|
||||||
|
|
||||||
m_pipelineState = m_device->CreatePipelineState(pipelineDesc);
|
|
||||||
if (m_pipelineState == nullptr || !m_pipelineState->IsValid()) {
|
if (m_pipelineState == nullptr || !m_pipelineState->IsValid()) {
|
||||||
DestroyResources();
|
DestroyResources();
|
||||||
return false;
|
return false;
|
||||||
@@ -375,6 +327,7 @@ void BuiltinObjectIdOutlinePass::DestroyResources() {
|
|||||||
|
|
||||||
m_device = nullptr;
|
m_device = nullptr;
|
||||||
m_backendType = RHI::RHIType::D3D12;
|
m_backendType = RHI::RHIType::D3D12;
|
||||||
|
m_builtinObjectIdOutlineShader.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Passes
|
} // namespace Passes
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ constexpr const char* kBuiltinTexturePrefix = "builtin://textures/";
|
|||||||
constexpr const char* kBuiltinDefaultPrimitiveMaterialPath = "builtin://materials/default-primitive";
|
constexpr const char* kBuiltinDefaultPrimitiveMaterialPath = "builtin://materials/default-primitive";
|
||||||
constexpr const char* kBuiltinForwardLitShaderPath = "builtin://shaders/forward-lit";
|
constexpr const char* kBuiltinForwardLitShaderPath = "builtin://shaders/forward-lit";
|
||||||
constexpr const char* kBuiltinObjectIdShaderPath = "builtin://shaders/object-id";
|
constexpr const char* kBuiltinObjectIdShaderPath = "builtin://shaders/object-id";
|
||||||
|
constexpr const char* kBuiltinObjectIdOutlineShaderPath = "builtin://shaders/object-id-outline";
|
||||||
constexpr const char* kBuiltinInfiniteGridShaderPath = "builtin://shaders/infinite-grid";
|
constexpr const char* kBuiltinInfiniteGridShaderPath = "builtin://shaders/infinite-grid";
|
||||||
constexpr const char* kBuiltinDefaultPrimitiveTexturePath = "builtin://textures/default-primitive-albedo";
|
constexpr const char* kBuiltinDefaultPrimitiveTexturePath = "builtin://textures/default-primitive-albedo";
|
||||||
constexpr float kPi = 3.14159265358979323846f;
|
constexpr float kPi = 3.14159265358979323846f;
|
||||||
@@ -309,6 +310,109 @@ void main() {
|
|||||||
}
|
}
|
||||||
)";
|
)";
|
||||||
|
|
||||||
|
const char kBuiltinObjectIdOutlineHlsl[] = R"(
|
||||||
|
cbuffer OutlineConstants : register(b0) {
|
||||||
|
float4 gViewportSizeAndTexelSize;
|
||||||
|
float4 gOutlineColor;
|
||||||
|
float4 gSelectedInfo;
|
||||||
|
float4 gSelectedObjectColors[256];
|
||||||
|
};
|
||||||
|
|
||||||
|
Texture2D gObjectIdTexture : register(t0);
|
||||||
|
|
||||||
|
struct VSOutput {
|
||||||
|
float4 position : SV_POSITION;
|
||||||
|
};
|
||||||
|
|
||||||
|
VSOutput MainVS(uint vertexId : SV_VertexID) {
|
||||||
|
static const float2 positions[3] = {
|
||||||
|
float2(-1.0, -1.0),
|
||||||
|
float2(-1.0, 3.0),
|
||||||
|
float2( 3.0, -1.0)
|
||||||
|
};
|
||||||
|
|
||||||
|
VSOutput output;
|
||||||
|
output.position = float4(positions[vertexId], 0.0, 1.0);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
int2 ClampPixelCoord(int2 pixelCoord) {
|
||||||
|
const int2 maxCoord = int2(
|
||||||
|
max((int)gViewportSizeAndTexelSize.x - 1, 0),
|
||||||
|
max((int)gViewportSizeAndTexelSize.y - 1, 0));
|
||||||
|
return clamp(pixelCoord, int2(0, 0), maxCoord);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 LoadObjectId(int2 pixelCoord) {
|
||||||
|
return gObjectIdTexture.Load(int3(ClampPixelCoord(pixelCoord), 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsSelectedObject(float4 objectIdColor) {
|
||||||
|
if (objectIdColor.a <= 0.0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int selectedCount = min((int)gSelectedInfo.x, 256);
|
||||||
|
[loop]
|
||||||
|
for (int i = 0; i < selectedCount; ++i) {
|
||||||
|
const float4 selectedColor = gSelectedObjectColors[i];
|
||||||
|
if (all(abs(objectIdColor - selectedColor) <= float4(
|
||||||
|
0.0025,
|
||||||
|
0.0025,
|
||||||
|
0.0025,
|
||||||
|
0.0025))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 MainPS(VSOutput input) : SV_TARGET {
|
||||||
|
const int2 pixelCoord = int2(input.position.xy);
|
||||||
|
const bool debugSelectionMask = gSelectedInfo.y > 0.5;
|
||||||
|
const bool centerSelected = IsSelectedObject(LoadObjectId(pixelCoord));
|
||||||
|
|
||||||
|
if (debugSelectionMask) {
|
||||||
|
return centerSelected ? float4(1.0, 1.0, 1.0, 1.0) : float4(0.0, 0.0, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (centerSelected) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int outlineWidth = max((int)gSelectedInfo.z, 1);
|
||||||
|
float outline = 0.0;
|
||||||
|
[loop]
|
||||||
|
for (int y = -2; y <= 2; ++y) {
|
||||||
|
[loop]
|
||||||
|
for (int x = -2; x <= 2; ++x) {
|
||||||
|
if (x == 0 && y == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float distancePixels = length(float2((float)x, (float)y));
|
||||||
|
if (distancePixels > outlineWidth) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsSelectedObject(LoadObjectId(pixelCoord + int2(x, y)))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float weight = saturate(1.0 - ((distancePixels - 1.0) / max((float)outlineWidth, 1.0)));
|
||||||
|
outline = max(outline, weight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outline <= 0.001) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
|
||||||
|
return float4(gOutlineColor.rgb, gOutlineColor.a * outline);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
const char kBuiltinInfiniteGridHlsl[] = R"(
|
const char kBuiltinInfiniteGridHlsl[] = R"(
|
||||||
cbuffer GridConstants : register(b0) {
|
cbuffer GridConstants : register(b0) {
|
||||||
float4x4 gViewProjectionMatrix;
|
float4x4 gViewProjectionMatrix;
|
||||||
@@ -1125,6 +1229,41 @@ Shader* BuildBuiltinInfiniteGridShader(const Containers::String& path) {
|
|||||||
return shader;
|
return shader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Shader* BuildBuiltinObjectIdOutlineShader(const Containers::String& path) {
|
||||||
|
auto* shader = new Shader();
|
||||||
|
IResource::ConstructParams params;
|
||||||
|
params.name = Containers::String("Builtin Object Id Outline");
|
||||||
|
params.path = path;
|
||||||
|
params.guid = ResourceGUID::Generate(path);
|
||||||
|
params.memorySize = 0;
|
||||||
|
shader->Initialize(params);
|
||||||
|
|
||||||
|
const Containers::String passName("ObjectIdOutline");
|
||||||
|
shader->SetPassTag(passName, "LightMode", "ObjectIdOutline");
|
||||||
|
|
||||||
|
AddBuiltinShaderStageVariant(
|
||||||
|
*shader,
|
||||||
|
passName,
|
||||||
|
ShaderType::Vertex,
|
||||||
|
ShaderLanguage::HLSL,
|
||||||
|
ShaderBackend::D3D12,
|
||||||
|
kBuiltinObjectIdOutlineHlsl,
|
||||||
|
"MainVS",
|
||||||
|
"vs_5_0");
|
||||||
|
AddBuiltinShaderStageVariant(
|
||||||
|
*shader,
|
||||||
|
passName,
|
||||||
|
ShaderType::Fragment,
|
||||||
|
ShaderLanguage::HLSL,
|
||||||
|
ShaderBackend::D3D12,
|
||||||
|
kBuiltinObjectIdOutlineHlsl,
|
||||||
|
"MainPS",
|
||||||
|
"ps_5_0");
|
||||||
|
|
||||||
|
shader->m_memorySize = CalculateBuiltinShaderMemorySize(*shader);
|
||||||
|
return shader;
|
||||||
|
}
|
||||||
|
|
||||||
Material* BuildDefaultPrimitiveMaterial(const Containers::String& path) {
|
Material* BuildDefaultPrimitiveMaterial(const Containers::String& path) {
|
||||||
auto* material = new Material();
|
auto* material = new Material();
|
||||||
IResource::ConstructParams params;
|
IResource::ConstructParams params;
|
||||||
@@ -1231,6 +1370,10 @@ Containers::String GetBuiltinObjectIdShaderPath() {
|
|||||||
return Containers::String(kBuiltinObjectIdShaderPath);
|
return Containers::String(kBuiltinObjectIdShaderPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Containers::String GetBuiltinObjectIdOutlineShaderPath() {
|
||||||
|
return Containers::String(kBuiltinObjectIdOutlineShaderPath);
|
||||||
|
}
|
||||||
|
|
||||||
Containers::String GetBuiltinInfiniteGridShaderPath() {
|
Containers::String GetBuiltinInfiniteGridShaderPath() {
|
||||||
return Containers::String(kBuiltinInfiniteGridShaderPath);
|
return Containers::String(kBuiltinInfiniteGridShaderPath);
|
||||||
}
|
}
|
||||||
@@ -1327,6 +1470,8 @@ LoadResult CreateBuiltinShaderResource(const Containers::String& path) {
|
|||||||
shader = BuildBuiltinForwardLitShader(path);
|
shader = BuildBuiltinForwardLitShader(path);
|
||||||
} else if (path == GetBuiltinObjectIdShaderPath()) {
|
} else if (path == GetBuiltinObjectIdShaderPath()) {
|
||||||
shader = BuildBuiltinObjectIdShader(path);
|
shader = BuildBuiltinObjectIdShader(path);
|
||||||
|
} else if (path == GetBuiltinObjectIdOutlineShaderPath()) {
|
||||||
|
shader = BuildBuiltinObjectIdOutlineShader(path);
|
||||||
} else if (path == GetBuiltinInfiniteGridShaderPath()) {
|
} else if (path == GetBuiltinInfiniteGridShaderPath()) {
|
||||||
shader = BuildBuiltinInfiniteGridShader(path);
|
shader = BuildBuiltinInfiniteGridShader(path);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include "../../../RHI/integration/fixtures/RHIIntegrationFixture.h"
|
#include "../../../RHI/integration/fixtures/RHIIntegrationFixture.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <cstdlib>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
using namespace XCEngine::Components;
|
using namespace XCEngine::Components;
|
||||||
@@ -319,5 +320,8 @@ GTEST_API_ int main(int argc, char** argv) {
|
|||||||
Logger::Get().SetMinimumLevel(LogLevel::Debug);
|
Logger::Get().SetMinimumLevel(LogLevel::Debug);
|
||||||
|
|
||||||
testing::InitGoogleTest(&argc, argv);
|
testing::InitGoogleTest(&argc, argv);
|
||||||
return RUN_ALL_TESTS();
|
const int result = RUN_ALL_TESTS();
|
||||||
|
Logger::Get().Shutdown();
|
||||||
|
std::fflush(nullptr);
|
||||||
|
std::_Exit((result == 0 && !testing::UnitTest::GetInstance()->Failed()) ? 0 : 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ TEST(ShaderLoader, CanLoad) {
|
|||||||
EXPECT_TRUE(loader.CanLoad("test.hlsl"));
|
EXPECT_TRUE(loader.CanLoad("test.hlsl"));
|
||||||
EXPECT_TRUE(loader.CanLoad(GetBuiltinForwardLitShaderPath()));
|
EXPECT_TRUE(loader.CanLoad(GetBuiltinForwardLitShaderPath()));
|
||||||
EXPECT_TRUE(loader.CanLoad(GetBuiltinObjectIdShaderPath()));
|
EXPECT_TRUE(loader.CanLoad(GetBuiltinObjectIdShaderPath()));
|
||||||
|
EXPECT_TRUE(loader.CanLoad(GetBuiltinObjectIdOutlineShaderPath()));
|
||||||
EXPECT_TRUE(loader.CanLoad(GetBuiltinInfiniteGridShaderPath()));
|
EXPECT_TRUE(loader.CanLoad(GetBuiltinInfiniteGridShaderPath()));
|
||||||
EXPECT_FALSE(loader.CanLoad("test.txt"));
|
EXPECT_FALSE(loader.CanLoad("test.txt"));
|
||||||
EXPECT_FALSE(loader.CanLoad("test.png"));
|
EXPECT_FALSE(loader.CanLoad("test.png"));
|
||||||
@@ -154,6 +155,31 @@ TEST(ShaderLoader, LoadBuiltinInfiniteGridShaderBuildsD3D12Variants) {
|
|||||||
delete shader;
|
delete shader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(ShaderLoader, LoadBuiltinObjectIdOutlineShaderBuildsD3D12Variants) {
|
||||||
|
ShaderLoader loader;
|
||||||
|
LoadResult result = loader.Load(GetBuiltinObjectIdOutlineShaderPath());
|
||||||
|
ASSERT_TRUE(result);
|
||||||
|
ASSERT_NE(result.resource, nullptr);
|
||||||
|
|
||||||
|
Shader* shader = static_cast<Shader*>(result.resource);
|
||||||
|
ASSERT_NE(shader, nullptr);
|
||||||
|
ASSERT_TRUE(shader->IsValid());
|
||||||
|
|
||||||
|
const ShaderPass* pass = shader->FindPass("ObjectIdOutline");
|
||||||
|
ASSERT_NE(pass, nullptr);
|
||||||
|
ASSERT_EQ(pass->variants.Size(), 2u);
|
||||||
|
ASSERT_EQ(pass->tags.Size(), 1u);
|
||||||
|
EXPECT_EQ(pass->tags[0].name, "LightMode");
|
||||||
|
EXPECT_EQ(pass->tags[0].value, "ObjectIdOutline");
|
||||||
|
|
||||||
|
EXPECT_NE(shader->FindVariant("ObjectIdOutline", ShaderType::Vertex, ShaderBackend::D3D12), nullptr);
|
||||||
|
EXPECT_NE(shader->FindVariant("ObjectIdOutline", ShaderType::Fragment, ShaderBackend::D3D12), nullptr);
|
||||||
|
EXPECT_EQ(shader->FindVariant("ObjectIdOutline", ShaderType::Vertex, ShaderBackend::OpenGL), nullptr);
|
||||||
|
EXPECT_EQ(shader->FindVariant("ObjectIdOutline", ShaderType::Fragment, ShaderBackend::OpenGL), nullptr);
|
||||||
|
|
||||||
|
delete shader;
|
||||||
|
}
|
||||||
|
|
||||||
TEST(ShaderLoader, ResourceManagerLazilyLoadsBuiltinForwardLitShader) {
|
TEST(ShaderLoader, ResourceManagerLazilyLoadsBuiltinForwardLitShader) {
|
||||||
ResourceManager& manager = ResourceManager::Get();
|
ResourceManager& manager = ResourceManager::Get();
|
||||||
manager.Shutdown();
|
manager.Shutdown();
|
||||||
@@ -167,6 +193,10 @@ TEST(ShaderLoader, ResourceManagerLazilyLoadsBuiltinForwardLitShader) {
|
|||||||
ASSERT_TRUE(objectIdShaderHandle.IsValid());
|
ASSERT_TRUE(objectIdShaderHandle.IsValid());
|
||||||
ASSERT_NE(objectIdShaderHandle->FindPass("ObjectId"), nullptr);
|
ASSERT_NE(objectIdShaderHandle->FindPass("ObjectId"), nullptr);
|
||||||
|
|
||||||
|
ResourceHandle<Shader> objectIdOutlineShaderHandle = manager.Load<Shader>(GetBuiltinObjectIdOutlineShaderPath());
|
||||||
|
ASSERT_TRUE(objectIdOutlineShaderHandle.IsValid());
|
||||||
|
ASSERT_NE(objectIdOutlineShaderHandle->FindPass("ObjectIdOutline"), nullptr);
|
||||||
|
|
||||||
ResourceHandle<Shader> infiniteGridShaderHandle = manager.Load<Shader>(GetBuiltinInfiniteGridShaderPath());
|
ResourceHandle<Shader> infiniteGridShaderHandle = manager.Load<Shader>(GetBuiltinInfiniteGridShaderPath());
|
||||||
ASSERT_TRUE(infiniteGridShaderHandle.IsValid());
|
ASSERT_TRUE(infiniteGridShaderHandle.IsValid());
|
||||||
ASSERT_NE(infiniteGridShaderHandle->FindPass("InfiniteGrid"), nullptr);
|
ASSERT_NE(infiniteGridShaderHandle->FindPass("InfiniteGrid"), nullptr);
|
||||||
|
|||||||
Reference in New Issue
Block a user