1

I am trying to make a 2D game engine using OpenGL ES 2.0 (iOS for now). I've written Application layer in Objective C and a separate self contained RendererGLES20 in C++. No GL specific call is made outside the renderer. It is working perfectly.

But I have some design issues when using shaders. Each shader has its own unique attributes and uniforms that need to be set just before the main draw call (glDrawArrays in this case). For instance, in order to draw some geometry I would do:

void RendererGLES20::render(Model * model)
{
    // Set a bunch of uniforms
    glUniformMatrix4fv(.......);
    // Enable specific attributes, can be many
    glEnableVertexAttribArray(......);
    // Set a bunch of vertex attribute pointers:
    glVertexAttribPointer(positionSlot, 2, GL_FLOAT, GL_FALSE, stride, m->pCoords);

    // Now actually Draw the geometry
    glDrawArrays(GL_TRIANGLES, 0, m->vertexCount);

    // After drawing, disable any vertex attributes:
    glDisableVertexAttribArray(.......);
}

As you can see this code is extremely rigid. If I were to use another shader, say ripple effect, i would be needing to pass extra uniforms, vertex attribs etc. In other words I would have to change the RendererGLES20 render source code just to incorporate the new shader.

Is there any way to make the shader object totally generic? Like What if I just want to change the shader object and not worry about game source re-compiling? Any way to make the renderer agnostic of uniforms and attributes etc?. Even though we need to pass data to uniforms, what is the best place to do that? Model class? Is the model class aware of shader specific uniforms and attributes?

Following shows Actor class:

class Actor : public ISceneNode
{
    ModelController * model;
    AIController * AI;
};

Model controller class:

class ModelController
{
    class IShader * shader;
    int textureId;
    vec4 tint;
    float alpha;
    struct Vertex * vertexArray;
};

Shader class just contains the shader object, compiling and linking sub-routines etc.

In Game Logic class I am actually rendering the object:

void GameLogic::update(float dt)
{
    IRenderer * renderer = g_application->GetRenderer();

    Actor * a = GetActor(id);
    renderer->render(a->model);
}

Please note that even though Actor extends ISceneNode, I haven't started implementing SceneGraph yet. I will do that as soon as I resolve this issue.

Any ideas how to improve this? Related design patterns etc?

Slipp D. Thompson
  • 33,165
  • 3
  • 43
  • 43
fakhir
  • 157
  • 10

4 Answers4

4

The whole point of shaders is to be specific. Generic APIs and inflation of the state machine poisoned OpenGL for a long time (just look at the OpenGL-1.5 glTexEnv). The whole point of modern OpenGL (v3 and beyond) was to get rid of this genericity, so that shaders could be tailored for the task at hand with easy. Don't think shaders to be something separate from the renderer code that uses them. In fact shaders and client side renderer code are complementary and are usually developed in unison.

datenwolf
  • 159,371
  • 13
  • 185
  • 298
  • In the renderer code above, how would I swap it with another shader **without changing the renderer source**? For example I need to change the default shader to bloom shader for 10 seconds, then apply drop shadow shader having possibly different uniforms and attribs for the next 20 seconds, and then back to the default shader? – fakhir Jul 03 '13 at 12:51
  • 2
    @user2370784: Well, you don't. A bloom shader needs a different renderer (source) anyway. Get this in your mind: The GLSL shader source is part of your renderer source code. If you change the GLSL shader you need a different renderer anyway. GLSL shaders are not modules you're supposed to drop in arbitrarily. For example a ripple shader requires entirely different input than a bloom shader; not just different uniforms, but different vertex attributes as well. GLSL and renderer code go hand in hand. – datenwolf Jul 03 '13 at 14:08
  • Hmm, that is very interesting. I thought shaders were detachable components like textures (more or less). So the solution given by @Simon below is the best possible? Inherit a bunch of renderers according to specific shaders? – fakhir Jul 04 '13 at 03:15
  • @user2370784: Yes what Simon suggests is more or less the right approach. Personally I'd structure my code a little bit different, but, yes. – datenwolf Jul 04 '13 at 08:31
0

As datenwolf says, shaders are not supposed to be generic.

That said, in theory, you can just swap out shaders code as you wish, as long as it uses all the same uniform, in and out parameters nothing will really be able to tell the difference. That said, this is probably not a good idea at all.

If you want such flexibility, you probably want to look at introducing some sort intermediary scripting layer. Your engine can provide this scripting layer all the information you want it to, and the script layer can take care of working out what uniforms to set and what to set them to. It is not a trivial thing do, but then what you are trying to do is not exactly easy.

