Close shader authoring usepass regressions

This commit is contained in:
2026-04-07 18:37:11 +08:00
parent 5a3232c099
commit 2f9b1696cd
12 changed files with 1908 additions and 547 deletions

View File

@@ -19,6 +19,9 @@ bool IsBuiltinMeshPath(const Containers::String& path);
bool IsBuiltinMaterialPath(const Containers::String& path);
bool IsBuiltinShaderPath(const Containers::String& path);
bool IsBuiltinTexturePath(const Containers::String& path);
bool TryGetBuiltinShaderPathByShaderName(
const Containers::String& shaderName,
Containers::String& outPath);
const char* GetBuiltinPrimitiveDisplayName(BuiltinPrimitiveType primitiveType);
Containers::String GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType primitiveType);
@@ -28,6 +31,7 @@ Containers::String GetBuiltinUnlitShaderPath();
Containers::String GetBuiltinDepthOnlyShaderPath();
Containers::String GetBuiltinShadowCasterShaderPath();
Containers::String GetBuiltinObjectIdShaderPath();
Containers::String GetBuiltinObjectIdOutlineShaderPath();
Containers::String GetBuiltinSkyboxShaderPath();
Containers::String GetBuiltinColorScalePostProcessShaderPath();
Containers::String GetBuiltinFinalColorShaderPath();

View File

