OpenGL Cheatsheet

Drawing

Use this OpenGL reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Draw Call Fundamentals

Every draw call reads vertex data from the currently bound VAO and submits primitives to the pipeline.

glBindVertexArray(vao);
glUseProgram(prog);

// Non-indexed
glDrawArrays(GL_TRIANGLES, 0, 6);  // first=0, count=6 vertices

// Indexed
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void*)0);
// last arg = byte offset into the EBO (not a pointer when EBO is bound)

Primitive Modes

ModeDescriptionMin verts to draw
GL_POINTSOne point per vertex1
GL_LINESIndependent line segments (2 verts each)2
GL_LINE_STRIPConnected line segments2
GL_LINE_LOOPStrip + closing segment2
GL_TRIANGLESIndependent triangles (3 verts each)3
GL_TRIANGLE_STRIPShared-edge triangles3
GL_TRIANGLE_FANFan from first vertex3
GL_LINES_ADJACENCYLines with 2 adjacency verts each4
GL_LINE_STRIP_ADJACENCYStrip with adjacency4
GL_TRIANGLES_ADJACENCYTriangles with edge neighbors6
GL_TRIANGLE_STRIP_ADJACENCYStrip with adjacency6
GL_PATCHESPatch for tessellation shadersGL_PATCH_VERTICES
// Set patch size for tessellation
glPatchParameteri(GL_PATCH_VERTICES, 3);  // triangles (default)

All Draw Functions

Non-indexed

glDrawArrays(mode, first, count);

// Instanced — gl_InstanceID in shader
glDrawArraysInstanced(mode, first, count, instanceCount);

// Instanced with base instance (gl_BaseInstance)
glDrawArraysInstancedBaseInstance(mode, first, count, instanceCount, baseInstance);

// Multi-draw — separate range per sub-call
const GLint   firsts[]  = { 0, 6 };
const GLsizei counts[]  = { 6, 3 };
glMultiDrawArrays(mode, firsts, counts, 2);

// Indirect (command from GPU buffer)
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, indirectBuf);
glDrawArraysIndirect(mode, (void*)offset);

// Multi indirect (GL 4.3+)
glMultiDrawArraysIndirect(mode, (void*)offset, drawCount, stride);

Indexed

glDrawElements(mode, count, indexType, offsetPtr);

glDrawElementsBaseVertex(mode, count, indexType, offsetPtr, baseVertex);
// baseVertex added to each index before fetching (allows shared VBO regions)

glDrawElementsInstanced(mode, count, indexType, offsetPtr, instanceCount);

glDrawElementsInstancedBaseVertex(mode, count, indexType, offsetPtr,
                                   instanceCount, baseVertex);

glDrawElementsInstancedBaseVertexBaseInstance(mode, count, indexType, offsetPtr,
                                              instanceCount, baseVertex, baseInstance);

glDrawRangeElements(mode, start, end, count, indexType, offsetPtr);
// Hint: indices are in [start, end] — may improve performance

glMultiDrawElements(mode, counts, indexType, offsets, drawCount);

glMultiDrawElementsBaseVertex(mode, counts, indexType, offsets, drawCount, baseVertices);

glDrawElementsIndirect(mode, indexType, (void*)offset);

glMultiDrawElementsIndirect(mode, indexType, (void*)offset, drawCount, stride);

Indirect draw command structs

// For glDrawArraysIndirect
struct DrawArraysIndirectCommand {
    GLuint count;
    GLuint instanceCount;
    GLuint first;
    GLuint baseInstance;
};

// For glDrawElementsIndirect
struct DrawElementsIndirectCommand {
    GLuint count;
    GLuint instanceCount;
    GLuint firstIndex;   // index into EBO (in element units, not bytes)
    GLint  baseVertex;
    GLuint baseInstance;
};

Instanced Rendering

Draw many copies of the same geometry in one call. Use gl_InstanceID or per-instance vertex attributes.

// Per-instance vertex attribute (divisor = 1 means advance once per instance)
glVertexAttribDivisor(attribIndex, 1);  // 0 = per-vertex (default), N = per N instances

// DSA equivalent
glVertexArrayBindingDivisor(vao, bindingIndex, 1);
// Vertex shader
layout(location = 2) in vec3 aOffset;  // divisor=1 attribute

void main() {
    gl_Position = uVP * vec4(aPos + aOffset, 1.0);
}

Transform Feedback

Capture transformed vertices back to a buffer before rasterization.

// Setup
GLuint tfo;
glGenTransformFeedbacks(1, &tfo);
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, tfo);

const char* varyings[] = { "outPos", "outVel" };
glTransformFeedbackVaryings(prog, 2, varyings, GL_INTERLEAVED_ATTRIBS);
// GL_SEPARATE_ATTRIBS: one binding point per varying
glLinkProgram(prog);  // must relink after setting varyings

glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, captureBuffer);

// Draw
glEnable(GL_RASTERIZER_DISCARD);  // optional: skip fragment stage
glBeginTransformFeedback(GL_TRIANGLES);
    glDrawArrays(GL_TRIANGLES, 0, vertCount);
