Skip to content

Commit

Permalink
Refactor|Model Renderer|libgui: Added ModelDrawable::Appearance
Browse files Browse the repository at this point in the history
Added a new object that contains parameters that affect how a model
gets drawn. ModelDrawable::Appearance contains the rendering passes,
mask for active passes, material to use for each pass, and callbacks
for changing the program and pass.

This allows StateAnimator to maintain this information internally for
each animated object, rather than having to pass it to the model
renderer that does the draw calls.
  • Loading branch information
skyjake committed Oct 19, 2015
1 parent d91a66d commit 2bb0699
Show file tree
Hide file tree
Showing 5 changed files with 269 additions and 206 deletions.
16 changes: 1 addition & 15 deletions doomsday/apps/client/include/render/stateanimator.h
Expand Up @@ -51,21 +51,7 @@ class StateAnimator : public de::ModelDrawable::Animator

de::ddouble currentTime(int index) const;

/**
* Returns a bit mask that specifies which rendering passes are currently
* enabled for this object. This should be passed to ModelDrawable::draw().
*/
QBitArray passMask() const;

/**
* Determines the material to use during a rendering pass. These are
* determined by the "material" variables in the object's namespace.
*
* @param passName Name of the rendering pass.
*
* @return Material index.
*/
de::duint materialForPass(de::String const &passName) const;
de::ModelDrawable::Appearance const &appearance() const;

enum BindOperation { Bind, Unbind };

Expand Down
24 changes: 1 addition & 23 deletions doomsday/apps/client/src/render/modelrenderer.cpp
Expand Up @@ -607,29 +607,7 @@ DENG2_PIMPL(ModelRenderer)
{
DENG2_ASSERT(p.auxData != nullptr);

p.model->draw(
p.animator,
p.auxData->passes.isEmpty()? nullptr : &p.auxData->passes,
p.animator->passMask(),

// Callback for when the program changes:
[&p] (GLProgram &program, ModelDrawable::ProgramBinding binding)
{
p.animator->bindUniforms(program,
binding == ModelDrawable::AboutToBind? StateAnimator::Bind :
StateAnimator::Unbind);
},

// Callback for each rendering pass:
[&p] (ModelDrawable::Pass const &pass, ModelDrawable::PassState state)
{
p.model->setMaterial(p.animator->materialForPass(pass.name));
p.animator->bindPassUniforms(*p.model->currentProgram(),
pass.name,
state == ModelDrawable::PassBegun? StateAnimator::Bind :
StateAnimator::Unbind);
}
);
p.model->draw(&p.animator->appearance(), p.animator);
}
};

Expand Down
179 changes: 126 additions & 53 deletions doomsday/apps/client/src/render/stateanimator.cpp
Expand Up @@ -32,6 +32,7 @@ static String const DEF_PROBABILITY("prob");
static String const DEF_ROOT_NODE ("node");
static String const DEF_LOOPING ("looping");
static String const DEF_PRIORITY ("priority");
static String const DEF_RENDER ("render");
static String const DEF_PASS ("pass");
static String const DEF_VARIABLE ("variable");
static String const DEF_WRAP ("wrap");
Expand Down Expand Up @@ -117,9 +118,11 @@ DENG2_PIMPL(StateAnimator)
ModelRenderer::AuxiliaryData const *auxData;
QHash<String, Sequence> pendingAnimForNode;
String currentStateName;
Record names; ///< Local context for scripts.
QBitArray passMask;
QHash<String, int> passIndexLookup;
Record names; ///< Local context for scripts, i.e., per-object model state.

ModelDrawable::Appearance appearance;
QHash<String, int> indexForPassName;
QHash<Variable *, int> passForMaterialVariable;

