From d3b45f38845167151e87c530b9d5ef72077a5166 Mon Sep 17 00:00:00 2001
From: b0xcat <me@b0xcat.nl>
Date: Tue, 5 May 2026 22:08:08 +0200
Subject: [PATCH 4/5] Add option for ctx cb mode to save memory (~100kb saved
 by not having a full framebuffer)

---
 drivers/gc9a01/display.c | 100 +++++++++++++++++++++++++++++----------
 1 file changed, 75 insertions(+), 25 deletions(-)

diff --git a/drivers/gc9a01/display.c b/drivers/gc9a01/display.c
index 3062800..2bf3c93 100644
--- a/drivers/gc9a01/display.c
+++ b/drivers/gc9a01/display.c
@@ -30,38 +30,62 @@ static MP_DEFINE_CONST_FUN_OBJ_0(get_fps_obj, get_fps);
 #define TILDAGON_DISPLAY_WIDTH  240
 #define TILDAGON_DISPLAY_HEIGHT 240
 
-EXT_RAM_BSS_ATTR
-static uint8_t tildagon_fb[TILDAGON_DISPLAY_WIDTH * TILDAGON_DISPLAY_HEIGHT * 2];
 static Ctx *tildagon_ctx = NULL;
 
+// #define TILDAGON_CTX_CB_MODE
+
+#ifdef TILDAGON_CTX_CB_MODE
+
+#define TILDAGON_SCRATCH_ROWS 32
+
+// EXT_RAM_BSS_ATTR
+static uint8_t tildagon_scratch[TILDAGON_DISPLAY_WIDTH * TILDAGON_SCRATCH_ROWS * 2];
+
+static void tildagon_set_pixels(Ctx *ctx, void *user_data,
+                                int x, int y, int w, int h, void *buf) {
+    flow3r_bsp_display_send_rect(buf, x, y, w, h);
+}
+
 Ctx *tildagon_gfx_ctx(void)
 {
   if (tildagon_ctx == NULL)
   {
-    tildagon_ctx = ctx_new_for_framebuffer (tildagon_fb, TILDAGON_DISPLAY_WIDTH, TILDAGON_DISPLAY_HEIGHT, TILDAGON_DISPLAY_WIDTH * 2, CTX_FORMAT_RGB565_BYTESWAPPED);
+    tildagon_ctx = ctx_new_cb(TILDAGON_DISPLAY_WIDTH, TILDAGON_DISPLAY_HEIGHT,
+                              CTX_FORMAT_RGB565_BYTESWAPPED,
+                              tildagon_set_pixels, NULL,
+                              NULL, NULL,
+                              sizeof(tildagon_scratch),
+                              tildagon_scratch,
+                              CTX_FLAG_HASH_CACHE);
   }
   return tildagon_ctx;
 }
 
-void tildagon_start_frame(Ctx *ctx)
+void tildagon_end_frame(Ctx *ctx)
 {
-  int32_t offset_x = FLOW3R_BSP_DISPLAY_WIDTH / 2;
-  int32_t offset_y = FLOW3R_BSP_DISPLAY_HEIGHT / 2;
-
-  ctx_save (ctx);
-  ctx_identity (ctx);
-  ctx_apply_transform (ctx, 1.0f, 0.0f, offset_x, 0.0f, 1.0f, offset_y, 0.0f, 0.0f, 1.0f);
+  ctx_restore (ctx);
+  ctx_end_frame (ctx);
+  st3m_gfx_fps_update ();
 }
 
