Skip to content
Permalink
Browse files

Implement setting shader uniforms from script (#1206)

Uniform variables are read from the "Uniforms" proplist set on Scenario
or on individual objects. Proplist keys are uniform names. Values can
either be an int or an array of one to four ints in C4Script. In GLSL,
the uniforms then need a matching type (int/ivec2/ivec3/ivec4). There is
no error reporting; uniforms are only set if both name and type match.

The implementation walks the "Uniforms" proplists on each Draw call. We
may need to cache the uniform maps if this turns out to be too slow.
  • Loading branch information
lluchs committed Nov 12, 2016
1 parent bfc830a commit 6847e50e790c4baa32894fa2f7d56febf107b14e
@@ -386,6 +386,8 @@ void C4Viewport::Execute()
C4Surface *target = pWindow ? pWindow->pSurface : FullScreen.pSurface;
cgo.Set(target,DrawX,DrawY,float(ViewWdt)/Zoom,float(ViewHgt)/Zoom,GetViewX(),GetViewY(),Zoom);
pDraw->PrepareRendering(target);
// Load script uniforms from Global.Uniforms
auto uniform_pop = pDraw->scriptUniform.Push(::GameScript.ScenPropList.getPropList());
// Do not spoil game contents on owner-less viewport
bool draw_game = true;
if (Player == NO_OWNER)
@@ -181,6 +181,7 @@ void C4Draw::Default()
ZoomX = 0; ZoomY = 0; Zoom = 1;
MeshTransform = nullptr;
fUsePerspective = false;
scriptUniform.Clear();
}

void C4Draw::Clear()
@@ -97,6 +97,7 @@ class C4Draw
float gamma[C4MaxGammaRamps][3]; // input gammas
float gammaOut[3]; // combined gamma
int MaxTexSize;
C4ScriptUniform scriptUniform; // uniforms added to all draw calls
protected:
float fClipX1,fClipY1,fClipX2,fClipY2; // clipper in unzoomed coordinates
float fStClipX1,fStClipY1,fStClipX2,fStClipY2; // stored clipper in unzoomed coordinates
@@ -497,6 +497,8 @@ void CStdGL::SetupMultiBlt(C4ShaderCall& call, const C4BltTransform* pTransform,

if (pFoW != nullptr && normalTex != 0)
call.SetUniformMatrix3x3Transpose(C4SSU_NormalMatrix, StdMeshMatrix::Inverse(StdProjectionMatrix::Upper3x4(modelview)));

scriptUniform.Apply(call);
}

void CStdGL::PerformMultiPix(C4Surface* sfcTarget, const C4BltVertex* vertices, unsigned int n_vertices, C4ShaderCall* shader_call)
@@ -956,6 +956,8 @@ namespace
}
}

pDraw->scriptUniform.Apply(call);

size_t vertex_count = 3 * instance.GetNumFaces();
assert (vertex_buffer_offset % sizeof(StdMeshVertex) == 0);
size_t base_vertex = vertex_buffer_offset / sizeof(StdMeshVertex);
@@ -755,3 +755,99 @@ bool C4ScriptShader::Remove(int id)
}
return false;
}

std::unique_ptr<C4ScriptUniform::Popper> C4ScriptUniform::Push(C4PropList* proplist)
{
#ifdef USE_CONSOLE
return std::unique_ptr<C4ScriptUniform::Popper>();
#else
C4Value ulist;
if (!proplist->GetProperty(P_Uniforms, &ulist) || ulist.GetType() != C4V_PropList)
return std::unique_ptr<C4ScriptUniform::Popper>();

uniformStack.emplace();
auto& uniforms = uniformStack.top();
Uniform u;
for (const C4Property* prop : *ulist.getPropList())
{
if (!prop->Key) continue;
switch (prop->Value.GetType())
{
case C4V_Int:
u.type = GL_INT;
u.intVec[0] = prop->Value._getInt();
break;
case C4V_Array:
{
auto array = prop->Value._getArray();
switch (array->GetSize())
{
case 1: u.type = GL_INT; break;
case 2: u.type = GL_INT_VEC2; break;
case 3: u.type = GL_INT_VEC3; break;
case 4: u.type = GL_INT_VEC4; break;
default: continue;
}
for (int32_t i = 0; i < array->GetSize(); i++)
{
auto& item = array->_GetItem(i);
switch (item.GetType())
{
case C4V_Int:
u.intVec[i] = item._getInt();
break;
default:
goto skip;
}
}
break;
}
default:
continue;
}
// Uniform is now filled properly. Note that array contents are undefined for higher slots
// when "type" only requires a smaller array.
uniforms.insert({prop->Key->GetCStr(), u});
skip:;
}
// Debug
/*
for (auto& p : uniforms)
{
LogF("Uniform %s (type %d) = %d %d %d %d", p.first.c_str(), p.second.type, p.second.intVec[0], p.second.intVec[1], p.second.intVec[2], p.second.intVec[3]);
}
*/
return std::make_unique<C4ScriptUniform::Popper>(this);
#endif
}