thecoshman
  • 8,394
  • 8
  • 55
  • 77
  • Well I am not talking about generalising the shader code itself (i.e: bloating it with ifs/defs, switch/cases etc). I am looking at making the shader object layer (which actually manages shaders) a bit more efficient and abstract. What if i want to load bloom shader for 5 seconds and then back to default shader? There is no way I can do that in the above source without changing renderer code. – fakhir Jul 03 '13 at 12:55
  • From the sounds of it, you don't fully understand how you would manage shaders. They are linked together in a program, so if you wanted to use a different program for a bit, you would just 'use' it for a bit, then 'use' the next one. If you want to change just one shader within a program, you would have to relink the entire program . And that is just a hard 'limit' of how OpenGL works. – thecoshman Jul 03 '13 at 13:22
  • I am aware of that. Consider the following: Default shader takes MVP matrix as uniform along with texture sampler. If I want to enable say ripple effect, I would have to pass not only MVP and sampler, but also the delta time (or other var) so that it could calculate ripples properly. Also I would want to send the diameter of the ripples as a uniform. Now these extra uniforms are no where into play in the default shader. So you can't simply do "glUseProgram" in the above mentioned case! You will have to change the code that passes uniforms/attribs to the shader as well. – fakhir Jul 03 '13 at 13:35
  • Well I am here to learn :). Suppose my character collects a power up, he starts to glow using bloom shader. After 10 sec default shader is attached to the same geometry. How to do this efficiently? Same geometry using different shaders? Do you think I should put a big switch/case statement in the current renderer to cope up with changing shaders ? – fakhir Jul 03 '13 at 13:45
  • It's hard to say. Rendering engines are complex beasts. You probably want have some sort of 'effect mode' that you can toggle. Perhaps wrap the program and the setting of the uniforms together, so that you can just call one or the other. – thecoshman Jul 03 '13 at 14:02
0

When no attributes or uniforms change then you can change the shader without having to recompile your c++ code. You do this by either loading you shader code from a text file with glShaderSource and then compiling it, or by loading a precompiled binary file with glShaderBinary.

To get some sort of encapsulation of the different rendering methods you can have different renderers that inherit from RendererGLES20. That way only the renderer needs to know what attributes and uniforms the shader needs.

class RendererGLES20
{
public:
    virtual void render(Model * model) = 0;

    // ...
}

class RippleRenderer : public RendererGLES20
{
    virtual void render(Model * model);
    // ...
}

class BlinnPhongRenderer : public RendererGLES20
{
    virtual void render(Model * model);
    // ...
}

class CartoonRenderer : public RendererGLES20
{
    virtual void render(Model * model);
    // ...
}
Simon
  • 6,293
  • 2
  • 28
  • 34
  • Well in real situation attributes and uniforms do change. Furthermore in a typical game there tend to be a lot of shaders, 10-15 maybe. So that is 15 extended classes ONLY for RendererGLES20. What if I wanted to port the game for Wii (fixed pipeline) or XNA (directX). You can make a C++ DLL and interface with C# but you must rewrite entire renderer class and the 15 other extended renderers. Furthermore each time I add a new shader I would have to extend a new renderer and if I decide to discard some shader I would have to delete the related class. Not a good idea! – fakhir Jul 03 '13 at 13:12
  • I would like to know if we can make the renderer totally agnostic of the shader internal details. – fakhir Jul 03 '13 at 13:12
  • Well it's just like datenwolf said, the newer versions of OpengGL sacrifices programmer convenience in favor of performance and flexibility so there is no way to make it generic. This is just one way to share some of the rendering code and member variables. – Simon Jul 03 '13 at 13:26
  • @Simon: "Loading shaders from a precompiled binary" – preocompiled binaries are not generic but depend on the excact OS/driver/GPU combination. This mechanism is intended to be used for caching, so that programs are not stalled by the compilation of complex shaders, but can used a cached binary that's been compiled in a previous program run. **You can't ship your program with binary shaders!** – datenwolf Jul 04 '13 at 08:29
  • @datenwolf Yes, but on a given platform and as long as attributes and uniforms don't change, they are interchangable. My understanding was that the aim of the question was to acheive maintainable code. As far as packaging and shipping goes, it's possible to have several sets of platform specific binaries. – Simon Jul 04 '13 at 10:26
  • @Simon: Please read the specification regarding shader binaries: Your program **must** ship with the shader source code. A driver may decide to discard a shader binary for whatever reason it sees as configuration change. – datenwolf Jul 04 '13 at 10:33
0

I'm not claiming to be an expert (I'm in the exact same stage of learning to use OpenGL as you) but here is what I would try:

Maybe you can make an abstract RenderEffect class and pass a (list of) of these to RendererGLES20::render(). From it you would derive classes such as RippleEffect and BloomEffect. A RenderEffect object would contain all the information your renderer needs to make the necessary ogl state adjustments specific to your shader. It would essentially be a container class of:

  • A list of 'UniformInfo' objects containing uniform name/id, transpose bool and (a pointer to) the value. Type and component count can be queried so no need to store it here.
  • A list of 'VertexAttribInfo' objects containing all necessary info for glVertexAttribPointer*() just like UniformInfo. Again, some things like component count can be queried.

Derived classes can declare a constructor with arguments that match the info needed for that specific effect and would store it in the lists defined by their parent class. So the constructor of RippleEffect might have parameters for delta t and diameter and when it is called it creates and stores a UniformInfo object for each.

Finally, you can make all of this data-driven and specify all of the info in a text file. That would be just one step short of creating a scripting-system like thecoshman proposed.

PS: UniformInfo objects and VertexAttribInfo objects would store or reference values of different types. To deal with this you could make different classes for each type but I recommend storing just a void* or the likes. Another option is using templates but I don't know anything about objective-c so I don't know if that is possible.

Jupiter
  • 1,421
  • 2
  • 12
  • 31