AEUPH commited on
Commit
2a4386e
·
verified ·
1 Parent(s): ec44f6b

Fix black tiles on connect; 64px tiles, byte-based delta choice

Browse files
Files changed (1) hide show
  1. app.py +56 -13
app.py CHANGED
@@ -51,7 +51,12 @@ FB = int(os.environ.get("NEURALOS_FB", "1024")) # framebuffer edge
51
  TASKBAR_H = 40
52
  TITLEBAR_H = 26
53
  CHROME_BORDER = 3
54
- TILE = 128 # dirty-tile granularity
 
 
 
 
 
55
 
56
  # auto | mock | cpu | cuda
57
  ENGINE_MODE = os.environ.get("NEURALOS_ENGINE", "auto").lower()
@@ -1058,10 +1063,12 @@ class Compositor:
1058
  self.metrics["idle"] += 1
1059
  self.metrics["tiles"] = 0
1060
  return ("none", None, None)
1061
- # Per-image WebP overhead means lots of small tiles can cost MORE bytes
1062
- # than one full frame, so choose on dirty AREA, not tile count.
1063
- area = sum(w * h for (_, _, w, h) in rects)
1064
- full = self.last is None or area > 0.08 * FB * FB or len(rects) > 24
 
 
1065
  if full:
1066
  frame = self.stack.composite()
1067
  payload = self._encode(frame)
@@ -1153,18 +1160,51 @@ def refinement_worker():
1153
  print("[refine]", e)
1154
 
1155
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1156
  def broadcast_dirty(clients):
1157
  with STATE_LOCK:
1158
  for (text, ttl) in list(KERNEL.overlays):
1159
  if ttl < time.time():
1160
  KERNEL.overlays.remove((text, ttl))
1161
  result = COMPOSITOR.render()
1162
- stats = dict(COMPOSITOR.metrics)
1163
- stats["cache"] = {"hits": STORE.hits, "misses": STORE.misses}
1164
- stats["engine"] = get_engine().stats()
1165
- stats["windows"] = len(KERNEL.windows)
1166
- stats["menu"] = KERNEL.menu_open
1167
- stats["buffer"] = KERNEL.text_buffer
1168
  kind, payload, blob = result
1169
  if kind == "none":
1170
  return result
@@ -1327,9 +1367,12 @@ try:
1327
  def kernel_ws(ws):
1328
  CLIENTS.add(ws)
1329
  try:
1330
- get_engine()
1331
  send_log(ws, "Kernel attached")
1332
- broadcast_dirty(CLIENTS) # push the initial full frame now
 
 
 
 
1333
  while True:
1334
  data = ws.receive()
1335
  if not data:
 
51
  TASKBAR_H = 40
52
  TITLEBAR_H = 26
53
  CHROME_BORDER = 3
54
+ TILE = 64 # dirty-tile granularity (16x16 grid)
55
+ # Full-vs-patch decision constants (measured, quality 82): every patch pays a
56
+ # fresh WebP header, so many small tiles can cost more than one full frame. We
57
+ # therefore compare estimated shipped BYTES, not tile count.
58
+ PATCH_OVERHEAD = 700 # bytes of per-image WebP overhead
59
+ PX_RATE = 0.019 # encoded bytes per pixel for UI content
60
 
61
  # auto | mock | cpu | cuda
62
  ENGINE_MODE = os.environ.get("NEURALOS_ENGINE", "auto").lower()
 
1063
  self.metrics["idle"] += 1
1064
  self.metrics["tiles"] = 0
1065
  return ("none", None, None)
1066
+ # A full frame is one image; a patch set is N images, each with its own
1067
+ # WebP header. Ship whichever is estimated to cost fewer bytes.
1068
+ dirty_px = sum(w * h for (_, _, w, h) in rects)
1069
+ est_patch = PATCH_OVERHEAD * len(rects) + PX_RATE * dirty_px
1070
+ est_full = PATCH_OVERHEAD + PX_RATE * FB * FB
1071
+ full = self.last is None or est_patch >= 0.9 * est_full
1072
  if full:
1073
  frame = self.stack.composite()
1074
  payload = self._encode(frame)
 
1160
  print("[refine]", e)
1161
 
1162
 
1163
+ def _frame_stats():
1164
+ """Metrics + kernel state shipped in every frame header.
1165
+
1166
+ Deliberately does NOT call get_engine(): during a cold boot that blocks on
1167
+ model load, and the first frame must not wait behind it."""
1168
+ stats = dict(COMPOSITOR.metrics)
1169
+ stats["cache"] = {"hits": STORE.hits, "misses": STORE.misses}
1170
+ stats["engine"] = ENGINE.stats() if ENGINE is not None else \
1171
+ {"engine": "loading", "device": "-", "lcm": False}
1172
+ stats["windows"] = len(KERNEL.windows)
1173
+ stats["menu"] = KERNEL.menu_open
1174
+ stats["buffer"] = KERNEL.text_buffer
1175
+ return stats
1176
+
1177
+
1178
+ def send_full_to(ws):
1179
+ """Push a complete frame to ONE just-connected client.
1180
+
1181
+ The compositor streams a single delta stream shared by every client, so a
1182
+ client that joins after the first frame would otherwise only ever receive
1183
+ the tiles the cursor/caret happens to dirty -- most of its canvas stays
1184
+ blank ("black tiles until mouseover"). Each new socket therefore starts
1185
+ from a full snapshot. Chrome is procedural, so this is cheap and never
1186
+ touches the model."""
1187
+ with STATE_LOCK:
1188
+ COMPOSITOR.build() # layers must exist before compositing
1189
+ COMPOSITOR.stack.take_dirty_rects() # we are shipping the whole state
1190
+ frame = COMPOSITOR.stack.composite()
1191
+ blob = COMPOSITOR._encode(frame)
1192
+ COMPOSITOR.last = frame
1193
+ COMPOSITOR.metrics["full"] += 1
1194
+ COMPOSITOR.metrics["bytes"] = len(blob)
1195
+ COMPOSITOR.metrics["px"] = FB * FB
1196
+ COMPOSITOR.metrics["tiles"] = 0
1197
+ stats = _frame_stats()
1198
+ ws.send(pack(T_FULL, {"w": FB, "h": FB, **stats}, blob))
1199
+
1200
+
1201
  def broadcast_dirty(clients):
1202
  with STATE_LOCK:
1203
  for (text, ttl) in list(KERNEL.overlays):
1204
  if ttl < time.time():
1205
  KERNEL.overlays.remove((text, ttl))
1206
  result = COMPOSITOR.render()
1207
+ stats = _frame_stats()
 
 
 
 
 
1208
  kind, payload, blob = result
1209
  if kind == "none":
1210
  return result
 
1367
  def kernel_ws(ws):
1368
  CLIENTS.add(ws)
1369
  try:
 
1370
  send_log(ws, "Kernel attached")
1371
+ # Paint the whole desktop for this client FIRST: chrome is
1372
+ # procedural, so the first frame need not wait behind engine load.
1373
+ send_full_to(ws)
1374
+ get_engine()
1375
+ broadcast_dirty(CLIENTS) # then stream deltas as usual
1376
  while True:
1377
  data = ws.receive()
1378
  if not data: