Dev:2.8/Source/OpenGL/ImmediateModeReplacement
Replace OpenGL immediate mode in Blender 2.8
This is an ongoing project, see the progress at the task list.
What needs to be done, and why?
We are transitioning to “modern” OpenGL core profile in order to build an awesome new 3D viewport for Blender 2.8 and to ease future development. Blender’s entire interface (not just the 3D view) is drawn with OpenGL so we have a lot of areas to cover. The glBegin/glVertex/glEnd method of drawing was deprecated in OpenGL 3.0, and is completely absent from GL 3.3 core profile which we are targeting.
But for us immediate mode is still useful:
- Much of Blender code uses it. Converting every spot to use VBOs & glDrawArrays will put us behind schedule. It is also error prone; new bugs put us further behind schedule.
- Sometimes drawing things in a procedural way is more natural. Converting to VBOs obscures what we are trying to do.
So we’re going to support this style of coding with a modernized immediate mode work-alike that is compatible with modern GL. And compatible with Vulkan, which never had such a drawing mode. The new API is powered by the Gawain library, tailor made for Blender.
All Blender code that calls legacy OpenGL functions must transition to the new API before we can throw the big switch to enable core profile. It’s time for more people to get involved!
Who can help out?
Anyone! Well anyone that can program in C. If you’re familiar with traditional OpenGL (glBegin, glVertex, glEnd) you’ve got a head start. Even if OpenGL is new to you, our replacement API is small and easy to learn.
What exactly needs to be recoded?
Any calls to glBegin, glEnd, glVertex, glColor, glNormal, glTexCoord, glRect.
Blender also has utility functions that call old GL functions. UI_ThemeColor, fdrawline, sdrawline, and more. The focus should be on updating code that calls these, not on the utility functions themselves.
Some conversion tasks take 15 minutes; others take a whole day or weekend.
What does not need to be recoded?
State-setting functions such as glEnable, glDisable, glPointSize, glBlendFunc, glDepthFunc, glDepthMask, glColorMask …
glLineWidth is also legacy, but let’s not worry about that just yet!
We have bigger plans for grease pencil, mesh drawing, and the GPU library, so leave them alone. See the task progress for an exact list of files.
When in doubt, ask on #blendercoders IRC or the bf-viewport mailing list.
New immediate mode API
Here’s a small example of legacy OpenGL followed by the new API. We’ll be drawing a triangle that has one corner with color A and the other two corners color B, the colors blending smoothly across the triangle.
// OpenGL immediate mode
glShadeModel(GL_SMOOTH);
glBegin(GL_TRIANGLES);
glColor3ubv(colorA);
glVertex2f(x1, y1);
glColor3ubv(colorB);
glVertex2f(x2, y2);
glVertex2f(x3, y3);
glEnd();
// Gawain immediate mode
VertexFormat *format = immVertexFormat();
unsigned int pos = add_attrib(format, "pos", COMP_F32, 2, KEEP_FLOAT);
unsigned int color = add_attrib(format, "color", COMP_U8, 3, NORMALIZE_INT_TO_FLOAT);
immBindBuiltinProgram(GPU_SHADER_2D_SMOOTH_COLOR);
immBegin(PRIM_TRIANGLES, 3);
immAttrib3ubv(color, colorA);
immVertex2f(pos, x1, y1);
immAttrib3ubv(color, colorB);
immVertex2f(pos, x2, y2);
immVertex2f(pos, x3, y3);
immEnd();
immUnbindProgram();
Similarities to legacy OpenGL
Both APIs follow this basic pattern:
Begin(primitive)
Attributes
Vertex
Attributes
Vertex
End()
If you skip some attributes of a vertex, they will be carried over from the previous vertex.
If a shader has a vec3 pos
input, you will usually specify that attribute as "pos", COMP_F32, 3, KEEP_FLOAT
. The name must match exactly but the format just needs to more-or-less match. For example COMP_F32, 2, KEEP_FLOAT
is compatible, as is COMP_S16, 3, CONVERT_INT_TO_FLOAT
. Type conversions between the vertex format and the GLSL shader are based on standard OpenGL. As explained in the OpenGL wiki:
The size parameter … can be any number 1-4. Note that size does not have to exactly match the size used by the vertex shader. If the vertex shader has fewer components than the attribute provides, then the extras are ignored. If the vertex shader has more components than the array provides, the extras are given values from the vector (0, 0, 0, 1) for the missing XYZW components.
Differences from legacy OpenGL
The new immediate mode is closely tied to programmable shaders, and this affects how it is used. First notice that you have to spell out the vertex format you’ll be using, then bind a shader that uses a compatible vertex format. The old API might or might not use shaders but the new API always does.
immBegin takes an additional argument: vertex count. Most of the time you know how many vertices you’re about to draw, and letting Gawain know this helps it allocate just the right amount of space. For situations where you can’t give an exact count, use immBeginAtMost. Any space not used by BeginAtMost will be reclaimed and available for the next Begin, so you’re not “wasting” it.
In the new API, vertex attribute values can only be set between Begin and End, and affect the current vertex. It might seem like OpenGL works this way but it’s an illusion. glColor (for example) does not set the current vertex’s color. It sets the current color. This can happen inside or outside a Begin/End pair. glVertex—which can be called only between Begin/End—copies the current color and other attributes, provides position values, and adds a complete vertex. The new API has no concept of “current color”.
Actually it has no concept of “color” at all! All attributes are generic, and only have semantic meaning in the shader. Instead of functions for Color, Normal, TexCoord, etc. we have only immAttrib and immVertex.
Strict error checking
When debugging OpenGL it’s common to stare at a blank screen wondering where your primitives are since your code obviously says to draw them right there on the screen. Could be a typo in any one of the last 50 lines of code… When you make a mistake in the new API, it will let you know immediately with an assert. This is by design. It will save us hours of wondering where that triangle could be, when we could be fixing the exact line of code and moving on! (I designed and implemented this API, and it catches my mistakes daily).
Examples of error checking:
- immBegin(xxx, 10) then you call immVertex an 11th time.
- immEnd() when you called immVertex only 7 times. Use immBegin(xxx, 7) or immBeginAtMost(xxx, 10).
- immAttrib or immVertex outside of immBegin/End.
- immVertex2f(pos, x, y) when you defined pos as a float vec3 (or an integer vec2).
- VertexFormat does not match the shader’s inputs. immBindProgram will catch this.
- immBegin(PRIM_LINES, 5). Each line has 2 vertices, so what is the 5th one for?
During development these strict checks will be left on for debug and release builds. Most of this error checking will be turned off for the final 2.8 release.
Organizing the work
The general plan is for people to independently choose what to work on, claim a file or set of functions, then commit or make a patch. After reading this doc, head over to developer.blender.org/T49043 to see what has already been done and what is currently being worked on. The blender2.8 branch has the new drawing API; all work should happen there. If you have commit access, add “Part of T49043” to your commit message. For those without commit access, make a diff on Phabricator and mention the T49043 task. That way we can all see each others’ progress. Please include a rough time estimate when you claim a file or set of functions.
See GPU_immediate.h, gawain/immediate.h, and gawain/vertex_format.h for the complete API. If you need a function that is not implemented by Gawain, add a comment to T49043. The API is minimal and follows the YAGNI principle. But as soon as you do need something, I’d be happy to add it.
See GPU_shader.h and gpu/shaders/*.glsl for the available built-in shaders. If none of these does exactly what you need, feel free to add a new one or ask for it to be made. I’ve added several shaders recently and can crank them out pretty quickly.
Let’s get this done!
— Mike Erwin, 3 October 2016