Replies: 1 comment 1 reply
-
I can explain how we do this in WhirlyGlobe-Maply if that helps. The short version is we broke out all the uniform types we care about and require that they be wired up for a draw call. This is a very OpenGL ES 2.0 approach. Using Uniform Buffers would be superior and we do something like that with Metal. Good things:
Bad things:
With Metal and GLES we found that there's not much to be shared. We could have built a named uniform mechanism for Metal, but it isn't how Metal wants to work and it would slow things down. Point being your top level Program or Shader object is either going to be very bland or force a lot of work on your subclasses. As an example of how this plays out, let's say we have a custom shader for Metal and for OpenGL ES. Maybe a particle shader, as it's a simple idea. You have to feed in speed and size and texture and all that other good stuff. Texture setting would have a generic top level method in the Program with implementations in each subclass for a rendering SDK. But uniform setting would not necessarily. On iOS we'd pass in a struct wrapped in an NSData. On Android we'd call out individual uniforms by name. If we decide that functionality is cross platform, then we build a high level interface on each SDK implementation and expose a generic set of options to be manipulated. This also lets you hide threading details and such. Because if you try to manipulate uniforms on the main loop on Android, it won't go well. There are different reasons that won't work with Metal, like frames in flight. I'm not sure I'm suggesting the named uniform interface here. Perhaps we could require UBO support for anything new, which syncs up well with Metal and make that interface as flexible as it needs to be. If you're passing buffers around, it doesn't need to be very. Then if you don't have UBO support you're back on the old shaders and the old layers. |
Beta Was this translation helpful? Give feedback.
-
Bringing this discussion on to GH, sharing my current findings, documentation and design intentions for shaders.
As part of implementing the proposed shader changes, one of our objectives is to simplify the way shaders work and make it possible for users outside the toolkit to create and replace the built-in programs. Existing programs are heavily templated and the associated layer rendering implementations rely on some of these template parameters.
Take a look at how things are done currently:
mbgl::Program
, providing uniform, attribute and texture layouts via template parametersmbgl::Program
instantiates the actual shader object (gfx::Program
) by runningcontext.createProgram<TopLevelClass>
gfx::Context::createProgram<T>
calls the free-functionBackend::Create<Type::OpenGL, ...>
, backend template-parameter is hard-codedunique_ptr<gl::Program<T>>
is finally createdgl::Program
manages shader creation & permutation, implements the actual draw call, configures the fixed-function state and locates the shader source-code (programs::gl::ShaderSource<T>::vertexOffset
, also done via template specialization and hard-coded to read from the blob)gl::Program
andgfx::Program
both accept, as a template parameter, the top-level class inheritingmbgl::Program
so they can see the program-specific template parameters (consuming parameters, defined here).With the addition of a generic base for
mbgl::Program<>
(gfx::Shader
), we can store all programs in a registry asgfx::Shader
instances. Our hierarchy would like this:We can then include a shader registry side-by-side with the existing program collection
mbgl::Programs
inRendererStaticData
like so:An additional method,
mbgl::Programs::registerWith(ShaderRegistry&)
will serve to 'install' the built-in shaders in the registry instance. This would look like so:If we want to pass shader source ourselves instead of loading from the blob, we can pass it via
ProgramParameters
, perhaps like so:In this way, we can fallback to loading from the blob if the optional source strings in
programParameters
are empty.So far, so good. But now, what if we want to add a new uniform? Maybe a new texture? These are done via template parameters and would thus require implementing a new type derived from
mbgl::Program
. That also means creating a new template specialization forgl::Program<Name>
to create the new type. Layer rendering also refers to programs (ie: FillProgram) by type, meaning to change the type of program for fill layers we have to change the render_fill_layer implementation. Currently, I feel the only approach to supporting this is waiting for drawables to replace the existing render_x_layer approach. Users replacing programs outright will most likely require this anyways to bind additional textures, set new uniforms or whatever it is they've added to the shader.I'm curious what others think about this approach. Should we try and flatten the program hierarchy or keep it mostly unchanged? Is there value in being able to change program source without (currently) replacing the program class & implementation (uniforms, textures, paint props)? And should we try and change the
gfx::Program
-mbgl::Program
composition-by-template-specialization mechanism?Beta Was this translation helpful? Give feedback.
All reactions