void C4ScriptUniform::Clear()
{
uniformStack = {};
uniformStack.emplace();
}

void C4ScriptUniform::Apply(C4ShaderCall& call)
{
#ifndef USE_CONSOLE
for (auto& p : uniformStack.top())
{
// The existing SetUniform* methods only work for pre-defined indexed uniforms. The script
// uniforms are unknown at shader compile time, so we have to use OpenGL functions directly
// here.
GLint loc = glGetUniformLocation(call.pShader->hProg, p.first.c_str());
// Is this uniform defined in the shader?
if (loc == -1) continue;
auto& intVec = p.second.intVec;
switch (p.second.type)
{
case GL_INT: glUniform1i(loc, intVec[0]); break;
case GL_INT_VEC2: glUniform2i(loc, intVec[0], intVec[1]); break;
case GL_INT_VEC3: glUniform3i(loc, intVec[0], intVec[1], intVec[2]); break;
case GL_INT_VEC4: glUniform4i(loc, intVec[0], intVec[1], intVec[2], intVec[3]); break;
default:
assert(false && "unsupported uniform type");
}
}
#endif
}
@@ -31,6 +31,8 @@
#include <GL/glew.h>
#endif

#include <stack>

// Shader version
const int C4Shader_Version = 150; // GLSL 1.50 / OpenGL 3.2

@@ -60,6 +62,7 @@ const int C4Shader_Vertex_PositionPos = 80;
class C4Shader
{
friend class C4ShaderCall;
friend class C4ScriptUniform;
public:
C4Shader();
~C4Shader();
@@ -182,6 +185,7 @@ class C4Shader
#ifndef USE_CONSOLE
class C4ShaderCall
{
friend class C4ScriptUniform;
public:
C4ShaderCall(const C4Shader *pShader)
: fStarted(false), pShader(pShader), iUnits(0)
@@ -344,4 +348,42 @@ class C4ScriptShader

extern C4ScriptShader ScriptShader;

class C4ScriptUniform
{
friend class C4Shader;

struct Uniform
{
#ifndef USE_CONSOLE
GLenum type;
union
{
int intVec[4];
// TODO: Support for other uniform types.
};
#endif
};

std::stack<std::map<std::string, Uniform>> uniformStack;

public:
class Popper
{
C4ScriptUniform* p;
size_t size;
public:
Popper(C4ScriptUniform* p) : p(p), size(p->uniformStack.size()) { }
~Popper() { assert(size == p->uniformStack.size()); p->uniformStack.pop(); }
};

// Remove all uniforms.
void Clear();
// Walk the proplist `proplist.Uniforms` and add uniforms. Automatically pops when the return value is destroyed.
std::unique_ptr<Popper> Push(C4PropList* proplist);
// Apply uniforms to a shader call.
void Apply(C4ShaderCall& call);

C4ScriptUniform() { Clear(); }
};

#endif // INC_C4Shader
@@ -981,6 +981,8 @@ void C4LandscapeRenderGL::Draw(const C4TargetFacet &cgo, const C4FoWRegion *Ligh
ShaderCall.SetUniform1f(C4LRU_AmbientBrightness, Light->getFoW()->Ambient.GetBrightness());
}

pDraw->scriptUniform.Apply(ShaderCall);

// Start binding textures
if(shader->HaveUniform(C4LRU_LandscapeTex))
{
@@ -1808,6 +1808,9 @@ void C4Object::Draw(C4TargetFacet &cgo, int32_t iByPlayer, DrawMode eDrawMode, f
// visible?
if (!IsVisible(iByPlayer, !!eDrawMode)) return;

// Set up custom uniforms.
auto uniform_popper = pDraw->scriptUniform.Push(this);

// Line
if (Def->Line) { DrawLine(cgo, iByPlayer); return; }

@@ -300,6 +300,7 @@ C4StringTable::C4StringTable()
P[P_EditorInitialize] = "EditorInitialize";
P[P_EditorPlacementLimit] = "EditorPlacementLimit";
P[P_Sorted] = "Sorted";
P[P_Uniforms] = "Uniforms";
P[DFA_WALK] = "WALK";
P[DFA_FLIGHT] = "FLIGHT";
P[DFA_KNEEL] = "KNEEL";
@@ -524,6 +524,7 @@ enum C4PropertyName
P_EditorInitialize,
P_EditorPlacementLimit,
P_Sorted,
P_Uniforms,
// Default Action Procedures
DFA_WALK,
DFA_FLIGHT,

0 comments on commit 6847e50

Please sign in to comment.
You can’t perform that action at this time.