glEndTransformFeedback();
glDisable(GL_RASTERIZER_DISCARD);

Conditional Rendering

Draw only if a query recorded at least one sample.

GLuint query;
glGenQueries(1, &query);
glBeginQuery(GL_SAMPLES_PASSED, query);
    // bounding box draw
glEndQuery(GL_SAMPLES_PASSED);

glBeginConditionalRender(query, GL_QUERY_WAIT);
    // expensive draw — skipped if query result == 0
glEndConditionalRender();

Conditional render modes

ModeBehavior
GL_QUERY_WAITStall until result available
GL_QUERY_NO_WAITSkip if result unavailable (may draw anyway)
GL_QUERY_BY_REGION_WAITStall; only renders in screen region
GL_QUERY_BY_REGION_NO_WAITNo stall; only in region

Occlusion Queries

GLuint query;
glGenQueries(1, &query);

glBeginQuery(GL_SAMPLES_PASSED, query);  // or GL_ANY_SAMPLES_PASSED
    // draw occluder
glEndQuery(GL_SAMPLES_PASSED);

// Non-blocking check
GLint available = 0;
glGetQueryObjectiv(query, GL_QUERY_RESULT_AVAILABLE, &available);
if (available) {
    GLuint result;
    glGetQueryObjectuiv(query, GL_QUERY_RESULT, &result);
}

Query types

TypeWhat it counts
GL_SAMPLES_PASSEDFragments that pass depth/stencil
GL_ANY_SAMPLES_PASSEDReturns 1 or 0 (boolean)
GL_ANY_SAMPLES_PASSED_CONSERVATIVEConservative (faster) boolean
GL_PRIMITIVES_GENERATEDPrimitives emitted by geometry stage
GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTENPrimitives written to XFB buffer
GL_TIME_ELAPSEDGPU time in nanoseconds
GL_TIMESTAMPAbsolute GPU timestamp

Timer Queries

GLuint queries[2];
glGenQueries(2, queries);

glQueryCounter(queries[0], GL_TIMESTAMP);
    // draw calls
glQueryCounter(queries[1], GL_TIMESTAMP);

GLuint64 t0, t1;
glGetQueryObjectui64v(queries[0], GL_QUERY_RESULT, &t0);
glGetQueryObjectui64v(queries[1], GL_QUERY_RESULT, &t1);
double ms = (t1 - t0) / 1e6;

Scissor Test

glEnable(GL_SCISSOR_TEST);
glScissor(x, y, width, height);  // pixels, lower-left origin
// Only fragments within this rect are written
glDisable(GL_SCISSOR_TEST);

Multi-viewport / Layered Rendering (GL 4.1+)

// Set multiple viewports at once
float vps[8] = { 0,0,400,600,  400,0,400,600 };
glViewportArrayv(0, 2, vps);

float depths[4] = { 0,1,  0,1 };
glDepthRangeArrayv(0, 2, depths);

// Geometry shader selects viewport/layer
// gl_ViewportIndex = 0 or 1;
// gl_Layer = cubemap face index;

Sync Objects / Memory Barriers

// Insert a fence
GLsync fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);

// CPU waits for GPU
GLenum result = glClientWaitSync(fence, GL_SYNC_FLUSH_COMMANDS_BIT, 5000000000ULL);
// result: GL_ALREADY_SIGNALED, GL_CONDITION_SATISFIED, GL_TIMEOUT_EXPIRED, GL_WAIT_FAILED

// GPU waits for fence (no CPU stall)
glWaitSync(fence, 0, GL_TIMEOUT_IGNORED);

glDeleteSync(fence);

// Memory barrier — order shader writes before subsequent reads
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT
              | GL_TEXTURE_FETCH_BARRIER_BIT);

glMemoryBarrier bits

BitProtects
GL_VERTEX_ATTRIB_ARRAY_BARRIER_BITVBO reads after shader writes
GL_ELEMENT_ARRAY_BARRIER_BITEBO reads
GL_UNIFORM_BARRIER_BITUniform reads after SSBO writes
GL_TEXTURE_FETCH_BARRIER_BITTexture sampling after image writes
GL_SHADER_IMAGE_ACCESS_BARRIER_BITImage load/store ordering
GL_COMMAND_BARRIER_BITIndirect draw commands
GL_PIXEL_BUFFER_BARRIER_BITPBO reads/writes
GL_TEXTURE_UPDATE_BARRIER_BITglTexImage calls
GL_BUFFER_UPDATE_BARRIER_BITglBufferData / map
GL_FRAMEBUFFER_BARRIER_BITFBO reads after rendering
GL_TRANSFORM_FEEDBACK_BARRIER_BITXFB buffer reads
GL_ATOMIC_COUNTER_BARRIER_BITAtomic counter reads
GL_SHADER_STORAGE_BARRIER_BITSSBO reads/writes
GL_ALL_BARRIER_BITSAll of the above