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.
directional-lights
Lukas Werling 2016-11-12 21:57:10 +01:00
parent bfc830a103
commit 6847e50e79
11 changed files with 153 additions and 0 deletions

View File

@ -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)

View File

@ -181,6 +181,7 @@ void C4Draw::Default()
ZoomX = 0; ZoomY = 0; Zoom = 1;
MeshTransform = nullptr;
fUsePerspective = false;
scriptUniform.Clear();
}
void C4Draw::Clear()

View File

@ -97,6 +97,7 @@ public:
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

View File

@ -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)

View File

@ -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);

View File

@ -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
}

View File

@ -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 @@ public:
#ifndef USE_CONSOLE
class C4ShaderCall
{
friend class C4ScriptUniform;
public:
C4ShaderCall(const C4Shader *pShader)
: fStarted(false), pShader(pShader), iUnits(0)
@ -344,4 +348,42 @@ public: // Interface for script
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

View File

@ -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))
{

View File

@ -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; }

View File

@ -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";

View File

@ -524,6 +524,7 @@ enum C4PropertyName
P_EditorInitialize,
P_EditorPlacementLimit,
P_Sorted,
P_Uniforms,
// Default Action Procedures
DFA_WALK,
DFA_FLIGHT,