/**
* Animatable variable bound to a GL uniform. The value can have 1...4 float
Expand Down Expand Up @@ -203,34 +206,62 @@ DENG2_PIMPL(StateAnimator)
typedef QHash<String, RenderVar *> RenderVars;
QHash<String, RenderVars> passVars;

QHash<String, Variable *> passMaterials;

Instance(Public *i, DotPath const &id)
: Base(i)
, auxData(ClientApp::renderSystem().modelRenderer().auxiliaryData(id))
{
names.addText(VAR_ID, id).setReadOnly();
names.add(VAR_ASSET).set(new RecordValue(App::asset(id).accessedRecord())).setReadOnly();

// VAR_SELF should point to the thing's namespace, or player's for psprites
/// @todo VAR_SELF should point to the thing's namespace, or player's
/// namespace for psprites. -jk

initShaderVariables();
initVariables();

// Set up the appearance.
appearance.programCallback = [this] (GLProgram &program, ModelDrawable::ProgramBinding binding)
{
self.bindUniforms(program,
binding == ModelDrawable::AboutToBind? StateAnimator::Bind :
StateAnimator::Unbind);
};
appearance.passCallback = [this] (ModelDrawable::Pass const &pass, ModelDrawable::PassState state)
{
self.bindPassUniforms(*self.model().currentProgram(),
pass.name,
state == ModelDrawable::PassBegun? StateAnimator::Bind :
StateAnimator::Unbind);
};
}

~Instance()
{
deinitShaderVariables();
deinitVariables();
}

void initShaderVariables()
void initVariables()
{
int passIndex = 0;
passIndexLookup.clear();
int const passCount = auxData->passes.size();

// Clear lookups affected by the variables.
indexForPassName.clear();
appearance.passMaterial.clear();
if(!passCount)
{
// Material to be used with the default pass.
appearance.passMaterial << 0;
}
else
{
for(int i = 0; i < passCount; ++i) appearance.passMaterial << 0;
}
appearance.passMask.resize(passCount);

int passIndex = 0;
auto const &def = names[VAR_ASSET].valueAsRecord();
if(def.has("render"))
if(def.has(DEF_RENDER))
{
Record const &renderBlock = def.subrecord("render");
Record const &renderBlock = def.subrecord(DEF_RENDER);

initVariablesForPass(renderBlock);

Expand All @@ -239,7 +270,7 @@ DENG2_PIMPL(StateAnimator)
auto passes = ScriptedInfo::subrecordsOfType(DEF_PASS, renderBlock);
DENG2_FOR_EACH_CONST(Record::Subrecords, i, passes)
{
passIndexLookup[i.key()] = passIndex++;
indexForPassName[i.key()] = passIndex++;

Record &passRec = names.addRecord(i.key());
passRec.addBoolean(VAR_ENABLED,
Expand All @@ -250,13 +281,11 @@ DENG2_PIMPL(StateAnimator)
}
}

DENG2_ASSERT(passIndex == auxData->passes.size());
DENG2_ASSERT(passIndexLookup.size() == auxData->passes.size());
DENG2_ASSERT(passIndex == passCount);
DENG2_ASSERT(indexForPassName.size() == passCount);

passMask.resize(auxData->passes.size());
updatePassMask();

qDebug() << "Namespaces:\n" << names.asText();
updatePassMaterials();
}

void initVariablesForPass(Record const &block, String const &passName = PASS_GLOBAL)
Expand All @@ -265,13 +294,15 @@ DENG2_PIMPL(StateAnimator)

// Each pass has a variable for selecting the material.
// The default value is optionally specified in the definition.
passMaterials.insert(passName,
&names.addText(passName.concatenateMember(VAR_MATERIAL),
block.gets(DEF_MATERIAL, DEFAULT_MATERIAL)));
Variable &passMaterialVar = names.addText(passName.concatenateMember(VAR_MATERIAL),
block.gets(DEF_MATERIAL, DEFAULT_MATERIAL));
passMaterialVar.audienceForChange() += this;
passForMaterialVariable.insert(&passMaterialVar, auxData->passes.findName(passName));

/// @todo Should observe if the variable above is deleted unexpectedly. -jk

// Look up the variable declarations.
// Create the animated variables to be used with the shader based
// on the pass definitions.
auto vars = ScriptedInfo::subrecordsOfType(DEF_VARIABLE, block);
DENG2_FOR_EACH_CONST(Record::Subrecords, i, vars)
{
Expand Down Expand Up @@ -345,6 +376,8 @@ DENG2_PIMPL(StateAnimator)
// Uniform to be passed to the shader.
var->uniform = new GLUniform(i.key().toLatin1(), uniformType);

// Compose a lookup for quickly finding the variables of each pass
// (by pass name).
passVars[passName][i.key()] = var.release();
}
}
Expand All @@ -356,20 +389,80 @@ DENG2_PIMPL(StateAnimator)
.setReadOnly();
}

void deinitShaderVariables()
void deinitVariables()
{
for(RenderVars const &vars : passVars.values())
{
qDeleteAll(vars.values());
}
passVars.clear();
passMaterials.clear();
appearance.passMaterial.clear();
}

void variableValueChanged(Variable &, Value const &)
Variable const &materialVariableForPass(duint passIndex) const
{
// This is called when one of the "(pass).enabled" variables is modified.
updatePassMask();
if(!auxData->passes.isEmpty())
{
String const varName = auxData->passes.at(passIndex).name.concatenateMember(VAR_MATERIAL);
if(names.has(varName))
{
return names[varName];
}
}
return names[VAR_MATERIAL];
}

void updatePassMaterials()
{
for(int i = 0; i < appearance.passMaterial.size(); ++i)
{
appearance.passMaterial[i] = materialForUserProvidedName(
materialVariableForPass(i).value().asText());
}
}

void variableValueChanged(Variable &var, Value const &newValue)
{
if(var.name() == VAR_MATERIAL)
{
// Update the corresponding pass material.
int passIndex = passForMaterialVariable[&var];
if(passIndex < 0)
{
updatePassMaterials();
}
else
{
appearance.passMaterial[passIndex] = materialForUserProvidedName(newValue.asText());
}
}
else
{
DENG2_ASSERT(var.name() == VAR_ENABLED);

// This is called when one of the "(pass).enabled" variables is modified.
updatePassMask();
}
}

/**
* Determines the material to use during a rendering pass. These are
* determined by the "material" variables in the object's namespace.
*
* @param materialName Name of the material to use.
*
* @return Material index.
*/
duint materialForUserProvidedName(String const &materialName) const
{
auto const matIndex = auxData->materialIndexForName.constFind(materialName);
if(matIndex != auxData->materialIndexForName.constEnd())
{
return matIndex.value();
}
LOG_RES_WARNING("Asset \"%s\" does not have a material called '%s'")
<< names.gets(VAR_ID) << materialName;
return 0; // default material
}

