Draw2D: immediate-mode 2D drawing¶
simvx.graphics.draw2d.Draw2D is the immediate-mode 2D API used by every
on_draw callback. It owns one mutable per-frame buffer (Draw2D._ops) and
the renderer walks that buffer in order each frame.
Submission order is the GPU order¶
Every draw call (draw_rect, draw_line, draw_circle, draw_text,
draw_texture, draw_texture_region, draw_nine_patch, fill_*) appends one
Op to Draw2D._ops. The renderer (Draw2DPass) walks that list in order
and emits draws in the same order: there is no other ordering mechanism.
def on_draw(self, renderer):
renderer.draw_texture(self.bg_texture, 0, 0, 320, 240)
renderer.draw_rect((0, 0), (320, 240), filled=True, colour=(0, 0, 0, 0.7))
renderer.draw_text("Paused", (140, 110), colour=(1, 1, 1, 1), scale=2.0)
The texture paints first, then a dim rect dims the texture, then the text sits on top of both. This works regardless of whether the calls are fills, lines, text, or textures: the submission sequence is preserved end-to-end.
Tree-level z-ordering still uses Node2D.z_index for ordering sibling node
visits. Within a single on_draw body, call order is the contract.
What’s coalesced¶
Adjacent ops sharing the same (kind, clip, tex_id) collapse into a single
GPU draw. Typical scenes that group sprites by atlas pay one draw per atlas;
ordering different kinds (rect → texture → rect) pays one draw per kind
transition. Pipeline binding only happens on kind changes; scissor only on
clip changes.
Clipping¶
Draw2D.push_clip(x, y, w, h) / pop_clip() / reset_clip() mutate a clip
stack. Nested push_clip calls intersect with the current clip. Each op
snapshots the current clip at submission time: a clip change after an op is
appended doesn’t affect that op.
renderer.push_clip(0, 0, 200, 200)
renderer.draw_rect((-50, -50), (300, 300), filled=True, colour=(1, 0, 0, 1))
renderer.pop_clip()
What was removed¶
new_layer() is gone. It used to be the escape hatch for “force a flush” when
the renderer reordered draws within a batch. The renderer no longer reorders,
so the escape hatch isn’t needed. If you find old code that calls
renderer.new_layer(), delete the call: submission order suffices.
The 4 legacy per-kind staging lists (_fill_verts, _line_verts,
_text_verts, _textured_quads) and the batch tuple format (_get_batches,
_flush_batch, _batches) are also gone. The single _ops list is the
canonical mutable state.