Skip to content
CrossFly is 100% open sourceFollow us on GitHub
CrossFlyCrossFly
Get started

This page was machine-translated from Portuguese and may contain errors. The Portuguese version is canonical.

The rendering pipeline

Understanding how drawing becomes pixels explains several of the framework’s performance rules.

Paint
high-level facade
(rect, text, line, image)
local coords + clip

gl / Renderers
one renderer per primitive
(each one is a shader)

OpenGL

The drawing layers

  • Paint is the facade you draw with in onPaint(...). It thinks in local coordinates, clip, and translation — and knows nothing about shaders or OpenGL. You say “draw this rectangle here”; how that becomes triangles and GPU calls is not your problem.
  • gl / renderers — there is one specialized renderer per primitive (rectangle, rounded rectangle, line, text, image…), each essentially a shader. Paint delegates to the right renderer.
  • OpenGL — the final draw calls.

The central concept: batching

Renderers group drawings of the same type into a batch to reduce GPU calls. The rule that follows from this:

Switching primitive type “closes” the previous batch. Drawing order is always preserved, but alternating types (rectangle → text → rectangle → text) forces multiple flushes; grouping calls of the same type is what gives performance.

In practice, in heavy custom drawing, drawing all rectangles together and then all text yields more than interleaving them.

On-demand render + whole-tree repaint

Two properties of the render model that affect how you write onPaint:

  • On demand: the screen only redraws when someone requests it (repaint() → requestRendering()). Without a request, no new frame is generated — it saves battery. (The threading angle of this is in The paint thread.)
  • No dirty rectangles: when a frame happens, the entire visible tree is repainted, not just the part that changed. There is no partial repaint.

The practical consequence is a principle:

Keep onPaint cheap. It runs for the entire tree on every rendered frame — no allocating objects, loading resources, or doing expensive computation inside it.

How the frame is assembled (high-level view)

On each rendered frame, the EventsProcessor paints the window stack (via paintWindows(...)): the topmost window on top, the ones below behind (popups leave the previous one visible). Each visible component receives its onPaint(Paint).

// minimal, cheap onPaint: reuse temporaries, draw in local coords (0,0 = top-left)
@Override public void onPaint(Paint paint) throws Exception {
   paint.rect(scratch.bounds(0, 0, width, height).fill(bg));   // 'scratch' is a reused GRect
}

See also

Was this page helpful?