Notes

Thoughts, write-ups, and things worth remembering.

Qt OpenGL widget lifecycle

#graphics#opengl#qt Updated Oct 13, 2026

QOpenGLWidget calls three virtual overrides for you. You never call them; you call update() to ask for a repaint. Same idea as UE’s BeginPlay / Tick.

Who calls what

main()
 └─ MainWindow w → setupUi → new MyGL(parent)   constructor: no GL context yet
 └─ w.show()
 └─ a.exec()                                    event loop; Qt calls the rest
     ├─ initializeGL()   once, context now exists
     ├─ resizeGL(w, h)   first show + every resize
     └─ paintGL()        every repaint request

The widget is created by the .ui file (custom widget), so no new MyGL appears in project code.

FunctionPut here
ConstructorNon-GL setup only (timer, mouse tracking)
initializeGLinitializeOpenGLFunctions() first, then shaders, locations, buffers
resizeGLScreen-size uniforms
paintGLglClear + every draw call for one frame

update() vs paintGL()

update():  mark dirty, return immediately
           ... event loop ...
           makeCurrent → bind widget FBO → viewport → paintGL() → composite to screen
paintGL(): just your code, none of the above

Calling paintGL() directly skips the context and framebuffer setup and nothing gets presented. Multiple update() calls merge into one repaint.

Frame loop

QTimer every 16 ms (~60 fps) → tick() → update() → Qt → paintGL().
One paintGL per frame. Different shaders = several glUseProgram + draw pairs inside it.

Where gl calls are allowed

The context is only guaranteed current inside initializeGL, resizeGL and paintGL. Slots like tick() and mouseMoveEvent() should only update C++ values; the matching glUniform* goes in the draw path. The alternative is wrapping gl calls there in makeCurrent() / doneCurrent().

OpenGL Hub· OpenGL frame workflow · Qt signals and slots · Callback functions