18#include "spdlog/spdlog.h"
26 std::unordered_map<GraphicsDevice*, std::shared_ptr<ProgramLibrary>> programLibraries;
28 uint64_t fnv1a64(
const std::string& text)
30 uint64_t hash = 1469598103934665603ull;
31 for (
const char c : text) {
32 hash ^=
static_cast<uint8_t
>(c);
33 hash *= 1099511628211ull;
38 void appendFeatureDefine(std::string& output,
const char* name,
const bool enabled)
42 output += enabled ?
" 1\n" :
" 0\n";
45 struct ShaderChunkRegistry
47 std::unordered_map<std::string, std::string> chunkSources;
48 std::filesystem::path rootPath;
51 std::string readTextFile(
const std::filesystem::path& path)
53 std::ifstream in(path, std::ios::in | std::ios::binary);
58 std::ostringstream buffer;
63 std::filesystem::path projectRootFromThisSource()
65 auto path = std::filesystem::path(__FILE__).parent_path();
66 for (
int i = 0; i < 4; ++i) {
67 path = path.parent_path();
72 std::optional<ShaderChunkRegistry> loadShaderChunks()
74 const auto sourceRoot = projectRootFromThisSource();
75 const auto cwd = std::filesystem::current_path();
76 const std::array<std::filesystem::path, 4> chunkRoots = {
77 sourceRoot /
"engine/shaders/metal/chunks",
78 cwd /
"engine/shaders/metal/chunks",
79 cwd.parent_path() /
"engine/shaders/metal/chunks",
80 cwd.parent_path().parent_path() /
"engine/shaders/metal/chunks"
83 for (
const auto& root : chunkRoots) {
84 ShaderChunkRegistry registry;
85 registry.rootPath = root;
87 if (!std::filesystem::exists(root) || !std::filesystem::is_directory(root)) {
91 for (
const auto& entry : std::filesystem::directory_iterator(root)) {
92 if (!entry.is_regular_file() || entry.path().extension() !=
".metal") {
96 const auto chunkName = entry.path().stem().string();
97 const auto chunkSource = readTextFile(entry.path());
98 if (!chunkSource.empty()) {
99 registry.chunkSources[chunkName] = chunkSource;
103 if (!registry.chunkSources.empty()) {
104 spdlog::info(
"Loaded shader chunks from {}", root.string());
112 const ShaderChunkRegistry* getShaderChunks()
114 static std::optional<ShaderChunkRegistry> chunks = loadShaderChunks();
115 return chunks ? &*chunks :
nullptr;
123 for (
const char* name : names) {
124 if (
const auto* value = material->parameter(name)) {
136 if (
const auto* v = std::get_if<bool>(value)) {
140 if (
const auto* v = std::get_if<int32_t>(value)) {
144 if (
const auto* v = std::get_if<uint32_t>(value)) {
148 if (
const auto* v = std::get_if<float>(value)) {
160 if (
const auto* v = std::get_if<int32_t>(value)) {
161 out =
static_cast<int>(*v);
164 if (
const auto* v = std::get_if<uint32_t>(value)) {
165 out =
static_cast<int>(*v);
168 if (
const auto* v = std::get_if<float>(value)) {
169 out =
static_cast<int>(*v);
172 if (
const auto* v = std::get_if<bool>(value)) {
179 bool hasTextureParameter(
const Material* material, std::initializer_list<const char*> names)
181 if (
const auto* value = getMaterialParameter(material, names)) {
182 if (
const auto* texture = std::get_if<Texture*>(value)) {
183 return *texture !=
nullptr;
192 (void)standardMaterial;
197 "forward-fragment-head",
198 "forward-fragment-lighting",
199 "forward-fragment-tail"
204 "forward-fragment-head",
205 "forward-fragment-lighting",
206 "forward-fragment-tail"
217 if (name.empty() || chunkOrder.empty()) {
218 spdlog::error(
"ProgramLibrary::registerProgram rejected invalid program registration");
221 _registeredPrograms[name] = chunkOrder;
226 return _registeredPrograms.find(name) != _registeredPrograms.end();
229 void setProgramLibrary(
const std::shared_ptr<GraphicsDevice>& device,
const std::shared_ptr<ProgramLibrary>& library)
231 assert(library !=
nullptr &&
"ProgramLibrary cannot be null");
232 programLibraries[device.get()] = library;
238 std::shared_ptr<ProgramLibrary>
getProgramLibrary(
const std::shared_ptr<GraphicsDevice>& device)
240 const auto it = programLibraries.find(device.get());
241 return it != programLibraries.end() ? it->second :
nullptr;
244 ProgramLibrary::ShaderVariantOptions ProgramLibrary::buildForwardVariantOptions(
const Material* material,
245 const bool transparentPass,
const bool dynamicBatch)
const
247 ShaderVariantOptions options{};
248 options.transparentPass = transparentPass;
249 options.skybox = material && material->isSkybox();
250 options.alphaTest = material && material->alphaMode() ==
AlphaMode::MASK;
253 const auto* stdMat =
dynamic_cast<const StandardMaterial*
>(material);
260 if (
int cullModeValue =
static_cast<int>(effectiveCullMode);
261 readParameterInt(getMaterialParameter(material, {
"material_cullMode",
"cullMode"}), cullModeValue)) {
264 effectiveCullMode =
static_cast<CullMode>(cullModeValue);
272 options.baseColorMap = (stdMat->diffuseMap() || stdMat->baseColorTexture());
273 options.normalMap = (stdMat->normalMap() || stdMat->normalTexture());
274 options.metallicRoughnessMap = (stdMat->metalnessMap() || stdMat->metallicRoughnessTexture());
275 options.occlusionMap = (stdMat->aoMap() || stdMat->occlusionTexture());
276 options.emissiveMap = (stdMat->emissiveMap() || stdMat->emissiveTexture());
278 options.baseColorMap = (material && material->hasBaseColorTexture()) ||
279 hasTextureParameter(material, {
"texture_baseColorMap",
"texture_diffuseMap",
"baseColorTexture"});
280 options.normalMap = (material && material->hasNormalTexture()) ||
281 hasTextureParameter(material, {
"texture_normalMap",
"normalTexture"});
282 options.metallicRoughnessMap = (material && material->hasMetallicRoughnessTexture()) ||
283 hasTextureParameter(material, {
"texture_metallicRoughnessMap",
"metallicRoughnessTexture"});
284 options.occlusionMap = (material && material->hasOcclusionTexture()) ||
285 hasTextureParameter(material, {
"texture_occlusionMap",
"occlusionTexture"});
286 options.emissiveMap = (material && material->hasEmissiveTexture()) ||
287 hasTextureParameter(material, {
"texture_emissiveMap",
"emissiveTexture"});
291 bool skyboxOverride = options.skybox;
292 if (readParameterBool(getMaterialParameter(material, {
"material_isSkybox",
"isSkybox"}), skyboxOverride)) {
293 options.skybox = skyboxOverride;
298 const uint64_t variantBits = material ? material->shaderVariantKey() : 0ull;
299 options.shadowMapping = !options.skybox || ((variantBits & (1ull << 10)) != 0ull);
301 options.fog = stdMat->useFog() && !options.skybox;
303 options.fog = !options.skybox || ((variantBits & (1ull << 11)) != 0ull);
307 options.parallax = (stdMat->heightMap() !=
nullptr);
309 options.parallax = (variantBits & (1ull << 12)) != 0ull;
313 options.clearcoat = stdMat->clearCoat() > 0.0f;
315 options.clearcoat = (variantBits & (1ull << 13)) != 0ull;
319 options.anisotropy = (stdMat->anisotropy() != 0.0f);
321 options.anisotropy = (variantBits & (1ull << 14)) != 0ull;
325 options.sheen = (stdMat->sheenRoughness() > 0.0f ||
326 stdMat->sheenColor() != Color(0.0f, 0.0f, 0.0f, 1.0f));
328 options.sheen = (variantBits & (1ull << 15)) != 0ull;
332 options.iridescence = (stdMat->iridescenceIntensity() > 0.0f);
334 options.iridescence = (variantBits & (1ull << 16)) != 0ull;
338 options.transmission = (stdMat->transmissionFactor() > 0.0f);
340 options.transmission = (variantBits & (1ull << 17)) != 0ull;
342 options.lightClustering = _clusteredLightingEnabled || (variantBits & (1ull << 18)) != 0ull;
343 options.ssao = _ssaoEnabled || (variantBits & (1ull << 19)) != 0ull;
344 options.lightProbes = (variantBits & (1ull << 20)) != 0ull;
345 options.vertexColors = (variantBits & (1ull << 21)) != 0ull;
346 options.skinning = (variantBits & (1ull << 22)) != 0ull;
347 options.morphing = (variantBits & (1ull << 23)) != 0ull;
350 options.specGloss = (stdMat->specGlossMap() !=
nullptr);
352 options.specGloss = (variantBits & (1ull << 24)) != 0ull;
356 options.orenNayar = stdMat->useOrenNayar();
358 options.orenNayar = (variantBits & (1ull << 25)) != 0ull;
362 options.detailNormals = (stdMat->detailNormalMap() !=
nullptr);
364 options.detailNormals = (variantBits & (1ull << 26)) != 0ull;
368 options.displacement = (stdMat->displacementMap() !=
nullptr);
370 options.displacement = (variantBits & (1ull << 27)) != 0ull;
372 options.atmosphere = (_atmosphereEnabled && options.skybox) || (variantBits & (1ull << 28)) != 0ull;
373 options.pointSpotAttenuation = !options.skybox || ((variantBits & (1ull << 29)) != 0ull);
374 options.multiLight = !options.skybox || ((variantBits & (1ull << 30)) != 0ull);
375 options.instancing = (variantBits & (1ull << 33)) != 0ull;
376 options.pointSize = (variantBits & (1ull << 31)) != 0ull;
377 options.unlit = (variantBits & (1ull << 32)) != 0ull;
381 options.shadowCatcher = stdMat->shadowCatcher();
386 if (options.skybox && _skyCubemapAvailable) {
387 options.skyCubemap =
true;
393 options.planarReflection = (stdMat->reflectionMap() !=
nullptr);
398 options.planarReflectionDepthPass = _planarReflectionDepthPass;
402 options.localShadows = _localShadowsEnabled && !options.skybox;
405 options.omniShadows = _omniShadowsEnabled && !options.skybox;
409 options.areaLights = _areaLightsEnabled && !options.skybox;
413 options.dynamicBatch = dynamicBatch;
418 std::string ProgramLibrary::resolveProgramName(
const ShaderVariantOptions& options)
420 return options.skybox ?
"skybox" :
"forward";
423 uint64_t ProgramLibrary::makeVariantKey(
const std::string& programName,
const ShaderVariantOptions& options,
const Material* material)
const
432 uint64_t key = fnv1a64(programName);
433 key ^= options.transparentPass ? (1ull << 63) : 0ull;
434 key ^= options.skybox ? (1ull << 62) : 0ull;
435 key ^= options.baseColorMap ? (1ull << 0) : 0ull;
436 key ^= options.normalMap ? (1ull << 1) : 0ull;
437 key ^= options.metallicRoughnessMap ? (1ull << 2) : 0ull;
438 key ^= options.occlusionMap ? (1ull << 3) : 0ull;
439 key ^= options.emissiveMap ? (1ull << 4) : 0ull;
440 key ^= options.alphaTest ? (1ull << 5) : 0ull;
441 key ^= options.doubleSided ? (1ull << 6) : 0ull;
442 key ^= options.shadowMapping ? (1ull << 8) : 0ull;
443 key ^= options.fog ? (1ull << 9) : 0ull;
444 key ^= options.vertexColors ? (1ull << 10) : 0ull;
445 key ^= options.pointSpotAttenuation ? (1ull << 11) : 0ull;
446 key ^= options.multiLight ? (1ull << 12) : 0ull;
447 key ^= options.envAtlas ? (1ull << 13) : 0ull;
451 key ^= options.parallax ? (1ull << 14) : 0ull;
452 key ^= options.clearcoat ? (1ull << 15) : 0ull;
453 key ^= options.anisotropy ? (1ull << 16) : 0ull;
454 key ^= options.sheen ? (1ull << 17) : 0ull;
455 key ^= options.iridescence ? (1ull << 18) : 0ull;
456 key ^= options.transmission ? (1ull << 19) : 0ull;
457 key ^= options.lightClustering ? (1ull << 20) : 0ull;
458 key ^= options.ssao ? (1ull << 21) : 0ull;
459 key ^= options.lightProbes ? (1ull << 22) : 0ull;
460 key ^= options.skinning ? (1ull << 23) : 0ull;
461 key ^= options.morphing ? (1ull << 24) : 0ull;
462 key ^= options.specGloss ? (1ull << 25) : 0ull;
463 key ^= options.orenNayar ? (1ull << 26) : 0ull;
464 key ^= options.detailNormals ? (1ull << 27) : 0ull;
465 key ^= options.displacement ? (1ull << 28) : 0ull;
466 key ^= options.atmosphere ? (1ull << 29) : 0ull;
467 key ^= options.shadowCatcher ? (1ull << 30) : 0ull;
468 key ^= options.skyCubemap ? (1ull << 31) : 0ull;
469 key ^= options.instancing ? (1ull << 32) : 0ull;
470 key ^= options.planarReflection ? (1ull << 33) : 0ull;
471 key ^= options.planarReflectionDepthPass ? (1ull << 34) : 0ull;
472 key ^= options.localShadows ? (1ull << 35) : 0ull;
473 key ^= options.omniShadows ? (1ull << 36) : 0ull;
474 key ^= options.dynamicBatch ? (1ull << 37) : 0ull;
475 key ^= options.pointSize ? (1ull << 38) : 0ull;
476 key ^= options.areaLights ? (1ull << 40) : 0ull;
477 key ^= options.unlit ? (1ull << 39) : 0ull;
481 std::string ProgramLibrary::composeProgramVariantMetalSource(
const std::string& programName,
const ShaderVariantOptions& options,
482 const std::string& vertexEntry,
const std::string& fragmentEntry)
484 const auto* chunks = getShaderChunks();
486 spdlog::error(
"Failed to load shader chunks from engine/shaders/metal/chunks.");
489 const auto programChunks = _registeredPrograms.find(programName);
490 if (programChunks == _registeredPrograms.end() || programChunks->second.empty()) {
491 spdlog::error(
"ProgramLibrary is missing registered chunk order for program '{}'.", programName);
496 source.reserve(24 * 1024);
498 appendFeatureDefine(source,
"VT_FEATURE_SKYBOX", options.skybox);
499 appendFeatureDefine(source,
"VT_FEATURE_TRANSPARENT_PASS", options.transparentPass);
500 appendFeatureDefine(source,
"VT_FEATURE_ALPHA_TEST", options.alphaTest);
501 appendFeatureDefine(source,
"VT_FEATURE_DOUBLE_SIDED", options.doubleSided);
502 appendFeatureDefine(source,
"VT_FEATURE_BASE_COLOR_MAP", options.baseColorMap);
503 appendFeatureDefine(source,
"VT_FEATURE_NORMAL_MAP", options.normalMap);
504 appendFeatureDefine(source,
"VT_FEATURE_METAL_ROUGHNESS_MAP", options.metallicRoughnessMap);
505 appendFeatureDefine(source,
"VT_FEATURE_OCCLUSION_MAP", options.occlusionMap);
506 appendFeatureDefine(source,
"VT_FEATURE_EMISSIVE_MAP", options.emissiveMap);
507 appendFeatureDefine(source,
"VT_FEATURE_ENV_ATLAS", options.envAtlas);
509 appendFeatureDefine(source,
"VT_FEATURE_SHADOWS", options.shadowMapping);
510 appendFeatureDefine(source,
"VT_FEATURE_FOG", options.fog);
511 appendFeatureDefine(source,
"VT_FEATURE_PARALLAX", options.parallax);
512 appendFeatureDefine(source,
"VT_FEATURE_CLEARCOAT", options.clearcoat);
513 appendFeatureDefine(source,
"VT_FEATURE_ANISOTROPY", options.anisotropy);
514 appendFeatureDefine(source,
"VT_FEATURE_SHEEN", options.sheen);
515 appendFeatureDefine(source,
"VT_FEATURE_IRIDESCENCE", options.iridescence);
516 appendFeatureDefine(source,
"VT_FEATURE_TRANSMISSION", options.transmission);
517 appendFeatureDefine(source,
"VT_FEATURE_LIGHT_CLUSTERING", options.lightClustering);
518 appendFeatureDefine(source,
"VT_FEATURE_SSAO", options.ssao);
519 appendFeatureDefine(source,
"VT_FEATURE_LIGHT_PROBES", options.lightProbes);
520 appendFeatureDefine(source,
"VT_FEATURE_VERTEX_COLORS", options.vertexColors);
521 appendFeatureDefine(source,
"VT_FEATURE_SKINNING", options.skinning);
522 appendFeatureDefine(source,
"VT_FEATURE_MORPHS", options.morphing);
523 appendFeatureDefine(source,
"VT_FEATURE_SPEC_GLOSS", options.specGloss);
524 appendFeatureDefine(source,
"VT_FEATURE_OREN_NAYAR", options.orenNayar);
525 appendFeatureDefine(source,
"VT_FEATURE_DETAIL_NORMALS", options.detailNormals);
526 appendFeatureDefine(source,
"VT_FEATURE_DISPLACEMENT", options.displacement);
527 appendFeatureDefine(source,
"VT_FEATURE_ATMOSPHERE", options.atmosphere);
528 appendFeatureDefine(source,
"VT_FEATURE_AREA_LIGHTS", options.areaLights);
529 appendFeatureDefine(source,
"VT_FEATURE_POINT_SPOT_ATTENUATION", options.pointSpotAttenuation);
530 appendFeatureDefine(source,
"VT_FEATURE_MULTI_LIGHT", options.multiLight);
531 appendFeatureDefine(source,
"VT_FEATURE_SHADOW_CATCHER", options.shadowCatcher);
532 appendFeatureDefine(source,
"VT_FEATURE_SKY_CUBEMAP", options.skyCubemap);
533 appendFeatureDefine(source,
"VT_FEATURE_SURFACE_LIC", options.surfaceLIC);
534 appendFeatureDefine(source,
"VT_FEATURE_INSTANCING", options.instancing);
535 appendFeatureDefine(source,
"VT_FEATURE_PLANAR_REFLECTION", options.planarReflection);
536 appendFeatureDefine(source,
"VT_FEATURE_PLANAR_REFLECTION_DEPTH_PASS", options.planarReflectionDepthPass);
537 appendFeatureDefine(source,
"VT_FEATURE_LOCAL_SHADOWS", options.localShadows);
538 appendFeatureDefine(source,
"VT_FEATURE_OMNI_SHADOWS", options.omniShadows);
539 appendFeatureDefine(source,
"VT_FEATURE_DYNAMIC_BATCH", options.dynamicBatch);
540 appendFeatureDefine(source,
"VT_FEATURE_POINT_SIZE", options.pointSize);
541 appendFeatureDefine(source,
"VT_FEATURE_UNLIT", options.unlit);
546 source +=
"\n#define VT_VERTEX_ENTRY ";
547 source += vertexEntry;
548 source +=
"\n#define VT_FRAGMENT_ENTRY ";
549 source += fragmentEntry;
552 for (
const auto& chunkName : programChunks->second) {
553 const auto chunkIt = chunks->chunkSources.find(chunkName);
554 if (chunkIt == chunks->chunkSources.end()) {
555 spdlog::error(
"ProgramLibrary chunk '{}' is missing in '{}'.",
556 chunkName, chunks->rootPath.string());
559 source += chunkIt->second;
566 std::shared_ptr<Shader> ProgramLibrary::buildForwardShaderVariant(
const std::string& programName,
567 const ShaderVariantOptions& options,
const uint64_t variantKey)
569 ShaderDefinition definition;
570 definition.name =
"program-" + programName;
571 definition.name += options.transparentPass ?
"-transparent" :
"-opaque";
572 definition.name +=
"-" + std::to_string(variantKey);
573 const auto entryPrefix = programName ==
"shadow" ?
"pcShadow" :
"pcForward";
574 definition.vshader = entryPrefix + std::string(
"VS_") + std::to_string(variantKey);
575 definition.fshader = entryPrefix + std::string(
"FS_") + std::to_string(variantKey);
576 const std::string sourceCode = composeProgramVariantMetalSource(programName, options, definition.vshader, definition.fshader);
577 if (sourceCode.empty()) {
580 return createShader(_device.get(), definition, sourceCode);
584 const bool dynamicBatch)
590 const ShaderVariantOptions options = buildForwardVariantOptions(material, transparentPass, dynamicBatch);
591 const std::string programName = resolveProgramName(options);
593 spdlog::error(
"ProgramLibrary has no registered program '{}'.", programName);
596 const uint64_t key = makeVariantKey(programName, options, material);
598 const auto cached = _forwardShaderCache.find(key);
599 if (cached != _forwardShaderCache.end()) {
600 return cached->second;
603 auto warnFeature = [&](
const char* featureName,
const bool enabled) {
607 if (_warnedFeatureFlags.insert(featureName).second) {
608 spdlog::warn(
"Shader variant feature '{}' enabled but only chunk scaffolding is present. Full shader chunk port is pending.",
621 warnFeature(
"lightProbes", options.lightProbes);
623 warnFeature(
"skinning", options.skinning);
624 warnFeature(
"morphing", options.morphing);
625 warnFeature(
"specGloss", options.specGloss);
626 warnFeature(
"orenNayar", options.orenNayar);
627 warnFeature(
"detailNormals", options.detailNormals);
628 warnFeature(
"displacement", options.displacement);
631 auto shader = buildForwardShaderVariant(programName, options, key);
633 spdlog::error(
"Failed to build shader variant '{}' (key={:#x}, localShadows={}, shadows={}, envAtlas={})",
634 programName, key, options.localShadows, options.shadowMapping, options.envAtlas);
636 _forwardShaderCache[key] = shader;
646 ShaderVariantOptions options{};
647 options.skybox =
false;
648 options.transparentPass =
false;
649 options.alphaTest =
false;
650 options.doubleSided =
false;
651 options.shadowMapping =
true;
653 options.multiLight =
false;
654 options.dynamicBatch = dynamicBatch;
656 const uint64_t key = makeVariantKey(
"shadow", options,
nullptr);
657 const auto cached = _forwardShaderCache.find(key);
658 if (cached != _forwardShaderCache.end()) {
659 return cached->second;
662 auto shader = buildForwardShaderVariant(
"shadow", options, key);
663 _forwardShaderCache[key] = shader;
668 const bool transparentPass,
const bool dynamicBatch)
679 auto blendState = material ? material->
blendState() :
nullptr;
680 auto depthState = material ? material->
depthState() :
nullptr;
683 device->setShader(shader);
686 device->setBlendState(blendState);
689 device->setDepthState(depthState);
691 device->setMaterial(material);
Base class for GPU materials — owns uniform data, texture bindings, blend/depth state,...
const std::shared_ptr< Shader > & shaderOverride() const
std::variant< float, int32_t, uint32_t, bool, Color, Vector2, Vector3, Vector4, Matrix4, Texture * > ParameterValue
const std::shared_ptr< DepthState > & depthState() const
const std::shared_ptr< BlendState > & blendState() const
bool hasProgram(const std::string &name) const
void registerProgram(const std::string &name, const std::vector< std::string > &chunkOrder)
std::shared_ptr< Shader > getForwardShader(const Material *material, bool transparentPass, bool dynamicBatch=false)
ProgramLibrary(const std::shared_ptr< GraphicsDevice > &device, StandardMaterial *standardMaterial)
void bindMaterial(const std::shared_ptr< GraphicsDevice > &device, const Material *material, bool transparentPass, bool dynamicBatch=false)
std::shared_ptr< Shader > getShadowShader(bool dynamicBatch=false)
Full PBR material with metalness/roughness workflow and advanced surface features.
std::shared_ptr< Shader > createShader(GraphicsDevice *graphicsDevice, const ShaderDefinition &definition, const std::string &sourceCode)
void setProgramLibrary(const std::shared_ptr< GraphicsDevice > &device, const std::shared_ptr< ProgramLibrary > &library)
std::shared_ptr< ProgramLibrary > getProgramLibrary(const std::shared_ptr< GraphicsDevice > &device)