@@ -32,6 +32,7 @@ constexpr const char* kBuiltinUnlitShaderPath = "builtin://shaders/unlit";
constexpr const char* kBuiltinDepthOnlyShaderPath = "builtin://shaders/depth-only";
constexpr const char* kBuiltinShadowCasterShaderPath = "builtin://shaders/shadow-caster";
constexpr const char* kBuiltinObjectIdShaderPath = "builtin://shaders/object-id";
constexpr const char* kBuiltinObjectIdOutlineShaderPath = "builtin://shaders/object-id-outline";
constexpr const char* kBuiltinSkyboxShaderPath = "builtin://shaders/skybox";
constexpr const char* kBuiltinColorScalePostProcessShaderPath =
"builtin://shaders/color-scale-post-process";
@@ -46,22 +47,24 @@ struct MeshBuffers {
size_t CalculateBuiltinShaderMemorySize(const Shader& shader);
constexpr const char* kBuiltinForwardLitShaderManifestRelativePath =
"engine/assets/builtin/shaders/forward-lit/forward-lit.shader";
constexpr const char* kBuiltinUnlitShaderManifestRelativePath =
"engine/assets/builtin/shaders/unlit/unlit.shader";
constexpr const char* kBuiltinDepthOnlyShaderManifestRelativePath =
"engine/assets/builtin/shaders/depth-only/depth-only.shader";
constexpr const char* kBuiltinShadowCasterShaderManifestRelativePath =
"engine/assets/builtin/shaders/shadow-caster/shadow-caster.shader";
constexpr const char* kBuiltinObjectIdShaderManifestRelativePath =
"engine/assets/builtin/shaders/object-id/object-id.shader";
constexpr const char* kBuiltinSkyboxShaderManifestRelativePath =
"engine/assets/builtin/shaders/skybox/skybox.shader";
constexpr const char* kBuiltinColorScalePostProcessShaderManifestRelativePath =
"engine/assets/builtin/shaders/color-scale-post-process/color-scale-post-process.shader";
constexpr const char* kBuiltinFinalColorShaderManifestRelativePath =
"engine/assets/builtin/shaders/final-color/final-color.shader";
constexpr const char* kBuiltinForwardLitShaderAssetRelativePath =
"engine/assets/builtin/shaders/forward-lit.shader";
constexpr const char* kBuiltinUnlitShaderAssetRelativePath =
"engine/assets/builtin/shaders/unlit.shader";
constexpr const char* kBuiltinDepthOnlyShaderAssetRelativePath =
"engine/assets/builtin/shaders/depth-only.shader";
constexpr const char* kBuiltinShadowCasterShaderAssetRelativePath =
"engine/assets/builtin/shaders/shadow-caster.shader";
constexpr const char* kBuiltinObjectIdShaderAssetRelativePath =
"engine/assets/builtin/shaders/object-id.shader";
constexpr const char* kBuiltinObjectIdOutlineShaderAssetRelativePath =
"engine/assets/builtin/shaders/object-id-outline.shader";
constexpr const char* kBuiltinSkyboxShaderAssetRelativePath =
"engine/assets/builtin/shaders/skybox.shader";
constexpr const char* kBuiltinColorScalePostProcessShaderAssetRelativePath =
"engine/assets/builtin/shaders/color-scale-post-process.shader";
constexpr const char* kBuiltinFinalColorShaderAssetRelativePath =
"engine/assets/builtin/shaders/final-color.shader";
Containers::String NormalizeBuiltinAssetPath(const std::filesystem::path& path) {
return Containers::String(path.lexically_normal().generic_string().c_str());
@@ -98,7 +101,7 @@ bool TryResolveBuiltinAssetPathFromAnchor(
return false;
}
bool TryResolveBuiltinShaderManifestPath(
bool TryResolveBuiltinShaderAssetPath(
const std::filesystem::path& relativePath,
Containers::String& outPath) {
std::filesystem::path resolvedPath;
@@ -124,51 +127,54 @@ bool TryResolveBuiltinShaderManifestPath(
return false;
}
const char* GetBuiltinShaderManifestRelativePath(const Containers::String& builtinShaderPath) {
const char* GetBuiltinShaderAssetRelativePath(const Containers::String& builtinShaderPath) {
if (builtinShaderPath == Containers::String(kBuiltinForwardLitShaderPath)) {
return kBuiltinForwardLitShaderManifestRelativePath;
return kBuiltinForwardLitShaderAssetRelativePath;
}
if (builtinShaderPath == Containers::String(kBuiltinUnlitShaderPath)) {
return kBuiltinUnlitShaderManifestRelativePath;
return kBuiltinUnlitShaderAssetRelativePath;
}
if (builtinShaderPath == Containers::String(kBuiltinDepthOnlyShaderPath)) {
return kBuiltinDepthOnlyShaderManifestRelativePath;
return kBuiltinDepthOnlyShaderAssetRelativePath;
}
if (builtinShaderPath == Containers::String(kBuiltinShadowCasterShaderPath)) {
return kBuiltinShadowCasterShaderManifestRelativePath;
return kBuiltinShadowCasterShaderAssetRelativePath;
}
if (builtinShaderPath == Containers::String(kBuiltinObjectIdShaderPath)) {
return kBuiltinObjectIdShaderManifestRelativePath;
return kBuiltinObjectIdShaderAssetRelativePath;
}
if (builtinShaderPath == Containers::String(kBuiltinObjectIdOutlineShaderPath)) {
return kBuiltinObjectIdOutlineShaderAssetRelativePath;
}
if (builtinShaderPath == Containers::String(kBuiltinSkyboxShaderPath)) {
return kBuiltinSkyboxShaderManifestRelativePath;
return kBuiltinSkyboxShaderAssetRelativePath;
}
if (builtinShaderPath == Containers::String(kBuiltinColorScalePostProcessShaderPath)) {
return kBuiltinColorScalePostProcessShaderManifestRelativePath;
return kBuiltinColorScalePostProcessShaderAssetRelativePath;
}
if (builtinShaderPath == Containers::String(kBuiltinFinalColorShaderPath)) {
return kBuiltinFinalColorShaderManifestRelativePath;
return kBuiltinFinalColorShaderAssetRelativePath;
}
return nullptr;
}
bool TryResolveBuiltinShaderManifestPath(
bool TryResolveBuiltinShaderAssetPath(
const Containers::String& builtinShaderPath,
Containers::String& outPath) {
const char* relativePath = GetBuiltinShaderManifestRelativePath(builtinShaderPath);
const char* relativePath = GetBuiltinShaderAssetRelativePath(builtinShaderPath);
if (relativePath == nullptr) {
return false;
}
return TryResolveBuiltinShaderManifestPath(std::filesystem::path(relativePath), outPath);
return TryResolveBuiltinShaderAssetPath(std::filesystem::path(relativePath), outPath);
}
Shader* LoadBuiltinShaderFromManifest(
Shader* LoadBuiltinShaderFromAsset(
const Containers::String& builtinPath,
const Containers::String& manifestPath) {
const Containers::String& assetPath) {
ShaderLoader shaderLoader;
LoadResult result = shaderLoader.Load(manifestPath);
LoadResult result = shaderLoader.Load(assetPath);
if (!result || result.resource == nullptr) {
return nullptr;
}
@@ -180,13 +186,13 @@ Shader* LoadBuiltinShaderFromManifest(
return shader;
}
Shader* TryLoadBuiltinShaderFromManifest(const Containers::String& builtinPath) {
Containers::String manifestPath;
if (!TryResolveBuiltinShaderManifestPath(builtinPath, manifestPath)) {
Shader* TryLoadBuiltinShaderFromAsset(const Containers::String& builtinPath) {
Containers::String assetPath;
if (!TryResolveBuiltinShaderAssetPath(builtinPath, assetPath)) {
return nullptr;
}
return LoadBuiltinShaderFromManifest(builtinPath, manifestPath);
return LoadBuiltinShaderFromAsset(builtinPath, assetPath);
}
Math::Bounds ComputeBounds(const std::vector<StaticMeshVertex>& vertices) {
@@ -675,35 +681,39 @@ size_t CalculateBuiltinShaderMemorySize(const Shader& shader) {
}
Shader* BuildBuiltinForwardLitShader(const Containers::String& path) {
return TryLoadBuiltinShaderFromManifest(path);
return TryLoadBuiltinShaderFromAsset(path);
}
Shader* BuildBuiltinUnlitShader(const Containers::String& path) {
return TryLoadBuiltinShaderFromManifest(path);
return TryLoadBuiltinShaderFromAsset(path);
}
Shader* BuildBuiltinDepthOnlyShader(const Containers::String& path) {
return TryLoadBuiltinShaderFromManifest(path);
return TryLoadBuiltinShaderFromAsset(path);
}
Shader* BuildBuiltinShadowCasterShader(const Containers::String& path) {
return TryLoadBuiltinShaderFromManifest(path);
return TryLoadBuiltinShaderFromAsset(path);
}
Shader* BuildBuiltinObjectIdShader(const Containers::String& path) {
return TryLoadBuiltinShaderFromManifest(path);
return TryLoadBuiltinShaderFromAsset(path);
}
Shader* BuildBuiltinObjectIdOutlineShader(const Containers::String& path) {
return TryLoadBuiltinShaderFromAsset(path);
}
Shader* BuildBuiltinSkyboxShader(const Containers::String& path) {
return TryLoadBuiltinShaderFromManifest(path);
return TryLoadBuiltinShaderFromAsset(path);
}
Shader* BuildBuiltinColorScalePostProcessShader(const Containers::String& path) {
return TryLoadBuiltinShaderFromManifest(path);
return TryLoadBuiltinShaderFromAsset(path);
}
Shader* BuildBuiltinFinalColorShader(const Containers::String& path) {
return TryLoadBuiltinShaderFromManifest(path);
return TryLoadBuiltinShaderFromAsset(path);
}
Material* BuildDefaultPrimitiveMaterial(const Containers::String& path) {
@@ -776,6 +786,50 @@ bool IsBuiltinTexturePath(const Containers::String& path) {
return path.StartsWith(kBuiltinTexturePrefix);
}
bool TryGetBuiltinShaderPathByShaderName(
const Containers::String& shaderName,
Containers::String& outPath) {
if (shaderName == "Builtin Forward Lit") {
outPath = GetBuiltinForwardLitShaderPath();
return true;
}
if (shaderName == "Builtin Unlit") {
outPath = GetBuiltinUnlitShaderPath();
return true;
}
if (shaderName == "Builtin Depth Only") {
outPath = GetBuiltinDepthOnlyShaderPath();
return true;
}
if (shaderName == "Builtin Shadow Caster") {
outPath = GetBuiltinShadowCasterShaderPath();
return true;
}
if (shaderName == "Builtin Object Id") {
outPath = GetBuiltinObjectIdShaderPath();
return true;
}
if (shaderName == "Builtin Object Id Outline") {
outPath = GetBuiltinObjectIdOutlineShaderPath();
return true;
}
if (shaderName == "Builtin Skybox") {
outPath = GetBuiltinSkyboxShaderPath();
return true;
}
if (shaderName == "Builtin Color Scale Post Process") {
outPath = GetBuiltinColorScalePostProcessShaderPath();
return true;
}
if (shaderName == "Builtin Final Color") {
outPath = GetBuiltinFinalColorShaderPath();
return true;
}
outPath.Clear();
return false;
}
const char* GetBuiltinPrimitiveDisplayName(BuiltinPrimitiveType primitiveType) {
switch (primitiveType) {
case BuiltinPrimitiveType::Cube: return "Cube";
@@ -824,6 +878,10 @@ Containers::String GetBuiltinObjectIdShaderPath() {
return Containers::String(kBuiltinObjectIdShaderPath);
}
Containers::String GetBuiltinObjectIdOutlineShaderPath() {
return Containers::String(kBuiltinObjectIdOutlineShaderPath);
}
Containers::String GetBuiltinSkyboxShaderPath() {
return Containers::String(kBuiltinSkyboxShaderPath);
}
@@ -934,6 +992,8 @@ LoadResult CreateBuiltinShaderResource(const Containers::String& path) {
shader = BuildBuiltinShadowCasterShader(path);
} else if (path == GetBuiltinObjectIdShaderPath()) {
shader = BuildBuiltinObjectIdShader(path);
} else if (path == GetBuiltinObjectIdOutlineShaderPath()) {
shader = BuildBuiltinObjectIdOutlineShader(path);
} else if (path == GetBuiltinSkyboxShaderPath()) {
shader = BuildBuiltinSkyboxShader(path);
} else if (path == GetBuiltinColorScalePostProcessShaderPath()) {

View File

@@ -4,6 +4,47 @@ namespace XCEngine {
namespace Resources {
namespace Internal {
namespace {
std::vector<std::string> NormalizeDirectiveTokens(
const std::vector<std::string>& tokens,
size_t startIndex) {
std::vector<std::string> normalizedTokens;
normalizedTokens.reserve(tokens.size() > startIndex ? tokens.size() - startIndex : 0u);
for (size_t tokenIndex = startIndex; tokenIndex < tokens.size(); ++tokenIndex) {
if (tokens[tokenIndex] == ",") {
continue;
}
std::string normalizedToken = tokens[tokenIndex];
while (!normalizedToken.empty() && normalizedToken.back() == ',') {
normalizedToken.pop_back();
}
if (!normalizedToken.empty()) {
normalizedTokens.push_back(std::move(normalizedToken));
}
}
return normalizedTokens;
}
bool TryParseUInt8Value(const std::string& token, Core::uint8& outValue) {
try {
const unsigned long value = std::stoul(token, nullptr, 0);
if (value > 0xFFul) {
return false;
}
outValue = static_cast<Core::uint8>(value);
return true;
} catch (...) {
return false;
}
}
} // namespace
MaterialRenderState BuildDefaultAuthoringFixedFunctionState() {
MaterialRenderState state = {};
state.blendEnable = false;
@@ -146,6 +187,68 @@ bool TryParseAuthoringBlendFactor(const std::string& token, MaterialBlendFactor&
return false;
}
bool TryParseAuthoringBlendOp(const std::string& token, MaterialBlendOp& outOp) {
const Containers::String normalized = Containers::String(token.c_str()).Trim().ToLower();
if (normalized == "add") {
outOp = MaterialBlendOp::Add;
return true;
}
if (normalized == "sub" || normalized == "subtract") {
outOp = MaterialBlendOp::Subtract;
return true;
}
if (normalized == "revsub" || normalized == "reverse_subtract" || normalized == "reversesubtract") {
outOp = MaterialBlendOp::ReverseSubtract;
return true;
}
if (normalized == "min") {
outOp = MaterialBlendOp::Min;
return true;
}
if (normalized == "max") {
outOp = MaterialBlendOp::Max;
return true;
}
return false;
}
bool TryParseAuthoringStencilOp(const std::string& token, MaterialStencilOp& outOp) {
const Containers::String normalized = Containers::String(token.c_str()).Trim().ToLower();
if (normalized == "keep") {
outOp = MaterialStencilOp::Keep;
return true;
}
if (normalized == "zero") {
outOp = MaterialStencilOp::Zero;
return true;
}
if (normalized == "replace") {
outOp = MaterialStencilOp::Replace;
return true;
}
if (normalized == "incrsat" || normalized == "incr_sat") {
outOp = MaterialStencilOp::IncrSat;
return true;
}
if (normalized == "decrsat" || normalized == "decr_sat") {
outOp = MaterialStencilOp::DecrSat;
return true;
}
if (normalized == "invert") {
outOp = MaterialStencilOp::Invert;
return true;
}
if (normalized == "incrwrap" || normalized == "incr_wrap" || normalized == "incr") {
outOp = MaterialStencilOp::IncrWrap;
return true;
}
if (normalized == "decrwrap" || normalized == "decr_wrap" || normalized == "decr") {
outOp = MaterialStencilOp::DecrWrap;
return true;
}
return false;
}
bool TryParseAuthoringColorMask(const std::string& token, Core::uint8& outMask) {
const Containers::String normalized = Containers::String(token.c_str()).Trim().ToUpper();
if (normalized == "0") {
@@ -180,21 +283,7 @@ bool TryParseAuthoringColorMask(const std::string& token, Core::uint8& outMask)
bool TryParseAuthoringBlendDirective(
const std::vector<std::string>& tokens,
MaterialRenderState& outState) {
std::vector<std::string> normalizedTokens;
normalizedTokens.reserve(tokens.size());
for (const std::string& token : tokens) {
if (token == ",") {
continue;
}
std::string normalizedToken = token;
while (!normalizedToken.empty() && normalizedToken.back() == ',') {
normalizedToken.pop_back();
}
if (!normalizedToken.empty()) {
normalizedTokens.push_back(std::move(normalizedToken));
}
}
const std::vector<std::string> normalizedTokens = NormalizeDirectiveTokens(tokens, 0u);
if (normalizedTokens.size() != 2u &&
normalizedTokens.size() != 3u &&
@@ -242,6 +331,169 @@ bool TryParseAuthoringBlendDirective(
return true;
}
bool TryParseAuthoringBlendOpDirective(
const std::vector<std::string>& tokens,
MaterialRenderState& outState) {
const std::vector<std::string> normalizedTokens = NormalizeDirectiveTokens(tokens, 1u);
if (normalizedTokens.size() != 1u && normalizedTokens.size() != 2u) {
return false;
}
if (!TryParseAuthoringBlendOp(normalizedTokens[0], outState.blendOp)) {
return false;
}
if (normalizedTokens.size() == 2u) {
return TryParseAuthoringBlendOp(normalizedTokens[1], outState.blendOpAlpha);
}
outState.blendOpAlpha = outState.blendOp;
return true;
}
bool TryParseAuthoringOffsetDirective(
const std::vector<std::string>& tokens,
MaterialRenderState& outState) {
const std::vector<std::string> normalizedTokens = NormalizeDirectiveTokens(tokens, 1u);
if (normalizedTokens.size() != 2u) {
return false;
}
try {
outState.depthBiasFactor = std::stof(normalizedTokens[0]);
outState.depthBiasUnits = static_cast<Core::int32>(std::stol(normalizedTokens[1]));
return true;
} catch (...) {
return false;
}
}
bool TryParseAuthoringStencilDirective(
const std::vector<std::string>& tokens,
MaterialStencilState& outState) {
if (tokens.size() != 2u && tokens.size() != 3u) {
return false;
}
const Containers::String directive = Containers::String(tokens[0].c_str()).Trim().ToLower();
if (directive == "ref") {
Core::uint8 reference = 0;
if (!TryParseUInt8Value(tokens[1], reference)) {
return false;
}
outState.reference = reference;
outState.enabled = true;
return true;
}
if (directive == "readmask") {
Core::uint8 readMask = 0;
if (!TryParseUInt8Value(tokens[1], readMask)) {
return false;
}
outState.readMask = readMask;
outState.enabled = true;
return true;
}
if (directive == "writemask") {
Core::uint8 writeMask = 0;
if (!TryParseUInt8Value(tokens[1], writeMask)) {
return false;
}
outState.writeMask = writeMask;
outState.enabled = true;
return true;
}
auto applyComparison = [&](MaterialStencilFaceState& faceState) -> bool {
return TryParseAuthoringComparisonFunc(tokens[1], faceState.func);
};
auto applyOperation = [&](MaterialStencilFaceState& faceState, MaterialStencilOp MaterialStencilFaceState::* member) -> bool {
MaterialStencilOp op = MaterialStencilOp::Keep;
if (!TryParseAuthoringStencilOp(tokens[1], op)) {
return false;
}
faceState.*member = op;
return true;
};
if (directive == "comp") {
outState.enabled = true;
return applyComparison(outState.front) && applyComparison(outState.back);
}
if (directive == "pass") {
outState.enabled = true;
return applyOperation(outState.front, &MaterialStencilFaceState::passOp) &&
applyOperation(outState.back, &MaterialStencilFaceState::passOp);
}
if (directive == "fail") {
outState.enabled = true;
return applyOperation(outState.front, &MaterialStencilFaceState::failOp) &&
applyOperation(outState.back, &MaterialStencilFaceState::failOp);
}
if (directive == "zfail") {
outState.enabled = true;
return applyOperation(outState.front, &MaterialStencilFaceState::depthFailOp) &&
applyOperation(outState.back, &MaterialStencilFaceState::depthFailOp);
}
if (directive == "compfront") {
outState.enabled = true;
return applyComparison(outState.front);
}
if (directive == "passfront") {
outState.enabled = true;
return applyOperation(outState.front, &MaterialStencilFaceState::passOp);
}
if (directive == "failfront") {
outState.enabled = true;
return applyOperation(outState.front, &MaterialStencilFaceState::failOp);
}
if (directive == "zfailfront") {
outState.enabled = true;
return applyOperation(outState.front, &MaterialStencilFaceState::depthFailOp);
}
if (directive == "compback") {
outState.enabled = true;
return applyComparison(outState.back);
}
if (directive == "passback") {
outState.enabled = true;
return applyOperation(outState.back, &MaterialStencilFaceState::passOp);
}
if (directive == "failback") {
outState.enabled = true;
return applyOperation(outState.back, &MaterialStencilFaceState::failOp);
}
if (directive == "zfailback") {
outState.enabled = true;
return applyOperation(outState.back, &MaterialStencilFaceState::depthFailOp);
}
return false;
}
bool TryParseAuthoringUsePassReference(
const Containers::String& reference,
Containers::String& outShaderName,
Containers::String& outPassName) {
outShaderName.Clear();
outPassName.Clear();
const std::string referenceText = reference.Trim().CStr();
const size_t slashPos = referenceText.rfind('/');
if (slashPos == std::string::npos ||
slashPos == 0u ||
slashPos + 1u >= referenceText.size()) {
return false;
}
outShaderName = Containers::String(referenceText.substr(0, slashPos).c_str());
outPassName = Containers::String(referenceText.substr(slashPos + 1u).c_str());
return !outShaderName.Empty() && !outPassName.Empty();
}
void SetOrReplaceAuthoringTag(
std::vector<ShaderTagIR>& tags,
const Containers::String& name,

View File

@@ -1,19 +1,33 @@
#include "ShaderAuthoringLoader.h"
#include "../ShaderAuthoringParser.h"
#include "ShaderRuntimeBuildUtils.h"
#include "../ShaderSourceUtils.h"
#include "ShaderFileUtils.h"
#include "ShaderRuntimeBuildUtils.h"
#include <XCEngine/Resources/BuiltinResources.h>
#include <filesystem>
#include <unordered_set>
namespace XCEngine {
namespace Resources {
bool CollectShaderAuthoringDependencyPaths(
namespace {
namespace fs = std::filesystem;
bool CollectShaderAuthoringDependencyPathsRecursive(
const Containers::String& path,
const std::string& sourceText,
std::unordered_set<std::string>& seenShaderPaths,
std::unordered_set<std::string>& seenDependencyPaths,
Containers::Array<Containers::String>& outDependencies) {
outDependencies.Clear();
const fs::path normalizedShaderPath = fs::path(path.CStr()).lexically_normal();
const std::string shaderKey = normalizedShaderPath.generic_string();
if (!shaderKey.empty() && !seenShaderPaths.insert(shaderKey).second) {
return true;
}
ShaderIR shaderIR = {};
Containers::String parseError;
@@ -21,19 +35,91 @@ bool CollectShaderAuthoringDependencyPaths(
return false;
}
std::unordered_set<std::string> seenPaths;
CollectQuotedIncludeDependencyPaths(path, shaderIR.sharedProgramSource, seenPaths, outDependencies);
CollectQuotedIncludeDependencyPaths(path, shaderIR.sharedProgramSource, seenDependencyPaths, outDependencies);
for (const ShaderSubShaderIR& subShader : shaderIR.subShaders) {
CollectQuotedIncludeDependencyPaths(path, subShader.sharedProgramSource, seenPaths, outDependencies);
CollectQuotedIncludeDependencyPaths(path, subShader.sharedProgramSource, seenDependencyPaths, outDependencies);
for (const ShaderPassIR& pass : subShader.passes) {
CollectQuotedIncludeDependencyPaths(path, pass.sharedProgramSource, seenPaths, outDependencies);
CollectQuotedIncludeDependencyPaths(path, pass.programSource, seenPaths, outDependencies);
if (!pass.isUsePass) {
CollectQuotedIncludeDependencyPaths(path, pass.sharedProgramSource, seenDependencyPaths, outDependencies);
CollectQuotedIncludeDependencyPaths(path, pass.programSource, seenDependencyPaths, outDependencies);
continue;
}
Containers::String resolvedUsePassPath;
if (!ResolveShaderUsePassPath(
path,
shaderIR.name,
pass.usePassShaderName,
resolvedUsePassPath)) {
Containers::String builtinShaderPath;
if (!TryGetBuiltinShaderPathByShaderName(pass.usePassShaderName, builtinShaderPath)) {
return false;
}
if (!builtinShaderPath.Empty() &&
seenDependencyPaths.insert(ToStdString(builtinShaderPath)).second) {
outDependencies.PushBack(builtinShaderPath);
}
continue;
}
if (resolvedUsePassPath.Empty() || resolvedUsePassPath == path) {
continue;
}
if (IsBuiltinShaderPath(resolvedUsePassPath)) {
if (seenDependencyPaths.insert(ToStdString(resolvedUsePassPath)).second) {
outDependencies.PushBack(resolvedUsePassPath);
}
continue;
}
const fs::path normalizedDependencyPath =
fs::path(resolvedUsePassPath.CStr()).lexically_normal();
const Containers::String normalizedDependency =
normalizedDependencyPath.generic_string().c_str();
if (!normalizedDependency.Empty() &&
seenDependencyPaths.insert(ToStdString(normalizedDependency)).second) {
outDependencies.PushBack(normalizedDependency);
}
Containers::String referencedSourceText;
if (!ReadShaderTextFile(resolvedUsePassPath, referencedSourceText)) {
return false;
}
if (!CollectShaderAuthoringDependencyPathsRecursive(
resolvedUsePassPath,
referencedSourceText.CStr(),
seenShaderPaths,
seenDependencyPaths,
outDependencies)) {
return false;
}
}
}
return true;
}
} // namespace
bool CollectShaderAuthoringDependencyPaths(
const Containers::String& path,
const std::string& sourceText,
Containers::Array<Containers::String>& outDependencies) {
outDependencies.Clear();
std::unordered_set<std::string> seenShaderPaths;
std::unordered_set<std::string> seenDependencyPaths;
return CollectShaderAuthoringDependencyPathsRecursive(
path,
sourceText,
seenShaderPaths,
seenDependencyPaths,
outDependencies);
}
LoadResult LoadShaderAuthoring(
const Containers::String& path,
const std::string& sourceText) {

View File

@@ -17,7 +17,8 @@ bool ParseShaderAuthoring(
Shader,
Properties,
SubShader,
Pass
Pass,
Stencil
};
auto fail = [&outError](const std::string& message, size_t lineNumber) -> bool {
@@ -48,6 +49,7 @@ bool ParseShaderAuthoring(
BlockKind pendingBlock = BlockKind::None;
ShaderSubShaderIR* currentSubShader = nullptr;
ShaderPassIR* currentPass = nullptr;
MaterialRenderState* currentStencilRenderState = nullptr;
bool inProgramBlock = false;
bool inSharedIncludeBlock = false;
@@ -76,9 +78,193 @@ bool ParseShaderAuthoring(
return true;
};
auto tryParseFixedFunctionDirective =
[&](const std::string& line, bool& outMatched, std::string& outErrorMessage) -> bool {
outMatched = false;
MaterialRenderState* targetState = nullptr;
bool* hasFixedFunctionState = nullptr;
if (currentBlock() == BlockKind::Pass && currentPass != nullptr) {
targetState = &currentPass->fixedFunctionState;
hasFixedFunctionState = &currentPass->hasFixedFunctionState;
} else if (currentBlock() == BlockKind::SubShader && currentSubShader != nullptr) {
targetState = &currentSubShader->fixedFunctionState;
hasFixedFunctionState = &currentSubShader->hasFixedFunctionState;
} else {
return true;
}
std::vector<std::string> tokens;
if (StartsWithKeyword(line, "Cull")) {
outMatched = true;
if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() != 2u) {
outErrorMessage = "Cull directive must use Front, Back, or Off";
return false;
}
EnsureAuthoringFixedFunctionStateInitialized(*hasFixedFunctionState, *targetState);
if (!TryParseAuthoringCullMode(tokens[1], targetState->cullMode)) {
outErrorMessage = "Cull directive must use Front, Back, or Off";
return false;
}
return true;
}
if (StartsWithKeyword(line, "ZWrite")) {
outMatched = true;
bool enabled = false;
if (!TryTokenizeQuotedArguments(line, tokens) ||
tokens.size() != 2u ||
!TryParseAuthoringBoolDirectiveToken(tokens[1], enabled)) {
outErrorMessage = "ZWrite directive must use On or Off";
return false;
}
EnsureAuthoringFixedFunctionStateInitialized(*hasFixedFunctionState, *targetState);
targetState->depthWriteEnable = enabled;
return true;
}
if (StartsWithKeyword(line, "ZTest")) {
outMatched = true;
if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() != 2u) {
outErrorMessage = "ZTest directive uses an unsupported compare function";
return false;
}
EnsureAuthoringFixedFunctionStateInitialized(*hasFixedFunctionState, *targetState);
if (!TryParseAuthoringComparisonFunc(tokens[1], targetState->depthFunc)) {
outErrorMessage = "ZTest directive uses an unsupported compare function";
return false;
}
targetState->depthTestEnable = true;
return true;
}
if (StartsWithKeyword(line, "BlendOp")) {
outMatched = true;
if (!TryTokenizeQuotedArguments(line, tokens)) {
outErrorMessage = "BlendOp directive could not be tokenized";
return false;
}
EnsureAuthoringFixedFunctionStateInitialized(*hasFixedFunctionState, *targetState);
if (!TryParseAuthoringBlendOpDirective(tokens, *targetState)) {
outErrorMessage = "BlendOp directive uses an unsupported blend operation";
return false;
}
return true;
}
if (StartsWithKeyword(line, "Blend")) {
outMatched = true;
if (!TryTokenizeQuotedArguments(line, tokens)) {
outErrorMessage = "Blend directive could not be tokenized";
return false;
}
EnsureAuthoringFixedFunctionStateInitialized(*hasFixedFunctionState, *targetState);
if (!TryParseAuthoringBlendDirective(tokens, *targetState)) {
outErrorMessage = "Blend directive uses an unsupported factor combination";
return false;
}
return true;
}
if (StartsWithKeyword(line, "ColorMask")) {
outMatched = true;
if (!TryTokenizeQuotedArguments(line, tokens) ||
(tokens.size() != 2u && tokens.size() != 3u)) {
outErrorMessage = "ColorMask directive uses an unsupported channel mask";
return false;
}
EnsureAuthoringFixedFunctionStateInitialized(*hasFixedFunctionState, *targetState);
if (!TryParseAuthoringColorMask(tokens[1], targetState->colorWriteMask)) {
outErrorMessage = "ColorMask directive uses an unsupported channel mask";
return false;
}
return true;
}
if (StartsWithKeyword(line, "Offset")) {
outMatched = true;
if (!TryTokenizeQuotedArguments(line, tokens)) {
outErrorMessage = "Offset directive could not be tokenized";
return false;
}
EnsureAuthoringFixedFunctionStateInitialized(*hasFixedFunctionState, *targetState);
if (!TryParseAuthoringOffsetDirective(tokens, *targetState)) {
outErrorMessage = "Offset directive must provide factor and units";
return false;
}
return true;
}
return true;
};
auto tryParseStencilDirective =
[&](const std::string& line, bool& outMatched, std::string& outErrorMessage) -> bool {
outMatched = false;
if (currentBlock() != BlockKind::Stencil || currentStencilRenderState == nullptr) {
return true;
}
std::vector<std::string> tokens;
if (!TryTokenizeQuotedArguments(line, tokens)) {
outMatched = true;
outErrorMessage = "Stencil directive could not be tokenized";
return false;
}
outMatched = true;
if (!TryParseAuthoringStencilDirective(tokens, currentStencilRenderState->stencil)) {
outErrorMessage = "Stencil block contains an unsupported directive";
return false;
}
return true;
};
auto tryParseUsePassDirective =
[&](const std::string& line, bool& outMatched, std::string& outErrorMessage) -> bool {
outMatched = false;
if (currentBlock() != BlockKind::SubShader || currentSubShader == nullptr) {
return true;
}
if (!StartsWithKeyword(line, "UsePass")) {
return true;
}
outMatched = true;
std::vector<std::string> tokens;
if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() != 2u) {
outErrorMessage = "UsePass directive must reference \"ShaderName/PassName\"";
return false;
}
ShaderPassIR usePass = {};
usePass.isUsePass = true;
if (!TryParseAuthoringUsePassReference(
tokens[1].c_str(),
usePass.usePassShaderName,
usePass.usePassPassName)) {
outErrorMessage = "UsePass directive must reference \"ShaderName/PassName\"";
return false;
}
usePass.name = usePass.usePassPassName;
currentSubShader->passes.push_back(std::move(usePass));
return true;
};
for (size_t lineIndex = 0; lineIndex < lines.size(); ++lineIndex) {
const std::string& line = lines[lineIndex];
const size_t humanLine = lineIndex + 1;
const size_t humanLine = lineIndex + 1u;
if (inSharedIncludeBlock || inProgramBlock) {
if (line == "ENDHLSL" || line == "ENDCG") {
@@ -159,6 +345,24 @@ bool ParseShaderAuthoring(
}
blockStack.push_back(BlockKind::Pass);
break;
case BlockKind::Stencil:
if (currentPass != nullptr) {
EnsureAuthoringFixedFunctionStateInitialized(
currentPass->hasFixedFunctionState,
currentPass->fixedFunctionState);
currentStencilRenderState = &currentPass->fixedFunctionState;
} else if (currentSubShader != nullptr) {
EnsureAuthoringFixedFunctionStateInitialized(
currentSubShader->hasFixedFunctionState,
currentSubShader->fixedFunctionState);
currentStencilRenderState = &currentSubShader->fixedFunctionState;
} else {
return fail("Stencil block must be inside a SubShader or Pass", humanLine);
}
currentStencilRenderState->stencil.enabled = true;
blockStack.push_back(BlockKind::Stencil);
break;
case BlockKind::None:
default:
return fail("unexpected opening brace", humanLine);
@@ -175,7 +379,9 @@ bool ParseShaderAuthoring(
const BlockKind closingBlock = blockStack.back();
blockStack.pop_back();
if (closingBlock == BlockKind::Pass) {
if (closingBlock == BlockKind::Stencil) {
currentStencilRenderState = nullptr;
} else if (closingBlock == BlockKind::Pass) {
currentPass = nullptr;
} else if (closingBlock == BlockKind::SubShader) {
currentSubShader = nullptr;
@@ -212,7 +418,7 @@ bool ParseShaderAuthoring(
continue;
}
if (StartsWithKeyword(line, "Pass")) {
if (line == "Pass") {
pendingBlock = BlockKind::Pass;
continue;
}
@@ -288,88 +494,41 @@ bool ParseShaderAuthoring(
continue;
}
if (currentBlock() == BlockKind::SubShader && currentSubShader != nullptr) {
if (StartsWithKeyword(line, "Cull")) {
std::vector<std::string> tokens;
if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() != 2u) {
return fail("Cull directive must use Front, Back, or Off", humanLine);
}
EnsureAuthoringFixedFunctionStateInitialized(
currentSubShader->hasFixedFunctionState,
currentSubShader->fixedFunctionState);
if (!TryParseAuthoringCullMode(tokens[1], currentSubShader->fixedFunctionState.cullMode)) {
return fail("Cull directive must use Front, Back, or Off", humanLine);
}
if ((currentBlock() == BlockKind::SubShader || currentBlock() == BlockKind::Pass) &&
line == "Stencil") {
pendingBlock = BlockKind::Stencil;
continue;
}
if (StartsWithKeyword(line, "ZWrite")) {
std::vector<std::string> tokens;
bool enabled = false;
if (!TryTokenizeQuotedArguments(line, tokens) ||
tokens.size() != 2u ||
!TryParseAuthoringBoolDirectiveToken(tokens[1], enabled)) {
return fail("ZWrite directive must use On or Off", humanLine);
if (currentBlock() == BlockKind::Stencil) {
bool matched = false;
std::string errorMessage;
if (!tryParseStencilDirective(line, matched, errorMessage)) {
return fail(errorMessage, humanLine);
}
EnsureAuthoringFixedFunctionStateInitialized(
currentSubShader->hasFixedFunctionState,
currentSubShader->fixedFunctionState);
currentSubShader->fixedFunctionState.depthWriteEnable = enabled;
if (matched) {
continue;
}
if (StartsWithKeyword(line, "ZTest")) {
std::vector<std::string> tokens;
if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() != 2u) {
return fail("ZTest directive uses an unsupported compare function", humanLine);
}
EnsureAuthoringFixedFunctionStateInitialized(
currentSubShader->hasFixedFunctionState,
currentSubShader->fixedFunctionState);
if (!TryParseAuthoringComparisonFunc(tokens[1], currentSubShader->fixedFunctionState.depthFunc)) {
return fail("ZTest directive uses an unsupported compare function", humanLine);
if (currentBlock() == BlockKind::SubShader) {
bool matched = false;
std::string errorMessage;
if (!tryParseUsePassDirective(line, matched, errorMessage)) {
return fail(errorMessage, humanLine);
}
currentSubShader->fixedFunctionState.depthTestEnable = true;
if (matched) {
continue;
}
if (StartsWithKeyword(line, "Blend")) {
std::vector<std::string> tokens;
if (!TryTokenizeQuotedArguments(line, tokens)) {
return fail("Blend directive could not be tokenized", humanLine);
}
for (size_t tokenIndex = 1; tokenIndex < tokens.size(); ++tokenIndex) {
if (!tokens[tokenIndex].empty() && tokens[tokenIndex].back() == ',') {
tokens[tokenIndex].pop_back();
}
}
EnsureAuthoringFixedFunctionStateInitialized(
currentSubShader->hasFixedFunctionState,
currentSubShader->fixedFunctionState);
if (!TryParseAuthoringBlendDirective(tokens, currentSubShader->fixedFunctionState)) {
return fail("Blend directive uses an unsupported factor combination", humanLine);
}
continue;
}
if (StartsWithKeyword(line, "ColorMask")) {
std::vector<std::string> tokens;
if (!TryTokenizeQuotedArguments(line, tokens) ||
(tokens.size() != 2u && tokens.size() != 3u)) {
return fail("ColorMask directive uses an unsupported channel mask", humanLine);
}
EnsureAuthoringFixedFunctionStateInitialized(
currentSubShader->hasFixedFunctionState,
currentSubShader->fixedFunctionState);
if (!TryParseAuthoringColorMask(tokens[1], currentSubShader->fixedFunctionState.colorWriteMask)) {
return fail("ColorMask directive uses an unsupported channel mask", humanLine);
if (currentBlock() == BlockKind::SubShader || currentBlock() == BlockKind::Pass) {
bool matched = false;
std::string errorMessage;
if (!tryParseFixedFunctionDirective(line, matched, errorMessage)) {
return fail(errorMessage, humanLine);
}
if (matched) {
continue;
}
}
@@ -384,90 +543,6 @@ bool ParseShaderAuthoring(
continue;
}
if (StartsWithKeyword(line, "Cull")) {
std::vector<std::string> tokens;
if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() != 2u) {
return fail("Cull directive must use Front, Back, or Off", humanLine);
}
EnsureAuthoringFixedFunctionStateInitialized(
currentPass->hasFixedFunctionState,
currentPass->fixedFunctionState);
if (!TryParseAuthoringCullMode(tokens[1], currentPass->fixedFunctionState.cullMode)) {
return fail("Cull directive must use Front, Back, or Off", humanLine);
}
continue;
}
if (StartsWithKeyword(line, "ZWrite")) {
std::vector<std::string> tokens;
bool enabled = false;
if (!TryTokenizeQuotedArguments(line, tokens) ||
tokens.size() != 2u ||
!TryParseAuthoringBoolDirectiveToken(tokens[1], enabled)) {
return fail("ZWrite directive must use On or Off", humanLine);
}
EnsureAuthoringFixedFunctionStateInitialized(
currentPass->hasFixedFunctionState,
currentPass->fixedFunctionState);
currentPass->fixedFunctionState.depthWriteEnable = enabled;
continue;
}
if (StartsWithKeyword(line, "ZTest")) {
std::vector<std::string> tokens;
if (!TryTokenizeQuotedArguments(line, tokens) || tokens.size() != 2u) {
return fail("ZTest directive uses an unsupported compare function", humanLine);
}
EnsureAuthoringFixedFunctionStateInitialized(
currentPass->hasFixedFunctionState,
currentPass->fixedFunctionState);
if (!TryParseAuthoringComparisonFunc(tokens[1], currentPass->fixedFunctionState.depthFunc)) {
return fail("ZTest directive uses an unsupported compare function", humanLine);
}
currentPass->fixedFunctionState.depthTestEnable = true;
continue;
}
if (StartsWithKeyword(line, "Blend")) {
std::vector<std::string> tokens;
if (!TryTokenizeQuotedArguments(line, tokens)) {
return fail("Blend directive could not be tokenized", humanLine);
}
for (size_t tokenIndex = 1; tokenIndex < tokens.size(); ++tokenIndex) {
if (!tokens[tokenIndex].empty() && tokens[tokenIndex].back() == ',') {
tokens[tokenIndex].pop_back();
}
}
EnsureAuthoringFixedFunctionStateInitialized(
currentPass->hasFixedFunctionState,
currentPass->fixedFunctionState);
if (!TryParseAuthoringBlendDirective(tokens, currentPass->fixedFunctionState)) {
return fail("Blend directive uses an unsupported factor combination", humanLine);
}
continue;
}
if (StartsWithKeyword(line, "ColorMask")) {
std::vector<std::string> tokens;
if (!TryTokenizeQuotedArguments(line, tokens) ||
(tokens.size() != 2u && tokens.size() != 3u)) {
return fail("ColorMask directive uses an unsupported channel mask", humanLine);
}
EnsureAuthoringFixedFunctionStateInitialized(
currentPass->hasFixedFunctionState,
currentPass->fixedFunctionState);
if (!TryParseAuthoringColorMask(tokens[1], currentPass->fixedFunctionState.colorWriteMask)) {
return fail("ColorMask directive uses an unsupported channel mask", humanLine);
}
continue;
}
if (line == "HLSLPROGRAM" || line == "CGPROGRAM") {
inProgramBlock = true;
if (!consumeExtractedBlock(
@@ -503,6 +578,13 @@ bool ParseShaderAuthoring(
}
for (ShaderPassIR& pass : subShader.passes) {
if (pass.isUsePass) {
if (pass.usePassShaderName.Empty() || pass.usePassPassName.Empty()) {
return fail("a UsePass directive is missing a valid target", 0);
}
continue;
}
if (pass.name.Empty()) {
return fail("a Pass is missing a Name directive", 0);
}

View File

@@ -1,14 +1,300 @@
#include "ShaderRuntimeBuildUtils.h"
#include "../ShaderAuthoringParser.h"
#include "ShaderFileUtils.h"
#include "../ShaderSourceUtils.h"
#include "ShaderAuthoringInternal.h"
#include "ShaderFileUtils.h"
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Shader/ShaderLoader.h>
#include <filesystem>
#include <memory>
#include <system_error>
#include <unordered_map>
#include <unordered_set>
namespace XCEngine {
namespace Resources {
namespace {
namespace fs = std::filesystem;
Containers::String ResolveBuiltinShaderPathByAuthoringName(const Containers::String& shaderName) {
Containers::String builtinShaderPath;
if (TryGetBuiltinShaderPathByShaderName(shaderName, builtinShaderPath)) {
return builtinShaderPath;
}
return Containers::String();
}
void AddUniqueSearchRoot(
const fs::path& candidate,
std::vector<fs::path>& searchRoots,
std::unordered_set<std::string>& seenRoots) {
if (candidate.empty()) {
return;
}
const fs::path normalized = candidate.lexically_normal();
const std::string key = normalized.generic_string();
if (key.empty()) {
return;
}
if (seenRoots.insert(key).second) {
searchRoots.push_back(normalized);
}
}
bool TryReadDeclaredAuthoringShaderName(
const Containers::String& shaderPath,
Containers::String& outShaderName) {
outShaderName.Clear();
const Containers::Array<Core::uint8> data = ReadShaderFileData(shaderPath);
if (data.Empty()) {
return false;
}
const std::string sourceText = ToStdStringFromBytes(data);
std::vector<std::string> lines;
Internal::SplitShaderAuthoringLines(sourceText, lines);
if (lines.empty() || !Internal::StartsWithKeyword(lines.front(), "Shader")) {
return false;
}
std::vector<std::string> tokens;
if (!Internal::TryTokenizeQuotedArguments(lines.front(), tokens) || tokens.size() < 2u) {
return false;
}
outShaderName = tokens[1].c_str();
return !outShaderName.Empty();
}
bool TryResolveProjectShaderPathByAuthoringName(
const Containers::String& currentShaderPath,
const Containers::String& shaderName,
Containers::String& outResolvedPath) {
outResolvedPath.Clear();
fs::path currentPath(currentShaderPath.CStr());
if (!currentPath.is_absolute()) {
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
if (!resourceRoot.Empty()) {
currentPath = fs::path(resourceRoot.CStr()) / currentPath;
}
}
if (currentPath.empty()) {
return false;
}
std::vector<fs::path> searchRoots;
std::unordered_set<std::string> seenRoots;
AddUniqueSearchRoot(currentPath.parent_path(), searchRoots, seenRoots);
for (fs::path ancestor = currentPath.parent_path();
!ancestor.empty();
ancestor = ancestor.parent_path()) {
std::error_code ec;
const fs::path assetsRoot = ancestor / "Assets";
if (fs::exists(assetsRoot, ec) && fs::is_directory(assetsRoot, ec)) {
AddUniqueSearchRoot(assetsRoot, searchRoots, seenRoots);
}
const fs::path parent = ancestor.parent_path();
if (parent == ancestor) {
break;
}
}
std::vector<fs::path> matches;
std::unordered_set<std::string> seenMatches;
for (const fs::path& root : searchRoots) {
std::error_code ec;
if (!fs::exists(root, ec) || !fs::is_directory(root, ec)) {
continue;
}
for (fs::recursive_directory_iterator it(root, ec), end; !ec && it != end; it.increment(ec)) {
if (!it->is_regular_file()) {
continue;
}
const fs::path candidatePath = it->path();
if (candidatePath.extension() != ".shader") {
continue;
}
Containers::String declaredShaderName;
if (!TryReadDeclaredAuthoringShaderName(
Containers::String(candidatePath.generic_string().c_str()),
declaredShaderName) ||
declaredShaderName != shaderName) {
continue;
}
const std::string matchKey = candidatePath.lexically_normal().generic_string();
if (seenMatches.insert(matchKey).second) {
matches.push_back(candidatePath.lexically_normal());
}
}
}
if (matches.size() != 1u) {
return false;
}
outResolvedPath = matches.front().generic_string().c_str();
return true;
}
void ImportConcretePass(Shader& shader, const ShaderPass& sourcePass) {
if (ShaderPass* existingPass = shader.FindPass(sourcePass.name)) {
*existingPass = sourcePass;
return;
}
shader.AddPass(sourcePass);
}
ShaderPass BuildConcretePass(
const ShaderIR& shaderIR,
const ShaderSubShaderIR& subShader,
const ShaderPassIR& pass) {
ShaderPass shaderPass = {};
shaderPass.name = pass.name;
shaderPass.hasFixedFunctionState = pass.hasFixedFunctionState;
shaderPass.fixedFunctionState = pass.fixedFunctionState;
for (const ShaderTagIR& subShaderTag : subShader.tags) {
shaderPass.tags.PushBack({ subShaderTag.name, subShaderTag.value });
}
for (const ShaderTagIR& passTag : pass.tags) {
shaderPass.tags.PushBack({ passTag.name, passTag.value });
}
for (const ShaderResourceBindingDesc& resourceBinding : pass.resources) {
shaderPass.resources.PushBack(resourceBinding);
}
for (const ShaderKeywordDeclaration& keywordDeclaration : pass.keywordDeclarations) {
shaderPass.keywordDeclarations.PushBack(keywordDeclaration);
}
if (pass.programSource.Empty()) {
return shaderPass;
}
Containers::String combinedSource;
AppendAuthoringSourceBlock(combinedSource, shaderIR.sharedProgramSource);
AppendAuthoringSourceBlock(combinedSource, subShader.sharedProgramSource);
AppendAuthoringSourceBlock(combinedSource, pass.sharedProgramSource);
AppendAuthoringSourceBlock(combinedSource, pass.programSource);
const Containers::String strippedCombinedSource = StripShaderAuthoringPragmas(combinedSource);
const std::vector<ShaderKeywordSet> keywordSets =
BuildShaderKeywordVariantSets(pass.keywordDeclarations);
for (const ShaderKeywordSet& keywordSet : keywordSets) {
const Containers::String variantSource =
BuildKeywordVariantSource(strippedCombinedSource, keywordSet);
ShaderStageVariant vertexVariant = {};
vertexVariant.stage = ShaderType::Vertex;
vertexVariant.backend = ShaderBackend::Generic;
vertexVariant.language = ShaderLanguage::HLSL;
vertexVariant.requiredKeywords = keywordSet;
vertexVariant.entryPoint =
!pass.vertexEntryPoint.Empty()
? pass.vertexEntryPoint
: GetDefaultEntryPoint(ShaderLanguage::HLSL, ShaderType::Vertex);
vertexVariant.profile = GetDefaultProfile(
ShaderLanguage::HLSL,
ShaderBackend::Generic,
ShaderType::Vertex);
vertexVariant.sourceCode = variantSource;
shaderPass.variants.PushBack(vertexVariant);
ShaderStageVariant fragmentVariant = {};
fragmentVariant.stage = ShaderType::Fragment;
fragmentVariant.backend = ShaderBackend::Generic;
fragmentVariant.language = ShaderLanguage::HLSL;
fragmentVariant.requiredKeywords = keywordSet;
fragmentVariant.entryPoint =
!pass.fragmentEntryPoint.Empty()
? pass.fragmentEntryPoint
: GetDefaultEntryPoint(ShaderLanguage::HLSL, ShaderType::Fragment);
fragmentVariant.profile = GetDefaultProfile(
ShaderLanguage::HLSL,
ShaderBackend::Generic,
ShaderType::Fragment);
fragmentVariant.sourceCode = variantSource;
shaderPass.variants.PushBack(fragmentVariant);
}
return shaderPass;
}
bool TryResolveUsePass(
const Containers::String& currentShaderPath,
const Containers::String& currentShaderName,
const Containers::String& referencedShaderName,
const Containers::String& referencedPassName,
const std::unordered_map<std::string, ShaderPass>& localConcretePasses,
ShaderPass& outPass,
Containers::String& outError) {
if (referencedShaderName == currentShaderName) {
const auto passIt = localConcretePasses.find(ToStdString(referencedPassName));
if (passIt == localConcretePasses.end()) {
outError =
Containers::String("UsePass could not resolve local pass: ") + referencedPassName;
return false;
}
outPass = passIt->second;
return true;
}
Containers::String referencedShaderPath;
if (!ResolveShaderUsePassPath(
currentShaderPath,
currentShaderName,
referencedShaderName,
referencedShaderPath)) {
outError =
Containers::String("UsePass could not resolve referenced shader: ") +
referencedShaderName;
return false;
}
ShaderLoader loader;
LoadResult referencedShaderResult = loader.Load(referencedShaderPath);
if (!referencedShaderResult || referencedShaderResult.resource == nullptr) {
outError =
Containers::String("UsePass failed to load referenced shader: ") + referencedShaderName;
return false;
}
std::unique_ptr<Shader> referencedShader(static_cast<Shader*>(referencedShaderResult.resource));
const ShaderPass* referencedPass = referencedShader->FindPass(referencedPassName);
if (referencedPass == nullptr) {
outError =
Containers::String("UsePass could not find referenced pass: ") + referencedPassName;
return false;
}
outPass = *referencedPass;
return true;
}
} // namespace
Containers::String GetDefaultEntryPoint(ShaderLanguage language, ShaderType stage) {
if (language == ShaderLanguage::HLSL) {
switch (stage) {
@@ -98,6 +384,33 @@ size_t CalculateShaderMemorySize(const Shader& shader) {
return memorySize;
}
bool ResolveShaderUsePassPath(
const Containers::String& currentShaderPath,
const Containers::String& currentShaderName,
const Containers::String& targetShaderName,
Containers::String& outResolvedPath) {
outResolvedPath.Clear();
if (targetShaderName.Empty()) {
return false;
}
if (targetShaderName == currentShaderName) {
outResolvedPath = currentShaderPath;
return true;
}
outResolvedPath = ResolveBuiltinShaderPathByAuthoringName(targetShaderName);
if (!outResolvedPath.Empty()) {
return true;
}
return TryResolveProjectShaderPathByAuthoringName(
currentShaderPath,
targetShaderName,
outResolvedPath);
}
LoadResult BuildShaderFromIR(
const Containers::String& path,
const ShaderIR& shaderIR) {
@@ -113,76 +426,56 @@ LoadResult BuildShaderFromIR(
shader->AddProperty(property);
}
std::unordered_map<std::string, ShaderPass> localConcretePasses;
for (const ShaderSubShaderIR& subShader : shaderIR.subShaders) {
for (const ShaderPassIR& pass : subShader.passes) {
ShaderPass shaderPass = {};
shaderPass.name = pass.name;
shaderPass.hasFixedFunctionState = pass.hasFixedFunctionState;
shaderPass.fixedFunctionState = pass.fixedFunctionState;
shader->AddPass(shaderPass);
if (pass.isUsePass) {
continue;
}
ShaderPass concretePass = BuildConcretePass(shaderIR, subShader, pass);
const std::string passKey = ToStdString(concretePass.name);
if (passKey.empty()) {
return LoadResult("Shader authoring produced a pass with an empty name");
}
if (!localConcretePasses.emplace(passKey, std::move(concretePass)).second) {
return LoadResult(
Containers::String("Shader authoring produced duplicate pass name: ") + pass.name);
}
}
}
for (const ShaderSubShaderIR& subShader : shaderIR.subShaders) {
for (const ShaderPassIR& pass : subShader.passes) {
if (pass.isUsePass) {
ShaderPass importedPass = {};
Containers::String importError;
if (!TryResolveUsePass(
path,
shaderIR.name,
pass.usePassShaderName,
pass.usePassPassName,
localConcretePasses,
importedPass,
importError)) {
return LoadResult(importError);
}
ImportConcretePass(*shader, importedPass);
for (const ShaderTagIR& subShaderTag : subShader.tags) {
shader->SetPassTag(pass.name, subShaderTag.name, subShaderTag.value);
shader->SetPassTag(importedPass.name, subShaderTag.name, subShaderTag.value);
}
for (const ShaderTagIR& passTag : pass.tags) {
shader->SetPassTag(pass.name, passTag.name, passTag.value);
continue;
}
for (const ShaderResourceBindingDesc& resourceBinding : pass.resources) {
shader->AddPassResourceBinding(pass.name, resourceBinding);
}
for (const ShaderKeywordDeclaration& keywordDeclaration : pass.keywordDeclarations) {
shader->AddPassKeywordDeclaration(pass.name, keywordDeclaration);
const auto passIt = localConcretePasses.find(ToStdString(pass.name));
if (passIt == localConcretePasses.end()) {
return LoadResult(
Containers::String("Shader authoring lost concrete pass during build: ") + pass.name);
}
if (!pass.programSource.Empty()) {
Containers::String combinedSource;
AppendAuthoringSourceBlock(combinedSource, shaderIR.sharedProgramSource);
AppendAuthoringSourceBlock(combinedSource, subShader.sharedProgramSource);
AppendAuthoringSourceBlock(combinedSource, pass.sharedProgramSource);
AppendAuthoringSourceBlock(combinedSource, pass.programSource);
const Containers::String strippedCombinedSource =
StripShaderAuthoringPragmas(combinedSource);
const std::vector<ShaderKeywordSet> keywordSets =
BuildShaderKeywordVariantSets(pass.keywordDeclarations);
for (const ShaderKeywordSet& keywordSet : keywordSets) {
const Containers::String variantSource =
BuildKeywordVariantSource(strippedCombinedSource, keywordSet);
ShaderStageVariant vertexVariant = {};
vertexVariant.stage = ShaderType::Vertex;
vertexVariant.backend = ShaderBackend::Generic;
vertexVariant.language = ShaderLanguage::HLSL;
vertexVariant.requiredKeywords = keywordSet;
vertexVariant.entryPoint =
!pass.vertexEntryPoint.Empty()
? pass.vertexEntryPoint
: GetDefaultEntryPoint(ShaderLanguage::HLSL, ShaderType::Vertex);
vertexVariant.profile = GetDefaultProfile(
ShaderLanguage::HLSL,
ShaderBackend::Generic,
ShaderType::Vertex);
vertexVariant.sourceCode = variantSource;
shader->AddPassVariant(pass.name, vertexVariant);
ShaderStageVariant fragmentVariant = {};
fragmentVariant.stage = ShaderType::Fragment;
fragmentVariant.backend = ShaderBackend::Generic;
fragmentVariant.language = ShaderLanguage::HLSL;
fragmentVariant.requiredKeywords = keywordSet;
fragmentVariant.entryPoint =
!pass.fragmentEntryPoint.Empty()
? pass.fragmentEntryPoint
: GetDefaultEntryPoint(ShaderLanguage::HLSL, ShaderType::Fragment);
fragmentVariant.profile = GetDefaultProfile(
ShaderLanguage::HLSL,
ShaderBackend::Generic,
ShaderType::Fragment);
fragmentVariant.sourceCode = variantSource;
shader->AddPassVariant(pass.name, fragmentVariant);
}
}
ImportConcretePass(*shader, passIt->second);
}
}
@@ -190,32 +483,5 @@ LoadResult BuildShaderFromIR(
return LoadResult(shader.release());
}
LoadResult LoadLegacySingleStageShader(
const Containers::String& path,
const std::string& sourceText) {
auto shader = std::make_unique<Shader>();
shader->m_path = path;
shader->m_name = path;
shader->m_guid = ResourceGUID::Generate(path);
const Containers::String ext = GetShaderPathExtension(path).ToLower();
if (ext == "hlsl") {
shader->SetShaderLanguage(ShaderLanguage::HLSL);
} else {
shader->SetShaderLanguage(ShaderLanguage::GLSL);
}
shader->SetShaderType(DetectShaderTypeFromPath(path));
shader->SetSourceCode(sourceText.c_str());
shader->m_isValid = true;
shader->m_memorySize =
sizeof(Shader) +
shader->m_name.Length() +
shader->m_path.Length() +
shader->GetSourceCode().Length();
return LoadResult(shader.release());
}
} // namespace Resources
} // namespace XCEngine

View File

@@ -22,9 +22,11 @@ LoadResult BuildShaderFromIR(
const Containers::String& path,
const ShaderIR& shaderIR);
LoadResult LoadLegacySingleStageShader(
const Containers::String& path,
const std::string& sourceText);
bool ResolveShaderUsePassPath(
const Containers::String& currentShaderPath,
const Containers::String& currentShaderName,
const Containers::String& targetShaderName,
Containers::String& outResolvedPath);
} // namespace Resources
} // namespace XCEngine

View File

@@ -16,6 +16,9 @@ struct ShaderTagIR {
struct ShaderPassIR {
Containers::String name;
bool isUsePass = false;
Containers::String usePassShaderName;
Containers::String usePassPassName;
bool hasFixedFunctionState = false;
MaterialRenderState fixedFunctionState = {};
std::vector<ShaderTagIR> tags;

View File

@@ -19,6 +19,7 @@ if(MSVC)
set_target_properties(rendering_unit_tests PROPERTIES
LINK_FLAGS "/NODEFAULTLIB:libcpmt.lib /NODEFAULTLIB:libcmt.lib"
)
target_compile_options(rendering_unit_tests PRIVATE /FS)
endif()
target_link_libraries(rendering_unit_tests PRIVATE

View File

@@ -13,6 +13,7 @@ if(MSVC)
set_target_properties(material_tests PROPERTIES
LINK_FLAGS "/NODEFAULTLIB:libcpmt.lib /NODEFAULTLIB:libcmt.lib"
)
target_compile_options(material_tests PRIVATE /FS)
endif()
target_link_libraries(material_tests

View File

@@ -13,6 +13,7 @@ if(MSVC)
set_target_properties(shader_tests PROPERTIES
LINK_FLAGS "/NODEFAULTLIB:libcpmt.lib /NODEFAULTLIB:libcmt.lib"
)
target_compile_options(shader_tests PRIVATE /FS)
endif()
target_link_libraries(shader_tests

File diff suppressed because it is too large Load Diff