void updatePassMask()
Expand All @@ -378,11 +471,11 @@ DENG2_PIMPL(StateAnimator)
return sub.getb(DEF_ENABLED, false);
});

passMask.fill(false);
appearance.passMask.fill(false);
for(String name : enabledPasses.keys())
{
DENG2_ASSERT(passIndexLookup.contains(name));
passMask.setBit(passIndexLookup[name], true);
DENG2_ASSERT(indexForPassName.contains(name));
appearance.passMask.setBit(indexForPassName[name], true);
}
}

Expand Down Expand Up @@ -581,29 +674,9 @@ ddouble StateAnimator::currentTime(int index) const
return ModelDrawable::Animator::currentTime(index); // + frameTimePos;
}

QBitArray StateAnimator::passMask() const
ModelDrawable::Appearance const &StateAnimator::appearance() const
{
return d->passMask;
}

duint StateAnimator::materialForPass(String const &passName) const
{
auto const iter = d->passMaterials.constFind(passName);
if(iter != d->passMaterials.constEnd())
{
Variable const *material = iter.value();
DENG2_ASSERT(material != nullptr);
auto const matIndex = d->auxData->materialIndexForName.constFind(material->value().asText());
if(matIndex != d->auxData->materialIndexForName.constEnd())
{
return matIndex.value();
}
}
else
{
return materialForPass(PASS_GLOBAL);
}
return 0; // default material
return d->appearance;
}

void StateAnimator::bindUniforms(GLProgram &program, BindOperation operation) const
Expand Down

0 comments on commit 2bb0699

Please sign in to comment.