-static mp_obj_t get_ctx() {
-    Ctx *ctx = tildagon_gfx_ctx();
-    assert (ctx);
-    tildagon_start_frame (ctx);
-    return mp_ctx_from_ctx(ctx);
+#else /* framebuffer mode */
+
+EXT_RAM_BSS_ATTR
+static uint8_t tildagon_fb[TILDAGON_DISPLAY_WIDTH * TILDAGON_DISPLAY_HEIGHT * 2];
+
+Ctx *tildagon_gfx_ctx(void)
+{
+  if (tildagon_ctx == NULL)
+  {
+    tildagon_ctx = ctx_new_for_framebuffer(tildagon_fb,
+                              TILDAGON_DISPLAY_WIDTH, TILDAGON_DISPLAY_HEIGHT,
+                              TILDAGON_DISPLAY_WIDTH * 2,
+                              CTX_FORMAT_RGB565_BYTESWAPPED);
+  }
+  return tildagon_ctx;
 }
-static MP_DEFINE_CONST_FUN_OBJ_0(get_ctx_obj, get_ctx);
 
-void tildagon_blit_fb (void)
+static void tildagon_blit_fb(void)
 {
   flow3r_bsp_display_send_fb(tildagon_fb, 16);
 }
@@ -88,6 +112,32 @@ void tildagon_end_frame(Ctx *ctx)
   st3m_gfx_fps_update ();
 }
 
+#endif /* TILDAGON_CTX_CB_MODE */
+
+void tildagon_start_frame(Ctx *ctx)
+{
+  int32_t offset_x = FLOW3R_BSP_DISPLAY_WIDTH / 2;
+  int32_t offset_y = FLOW3R_BSP_DISPLAY_HEIGHT / 2;
+
+  ctx_save (ctx);
+#ifndef TILDAGON_CTX_CB_MODE
+  // In framebuffer mode, identity resets any leftover state since
+  // ctx_end_frame() is never called. In cb mode ctx_end_frame() resets
+  // the state and the identity would override the tile offset that the
+  // cb backend applies via ctx_translate before replaying the drawlist.
+  ctx_identity (ctx);
+#endif
+  ctx_apply_transform (ctx, 1.0f, 0.0f, offset_x, 0.0f, 1.0f, offset_y, 0.0f, 0.0f, 1.0f);
+}
+
+static mp_obj_t get_ctx() {
+    Ctx *ctx = tildagon_gfx_ctx();
+    assert (ctx);
+    tildagon_start_frame (ctx);
+    return mp_ctx_from_ctx(ctx);
+}
+static MP_DEFINE_CONST_FUN_OBJ_0(get_ctx_obj, get_ctx);
+
 static mp_obj_t end_frame(mp_obj_t ctx) {
     mp_ctx_obj_t *self = MP_OBJ_TO_PTR(ctx);
     tildagon_end_frame (self->ctx);
@@ -109,25 +159,25 @@ static mp_obj_t hexagon(size_t n_args, const mp_obj_t *args) {
     float x = mp_obj_get_float(args[1]);
     float y = mp_obj_get_float(args[2]);
     float dim = mp_obj_get_float(args[3]);
-    
+
     // All the internal angles are 120 degrees, or 2/3 pi radians
     // This translates to either an offset of (1, 0) or the pair below
     float minor_component = cos(M_PI / 3);
     float major_component = sin(M_PI / 3);
-    
+
     // Stash the caller's axes
     ctx_save(ctx->ctx);
-    
+
     // Set the origin to the centre of the hexagon and scale to the size
     ctx_translate (ctx->ctx, x, y);
     ctx_scale (ctx->ctx, dim, dim);
-    
+
     // Rotate so point is at the top - the drawing code has the flat side at the top
     ctx_rotate(ctx->ctx, M_PI / 2.0f);
-    
+
     // Move to the start of the top left line
     ctx_move_to(ctx->ctx, -minor_component, -major_component);
-    
+
     // Draw the six segments
     ctx_rel_line_to(ctx->ctx, 1.0f, 0.0f);
     ctx_rel_line_to(ctx->ctx, minor_component, major_component);
@@ -135,10 +185,10 @@ static mp_obj_t hexagon(size_t n_args, const mp_obj_t *args) {
     ctx_rel_line_to(ctx->ctx, -1.0f, 0.0f);
     ctx_rel_line_to(ctx->ctx, -minor_component, -major_component);
     ctx_rel_line_to(ctx->ctx, minor_component, -major_component);
-    
+
     // Fill the hexagon
     ctx_fill(ctx->ctx);
-    
+
     // Restore the axes
     ctx_restore(ctx->ctx);
 
-- 
2.53.0

