| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668 |
- // Copyright (C) 2009-2021, Panagiotis Christopoulos Charitos and contributors.
- // All rights reserved.
- // Code licensed under the BSD License.
- // http://www.anki3d.org/LICENSE
- #include <AnKi/Resource/MaterialResource.h>
- #include <AnKi/Resource/ResourceManager.h>
- #include <AnKi/Resource/ImageResource.h>
- #include <AnKi/Util/Xml.h>
- namespace anki
- {
- static const Array<CString, U32(BuiltinMutatorId::COUNT)> BUILTIN_MUTATOR_NAMES = {
- {"NONE", "ANKI_SUB_TECHNIQUE", "ANKI_LOD", "ANKI_BONES", "ANKI_VELOCITY"}};
- class BuiltinVarInfo
- {
- public:
- const char* m_name;
- ShaderVariableDataType m_type;
- U32 m_arraySize;
- };
- static const Array<BuiltinVarInfo, U(BuiltinMaterialVariableId::COUNT)> BUILTIN_INFOS = {
- {{"NONE", ShaderVariableDataType::NONE, 0},
- {"m_ankiModelMatrix", ShaderVariableDataType::MAT3X4, 1},
- {"m_ankiPreviousModelMatrix", ShaderVariableDataType::MAT3X4, 1},
- {"m_ankiBoneTransformsAddress", ShaderVariableDataType::UVEC2, 1},
- {"m_ankiPrevBoneTransformsAddress", ShaderVariableDataType::UVEC2, 1},
- {"m_ankiPositionsVertexBufferAddresses", ShaderVariableDataType::UVEC2, MAX_LOD_COUNT},
- {"m_ankiOthersVertexBufferAddresses", ShaderVariableDataType::UVEC2, MAX_LOD_COUNT},
- {"m_ankiBoneWeightsVertexBufferAddresses", ShaderVariableDataType::UVEC2, MAX_LOD_COUNT}}};
- static ANKI_USE_RESULT Error checkBuiltin(CString name, ShaderVariableDataType dataType, U32 arraySize,
- BuiltinMaterialVariableId& outId)
- {
- outId = BuiltinMaterialVariableId::NONE;
- for(BuiltinMaterialVariableId id : EnumIterable<BuiltinMaterialVariableId>())
- {
- if(id == BuiltinMaterialVariableId::NONE)
- {
- continue;
- }
- if(BUILTIN_INFOS[id].m_name == name)
- {
- outId = id;
- if(BUILTIN_INFOS[id].m_type != dataType || BUILTIN_INFOS[id].m_arraySize != arraySize)
- {
- ANKI_RESOURCE_LOGE("Incorect type for builtin: %s", name.cstr());
- return Error::USER_DATA;
- }
- break;
- }
- }
- if(outId == BuiltinMaterialVariableId::NONE && (name.find("m_anki") == 0 || name.find("u_anki") == 0))
- {
- ANKI_RESOURCE_LOGE("Unknown builtin var: %s", name.cstr());
- return Error::USER_DATA;
- }
- return Error::NONE;
- }
- // This is some trickery to select calling between XmlElement::getAttributeNumber and XmlElement::getAttributeNumbers
- namespace
- {
- template<typename T>
- class IsShaderVarDataTypeAnArray
- {
- public:
- static constexpr Bool VALUE = false;
- };
- #define ANKI_SVDT_MACRO(capital, type, baseType, rowCount, columnCount) \
- template<> \
- class IsShaderVarDataTypeAnArray<type> \
- { \
- public: \
- static constexpr Bool VALUE = rowCount * columnCount > 1; \
- };
- #include <AnKi/Gr/ShaderVariableDataTypeDefs.h>
- #undef ANKI_SVDT_MACRO
- template<typename T, Bool isArray = IsShaderVarDataTypeAnArray<T>::VALUE>
- class GetAttribute
- {
- public:
- ANKI_USE_RESULT Error operator()(const XmlElement& el, T& out)
- {
- return el.getAttributeNumbers("value", out);
- }
- };
- template<typename T>
- class GetAttribute<T, false>
- {
- public:
- ANKI_USE_RESULT Error operator()(const XmlElement& el, T& out)
- {
- return el.getAttributeNumber("value", out);
- }
- };
- } // namespace
- MaterialVariable::MaterialVariable()
- {
- m_Mat4 = Mat4::getZero();
- }
- MaterialVariable::~MaterialVariable()
- {
- }
- MaterialResource::MaterialResource(ResourceManager* manager)
- : ResourceObject(manager)
- {
- }
- MaterialResource::~MaterialResource()
- {
- for(MaterialVariable& var : m_vars)
- {
- var.m_name.destroy(getAllocator());
- }
- m_vars.destroy(getAllocator());
- for(Technique& t : m_techniques)
- {
- t.m_nonBuiltinsMutation.destroy(getAllocator());
- }
- }
- Error MaterialResource::load(const ResourceFilename& filename, Bool async)
- {
- XmlDocument doc;
- XmlElement el;
- Bool present = false;
- ANKI_CHECK(openFileParseXml(filename, doc));
- // <material>
- XmlElement rootEl;
- ANKI_CHECK(doc.getChildElement("material", rootEl));
- // shadow
- ANKI_CHECK(rootEl.getAttributeNumberOptional("shadow", m_shadow, present));
- m_shadow = m_shadow != 0;
- // <techniques>
- XmlElement techniquesEl;
- ANKI_CHECK(rootEl.getChildElement("techniques", techniquesEl));
- XmlElement techniqueEl;
- ANKI_CHECK(techniquesEl.getChildElement("technique", techniqueEl));
- do
- {
- ANKI_CHECK(parseTechnique(techniqueEl, async));
- ANKI_CHECK(techniqueEl.getNextSiblingElement("technique", techniqueEl));
- } while(techniqueEl);
- // <inputs>
- ANKI_CHECK(rootEl.getChildElementOptional("inputs", el));
- if(el)
- {
- ANKI_CHECK(parseInputs(el, async));
- }
- return Error::NONE;
- }
- Error MaterialResource::parseTechnique(XmlElement techniqueEl, Bool async)
- {
- // name
- CString name;
- ANKI_CHECK(techniqueEl.getAttributeText("name", name));
- RenderingTechnique techniqueId;
- if(name == "GBuffer")
- {
- techniqueId = RenderingTechnique::GBUFFER;
- m_presentTechniques |= RenderingTechniqueBit::GBUFFER;
- }
- else if(name == "ForwardShading")
- {
- techniqueId = RenderingTechnique::FORWARD_SHADING;
- m_presentTechniques |= RenderingTechniqueBit::FORWARD_SHADING;
- }
- else if(name == "RtShadows")
- {
- techniqueId = RenderingTechnique::RT_SHADOWS;
- m_presentTechniques |= RenderingTechniqueBit::RT_SHADOWS;
- }
- else
- {
- ANKI_RESOURCE_LOGE("Unrecognized technique name: %s", name.cstr());
- return Error::USER_DATA;
- }
- Technique& technique = m_techniques[techniqueId];
- // shaderProgram
- CString fname;
- ANKI_CHECK(techniqueEl.getAttributeText("shaderProgram", fname));
- ANKI_CHECK(getManager().loadResource(fname, technique.m_prog, async));
- // Create the vars
- ANKI_CHECK(createVars(technique.m_prog->getBinary()));
- // <mutation>
- XmlElement mutatorsEl;
- ANKI_CHECK(techniqueEl.getChildElementOptional("mutation", mutatorsEl));
- if(mutatorsEl)
- {
- ANKI_CHECK(parseMutators(mutatorsEl, technique));
- }
- // And find the builtin mutators
- ANKI_CHECK(findBuiltinMutators(technique));
- return Error::NONE;
- }
- Error MaterialResource::parseMutators(XmlElement mutatorsEl, Technique& technique)
- {
- XmlElement mutatorEl;
- ANKI_CHECK(mutatorsEl.getChildElement("mutator", mutatorEl));
- //
- // Process the non-builtin mutators
- //
- U32 mutatorCount = 0;
- ANKI_CHECK(mutatorEl.getSiblingElementsCount(mutatorCount));
- ++mutatorCount;
- ANKI_ASSERT(mutatorCount > 0);
- technique.m_nonBuiltinsMutation.create(getAllocator(), mutatorCount);
- mutatorCount = 0;
- do
- {
- SubMutation& smutation = technique.m_nonBuiltinsMutation[mutatorCount];
- // name
- CString mutatorName;
- ANKI_CHECK(mutatorEl.getAttributeText("name", mutatorName));
- if(mutatorName.isEmpty())
- {
- ANKI_RESOURCE_LOGE("Mutator name is empty");
- return Error::USER_DATA;
- }
- for(BuiltinMutatorId id : EnumIterable<BuiltinMutatorId>())
- {
- if(id == BuiltinMutatorId::NONE)
- {
- continue;
- }
- if(mutatorName == BUILTIN_MUTATOR_NAMES[id])
- {
- ANKI_RESOURCE_LOGE("Materials shouldn't list builtin mutators: %s", mutatorName.cstr());
- return Error::USER_DATA;
- }
- }
- if(mutatorName.find("ANKI_") == 0)
- {
- ANKI_RESOURCE_LOGE("Mutators can't start with ANKI_: %s", mutatorName.cstr());
- return Error::USER_DATA;
- }
- // value
- ANKI_CHECK(mutatorEl.getAttributeNumber("value", smutation.m_value));
- // Find mutator
- smutation.m_mutator = technique.m_prog->tryFindMutator(mutatorName);
- if(!smutation.m_mutator)
- {
- ANKI_RESOURCE_LOGE("Mutator not found in program %s", &mutatorName[0]);
- return Error::USER_DATA;
- }
- if(!smutation.m_mutator->valueExists(smutation.m_value))
- {
- ANKI_RESOURCE_LOGE("Value %d is not part of the mutator %s", smutation.m_value, &mutatorName[0]);
- return Error::USER_DATA;
- }
- // Advance
- ++mutatorCount;
- ANKI_CHECK(mutatorEl.getNextSiblingElement("mutator", mutatorEl));
- } while(mutatorEl);
- ANKI_ASSERT(mutatorCount == technique.m_nonBuiltinsMutation.getSize());
- return Error::NONE;
- }
- Error MaterialResource::findBuiltinMutators(Technique& technique)
- {
- U builtinMutatorCount = 0;
- // SUB_TECHNIQUE
- CString subTechniqueMutatorName = BUILTIN_MUTATOR_NAMES[BuiltinMutatorId::SUB_TECHNIQUE];
- const ShaderProgramResourceMutator* subTechniqueMutator = technique.m_prog->tryFindMutator(subTechniqueMutatorName);
- technique.m_builtinMutators[BuiltinMutatorId::SUB_TECHNIQUE] = subTechniqueMutator;
- if(subTechniqueMutator)
- {
- if(subTechniqueMutator->m_values.getSize() != U32(RenderingSubTechnique::COUNT) - 1)
- {
- ANKI_RESOURCE_LOGE("Mutator %s should have %u values in the program", subTechniqueMutatorName.cstr(),
- U32(RenderingSubTechnique::COUNT) - 1);
- return Error::USER_DATA;
- }
- U32 count = 0;
- for(RenderingSubTechnique p : EnumIterable<RenderingSubTechnique>())
- {
- if(subTechniqueMutator->m_values[count++] != I(p))
- {
- ANKI_RESOURCE_LOGE("Values of the %s mutator in the program are not the expected",
- subTechniqueMutatorName.cstr());
- return Error::USER_DATA;
- }
- }
- ++builtinMutatorCount;
- }
- // LOD
- CString lodMutatorName = BUILTIN_MUTATOR_NAMES[BuiltinMutatorId::LOD];
- const ShaderProgramResourceMutator* lodMutator = technique.m_prog->tryFindMutator(lodMutatorName);
- technique.m_builtinMutators[BuiltinMutatorId::LOD] = lodMutator;
- if(lodMutator)
- {
- if(lodMutator->m_values.getSize() > MAX_LOD_COUNT)
- {
- ANKI_RESOURCE_LOGE("Mutator %s should have at least %u values in the program", lodMutatorName.cstr(),
- U32(MAX_LOD_COUNT));
- return Error::USER_DATA;
- }
- for(U32 i = 0; i < lodMutator->m_values.getSize(); ++i)
- {
- if(lodMutator->m_values[i] != I(i))
- {
- ANKI_RESOURCE_LOGE("Values of the %s mutator in the program are not the expected",
- lodMutatorName.cstr());
- return Error::USER_DATA;
- }
- }
- technique.m_lodCount = U8(lodMutator->m_values.getSize());
- ++builtinMutatorCount;
- }
- // BONES
- CString bonesMutatorName = BUILTIN_MUTATOR_NAMES[BuiltinMutatorId::BONES];
- const ShaderProgramResourceMutator* bonesMutator = technique.m_prog->tryFindMutator(bonesMutatorName);
- technique.m_builtinMutators[BuiltinMutatorId::BONES] = bonesMutator;
- if(bonesMutator)
- {
- if(bonesMutator->m_values.getSize() != 2)
- {
- ANKI_RESOURCE_LOGE("Mutator %s should have 2 values in the program", bonesMutatorName.cstr());
- return Error::USER_DATA;
- }
- for(U32 i = 0; i < bonesMutator->m_values.getSize(); ++i)
- {
- if(bonesMutator->m_values[i] != I(i))
- {
- ANKI_RESOURCE_LOGE("Values of the %s mutator in the program are not the expected",
- bonesMutatorName.cstr());
- return Error::USER_DATA;
- }
- }
- ++builtinMutatorCount;
- // Find if the relevant members are present
- if(tryFindVariable(BUILTIN_INFOS[BuiltinMaterialVariableId::BONE_TRANSFORMS_ADDRESS].m_name) == nullptr
- || tryFindVariable(BUILTIN_INFOS[BuiltinMaterialVariableId::PREVIOUS_BONE_TRANSFORMS_ADDRESS].m_name)
- == nullptr)
- {
- ANKI_RESOURCE_LOGE("The program is using the %s mutator but AnKiGpuSceneDescriptor::%s or "
- "AnKiGpuSceneDescriptor::%s were not found",
- bonesMutatorName.cstr(),
- BUILTIN_INFOS[BuiltinMaterialVariableId::BONE_TRANSFORMS_ADDRESS].m_name,
- BUILTIN_INFOS[BuiltinMaterialVariableId::PREVIOUS_BONE_TRANSFORMS_ADDRESS].m_name);
- return Error::USER_DATA;
- }
- m_supportSkinning = true;
- }
- // VELOCITY
- CString velocityMutatorName = BUILTIN_MUTATOR_NAMES[BuiltinMutatorId::VELOCITY];
- const ShaderProgramResourceMutator* velocityMutator = technique.m_prog->tryFindMutator(velocityMutatorName);
- technique.m_builtinMutators[BuiltinMutatorId::VELOCITY] = velocityMutator;
- if(velocityMutator)
- {
- if(velocityMutator->m_values.getSize() != 2)
- {
- ANKI_RESOURCE_LOGE("Mutator %s should have 2 values in the program", velocityMutatorName.cstr());
- return Error::USER_DATA;
- }
- for(U32 i = 0; i < velocityMutator->m_values.getSize(); ++i)
- {
- if(velocityMutator->m_values[i] != I(i))
- {
- ANKI_RESOURCE_LOGE("Values of the %s mutator in the program are not the expected",
- velocityMutatorName.cstr());
- return Error::USER_DATA;
- }
- }
- ++builtinMutatorCount;
- }
- if(technique.m_nonBuiltinsMutation.getSize() + builtinMutatorCount != technique.m_prog->getMutators().getSize())
- {
- ANKI_RESOURCE_LOGE("Some mutatators are unacounted for");
- return Error::USER_DATA;
- }
- return Error::NONE;
- }
- Error MaterialResource::createVars(const ShaderProgramBinary& binary)
- {
- // Find b_ankiGpuSceneDescriptions
- Bool gpuSceneDescriptionsFound = false;
- for(const ShaderProgramBinaryBlock& block : binary.m_uniformBlocks)
- {
- if(block.m_name.getBegin() == CString("b_ankiGpuSceneDescriptions"))
- {
- if(block.m_set != 1 || block.m_binding != 0)
- {
- ANKI_RESOURCE_LOGE("b_ankiGpuSceneDescriptions has wrong binding or set");
- return Error::USER_DATA;
- }
- gpuSceneDescriptionsFound = true;
- break;
- }
- }
- if(!gpuSceneDescriptionsFound)
- {
- ANKI_RESOURCE_LOGE("b_ankiGpuSceneDescriptions not found");
- return Error::USER_DATA;
- }
- // Create the material variables
- const ShaderProgramBinaryStruct* gpuSceneDescriptionStruct = nullptr;
- for(const ShaderProgramBinaryStruct& struct_ : binary.m_structs)
- {
- if(CString(struct_.m_name.getBegin()) == "AnKiGpuSceneDescription")
- {
- gpuSceneDescriptionStruct = &struct_;
- break;
- }
- }
- if(gpuSceneDescriptionStruct == nullptr)
- {
- ANKI_RESOURCE_LOGE("AnKiGpuSceneDescription struct was not found in the binary");
- return Error::USER_DATA;
- }
- else if(m_gpuSceneDescriptionStructSize != 0 && gpuSceneDescriptionStruct->m_size)
- {
- ANKI_RESOURCE_LOGE("sizeof AnKiGpuSceneDescription doesn't match between techniques");
- return Error::USER_DATA;
- }
- else
- {
- m_gpuSceneDescriptionStructSize = gpuSceneDescriptionStruct->m_size;
- }
- for(const ShaderProgramBinaryStructMember& member : gpuSceneDescriptionStruct->m_members)
- {
- CString name = member.m_name.getBegin();
- if(member.m_type == ShaderVariableDataType::NONE)
- {
- ANKI_RESOURCE_LOGE("Non fundamental type was found in AnKiGpuSceneDescription: %s", name.cstr());
- return Error::NONE;
- }
- BuiltinMaterialVariableId builtinId;
- ANKI_CHECK(checkBuiltin(name, member.m_type, member.m_arraySize, builtinId));
- MaterialVariable* var = tryFindVariable(name);
- if(var)
- {
- if(var->m_dataType != member.m_type || var->m_builtin != builtinId
- || var->m_offsetInStruct != member.m_offset)
- {
- ANKI_RESOURCE_LOGE("Member variable doesn't match between techniques: %s", name.cstr());
- return Error::USER_DATA;
- }
- }
- else
- {
- var = m_vars.emplaceBack(getAllocator());
- var->m_name.create(getAllocator(), name);
- var->m_dataType = member.m_type;
- var->m_builtin = builtinId;
- var->m_offsetInStruct = member.m_offset;
- }
- }
- return Error::NONE;
- }
- Error MaterialResource::parseInputs(XmlElement inputsEl, Bool async)
- {
- // Connect the input variables
- XmlElement inputEl;
- ANKI_CHECK(inputsEl.getChildElementOptional("input", inputEl));
- while(inputEl)
- {
- // Get var name
- CString varName;
- ANKI_CHECK(inputEl.getAttributeText("name", varName));
- // Try find var
- MaterialVariable* foundVar = tryFindVariable(varName);
- if(foundVar == nullptr)
- {
- ANKI_RESOURCE_LOGE("Variable not found: %s", varName.cstr());
- return Error::USER_DATA;
- }
- if(foundVar->m_builtin != BuiltinMaterialVariableId::NONE)
- {
- ANKI_RESOURCE_LOGE("Shouldn't list builtin vars: %s", varName.cstr());
- return Error::USER_DATA;
- }
- // Find out if it's a texture by looking at the input value
- Bool isTexture = false;
- if(foundVar->m_dataType == ShaderVariableDataType::U16)
- {
- CString valueTxt;
- ANKI_CHECK(inputEl.getAttributeText("value", valueTxt));
- for(Char c : valueTxt)
- {
- if(std::isalpha(c))
- {
- isTexture = true;
- break;
- }
- }
- }
- // A value will be set
- foundVar->m_numericValueIsSet = true;
- if(isTexture)
- {
- CString texfname;
- ANKI_CHECK(inputEl.getAttributeText("value", texfname));
- ANKI_CHECK(getManager().loadResource(texfname, foundVar->m_image, async));
- foundVar->m_bindlessTextureIndex =
- U16(foundVar->m_image->getTextureView()->getOrCreateBindlessTextureIndex());
- }
- else
- {
- switch(foundVar->m_dataType)
- {
- #define ANKI_SVDT_MACRO(capital, type, baseType, rowCount, columnCount) \
- case ShaderVariableDataType::capital: \
- ANKI_CHECK(GetAttribute<type>()(inputEl, foundVar->ANKI_CONCATENATE(m_, type))); \
- break;
- #include <AnKi/Gr/ShaderVariableDataTypeDefs.h>
- #undef ANKI_SVDT_MACRO
- default:
- ANKI_ASSERT(0);
- break;
- }
- }
- // Advance
- ANKI_CHECK(inputEl.getNextSiblingElement("input", inputEl));
- }
- return Error::NONE;
- }
- const MaterialVariant& MaterialResource::getOrCreateVariant(const RenderingKey& key_) const
- {
- RenderingKey key = key_;
- const Technique& technique = m_techniques[key.m_renderingTechnique];
- ANKI_ASSERT(technique.m_prog.isCreated());
- key.m_lod = min<U8>(technique.m_lodCount - 1, key.m_lod);
- ANKI_ASSERT(!key.m_skinned || technique.m_builtinMutators[BuiltinMutatorId::BONES]);
- ANKI_ASSERT(!key.m_velocity || technique.m_builtinMutators[BuiltinMutatorId::VELOCITY]);
- MaterialVariant& variant =
- technique.m_variantMatrix[key.m_renderingSubTechnique][key.m_lod][key.m_skinned][key.m_velocity];
- // Check if it's initialized
- {
- RLockGuard<RWMutex> lock(technique.m_variantMatrixMtx);
- if(variant.m_prog.isCreated())
- {
- return variant;
- }
- }
- // Not initialized, init it
- WLockGuard<RWMutex> lock(technique.m_variantMatrixMtx);
- // Check again
- if(variant.m_prog.isCreated())
- {
- return variant;
- }
- ShaderProgramResourceVariantInitInfo initInfo(technique.m_prog);
- for(const SubMutation& m : technique.m_nonBuiltinsMutation)
- {
- initInfo.addMutation(m.m_mutator->m_name, m.m_value);
- }
- if(technique.m_builtinMutators[BuiltinMutatorId::SUB_TECHNIQUE])
- {
- initInfo.addMutation(technique.m_builtinMutators[BuiltinMutatorId::SUB_TECHNIQUE]->m_name,
- MutatorValue(key.m_renderingSubTechnique));
- }
- if(technique.m_builtinMutators[BuiltinMutatorId::LOD])
- {
- initInfo.addMutation(technique.m_builtinMutators[BuiltinMutatorId::LOD]->m_name, MutatorValue(key.m_lod));
- }
- if(technique.m_builtinMutators[BuiltinMutatorId::BONES])
- {
- initInfo.addMutation(technique.m_builtinMutators[BuiltinMutatorId::BONES]->m_name, key.m_skinned != 0);
- }
- if(technique.m_builtinMutators[BuiltinMutatorId::VELOCITY])
- {
- initInfo.addMutation(technique.m_builtinMutators[BuiltinMutatorId::VELOCITY]->m_name, key.m_velocity != 0);
- }
- const ShaderProgramResourceVariant* progVariant;
- technique.m_prog->getOrCreateVariant(initInfo, progVariant);
- variant.m_prog = progVariant->getProgram();
- return variant;
- }
- } // end namespace anki
|