19 FEB 2026

rahulmnavneeth

frame lifecycle

homedocs

Overview

A single call to mop_viewport_render executes a complete frame. There is no retained command buffer or deferred execution — the frame is processed synchronously.

Frame Sequence

mop_viewport_render(viewport)
  │
  ├── 1. rhi->frame_begin(device, framebuffer, clear_color)
  │       Backend clears color, depth, and object-ID buffers.
  │
  ├── 2. For each active mesh:
  │       ├── Compute MVP = projection * view * model
  │       ├── Build MopRhiDrawCall struct
  │       └── rhi->draw(device, framebuffer, &draw_call)
  │             Backend processes the draw call:
  │             ├── CPU: transform vertices, clip, rasterize
  │             ├── OpenGL: bind VAO, set uniforms, glDrawElements
  │             └── Vulkan: bind pipeline, push constants, vkCmdDrawIndexed
  │
  └── 3. rhi->frame_end(device, framebuffer)
          Backend finalizes the frame:
          ├── CPU: no-op (data is already in memory)
          ├── OpenGL: glFinish or fence sync
          └── Vulkan: vkQueueSubmit, vkQueueWaitIdle

CPU Backend Detail

For each draw call, the CPU backend:

  1. Reads vertex and index data from the RHI buffer
  2. For each triangle (3 indices): a. Transforms vertices to clip space via mvp b. Transforms normals via the model matrix (upper 3x3) c. Clips the triangle against 6 frustum planes (Sutherland-Hodgman) d. For each clipped sub-triangle:
    • Perspective division → NDC
    • Viewport transform (NDC → screen pixels, Y-flipped)
    • Backface culling (screen-space signed area test)
    • Flat shading (average face normal · light direction)
    • Half-space rasterization (solid) or Bresenham (wireframe)
    • Per-pixel: depth test → write color + depth + object_id

Post-Frame

After mop_viewport_render returns:

Timing

The frame is fully synchronous. mop_viewport_render does not return until all pixels are written.