T-505 item 3: the parts' pictures by part (engine 0073), the express second frame back; item 4 next

The owner's "cheaper part pictures" (2026-11-03). Engine 0073: each rect of a frame's parts list carries a
signature of its own (its outermost fixed or sticky layer's subtree, every subtree layer's last paint as the
stamp of one up-only counter, the part's scale, quarter-pixel phase, place inside its rect and size);
compose_post3/start3 (overview_ok 4) take the signatures the daemon holds and the pass reads only the rects
not in the list; the poll's 54 ints carry each rect's signature and the rects read. The daemon keeps eight
part pictures by signature and, with JIHAD_GPU_PARTS_PER=1 (or <state>/parts-per), sends the list; without
it E2's rule. The express lane's second frame on the first in flight is back when both are without their
parts (JIHAD_GPU_EXPRESS_SECOND_PARTS=1 or <state>/express-second-parts): its dress puts back what lay
beneath the first's parts and beneath the first's base's parts from the slot records (serials, the rows
read, a slot's previous record kept aside), never on a first that is itself two deep. JIHAD_GPU_VERIFY=1
now verifies the express lane's frames (the reference composed before the job; verifyCompare shared with the
main lane) and JIHAD_GPU_TEST_DRESS_HOLD=<pre|mid>:<ms> holds a first frame's dress so the second's copy
catches it. Both switches off by default: the defaults are the owner's call.

By finger: desktop Wikipedia 10.8 -> 11.4 fps flowing (its contents panel, the part that changes, is 310 of
the 340 KB: the lever is bounded by the changing part's size), the long page 19.2 / 19.4 / 18.1. Verify after
the pan mark: 0 wrong rows in every run on both pages, 142 two-deep frames among them, 55-80 of those with
the first's dress held (docs/rounds/t505-2026-11-03/notes.txt).

The inspector's review (Codex out of credits): REVISE, seven findings, all fixed (impl-review-findings-t505-
cpuparts.md, review 3). The verify then exposed a latent main-lane fault from review 2's F4 (2026-11-02): a
second job whose in-flight base the parts rule refused built on the newest finished frame but told the engine
it was the previous composite, so the frame was recorded 316 rows off and the next own-slot frames copied
wrong rows; fixed (srcInFlight in gpuJobStart; 0 wrong in three runs after).

Also: build/desktop/build-goanna.sh takes a jobs argument like the ARM script (the host short of memory);
tools/device/t502/README.md documents the switches; the kit's R4 criterion, the build site's T-505 row and
PICKUP's top section carry the state; the three variants run this head (default-check x3 passes).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
NotAlexNoyleandClaude Fable 5.1 committed 2026-11-03 12:47:59 -05:00
1 parent ffd6aae246
commit d331e4ed29
66 files changed
+73064 -171

No files matched your search

+5 -2
View File
@@ -53,11 +53,14 @@ cd "$SRC"
# Each step's failure stops the script (T-387, 2026-10-18): `./mach configure && ./mach build` hid a
# failed configure from set -e (a non-final command of an && list), and the script went on to
# install our components into the OLD dist and report "done" over an engine that never built.
# $2: parallel jobs for mach build (default: the mozconfig's), as build-goanna-arm.sh takes: one engine build at a
# time on this host, fewer jobs when memory is short (2026-11-03)
JOBS=${2:+-j$2}
case "$STAGE" in
configure) ./mach configure || { echo "ERROR: mach configure failed"; exit 1; } ;;
build) ./mach build || { echo "ERROR: mach build failed"; exit 1; } ;;
build) ./mach build $JOBS || { echo "ERROR: mach build failed"; exit 1; } ;;
all) ./mach configure || { echo "ERROR: mach configure failed"; exit 1; }
./mach build || { echo "ERROR: mach build failed"; exit 1; } ;;
./mach build $JOBS || { echo "ERROR: mach build failed"; exit 1; } ;;
*) echo "unknown stage: $STAGE (use configure|build|all)"; exit 2 ;;
esac
@@ -0,0 +1,631 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: NotAlexNoyle <alexnoyle@icloud.com>
Date: Tue, 3 Nov 2026 11:36:15 -0500
Subject: [PATCH] =?UTF-8?q?gpu=20compositor:=20the=20parts'=20picture=20by?=
=?UTF-8?q?=20part=20=E2=80=94=20each=20rect=20its=20own=20signature,=20a?=
=?UTF-8?q?=20pass=20reads=20only=20the=20rects=20the=20daemon=20lacks?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
T-505 (the owner's "cheaper part pictures", 2026-11-03). A frame without its parts (flag 128) used to carry
one signature of all its parts; when it moved, the parts pass composed and read every part. Now each rect of
the parts list has a signature of its own: the outermost fixed or sticky layer's subtree as before, every
subtree layer's last paint as the stamp of one counter that only goes up (CompositableTransactionParent stamps
each compositable update's layer, part or not: a layer that becomes a part later carries it — review F2), the
part's scale and the sub-pixel phase of its position, where it sits inside the rect and its size (review F3: a
part wider than the frame moving sideways, or moving inside a merged rect, keeps the rect's size), and the
rect's size; a rect no part's rect meets (a nested part's own) takes every part's with its index and place
(review F6). The whole signature is the rects' and the global paint count, so a daemon with the one signature
behaves as before (every part read when any moved).
jihad_offscreen_compose_post3/start3 (jihad_offscreen_overview_ok answers 4) take the list of signatures the
daemon holds pictures of; the pass packs and reads only the rects whose signature is not in it, in rect order,
and the poll's 54 ints carry each rect's signature (49..52) and the rects read as a mask (53). post2/start2
keep their meaning.
Measured by finger on desktop Wikipedia (its two sidebars; the contents panel, 310 of 340 KB, is the one that
changes): the pass reads the panel alone, 10.4 -> 11.4 fps flowing; the long page unchanged. Verify 0 wrong
rows over the scripted pans on both (docs/rounds/t505-2026-11-03/notes.txt).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---
.../ipc/CompositableTransactionParent.cpp | 10 +-
gfx/layers/ipc/CompositorBridgeParent.cpp | 268 ++++++++++++------
gfx/layers/ipc/CompositorBridgeParent.h | 7 +-
gfx/layers/opengl/CompositorOGL.h | 7 +
widget/PuppetWidget.cpp | 61 +++-
widget/PuppetWidget.h | 9 +-
6 files changed, 264 insertions(+), 98 deletions(-)
diff --git a/gfx/layers/ipc/CompositableTransactionParent.cpp b/gfx/layers/ipc/CompositableTransactionParent.cpp
index 6d6edb0043..356209d997 100644
--- a/gfx/layers/ipc/CompositableTransactionParent.cpp
+++ b/gfx/layers/ipc/CompositableTransactionParent.cpp
@@ -31,6 +31,7 @@ namespace mozilla {
namespace layers {
extern std::atomic<uint32_t> gJihadPartsGen; // Jihad T-505, CompositorBridgeParent.cpp
+void JihadNotePartPaint(Layer* aLayer); // Jihad T-505 (per part): that layer painted (its paint stamp)
void JihadReqNote(const char* aSrc); // Jihad T-505 diagnostic, CompositorBridgeParent.cpp
class ClientTiledLayerBuffer;
@@ -75,8 +76,13 @@ CompositableParentManager::ReceiveCompositableUpdate(const CompositableOperation
}
// Jihad T-505: an update to a layer under a fixed or sticky one moves the parts' generation (the daemon composes
// the page's fixed and sticky parts alone again when it moves: they changed)
- for (Layer* l = compositable->GetLayer(); l; l = l->GetParent()) {
- if (l->GetIsFixedPosition() || l->GetIsStickyPosition()) { ++gJihadPartsGen; break; }
+ {
+ // (per part, review F2) every layer's paint is stamped, part or not: a layer that becomes a part later carries it
+ Layer* painted = compositable->GetLayer();
+ if (painted) JihadNotePartPaint(painted);
+ for (Layer* l = painted; l; l = l->GetParent()) {
+ if (l->GetIsFixedPosition() || l->GetIsStickyPosition()) { ++gJihadPartsGen; break; }
+ }
}
switch (aEdit.detail().type()) {
diff --git a/gfx/layers/ipc/CompositorBridgeParent.cpp b/gfx/layers/ipc/CompositorBridgeParent.cpp
index 9aa08d7f9d..345efbadf9 100644
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -8,6 +8,7 @@
#include <unistd.h> // Jihad T-509: sleep() for JIHAD_GPU_TEST_STALL
#include <stdint.h> // for uint64_t
#include <map> // for _Rb_tree_iterator, etc
+#include <mutex> // Jihad T-505 (per part): sJihadPartPaintLock
#include <utility> // for pair
#include "LayerTransactionParent.h" // for LayerTransactionParent
#include "RenderTrace.h" // for RenderTraceLayers
@@ -30,6 +31,20 @@ static bool sJihadContentOnly = false; // Jihad T-505: the job is a frame withou
// sticky layer counts as a part, none of the fixed rows' handling applies
namespace mozilla { namespace layers {
std::atomic<uint32_t> gJihadPartsGen(0); // Jihad T-505: updates to layers under a fixed or sticky one
+// Jihad T-505 (per part): every layer's last paint, as the stamp of ONE counter that only goes up (review F2: a count
+// per part that started again after a clear could return to a value a held picture was signed with; a layer painted
+// while it was no part keeps its stamp for when it becomes one). CompositableTransactionParent notes each update; an
+// ImageBridge update may come on its own thread. Past 4096 layers the map is cleared: the stamps lost make every
+// part's signature differ once (a pass), and the next paints take new stamps, so a lost stamp never matches
+static std::mutex sJihadPaintStampLock;
+static uint32_t sJihadPaintStamp = 0;
+static std::map<Layer*, uint32_t> sJihadLayerPaint;
+void JihadNotePartPaint(Layer* aLayer)
+{
+ std::lock_guard<std::mutex> g(sJihadPaintStampLock);
+ if (sJihadLayerPaint.size() > 4096) sJihadLayerPaint.clear();
+ sJihadLayerPaint[aLayer] = ++sJihadPaintStamp;
+}
} } // (CompositableTransactionParent.cpp)
#include <cmath> // Jihad T-502: floor, ceil
#ifdef MOZ_WIDGET_GTK
@@ -1167,87 +1182,152 @@ CompositorBridgeParent::JihadHideForJob()
});
}
-// T-505 (E2): FNV-1a over what makes the parts' pixels: each layer in a fixed or sticky subtree (its identity and
-// type, opacity, content flags, mask, visible region; inside a part its transform and clip too, so an inner scroll or a
-// move inside the part counts; a colour layer's colour; an animation: every composite), the paints of layers under a
-// part (gJihadPartsGen) and the parts' sizes on the frame (a part crossing the frame's edge is new pixels). A content
-// leaf drawn after a part (above it, in paint order) whose pixels meet it is a cover: a popup over a fixed bar, a page
-// over a fixed background
+// T-505 (E2, per part): FNV-1a per outermost fixed or sticky layer over what makes its pixels: each layer of its subtree
+// (identity and type, opacity, content flags, mask, visible region; inside the part its transform and clip too, so an
+// inner scroll or a move inside the part counts; a colour layer's colour; an animation: every composite), the paints
+// under it (sJihadPartPaint) and, cut by the frame's top or bottom edge, the slice shown. Each rect of aRects takes the
+// signatures of the parts whose rects meet it and its own size (aRectSig; a rect no part's rect meets, a nested
+// part's own, takes every part's). The whole is the rects' signatures, their count and the global paint count
+// (gJihadPartsGen: a daemon with the one signature reads every part when any paint happened under one, as before).
+// A content leaf drawn after a part (above it, in paint order) whose pixels meet it is a cover: a popup over a fixed
+// bar, a page over a fixed background
uint32_t
-CompositorBridgeParent::JihadPartsSig(const gfx::IntRect* aRects, int aN, bool* aCovered, int aWidth, int aHeight)
+CompositorBridgeParent::JihadPartsSig(const gfx::IntRect* aRects, int aN, bool* aCovered, int aWidth, int aHeight, uint32_t* aRectSig)
{
- uint32_t h = 2166136261u;
+ const uint32_t kBasis = 2166136261u;
+ uint32_t h = kBasis; // the part being walked
auto mix = [&](uint32_t v) { h = (h ^ v) * 16777619u; };
auto mixf = [&](float f) { uint32_t u; memcpy(&u, &f, sizeof u); mix(u); };
auto mixr = [&](const gfx::IntRect& r) { mix(r.x); mix(r.y); mix(r.width); mix(r.height); };
- mix((uint32_t)gJihadPartsGen);
- for (int i = 0; i < aN; i++) { mix(aRects[i].width); mix(aRects[i].height); }
+ struct TopPart { uint32_t sig; gfx::IntRect ir; gfx::IntRect tb; }; // tb: its bounds on the frame, not inflated
+ TopPart tops[32];
+ int nTop = 0;
+ gfx::IntRect topIr, topTb;
+ std::map<Layer*, uint32_t> stamps; // the layers' paint stamps, taken once under the lock
+ {
+ std::lock_guard<std::mutex> g(sJihadPaintStampLock);
+ stamps = sJihadLayerPaint;
+ }
+ auto stamp = [&](Layer* l) { auto it = stamps.find(l); return it != stamps.end() ? it->second : 0u; };
+ const gfx::IntRect frame(0, 0, aWidth, aHeight);
*aCovered = false;
Layer* root = mLayerManager ? mLayerManager->GetRoot() : nullptr;
- if (!root) return h;
- static uint32_t sAnimTick = 0;
- static uint32_t sCoverLog = 0;
- int depth = 0;
- nsIntRegion partsSoFar;
- auto onFrame = [&](Layer* x, nsIntRegion& aOut) {
- gfx::Matrix m;
- if (!x->GetEffectiveTransform().Is2D(&m) || !m.PreservesAxisAlignedRectangles()) {
- const gfx::IntRect vb = x->GetLocalVisibleRegion().GetBounds().ToUnknownRect();
- aOut.OrWith(gfx::RoundedOut(x->GetEffectiveTransform().TransformBounds(gfx::Rect(vb.x, vb.y, vb.width, vb.height))));
- return;
- }
- const nsIntRegion visible = x->GetLocalVisibleRegion().ToUnknownRegion();
- for (auto it = visible.RectIter(); !it.Done(); it.Next()) {
- const gfx::IntRect& q = it.Get();
- aOut.OrWith(gfx::RoundedOut(x->GetEffectiveTransform().TransformBounds(gfx::Rect(q.x, q.y, q.width, q.height))));
- }
- };
- ForEachNode<ForwardIterator>(root,
- [&](Layer* l) {
- const bool part = l->GetIsFixedPosition() || l->GetIsStickyPosition();
- if (part || depth > 0) {
- mix((uint32_t)(uintptr_t)l); mix((uint32_t)l->GetType()); mixf(l->GetOpacity());
- mix(l->GetContentFlags());
- if (Layer* mk = l->GetMaskLayer()) { mix((uint32_t)(uintptr_t)mk); mixr(mk->GetVisibleRegion().GetBounds().ToUnknownRect()); } // (review F5)
- mixr(l->GetVisibleRegion().GetBounds().ToUnknownRect());
- if (l->GetClipRect()) mixr(l->GetClipRect()->ToUnknownRect()); // (review F5) a top-level part's clip too
- if (depth > 0) {
- const gfx::Matrix4x4 m = l->GetBaseTransform();
- for (int k = 0; k < 16; k++) mixf(m.components[k]);
- }
- if (l->GetMixBlendMode() != gfx::CompositionOp::OP_OVER) *aCovered = true; // (review F5) not drawable by "over" on the CPU
- if (part && depth == 0) {
- // (review F5) a part cut by the frame's top or bottom edge: its rect keeps its size while the slice shown
- // moves with it (a sticky element not yet stuck, taller than the frame or half off it): where it starts
- const gfx::IntRect vb = l->GetLocalVisibleRegion().GetBounds().ToUnknownRect();
- const gfx::IntRect tb = gfx::RoundedOut(l->GetEffectiveTransform().TransformBounds(gfx::Rect(vb.x, vb.y, vb.width, vb.height)));
- if (tb.y < 0 || tb.YMost() > aHeight) mix((uint32_t)tb.y);
- }
- if (l->GetType() == Layer::TYPE_COLOR) {
- const gfx::Color c = static_cast<ColorLayer*>(l)->GetColor();
- mixf(c.r); mixf(c.g); mixf(c.b); mixf(c.a);
- }
- if (!l->GetAnimations().IsEmpty()) mix(++sAnimTick);
- if (part && depth == 0) onFrame(l, partsSoFar);
- } else if (!l->GetFirstChild() && !partsSoFar.IsEmpty() && l->GetEffectiveOpacity() > 0.0f) {
- const Layer::LayerType t = l->GetType();
- if (t == Layer::TYPE_PAINTED || t == Layer::TYPE_COLOR || t == Layer::TYPE_IMAGE || t == Layer::TYPE_CANVAS) {
- nsIntRegion mine;
- onFrame(l, mine);
- mine.AndWith(partsSoFar);
- if (!mine.IsEmpty()) {
- if (!*aCovered && sCoverLog++ < 20) {
- const gfx::IntRect b = mine.GetBounds();
- JIHAD_GPU_LOG("[jihad-gpu] parts: layer %p (type %d) drawn above a part at %d,%d %dx%d\n", l, (int)t, b.x, b.y, b.width, b.height);
+ if (root) {
+ static uint32_t sAnimTick = 0;
+ static uint32_t sCoverLog = 0;
+ int depth = 0;
+ nsIntRegion partsSoFar;
+ auto onFrame = [&](Layer* x, nsIntRegion& aOut) {
+ gfx::Matrix m;
+ if (!x->GetEffectiveTransform().Is2D(&m) || !m.PreservesAxisAlignedRectangles()) {
+ const gfx::IntRect vb = x->GetLocalVisibleRegion().GetBounds().ToUnknownRect();
+ aOut.OrWith(gfx::RoundedOut(x->GetEffectiveTransform().TransformBounds(gfx::Rect(vb.x, vb.y, vb.width, vb.height))));
+ return;
+ }
+ const nsIntRegion visible = x->GetLocalVisibleRegion().ToUnknownRegion();
+ for (auto it = visible.RectIter(); !it.Done(); it.Next()) {
+ const gfx::IntRect& q = it.Get();
+ aOut.OrWith(gfx::RoundedOut(x->GetEffectiveTransform().TransformBounds(gfx::Rect(q.x, q.y, q.width, q.height))));
+ }
+ };
+ ForEachNode<ForwardIterator>(root,
+ [&](Layer* l) {
+ const bool part = l->GetIsFixedPosition() || l->GetIsStickyPosition();
+ if (part || depth > 0) {
+ if (part && depth == 0) {
+ // a new part: its own hash; its rect on the frame as JihadFixedRectsNow makes it, and its bounds; (review F3)
+ // its scale and the sub-pixel phase of its position (a quarter pixel): a picture made at another zoom
+ // or phase is other pixels
+ h = kBasis;
+ topIr = topTb = gfx::IntRect();
+ const gfx::IntRect vb = l->GetLocalVisibleRegion().GetBounds().ToUnknownRect();
+ if (!vb.IsEmpty()) {
+ topTb = gfx::RoundedOut(l->GetEffectiveTransform().TransformBounds(gfx::Rect(vb.x, vb.y, vb.width, vb.height)));
+ topIr = topTb;
+ topIr.Inflate(1);
+ topIr = topIr.Intersect(frame);
+ }
+ gfx::Matrix m2;
+ if (l->GetEffectiveTransform().Is2D(&m2)) {
+ mixf(m2._11); mixf(m2._22); mixf(m2._12); mixf(m2._21);
+ mix((uint32_t)lroundf((m2._31 - floorf(m2._31)) * 4.0f)); mix((uint32_t)lroundf((m2._32 - floorf(m2._32)) * 4.0f));
+ } else {
+ mix(0x3d3d3d3du);
+ }
+ }
+ mix((uint32_t)(uintptr_t)l); mix((uint32_t)l->GetType()); mixf(l->GetOpacity());
+ mix(stamp(l)); // its last paint (every layer of the subtree: review F2)
+ mix(l->GetContentFlags());
+ if (Layer* mk = l->GetMaskLayer()) { mix((uint32_t)(uintptr_t)mk); mixr(mk->GetVisibleRegion().GetBounds().ToUnknownRect()); } // (review F5)
+ mixr(l->GetVisibleRegion().GetBounds().ToUnknownRect());
+ if (l->GetClipRect()) mixr(l->GetClipRect()->ToUnknownRect()); // (review F5) a top-level part's clip too
+ if (depth > 0) {
+ const gfx::Matrix4x4 m = l->GetBaseTransform();
+ for (int k = 0; k < 16; k++) mixf(m.components[k]);
+ }
+ if (l->GetMixBlendMode() != gfx::CompositionOp::OP_OVER) *aCovered = true; // (review F5) not drawable by "over" on the CPU
+ if (part && depth == 0) {
+ // (review F5) a part cut by the frame's top or bottom edge: its rect keeps its size while the slice shown
+ // moves with it (a sticky element not yet stuck, taller than the frame or half off it): where it starts
+ const gfx::IntRect vb = l->GetLocalVisibleRegion().GetBounds().ToUnknownRect();
+ const gfx::IntRect tb = gfx::RoundedOut(l->GetEffectiveTransform().TransformBounds(gfx::Rect(vb.x, vb.y, vb.width, vb.height)));
+ if (tb.y < 0 || tb.YMost() > aHeight) mix((uint32_t)tb.y);
+ }
+ if (l->GetType() == Layer::TYPE_COLOR) {
+ const gfx::Color c = static_cast<ColorLayer*>(l)->GetColor();
+ mixf(c.r); mixf(c.g); mixf(c.b); mixf(c.a);
+ }
+ if (!l->GetAnimations().IsEmpty()) mix(++sAnimTick);
+ if (part && depth == 0) onFrame(l, partsSoFar);
+ } else if (!l->GetFirstChild() && !partsSoFar.IsEmpty() && l->GetEffectiveOpacity() > 0.0f) {
+ const Layer::LayerType t = l->GetType();
+ if (t == Layer::TYPE_PAINTED || t == Layer::TYPE_COLOR || t == Layer::TYPE_IMAGE || t == Layer::TYPE_CANVAS) {
+ nsIntRegion mine;
+ onFrame(l, mine);
+ mine.AndWith(partsSoFar);
+ if (!mine.IsEmpty()) {
+ if (!*aCovered && sCoverLog++ < 20) {
+ const gfx::IntRect b = mine.GetBounds();
+ JIHAD_GPU_LOG("[jihad-gpu] parts: layer %p (type %d) drawn above a part at %d,%d %dx%d\n", l, (int)t, b.x, b.y, b.width, b.height);
+ }
+ *aCovered = true;
}
- *aCovered = true;
}
}
- }
- if (part) depth++;
- },
- [&](Layer* l) { if (l->GetIsFixedPosition() || l->GetIsStickyPosition()) depth--; });
- return h;
+ if (part) depth++;
+ },
+ [&](Layer* l) {
+ if (!(l->GetIsFixedPosition() || l->GetIsStickyPosition())) return;
+ if (--depth > 0) return;
+ // the part walked: its signature, rect and bounds kept (past 32 parts, folded into the last)
+ if (nTop < 32) { tops[nTop].sig = h; tops[nTop].ir = topIr; tops[nTop].tb = topTb; nTop++; }
+ else { tops[31].sig = (tops[31].sig ^ h) * 16777619u; tops[31].ir = tops[31].ir.Union(topIr); tops[31].tb = tops[31].tb.Union(topTb); }
+ });
+ }
+ // each rect's signature, and the whole
+ uint32_t whole = kBasis;
+ auto mixw = [&](uint32_t v) { whole = (whole ^ v) * 16777619u; };
+ mixw((uint32_t)gJihadPartsGen); mixw((uint32_t)aN);
+ for (int k = 0; k < aN; k++) {
+ h = kBasis;
+ mix((uint32_t)aRects[k].width); mix((uint32_t)aRects[k].height);
+ bool any = false;
+ for (int i = 0; i < nTop; i++) {
+ if (tops[i].ir.IsEmpty() || !aRects[k].Intersects(tops[i].ir)) continue;
+ mix(tops[i].sig); any = true;
+ // (review F3) where the part sits inside the rect, and its size: a part wider than the frame moving sideways
+ // under a cut rect, a part moving inside a merged rect, keep the rect's size and would keep its picture
+ mix((uint32_t)(tops[i].tb.x - aRects[k].x)); mix((uint32_t)(tops[i].tb.y - aRects[k].y));
+ mix((uint32_t)tops[i].tb.width); mix((uint32_t)tops[i].tb.height);
+ }
+ if (!any) { // (review F6) a rect no part's rect meets: every part's, and its own index and place
+ for (int i = 0; i < nTop; i++) mix(tops[i].sig);
+ mix((uint32_t)k); mix((uint32_t)aRects[k].x); mix((uint32_t)aRects[k].y);
+ }
+ if (aRectSig) aRectSig[k] = h;
+ mixw(h);
+ }
+ return whole;
}
void
@@ -1483,11 +1563,12 @@ JihadFixedCols()
// T-544: the frame's fixed and stuck rects (the last JihadUpdateLastFixed's) out to the daemon
static void
-JihadStuckOut(JihadFrameRequest* aReq, const gfx::IntRect* aRects, int aN)
+JihadStuckOut(JihadFrameRequest* aReq, const gfx::IntRect* aRects, int aN, const uint32_t* aSig = nullptr)
{
aReq->nStuck = 0;
for (int k = 0; k < aN && aReq->nStuck < 4; k++) {
if (aRects[k].IsEmpty()) continue;
+ aReq->partsSig[aReq->nStuck] = aSig ? aSig[k] : 0; // T-505 (per part): the rect's own signature
int* o = aReq->stuck[aReq->nStuck++];
o[0] = aRects[k].x; o[1] = aRects[k].y; o[2] = aRects[k].XMost(); o[3] = aRects[k].YMost();
}
@@ -2032,7 +2113,8 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
mLayerManager->JihadPrepareOnly();
const int partN = JihadFixedRectsNow(aWidth, aHeight, parts, pk); // every fixed and sticky layer, whole rects
bool covered = false;
- aReq->partsGen = JihadPartsSig(parts, partN, &covered, aWidth, aHeight);
+ uint32_t rectSig[16] = {};
+ aReq->partsGen = JihadPartsSig(parts, partN, &covered, aWidth, aHeight, rectSig);
if (covered) aReq->partsFlags |= 1;
{
sJihadContentOnly = false; // the card's hold set keeps T-544's meaning: fixed layers and stuck ones
@@ -2047,11 +2129,25 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
}
}
mLayerManager->JihadEndTransactionNoComposite();
- JihadStuckOut(aReq, parts, partN);
- if (!covered && aReq->partsDest && aReq->nStuck > 0 && aReq->partsGen != aReq->partsHave) {
+ JihadStuckOut(aReq, parts, partN, rectSig);
+ // T-505 (per part): the rects to read — with the list of signatures the daemon holds, the ones not in it; with
+ // the one signature, every rect when the whole differs
+ int want = 0;
+ if (!covered && aReq->partsDest && aReq->nStuck > 0) {
+ if (aReq->nPartsHave >= 0) {
+ for (int k = 0; k < aReq->nStuck; k++) {
+ bool have = false;
+ for (int j = 0; j < aReq->nPartsHave && j < 16 && !have; j++) have = aReq->partsHaveList[j] == aReq->partsSig[k];
+ if (!have) want |= 1 << k;
+ }
+ } else if (aReq->partsGen != aReq->partsHave) {
+ want = (1 << aReq->nStuck) - 1;
+ }
+ }
+ if (want) {
size_t need = 0;
for (int k = 0; k < aReq->nStuck; k++)
- need += (size_t)(aReq->stuck[k][2] - aReq->stuck[k][0]) * (aReq->stuck[k][3] - aReq->stuck[k][1]) * 4;
+ if (want & (1 << k)) need += (size_t)(aReq->stuck[k][2] - aReq->stuck[k][0]) * (aReq->stuck[k][3] - aReq->stuck[k][1]) * 4;
// the rects cut into pieces no taller than the surface, on shelves across its width (12 regions at most)
struct Piece { int k, x, y, w, h, atX, atY; } pieces[12];
int np = 0, used = 0;
@@ -2059,6 +2155,7 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
np = 0; used = 0;
int shelfY = 0, shelfH = 0, cursorX = 0;
for (int k = 0; k < aReq->nStuck; k++) {
+ if (!(want & (1 << k))) continue; // a rect the daemon has
const int x0 = aReq->stuck[k][0], w = aReq->stuck[k][2] - x0;
for (int y = aReq->stuck[k][1]; y < aReq->stuck[k][3];) {
const int hgt = std::min(aCap, aReq->stuck[k][3] - y);
@@ -2105,9 +2202,11 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
mLayerManager->JihadSetRegions(nullptr, nullptr, 0);
const TimeStamp pr0 = TimeStamp::Now();
bool ok = true;
- size_t off[4] = {};
- for (int k = 1; k < aReq->nStuck; k++)
- off[k] = off[k - 1] + (size_t)(aReq->stuck[k - 1][2] - aReq->stuck[k - 1][0]) * (aReq->stuck[k - 1][3] - aReq->stuck[k - 1][1]) * 4;
+ size_t off[4] = {}; // each read rect's place in the buffer: the read ones packed in rect order
+ for (int k = 0, o = 0; k < aReq->nStuck; k++) {
+ off[k] = (size_t)o;
+ if (want & (1 << k)) o += (aReq->stuck[k][2] - aReq->stuck[k][0]) * (aReq->stuck[k][3] - aReq->stuck[k][1]) * 4;
+ }
for (int i = 0; i < np && ok; i++) {
if (aReq->cancelled) { ok = false; break; } // (review F6)
const int k = pieces[i].k, rw = aReq->stuck[k][2] - aReq->stuck[k][0];
@@ -2117,12 +2216,15 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
ogl->JihadEndBandPass();
ogl->JihadSetClearTransparent(false);
sJihadOverview = 0;
- if (ok) aReq->partsBytes = (int)need;
+ if (ok) { aReq->partsBytes = (int)need; aReq->partsRead = want; }
static uint32_t sPassLog = 0;
- if (sPassLog++ < 20 || (sPassLog % 50) == 0)
- JIHAD_GPU_LOG("[jihad-gpu] parts pass: %d parts, %d pieces on %d rows, %zu KB: compose %.1f ms, read %.1f ms%s\n",
- aReq->nStuck, np, used, need / 1024, (pr0 - pc0).ToMilliseconds(), (TimeStamp::Now() - pr0).ToMilliseconds(),
+ if (sPassLog++ < 20 || (sPassLog % 50) == 0) {
+ int nRead = 0;
+ for (int k = 0; k < aReq->nStuck; k++) if (want & (1 << k)) nRead++;
+ JIHAD_GPU_LOG("[jihad-gpu] parts pass: %d of %d parts (mask %x), %d pieces on %d rows, %zu KB: compose %.1f ms, read %.1f ms%s\n",
+ nRead, aReq->nStuck, want, np, used, need / 1024, (pr0 - pc0).ToMilliseconds(), (TimeStamp::Now() - pr0).ToMilliseconds(),
ok ? "" : " (failed)");
+ }
}
mLayerManager->JihadIgnoreDiff(false);
mLayerManager->JihadSetWorldTransform(world);
diff --git a/gfx/layers/ipc/CompositorBridgeParent.h b/gfx/layers/ipc/CompositorBridgeParent.h
index ce9fa9ccf2..e3d0ef657d 100644
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -546,9 +546,10 @@ public:
gfx::IntRect mJihadLastKeep[4], mJihadPrevKeep[4];
int mJihadPrevKeepN = 0;
void JihadHideForJob(); // Jihad T-504/T-505: the overview's and a content-only frame's layers hidden
- // Jihad T-505 (E2): the parts' signature (their layers, content, attributes and sizes); *aCovered when a content
- // layer is drawn above a part (the daemon cannot draw that part over the frame)
- uint32_t JihadPartsSig(const gfx::IntRect* aRects, int aN, bool* aCovered, int aWidth, int aHeight);
+ // Jihad T-505 (E2): the parts' signature (their layers, content, attributes and sizes), and each rect's own
+ // (aRectSig, aN of them: per part); *aCovered when a content layer is drawn above a part (the daemon cannot draw
+ // that part over the frame)
+ uint32_t JihadPartsSig(const gfx::IntRect* aRects, int aN, bool* aCovered, int aWidth, int aHeight, uint32_t* aRectSig);
int JihadFixedRectsNow(int aWidth, int aHeight, gfx::IntRect* aOut, gfx::IntRect* aKeep);
void JihadUpdateLastFixed(int aWidth, int aHeight);
bool mJihadSeqKnown = false;
diff --git a/gfx/layers/opengl/CompositorOGL.h b/gfx/layers/opengl/CompositorOGL.h
index f33ebe4908..2af33a0568 100644
--- a/gfx/layers/opengl/CompositorOGL.h
+++ b/gfx/layers/opengl/CompositorOGL.h
@@ -185,6 +185,13 @@ struct JihadFrameRequest {
uint8_t* partsDest = nullptr;
int partsCap = 0;
uint32_t partsHave = 0;
+ // T-505 (per part, 2026-11-03): each rect of stuck[] has a signature of its own (partsSig[k]: the parts it holds,
+ // their paints and its size). A job posted with the list of signatures the daemon holds pictures of (partsHaveList,
+ // nPartsHave >= 0: compose_post3/start3) reads only the rects whose signature is not in it, packed in rect order;
+ // partsRead says which (bit k). One posted with the one signature (post2/start2, nPartsHave -1) reads every rect
+ // when the whole differs, as before
+ uint32_t partsHaveList[16] = {}; int nPartsHave = -1;
+ uint32_t partsSig[4] = {}; int partsRead = 0; // out
int partsBytes = 0; // out: the bytes the job's parts pass wrote (0: no pass)
int partsFlags = 0; // out: 1 a content layer above a part (the frame cannot be dressed), 2 too big
int hold[4][4] = {}; // out: the card's hold set (x0, y0, x1, y1) of a frame without its parts
diff --git a/widget/PuppetWidget.cpp b/widget/PuppetWidget.cpp
index 5a45a2f1f2..9507c010e5 100644
--- a/widget/PuppetWidget.cpp
+++ b/widget/PuppetWidget.cpp
@@ -853,6 +853,7 @@ struct JihadReadJob {
bool overviewParts = false; // T-504 (flag 64): the overview's fixed and stuck parts alone
bool contentOnly = false; // T-505 (flag 128): the frame without its fixed and sticky parts
uint8_t* partsDest = nullptr; int partsCap = 0; uint32_t partsHave = 0; // T-505 (E2): its parts pass
+ uint32_t partsHaveList[16] = {}; int nPartsHave = -1; // T-505 (per part): the signatures the daemon holds (post3/start3)
int invLo, invHi; // the rows the composite changed
float scrollX = 0.0f, scrollY = 0.0f; bool scrollValid = false; // the card's scroll for the compositor
uint32_t token;
@@ -905,6 +906,8 @@ void JihadReadOnCompositorThread(JihadReadJob* job)
job->req.overviewParts = job->overviewParts;
job->req.contentOnly = job->contentOnly;
job->req.partsDest = job->partsDest; job->req.partsCap = job->partsCap; job->req.partsHave = job->partsHave;
+ job->req.nPartsHave = job->nPartsHave;
+ for (int i = 0; i < 16; i++) job->req.partsHaveList[i] = job->partsHaveList[i];
job->req.scrollX = job->scrollX; job->req.scrollY = job->scrollY; job->req.scrollValid = job->scrollValid;
job->req.done = &JihadJobDone; job->req.cookie = job;
// Once done() has run the daemon may forget (delete) the job at any moment (round 108's wake
@@ -941,7 +944,8 @@ uint32_t
PuppetWidget::JihadComposeStart(nsIDocShell* aDocShell, void* aDest, int aStride,
int aWidth, int aHeight, int aRowLo, int aRowHi, int aRow2Lo, int aRow2Hi, int aShift,
const void* aSrc, int aSrcY, int aFlags, int aColX0, int aColX1,
- void* aPartsDest, int aPartsCap, uint32_t aPartsHave)
+ void* aPartsDest, int aPartsCap, uint32_t aPartsHave,
+ const uint32_t* aPartsHaveList, int aNPartsHave)
{
if (!aDocShell || !aDest || aWidth <= 0 || aHeight <= 0 || aStride < aWidth * 4) {
return 0;
@@ -950,7 +954,7 @@ PuppetWidget::JihadComposeStart(nsIDocShell* aDocShell, void* aDest, int aStride
return 0;
}
return JihadPostJob(aDest, aStride, aWidth, aHeight, aRowLo, aRowHi, aRow2Lo, aRow2Hi, aShift, aSrc, aSrcY, aFlags,
- nullptr, false, aColX0, aColX1, aPartsDest, aPartsCap, aPartsHave);
+ nullptr, false, aColX0, aColX1, aPartsDest, aPartsCap, aPartsHave, aPartsHaveList, aNPartsHave);
}
bool
@@ -1042,7 +1046,8 @@ uint32_t
PuppetWidget::JihadPostJob(void* aDest, int aStride, int aWidth, int aHeight, int aRowLo, int aRowHi, int aRow2Lo,
int aRow2Hi, int aShift, const void* aSrc, int aSrcY, int aFlags,
const float* aView, bool aScrollValid, int aColX0, int aColX1,
- void* aPartsDest, int aPartsCap, uint32_t aPartsHave)
+ void* aPartsDest, int aPartsCap, uint32_t aPartsHave,
+ const uint32_t* aPartsHaveList, int aNPartsHave)
{
mozilla::layers::CompositorBridgeParent* bridge = JihadBridge();
MessageLoop* loop = mozilla::layers::CompositorThreadHolder::Loop();
@@ -1069,6 +1074,10 @@ PuppetWidget::JihadPostJob(void* aDest, int aStride, int aWidth, int aHeight, in
job->contentOnly = (aFlags & 128) != 0; // T-505
if (job->contentOnly && aPartsDest && aPartsCap > 0) { // T-505 (E2)
job->partsDest = static_cast<uint8_t*>(aPartsDest); job->partsCap = aPartsCap; job->partsHave = aPartsHave;
+ if (aPartsHaveList && aNPartsHave >= 0) { // (per part) the signatures the daemon holds pictures of
+ job->nPartsHave = std::min(aNPartsHave, 16);
+ for (int i = 0; i < job->nPartsHave; i++) job->partsHaveList[i] = aPartsHaveList[i];
+ }
}
if (job->colsKnown) {
job->colX0 = std::max(0, std::min(aColX0, aWidth)); job->colX1 = std::max(0, std::min(aColX1, aWidth));
@@ -1083,13 +1092,14 @@ uint32_t
PuppetWidget::JihadComposePost(void* aDest, int aStride, int aWidth, int aHeight, int aRowLo, int aRowHi,
int aRow2Lo, int aRow2Hi, int aShift, const void* aSrc, int aSrcY, int aFlags,
const float* aView, bool aScrollValid, int aColX0, int aColX1,
- void* aPartsDest, int aPartsCap, uint32_t aPartsHave)
+ void* aPartsDest, int aPartsCap, uint32_t aPartsHave,
+ const uint32_t* aPartsHaveList, int aNPartsHave)
{
if (!aDest || aWidth <= 0 || aHeight <= 0 || aStride < aWidth * 4) {
return 0;
}
return JihadPostJob(aDest, aStride, aWidth, aHeight, aRowLo, aRowHi, aRow2Lo, aRow2Hi, aShift, aSrc, aSrcY, aFlags,
- aView, aScrollValid, aColX0, aColX1, aPartsDest, aPartsCap, aPartsHave);
+ aView, aScrollValid, aColX0, aColX1, aPartsDest, aPartsCap, aPartsHave, aPartsHaveList, aNPartsHave);
}
int
@@ -1158,6 +1168,10 @@ PuppetWidget::JihadComposePoll(uint32_t aToken, bool aWait, int* aOut, bool* aSc
for (int k = 0; k < 4; k++)
for (int c = 0; c < 4; c++) aOut[33 + 4 * k + c] = k < job->req.nHold ? job->req.hold[k][c] : 0;
}
+ if (aOutLen >= 54) { // T-505 (per part): each rect's signature, and the rects the pass read (bit k)
+ for (int k = 0; k < 4; k++) aOut[49 + k] = (int)job->req.partsSig[k];
+ aOut[53] = job->req.partsRead;
+ }
}
if (aScrollOnly) *aScrollOnly = job->req.scrollOnly;
} else if (!aWait && job->req.composed) {
@@ -1535,8 +1549,9 @@ MOZ_EXPORT int
jihad_offscreen_overview_ok()
{
static const bool sPass = getenv("JIHAD_GPU_PASS") != nullptr;
- return sPass ? 0 : 3; // 2 (T-505): flag 128 too, and the poll's 30th int; 3 (E2): compose_post2/start2's parts
- // pass and the poll's 49 ints
+ return sPass ? 0 : 4; // 2 (T-505): flag 128 too, and the poll's 30th int; 3 (E2): compose_post2/start2's parts
+ // pass and the poll's 49 ints; 4 (per part): post3/start3 with the signatures the daemon
+ // holds, the poll's 54 ints (each rect's signature, the rects read)
}
MOZ_EXPORT int
jihad_offscreen_compose_peek(nsIWidget* aWidget, uint32_t aToken)
@@ -1608,6 +1623,38 @@ jihad_offscreen_compose_start2(nsIWidget* aWidget, nsIDocShell* aDocShell, void*
(aEngineShift & 4) ? aColX0 : 0, (aEngineShift & 4) ? aColX1 : 0,
aPartsDest, aPartsCap, aPartsHave);
}
+// T-505 (per part): post and start with the list of signatures the daemon holds part pictures of (aNHave of them, 16 at
+// most; 0 is a list: every part read): the pass reads only the rects whose signature is not in it
+MOZ_EXPORT uint32_t
+jihad_offscreen_compose_post3(nsIWidget* aWidget, void* aDest, int aStride, int aWidth, int aHeight,
+ int aRowLo, int aRowHi, int aRow2Lo, int aRow2Hi, int aShift,
+ const void* aSrc, int aSrcY, int aFlags, int aUseView, double aScale,
+ double aOffX, double aOffY, double aScrollX, double aScrollY, int aScrollValid,
+ int aColX0, int aColX1, void* aPartsDest, int aPartsCap, const uint32_t* aHave, int aNHave)
+{
+ if (!aWidget) {
+ return 0;
+ }
+ const float view[5] = { (float)aScale, (float)aOffX, (float)aOffY, (float)aScrollX, (float)aScrollY };
+ return static_cast<PuppetWidget*>(aWidget)->JihadComposePost(aDest, aStride, aWidth, aHeight, aRowLo, aRowHi,
+ aRow2Lo, aRow2Hi, aShift, aSrc, aSrcY, aFlags,
+ aUseView ? view : nullptr, aScrollValid != 0,
+ (aFlags & 4) ? aColX0 : 0, (aFlags & 4) ? aColX1 : 0,
+ aPartsDest, aPartsCap, 0, aHave, aHave ? std::max(aNHave, 0) : -1);
+}
+MOZ_EXPORT uint32_t
+jihad_offscreen_compose_start3(nsIWidget* aWidget, nsIDocShell* aDocShell, void* aDest, int aStride,
+ int aWidth, int aHeight, int aRowLo, int aRowHi, int aRow2Lo, int aRow2Hi, int aShift,
+ const void* aSrc, int aSrcY, int aEngineShift, int aColX0, int aColX1,
+ void* aPartsDest, int aPartsCap, const uint32_t* aHave, int aNHave)
+{
+ if (!aWidget) {
+ return 0;
+ }
+ return static_cast<PuppetWidget*>(aWidget)->JihadComposeStart(aDocShell, aDest, aStride, aWidth, aHeight, aRowLo, aRowHi, aRow2Lo, aRow2Hi, aShift, aSrc, aSrcY, aEngineShift,
+ (aEngineShift & 4) ? aColX0 : 0, (aEngineShift & 4) ? aColX1 : 0,
+ aPartsDest, aPartsCap, 0, aHave, aHave ? std::max(aNHave, 0) : -1);
+}
MOZ_EXPORT int
jihad_offscreen_compose_poll(nsIWidget* aWidget, uint32_t aToken, int* aOut, int* aScrollOnly)
{
diff --git a/widget/PuppetWidget.h b/widget/PuppetWidget.h
index 5378b5afa0..5d30174715 100644
--- a/widget/PuppetWidget.h
+++ b/widget/PuppetWidget.h
@@ -293,7 +293,8 @@ public:
int aWidth, int aHeight, int aRowLo, int aRowHi, int aRow2Lo = 0, int aRow2Hi = 0, int aShift = 0,
const void* aSrc = nullptr, int aSrcY = 0, int aFlags = 0, // aFlags: 1 engine shift, 2 verify, 4 columns
int aColX0 = 0, int aColX1 = 0, // T-511: the bands' stale columns (flag 4)
- void* aPartsDest = nullptr, int aPartsCap = 0, uint32_t aPartsHave = 0); // T-505 (E2)
+ void* aPartsDest = nullptr, int aPartsCap = 0, uint32_t aPartsHave = 0, // T-505 (E2)
+ const uint32_t* aPartsHaveList = nullptr, int aNPartsHave = -1); // T-505 (per part)
// aOut (10 ints, may be null): the rows the tree changed, the rows fixed layers cover, the two
// bands read, the frame's position and its valid flag; 12 for a job posted with flag 4 (T-511):
// the columns the composite changed; aScrollOnly: the composite's only change was the card's scroll
@@ -307,7 +308,8 @@ public:
uint32_t JihadComposePost(void* aDest, int aStride, int aWidth, int aHeight, int aRowLo, int aRowHi,
int aRow2Lo, int aRow2Hi, int aShift, const void* aSrc, int aSrcY, int aFlags,
const float* aView, bool aScrollValid, int aColX0 = 0, int aColX1 = 0,
- void* aPartsDest = nullptr, int aPartsCap = 0, uint32_t aPartsHave = 0); // T-505 (E2)
+ void* aPartsDest = nullptr, int aPartsCap = 0, uint32_t aPartsHave = 0, // T-505 (E2)
+ const uint32_t* aPartsHaveList = nullptr, int aNPartsHave = -1); // T-505 (per part)
void JihadComposeForget(uint32_t aToken);
// T-515: a job's state without taking its result: 1 done, 2 composed (being read), 0 pending, -1 unknown
int JihadComposePeek(uint32_t aToken);
@@ -471,7 +473,8 @@ private:
uint32_t JihadPostJob(void* aDest, int aStride, int aWidth, int aHeight, int aRowLo, int aRowHi, int aRow2Lo,
int aRow2Hi, int aShift, const void* aSrc, int aSrcY, int aFlags,
const float* aView = nullptr, bool aScrollValid = false, int aColX0 = 0, int aColX1 = 0,
- void* aPartsDest = nullptr, int aPartsCap = 0, uint32_t aPartsHave = 0);
+ void* aPartsDest = nullptr, int aPartsCap = 0, uint32_t aPartsHave = 0,
+ const uint32_t* aPartsHaveList = nullptr, int aNPartsHave = -1); // T-505 (per part)
uint32_t mJihadNextToken = 1;
float mJihadScrollX = 0.0f, mJihadScrollY = 0.0f; // T-502: the card's scroll for the compositor
bool mJihadScrollValid = false;
+1 -1
View File
@@ -9,7 +9,7 @@ UXP_BASE=6b18be917d462369593bfe611230a7003fd7d945
# The tip of our engine branch (NotAlexNoyle/UXP-Jihad, jihad/uxp-<UXP_TAG>: one commit per patch
# in build/desktop/patches) the build uses. The third_party/uxp gitlink and the OE recipe's SRCREV
# must both be this commit.
UXP_PIN=52ad9ab66ebbc1c28133a330ee2f35819565d61e
UXP_PIN=adfaa7ff3054c6076a9e6fddc85677f081f8841b
# What the user agent and the start pages say: Goanna version and the short UPSTREAM base.
GOANNA_VERSION=6.9
UXP_SHORT=6b18be9
@@ -34,7 +34,7 @@ COMPATIBLE_MACHINE = "(tenderloin|opal)"
# for real (build/engine/patch-queue.sh --check proves it reproduces SRCREV's tree):
# SRC_URI = "git://repo.palemoon.org/MoonchildProductions/UXP.git;protocol=https;tag=<UXP_TAG>"
SRC_URI = "git://${JIHAD_REPO}/third_party/uxp;protocol=file;nobranch=1"
SRCREV = "52ad9ab66ebbc1c28133a330ee2f35819565d61e"
SRCREV = "adfaa7ff3054c6076a9e6fddc85677f081f8841b"
S = "${WORKDIR}/git"
# Modern C++14 toolchain (device-build R1) — the stock dylan/device gcc cannot build UXP.
@@ -73,3 +73,26 @@ buffers' bookkeeping under the frame lock; the express lane's C1 gate.
Proposed criterion (cavekit-gpu-rendering.md R2): every row a frame reads back is composed in that frame's own
composite across the width it reads; the verify round includes a narrow, tall change on an own-slot frame (the load's
strip is one).
## Review 3 — the inspector on the parts by part and the express second frame (2026-11-03; Codex out of credits until Nov 4th)
The review gate for T-505's item 3 (engine commit `jihad/uxp-RB_20261027`'s per-part commit, the daemon's diff of the
same day): ONE ck:inspector, read-only. Verdict: REVISE (P1 3, P2 1, P3 3). Every finding fixed the same day, the
engine commit amended before it entered the queue.
| # | sev | where | finding | fix | status |
|---|---|---|---|---|---|
| F1 | P1 | daemon xStart | the two-deep restore covers two records, but after `mXJob = mXJob2` the second frame becomes the first and a third builds on it: its copy may hold the first's base's parts (caught in the second's raw copy before the second's restore), which two records do not reach | no second frame on a first that is itself two deep (`aBase->twoDeep`): the chain is two frames long | fixed; v-pe rounds 0 wrong |
| F2 | P1 | engine sJihadPartPaint | a per-part paint COUNT cleared at 64 entries could return to a value a held picture was signed with (the comment claimed the opposite); paints of a layer before it became a part, or while nested under another part, were not its own | every layer's last paint is the stamp of ONE counter that only goes up, stamped for every compositable update (part or not); each subtree layer's stamp is mixed into its part's signature; the map's clear at 4096 loses stamps, which only makes signatures differ | fixed |
| F3 | P1 (both modes, older than this change) | engine JihadPartsSig | the signature carried a part's size, not where it sits: a sticky header wider than the frame moving sideways under a rect at [0,w), a part moving inside a merged rect of constant size, a zoom rounding to the same rect size — all kept the old picture | each part meeting a rect mixes its position inside the rect and its size; a part's scale and the quarter-pixel phase of its translation are in its own signature | fixed; by reading (no such page in the test set) |
| F4 | P2 | the express verify | the reference's whole read sits between the first frame's read and the second's copy, so under verify the copy never catches the first's dress: the 55 two-deep frames verified did not exercise the race | `JIHAD_GPU_TEST_DRESS_HOLD=<pre|mid>:<ms>` holds a first frame's dress before its restores or between its record and its draw; a verify round with it (notes) | fixed; the hold rounds in `docs/rounds/t505-2026-11-03/notes.txt` |
| F5 | P3 | daemon cpuPartsDress | storing a picture the job read could evict a picture this frame draws (the least used, bumped only after the store): a frame not shown and a miss counted | every picture of this frame's signatures is made the newest before the store | fixed |
| F6 | P3 | engine JihadPartsSig | two rects no part's rect meets, of one size, took the same signature: one picture for both | the fallback mixes the rect's index and place | fixed |
| F7 | P3 | daemon xPoll | the `!mPage` path dropped the job without releasing its verify buffer; an empty `else if (twoDeep)` branch | `xVerifyDrop` there; the branch gone | fixed |
Checked and fine (the inspector): the pass's buffer layout and the daemon's reading of it (off[] over the wanted rects,
partsBytes against the read rects, a failed pass); the two-deep restore's shifts, order, own-slot case through
`mSlotPrev`, the serial lookup, no pixels written before a refusal; the failure paths; the express verify's guard
(baseline, request count, scroll, view, fixed rects, keeps, probe) and its buffer's release on every path;
the map's lock; older daemons' 29/49 ints; level 4's gate; post3/start3's argument order; the whole mode with
per-part pictures.
+2 -1
View File
@@ -58,7 +58,7 @@ Hacker News 36-37, desktop Wikipedia 13-14 (software 9-12.4).
### R3: Pans meet the chart, on real pages too
**Description:** A drag-scroll moves the content by compositing the painted displayport, not by repainting.
**Acceptance Criteria:**
- [ ] The scripted pan (0 → 3000 card px in 10 s, `tools/device/t502/pan-run.sh`) runs at 24 fps or better on `jihad-long-static.html` and on desktop en.wikipedia.org after its load, **with the card moving as under a finger** *(2026-10-26, RE-OPENED (the owner's choice): the scripted pan sent the card's scroll commands to the daemon and the card's view stood still, so every figure below left out the card's own redraw; it now tells the card to scroll and the card reports its scroll, as under a finger (`JIHAD_INJECT_PAN_NOCARD=1` is the old test). Measured so: desktop Wikipedia 91 frames in 10.3 s by the scripted pan, 86 in 11.9 s of eight real finger drags (~7 fps), the long page 222 in 11.6 s (~19); the readback waits behind LunaSysMgr's redraw of the card on the same GPU (a 15-row read 30-164 ms under a finger). The old test's figures follow.)* *(2026-10-26, T-527, MET with the old test on the owner's bench (sensord and atlas-sensord stopped: the owner's choice (b) of 2026-10-24), the packaged head: desktop Wikipedia 24.6 and 24.2 fps over the whole 10 s pan, the long page 34.2; with the sensors running the same build gives 22.7-23.6 and 32.8-33.1 (`docs/rounds/t527-2026-10-25/notes.txt`).)* *(2026-10-25, T-527, the sensors running, on engine 0063: the long page 29.5-35.4 fps; desktop Wikipedia's 2 s windows 22.3-28.5 after the first scroll's restyle, its mean over the pan 23.8 and 24.4 fps in two runs (21.7 in a third): the compositor thread is the bound (85 % of a core: compose ~8 ms, the copy from the base 10-14, the read 7-12 a frame). 2026-10-24, T-527, the owner's lever 3b: the bench's `sensord` takes ~30% of a core at idle (load 3.5). With it and `atlas-sensord` stopped the Wikipedia pan ran 25.0 / 27.5 / 27.4 fps against 21.4 / 20.9 / 22.4 with them running, the long page 33-36.5 against 30-35; both restored after. The floor is met on the bench without the sensors; with them it is 21-22, so the compositor thread stays the product's lever. 2026-10-19, T-515: the test page 30-35 fps; desktop Wikipedia 19-21 fps in 2 s windows, median frame gap 45 ms, with the frames made off the main thread. The page's script no longer gaps them (its 2.2 s restyle ran under frames every ~45 ms); the floor is the compositor thread's ~45 ms a Wikipedia frame, whichever lane asks for it: T-517. A page whose script holds the main thread 600 ms in every 700 pans at 26-32 fps (2.4-3.8 without the express lane).)*
- [ ] The scripted pan (0 → 3000 card px in 10 s, `tools/device/t502/pan-run.sh`) runs at 24 fps or better on `jihad-long-static.html` and on desktop en.wikipedia.org after its load, **with the card moving as under a finger** *(2026-10-26, RE-OPENED (the owner's choice): the scripted pan sent the card's scroll commands to the daemon and the card's view stood still, so every figure below left out the card's own redraw; it now tells the card to scroll and the card reports its scroll, as under a finger (`JIHAD_INJECT_PAN_NOCARD=1` is the old test). Measured so: desktop Wikipedia 91 frames in 10.3 s by the scripted pan, 86 in 11.9 s of eight real finger drags (~7 fps), the long page 222 in 11.6 s (~19); the readback waits behind LunaSysMgr's redraw of the card on the same GPU (a 15-row read 30-164 ms under a finger). The old test's figures follow.)* *(2026-10-26, T-527, MET with the old test on the owner's bench (sensord and atlas-sensord stopped: the owner's choice (b) of 2026-10-24), the packaged head: desktop Wikipedia 24.6 and 24.2 fps over the whole 10 s pan, the long page 34.2; with the sensors running the same build gives 22.7-23.6 and 32.8-33.1 (`docs/rounds/t527-2026-10-25/notes.txt`).)* *(2026-10-25, T-527, the sensors running, on engine 0063: the long page 29.5-35.4 fps; desktop Wikipedia's 2 s windows 22.3-28.5 after the first scroll's restyle, its mean over the pan 23.8 and 24.4 fps in two runs (21.7 in a third): the compositor thread is the bound (85 % of a core: compose ~8 ms, the copy from the base 10-14, the read 7-12 a frame). 2026-10-24, T-527, the owner's lever 3b: the bench's `sensord` takes ~30% of a core at idle (load 3.5). With it and `atlas-sensord` stopped the Wikipedia pan ran 25.0 / 27.5 / 27.4 fps against 21.4 / 20.9 / 22.4 with them running, the long page 33-36.5 against 30-35; both restored after. The floor is met on the bench without the sensors; with them it is 21-22, so the compositor thread stays the product's lever. 2026-10-19, T-515: the test page 30-35 fps; desktop Wikipedia 19-21 fps in 2 s windows, median frame gap 45 ms, with the frames made off the main thread. The page's script no longer gaps them (its 2.2 s restyle ran under frames every ~45 ms); the floor is the compositor thread's ~45 ms a Wikipedia frame, whichever lane asks for it: T-517. A page whose script holds the main thread 600 ms in every 700 pans at 26-32 fps (2.4-3.8 without the express lane).)* *(2026-11-03, T-505 item 3: the parts by part and the express second frame back — by finger 11.4-11.5 fps on Wikipedia, 18.0-18.3 on the long page: the compositor job and the card's own redraw bound it, not the parts pass (~5 % of the time) nor the lane's depth; item 4, the scroll-only bands composed on the CPU, is next.)*
- [x] On every page measured, the GPU path's pan is at least as fast as the software path's on the same pan (`NOGPU=1`), and the table in `docs/RENDERING-OPTIONS.md` says so per page *(2026-10-24, T-519, the same 10 s pan on each page both ways on engine 0058, the pan's 2 s windows: long static 30.4-33.3 against 10.8-12.5 fps, fixed header and footer 26.4-27.9 against 10.9-12.7, sticky-mix 16.5-19.0 against 8.6-10.0, the animation page panned 14.5-24.4 against 11.4-23.6, the self-scrolling page 22.8-27.7 against 10.5-11.4, Hacker News 37.5-48.0 against 6.3-10.2 (the pan overshoots its 1249 px, the last frames are past the page), desktop Wikipedia 18.5-25.9 against 2.7-2.9 (the 18.5 window is the first scroll's restyle). The table is in `docs/RENDERING-OPTIONS.md`; logs `docs/rounds/t519-2026-10-24/pan-*-gpu2-*` and `pan-*-sw-*`.)*
- [~] Frame gaps of 56 ms or more during a pan are accounted for (traced to their source) and the avoidable ones removed *(2026-10-26, T-527: on the owner's bench, desktop Wikipedia 22 and 16 gaps in 10 s: the express job itself 21 (a tree change's read, the copy from the base), a job queued behind the one before it 14, one tile-upload transaction, and once 415 ms with all three buffers out with the card; none waits on the main thread. Tried and not kept: a region read of the change, the own slot with stuck layers (notes.txt). What is left is the compositor job's cost on its one thread.)* *(2026-10-25, T-527: traced per gap with `tools/host/gap-trace.py` on the timeline (the express lane now notes why it made no frame): desktop Wikipedia's 10 s pan, 24 gaps in 252 frames — an express job of 50-100 ms (a frame whose tree changed: a whole read; the copy from the base with a stuck header) while the two-deep pipeline waits, a job queued 25-88 ms behind the page's tile-upload transaction, once all three buffers held by the card; none is the main thread any more. Removing them is the compositor job's floor (the read, the copy), still open. 2026-10-18, traced: desktop Wikipedia's pan has 3-13 a 2 s window. The stall sampler resolves them to the page's own restyle on the first scroll (955 ms, 36% selector matching: its sticky header), the layer paints of the displayport moves (58-121 ms: display list, glyphs, regions), and short misc events. None is the daemon's own work; the anchor distance does not change them (0.125 and 0.4 heights measured). What would remove them from the frames is the frames made off the main thread: T-515. 2026-10-19: done (T-515, the express lane): the main thread's stalls are covered by frames composed and published from a thread of their own; the gaps left are the compositor's own (T-517).)*
- [x] During a pan the card has content past the view's edges, as the software path's frames have: a gap between frames or a busy main thread shows the page, not grey *(added 2026-10-18 from the owner's "areas turn grey": the GPU frame carries 0.15 of the card's height above and below the view (engine 0046, `JIHAD_GPU_OVERSCAN`); verify 0 structurally wrong rows with it on the test page, sticky-mix, desktop Wikipedia and Hacker News; frame dumps looked at)*
@@ -73,6 +73,7 @@ Hacker News 36-37, desktop Wikipedia 13-14 (software 9-12.4).
- [x] Fixed and stuck content stays put on the card between frames too, while the card moves the frame it holds for a pan *(2026-10-26, T-544, engine 0068: the daemon tells the card each frame's fixed and stuck rects and the card draws them where the frame did; desktop Wikipedia's sidebars by finger at the same place in four mid-drag screenshots of four while the column moved 47-150 px (displaced 11-12 px in two of four without it), no ghost by eye)*
- [x] R2's verify passes on each case *(2026-10-24, T-519: 0 rows structurally wrong on `jihad-long-static.html`, `jihad-fixed-both.html`, `jihad-sticky-mix.html` and desktop Wikipedia under `JIHAD_GPU_VERIFY=1` over the 10 s pan; the per-page lines are in R2.)*
- [x] When the parts are drawn on the CPU (T-505, the owner's CPU/GPU split): every published frame's CPU-drawn parts come from a composite of the same tree state as the frame; the change signal covers content, attribute, structure and animation changes to a part; content drawn above a part is never drawn under it; `JIHAD_GPU_VERIFY=1` with the parts on shows zero wrong rows over a scripted pan on desktop Wikipedia and the long page *(added 2026-10-26 from the inspector's review, `context/impl/impl-review-findings-t505-cpuparts.md`. Engine 0072 (E2): the parts' picture read in the frame's own job when their signature moves; desktop Wikipedia verified, 27 shifted frames, 0 wrong rows during the pan (`docs/rounds/t505-2026-10-26/pan-v1-wiki-daemon-gpu.log`); 2026-11-02: the long page's verify run done — its one wrong frame (13 rows at the first shift, 2026-10-26) reproduced with the verify dumps and traced to the engine: a content-only frame's narrow tree change (the load's 4 px strip) was composed as a partial composite of those columns but read whole rows, the other columns the window target's last composite (the verify reference with the parts), so the header passed for the page beneath it (engine 0073 composes the rows whole); after it 85 verified frames, 78 shifted, 0 wrong rows (`docs/rounds/t505-2026-11-02/notes.txt`, pan-v4-long); the inspector's review of E2 (`context/impl/impl-review-findings-t505-cpuparts.md`, review 2) fixed too)*
- [x] The parts' pictures by part (T-505, the owner's item 3): every rect of a frame's parts list carries a signature of its own (its parts' layers, the paints under them, its size) and a pass composes and reads only the rects the daemon holds no picture of; the express lane's second frame builds on the first in flight when both are without their parts, its dress putting back what lay beneath the first's parts and beneath the first's base's; `JIHAD_GPU_VERIFY=1` checks the express lane's frames as the main lane's; 0 wrong rows over the scripted pans on desktop Wikipedia and the long page in both lanes *(added and met 2026-11-03: engine 0073 (per-rect signatures, `compose_post3`/`start3`, the poll's 54 ints) and the daemon's pictures by part, each behind a switch — `JIHAD_GPU_PARTS_PER=1`, `JIHAD_GPU_EXPRESS_SECOND_PARTS=1` — the defaults the owner's call. By finger on the build with the review's fixes: desktop Wikipedia 10.8 → 11.4 fps with the parts by part, 11.4 with the second frame (its two sidebars; the contents panel that changes is 310 of the 340 KB, so the lever is bounded by the changing part's size), the long page 19.2 → 19.4 / 18.1 (one part, no pass during the drags). Verify after the pan mark on that build: the main lane alone 55 and 52 (Wikipedia) / 80 (the long page) frames; both lanes with the second frame posted early (`JIHAD_GPU_EXPRESS_EARLY=1`) 35 / 66 frames with 13 / 29 two-deep ones, and with the first frame's dress held 80 ms before its restores or between its record and its draw (`JIHAD_GPU_TEST_DRESS_HOLD`, so the copy catches the dress) 34 / 55 and 34 / 61 frames with 13-16 / 24-27 two-deep ones — 0 wrong rows in restored or copied rows in every run (two single rows in rows those jobs read fresh at the load's end); frames looked at. On the way a latent main-lane fault from 2026-11-02 found and fixed: a second job whose in-flight base the parts rule refused built on the newest finished frame but told the engine it was the previous composite, so the frame was recorded 316 rows off and the next own-slot frames copied from the wrong rows (5 wrong frames in one run; 0 after the fix). `docs/rounds/t505-2026-11-03/notes.txt`; the review: one inspector (Codex out of credits), `context/impl/impl-review-findings-t505-cpuparts.md` review 3.)*
**Dependencies:** R2
### R5: Animation and new content meet the chart
+1 -1
View File
@@ -594,7 +594,7 @@ the mid-pan dumps, never by a screenshot. No device run while the owner is at th
| T-502 | The spike and the compositor-side scroll (rounds 1-137): patches 0040/0041/0042, the daemon's job pipeline, band reads on the band surface, the displayport, fixed/sticky rows, the composition fix for wide pages | cavekit-gpu-rendering.md | R2, R3 (part) | — | yes | L | DONE (commits through `bc4a8eaf`/`b30b9989`: test pan 27-34 fps, animation 26-31, HN 36-37, desktop Wikipedia 13-14 vs software 9-12.4) |
| T-503 | Fixed and sticky at minimum cost: opaque stuck layers kept (per opaque rect, the visible region's gaps), others read as columns, fixed header + footer as two bands; the draw-culling fix kept; verify and dumps on the long-static page, a header+footer page and desktop Wikipedia | cavekit-gpu-rendering.md | R4, R2 | T-502 | yes | M | DONE 2026-10-18 (rounds 138-150): opaque fixed/stuck layers kept per covered rect, the rest read as column chunks packed on the band surface, header + footer as two bands; the draw culling fixed; a sticky layer the engine laid out stuck counted (probed sticky ranges); the root composition covers what the card shows (Hacker News' blank bottom strip); verify made side-effect free with a structural test: 0 structurally wrong rows on the test page, `jihad-sticky-mix.html`, desktop Wikipedia and Hacker News. Pans: test page 27-34 fps, sticky-mix 15-17.5 (software 0.9-3.1), Wikipedia 16.4-18.5 (was 13-14; software 9-12.4), Hacker News 34-37.5, animation 24-32 |
| T-504 | Zoom keeps up: a scripted pinch and double-tap on Wikipedia and the test page with the frame timeline; first frame at the new zoom ≤ 250 ms; no grey at rest; tile uploads off the GPU's queue | cavekit-gpu-rendering.md | R6 | T-502 | yes | L | DONE 2026-10-26 (the owner's bounded overview: right after a zoom in the daemon composes a half-resolution picture of the page around the zoom-in point — the kept frame's rows and a frame's above and below, the scrolled content alone, rows no tile covered left out (engine 0069) — while the old tiles are still in the tree, and at a double tap out after a pan draws the card's view from it at twice its size with the fixed and stuck parts where they sit at the new position. The fixed and stuck parts are composed alone with their alpha and drawn over it where they sit at the new position. By finger on desktop Wikipedia (to 2.33, one or seven drags, back): the first frame 40-56 ms where the paint's came at 294-614 ms; frames looked at against the painted one, also on a page with a see-through fixed header and sticky sidebar; a pan past the picture still waits (the owner's bound, no black frame). An inspector review's six findings fixed (the readback's forced alpha, the parts' stale background, the probe, the scratch). Memory 1.8-2.8 MB and ~0.5 MB of parts beside the kept frame's 3.7; 7.5-9.4 MB of scratch for ~0.5 s. Cost: the zoom in's sharp frame later (682-779 ms against 519-545). `docs/rounds/t504-2026-10-26/notes.txt`) — earlier: IN PROGRESS 2026-10-26 (the owner: a bounded overview — a half-resolution picture of a few screens around the zoom-in point, ~4-8 MB per card) — earlier: PARTIAL 2026-10-18 (rounds 162-186: the zoom hold; the grey strip of a zoomed-in settle fixed; engine 0044 (tile textures converted at 1x1 and pooled: the Adreno's first write after a draw) and 0045 (no DEAA for axis-aligned scales: shader compiles in a zoom's first frame); the layers painted at the zoom's resolution above 1.1; double taps scripted (`t`), `JIHAD_GPU_ZOOM_DUMP`. Met: the scripted pinches and double taps with the timeline, the card covered at rest (dumps), uploads that do not wait, sharp zoomed text. Open: the first frame within 250 ms while the page's script holds the main thread (0.8-1.7 s on Wikipedia after its load) and one double tap in ten at ~400 ms: frames made off the main thread, with T-505); 2026-10-19, T-515: the zoom under script met (Wikipedia after load 10/10 within 225 ms); the double taps' slow ones are GPU draws: T-516; 2026-10-19 (rounds 334-341, `docs/rounds/t502-2026-10-17/dtap-r334-341.txt`): scripted double taps 2 of 18 over 250 ms (286, 367), the second core unplugged after an idle pause; the scripted tap skips the touch path's governor ramp, so a finger's tap is for T-514; a zoom's express frame now gives up uncomposed main-lane frames at the old zoom. Task token budget spent (the plugin's 45k): left PARTIAL for the owner) |
| T-505 | Real-page pans to the chart: desktop Wikipedia ≥ 24 fps; the 56 ms+ gaps traced (the main loop uncapped) and the avoidable ones removed; the per-page table against `NOGPU=1` | cavekit-gpu-rendering.md | R3 | T-503 | yes | L | IN PROGRESS 2026-11-02 (the review gate on 81f56bef + engine 446b22790a done by the inspector (Codex out of credits until Nov 4th): REVISE, six findings, all fixed (engine 0073; the daemon); PICKUP's item 2, the long page's one wrong verify frame, reproduced with the verify dumps and traced to the engine: a content-only frame's narrow tree change was composed as a partial composite of those columns but read whole rows, the other columns the window target's last composite (the verify reference with the parts), so the header passed for the page beneath it (F1; the fix composes the rows whole: `LayerManagerComposite::JihadWholeRows`); after it the long page's verify 85 frames, 78 shifted, 0 wrong (`docs/rounds/t505-2026-11-02/notes.txt`); CPU parts stay on by default (the owner's word, the review clean). Open, after the engine update to RB_20261027 (the owner's order of 2026-11-02): cheaper part pictures (a per-part signature, only the changed part read), then the CPU-composed bands) — earlier: IN PROGRESS 2026-10-26 (late: two inspector reviews (REVISE) -> E2, engine 0072: the parts' picture read in the frame's own job when their signature moves, exact (verify: 0 wrong rows in a Wikipedia pan, where the engine's parts showed 2 wrong frames); scripted Wikipedia 12.8 fps (9.4 without), the long page 23.6 (18.8); by finger Wikipedia 12.3 (9.5), the long page 18.3 (15.7) — the stale switch gone (as fast, exact); on by default once the re-review is clean (the owner's word); findings in `context/impl/impl-review-findings-t505-cpuparts.md`) — evening, the owner's "both, parts first": the parts on the CPU built behind `JIHAD_GPU_CPU_PARTS=1` (engine 0070, daemon 2bd3e6f1) and four faults on the way fixed (the picture never matching, the lanes' two kinds, engine 0071: own-slot frames diffed without the scroll, T-511's rule taking a slot stale whole); scripted pan with the card moving: desktop Wikipedia 9.4 -> 11.2 fps, 14.2 with `JIHAD_GPU_CPU_PARTS_STALE=1` (the owner's trade: the previous picture while a changed part's is made), the long page 18.8 -> 24.7; by finger Wikipedia 11-13 against ~10; frames looked at; whether to ship it on, and the stale trade, are the owner's; the CPU-composed bands next; `docs/rounds/t505-2026-10-26/notes.txt`) — earlier the same day: the cause proven and the card measured, the owner's "prove the cause first" and "measure first": the same daemon work makes desktop Wikipedia 32 fps and the long page 53 with LunaSysMgr drawing nothing, 8.6 / 13.6 with the card moving — the card's redraw on the shared GPU and memory makes every daemon stage ~3x slower (`JIHAD_GPU_NO_PUBLISH=1`, a diagnostic). The card's paint is Piranha's SOFTWARE blit (~10 ms, ~20 a second, most of them repaints); a surface kept per frame changed nothing; the frame wrapped without alpha halves the blit and cuts LunaSysMgr's CPU 15 % (shipped, `<state>/alphawrap` reverts) but not the pan rate. Codex's adversarial review: `context/impl/impl-review-findings-t505-codex.md`. The owner's plan, after "consider sharing tasks across the CPU and GPU": (1) pan frames composed on the GPU as the scrolled content alone and the fixed/sticky parts blended on the CPU, then (2) scroll-only bands composed on the CPU from the tiles, each behind a switch, measured by finger; `docs/rounds/t505-2026-10-26/notes.txt`) — earlier the same day, measured read-only, the owner's "measure first": under eight finger drags desktop Wikipedia 9.5 fps and the long page 16.6 while frames flow; a frame is bound by the readback path on the GPU shared with LunaSysMgr's redraw of the card — Wikipedia's band frames 64 ms median: the read's GPU wait 36, the copy from the base 16, the compose 14, its sticky sidebars read as columns doubling the read; the long page's 30 ms with 11 / 13 / 3; the long gaps are the job's GPU waits, none the main thread's; `docs/rounds/t505-2026-10-26/notes.txt`. The proposal is the owner's to choose) — earlier: IN PROGRESS 2026-10-26 (R3 re-opened against the corrected test, the owner's choice: the scripted pan never moved the card, so its 24 fps left out the card's own redraw; with the card moving desktop Wikipedia pans at ~7 fps under a finger and by the corrected scripted pan, the long page ~19 — the readback waits behind LunaSysMgr's redraw of the card on the same Adreno. Measured and not kept: the express thread's copy from the base (no gain), the card's scroll redraw coalesced to one in 33 or 50 ms (no gain, WebAppMgr's CPU unchanged). Earlier the same day: the owner: prototype the card shifting the frame it holds, the daemon sending only the new rows and the shift, behind a switch until measured; meanwhile desktop Wikipedia's pan is 24.0 fps with the sensors running since the Line truncated
| T-505 | Real-page pans to the chart: desktop Wikipedia ≥ 24 fps; the 56 ms+ gaps traced (the main loop uncapped) and the avoidable ones removed; the per-page table against `NOGPU=1` | cavekit-gpu-rendering.md | R3 | T-503 | yes | L | IN PROGRESS 2026-11-03 (item 3 done: the parts' pictures by part — engine 0073: each rect of the parts list signed on its own (its parts' subtree, the paints under its outermost layer, its size), `compose_post3`/`start3` with the signatures the daemon holds, the pass reads only the rects it lacks, the poll's 54 ints; the daemon keeps eight pictures by part — and the express lane's second frame on the first in flight back (review C1) under a double restore (the first's beneath and its base's, by slot record serial); each behind a switch (`JIHAD_GPU_PARTS_PER=1`, `JIHAD_GPU_EXPRESS_SECOND_PARTS=1`; the defaults the owner's call); `JIHAD_GPU_VERIFY=1` verifies the express lane's frames too. By finger (the build with the review's fixes) desktop Wikipedia 10.8 → 11.4 / 11.4 fps (the contents panel, 310 of 340 KB, is the part that changes: the lever is bounded by it), the long page 19.2 / 19.4 / 18.1; verify after the pan mark 0 wrong rows in both lanes, 142 two-deep frames among them, 55-80 with the first frame's dress held so the copy catches it (`docs/rounds/t505-2026-11-03/notes.txt`); the review gate one inspector (Codex out until Nov 4th): REVISE, seven findings, all fixed (`impl-review-findings-t505-cpuparts.md` review 3); a latent main-lane fault from review 2's F4 (a second job's wrong previous-composite flag, the frame recorded 316 rows off) found by the verify and fixed. Next: item 4, the scroll-only bands composed on the CPU from the tiles) — earlier: IN PROGRESS 2026-11-02 (the review gate on 81f56bef + engine 446b22790a done by the inspector (Codex out of credits until Nov 4th): REVISE, six findings, all fixed (engine 0073; the daemon); PICKUP's item 2, the long page's one wrong verify frame, reproduced with the verify dumps and traced to the engine: a content-only frame's narrow tree change was composed as a partial composite of those columns but read whole rows, the other columns the window target's last composite (the verify reference with the parts), so the header passed for the page beneath it (F1; the fix composes the rows whole: `LayerManagerComposite::JihadWholeRows`); after it the long page's verify 85 frames, 78 shifted, 0 wrong (`docs/rounds/t505-2026-11-02/notes.txt`); CPU parts stay on by default (the owner's word, the review clean). Open, after the engine update to RB_20261027 (the owner's order of 2026-11-02): cheaper part pictures (a per-part signature, only the changed part read), then the CPU-composed bands) — earlier: IN PROGRESS 2026-10-26 (late: two inspector reviews (REVISE) -> E2, engine 0072: the parts' picture read in the frame's own job when their signature moves, exact (verify: 0 wrong rows in a Wikipedia pan, where the engine's parts showed 2 wrong frames); scripted Wikipedia 12.8 fps (9.4 without), the long page 23.6 (18.8); by finger Wikipedia 12.3 (9.5), the long page 18.3 (15.7) — the stale switch gone (as fast, exact); on by default once the re-review is clean (the owner's word); findings in `context/impl/impl-review-findings-t505-cpuparts.md`) — evening, the owner's "both, parts first": the parts on the CPU built behind `JIHAD_GPU_CPU_PARTS=1` (engine 0070, daemon 2bd3e6f1) and four faults on the way fixed (the picture never matching, the lanes' two kinds, engine 0071: own-slot frames diffed without the scroll, T-511's rule taking a slot stale whole); scripted pan with the card moving: desktop Wikipedia 9.4 -> 11.2 fps, 14.2 with `JIHAD_GPU_CPU_PARTS_STALE=1` (the owner's trade: the previous picture while a changed part's is made), the long page 18.8 -> 24.7; by finger Wikipedia 11-13 against ~10; frames looked at; whether to ship it on, and the stale trade, are the owner's; the CPU-composed bands next; `docs/rounds/t505-2026-10-26/notes.txt`) — earlier the same day: the cause proven and the card measured, the owner's "prove the cause first" and "measure first": the same daemon work makes desktop Wikipedia 32 fps and the long page 53 with LunaSysMgr drawing nothing, 8.6 / 13.6 with the card moving — the card's redraw on the shared GPU and memory makes every daemon stage ~3x slower (`JIHAD_GPU_NO_PUBLISH=1`, a diagnostic). The card's paint is Piranha's SOFTWARE blit (~10 ms, ~20 a second, most of them repaints); a surface kept per frame changed nothing; the frame wrapped without alpha halves the blit and cuts LunaSysMgr's CPU 15 % (shipped, `<state>/alphawrap` reverts) but not the pan rate. Codex's adversarial review: `context/impl/impl-review-findings-t505-codex.md`. The owner's plan, after "consider sharing tasks across the CPU and GPU": (1) pan frames composed on the GPU as the scrolled content alone and the fixed/sticky parts blended on the CPU, then (2) scroll-only bands composed on the CPU from the tiles, eacLine truncated
| T-506 | Animation, new content and self-scroll measured against the chart (DOM edit and typing ≤ 106 ms) | cavekit-gpu-rendering.md | R5 | T-502 | yes | S | DONE 2026-10-18 (rounds 202-203: the animation page 26-31 fps reading only its changed rows; typing 22-62 ms and DOM edits 26-70 ms from the input to the first published frame with changed rows (an input-latency probe on the inject channel); the self-scrolling page followed at 10.7-13.9 fps) |
| T-507 | Every other card event on the GPU path: rotation, resize, select popups, caret, link and find highlights, thumbnails, navigation without stale frames | cavekit-gpu-rendering.md | R9 | T-502 | yes | M | DONE 2026-10-18 (rounds 204-212 on `jihad-card-events.html`: rotation to landscape and back, the keyboard's resize, the select list and pick, the caret, find-in-page, link navigation with back, forward and reload, the thumbnail; the thumbnail's view row and the popups' origin corrected for overscan; the test hook gains `<`, `>`, `R`, `O<orientation>`; the inject channel `thumb <path>`) |
| T-508 | Video and Flash on the GPU path: `<video>` (VP8, Theora, H.264) to the end, styled video (rounded, fade, under a modal), Flash draw/animate/input | cavekit-gpu-rendering.md | R7 | T-502 | yes | L | DONE 2026-10-19 (rounds 213-228: VP8, Theora, VP9 and H.264 to the end on the GPU path, at the clips' rates; styled video (rounded, fade, modal); Flash draws, animates and takes keys; `push-engine-update.sh` now carries FFmpeg's libavcodec/libavutil, which a device updated by it lacked (H.264, AAC and MP3 were refused on both paths); new pages `jihad-video-styled.html`, `jihad-anim-opacity.html`, `jihad-canplay.html`) |
+1
View File
@@ -97,6 +97,7 @@ modifications; the same changes are the commits of `jihad/uxp-RB_20261027`, one
| `0070-gpu-compositor-the-diff-leaves-the-card-s-scroll-out.patch` | `gfx/layers/ipc/CompositorBridgeParent.cpp` | T-505: the diff leaves the card's scroll out of a frame with no base to copy (built on its slot's own frame) too, kept only when the shift it measured is the card's since the last composite; a layer transaction during a pan is no longer the whole frame changed for such frames |
| `0071-gpu-compositor-the-parts-picture-in-the-frame-s-own-.patch` | `widget/PuppetWidget.{h,cpp}`, `gfx/layers/opengl/CompositorOGL.{h,cpp}`, `gfx/layers/ipc/CompositorBridgeParent.{h,cpp}`, `gfx/layers/composite/LayerManagerComposite.{h,cpp}` | T-505 (E2): `jihad_offscreen_compose_post2`/`start2` (`overview_ok` answers 3): a frame without its parts reads the parts' picture in its own job when their signature (layers, content, attributes, sizes) is not the daemon's — the parts alone, packed on the band surface, read with their alpha; the poll's 49 ints (signature, the pass's bytes, flags: a content layer above a part / too big, the card's hold set); no column reads without the parts; 0071 only without the parts or without fixed rects |
| `0072-gpu-compositor-a-frame-without-its-parts-composes-th.patch` | `gfx/layers/composite/LayerManagerComposite.{h,cpp}`, `gfx/layers/ipc/CompositorBridgeParent.{h,cpp}` | T-505: a frame without its parts reads the changed rows whole, so its diff-driven partial composite covers them across the frame's width (`JihadWholeRows`; the columns beside a narrow change used to be the window target's last composite: the long page's wrong verify frame, 2026-11-02); the inspector's review fixes: one sample time per job, fixed rects joined again after the reduction, the parts' signature over a cut part's slice, every part's clip and mask, blend modes as covered; the parts pass under the job's cancel flag |
| `0073-gpu-compositor-the-parts-picture-by-part-each-rect-i.patch` | `gfx/layers/ipc/CompositorBridgeParent.{h,cpp}`, `gfx/layers/ipc/CompositableTransactionParent.cpp`, `gfx/layers/opengl/CompositorOGL.h`, `widget/PuppetWidget.{h,cpp}` | T-505 (the owner's "cheaper part pictures", 2026-11-03): each rect of a frame's parts list carries a signature of its own (its outermost fixed or sticky layer's subtree, every subtree layer's last paint as the stamp of one counter that only goes up — every compositable update stamps its layer, part or not — the part's scale and quarter-pixel phase, where it sits inside the rect and its size; a rect no part's rect meets takes every part's with its index and place); `jihad_offscreen_compose_post3`/`start3` (`overview_ok` 4) take the list of signatures the daemon holds and the pass composes and reads only the rects not in it, packed in rect order; the poll's 54 ints carry each rect's signature and the rects read. The whole signature keeps post2/start2's meaning. The inspector's review 3 (F2, F3, F6) folded in |
| `0054-gpu-compositor-kept-rects-all-saved-before-any-is-wr.patch` | `gfx/layers/opengl/CompositorOGL.cpp`, `widget/PuppetWidget.cpp`, `dom/canvas/WebGLContext.cpp`, `widget/headless/GfxInfo.cpp` | T-517/T-511 review: a window-moved shift saves every kept rect before writing any; `jihad_offscreen_gpu_given_up()` / `jihad_gpu_compositor_on()`, so WebGL makes no EGL contexts once the daemon has given the GPU path up |
| `0053-gpu-compositor-a-shift-that-is-the-daemon-s-window-m.patch` | `gfx/layers/opengl/CompositorOGL.{h,cpp}`, `widget/PuppetWidget.cpp` | T-517: a compose job's shift may be the daemon's move of the frame's window in its segment (flag 8): the rows are in place, only the kept rects are copied from where they were |
| `0052-gpu-compositor-the-frame-s-bands-read-as-one-column-.patch` | `widget/PuppetWidget.{h,cpp}`, `gfx/layers/ipc/CompositorBridgeParent.cpp`, `gfx/layers/opengl/CompositorOGL.{h,cpp}` | T-511: a compose job may give its bands' stale columns (flag 4); with no shift, kept rows or fixed columns, the bands are read as the one column the change and they span when it is under three quarters of the width; the poll reports the change's columns (twelve ints); `JIHAD_GPU_NO_COLUMN_READ=1` reads whole rows |
+65 -1
View File
@@ -1,4 +1,4 @@
# PICKUP — Jihad Browser, current state (2026-08-10 body, deltas 2026-08-15 … 2026-11-03 below)
# PICKUP — Jihad Browser, current state (2026-08-10 body, deltas 2026-08-15 … 2026-11-03 evening below)
**Read this file first. It is the only current handoff.** Everything else in `docs/` is
reference material for a specific subsystem, not a status document.
@@ -52,6 +52,70 @@ Jihad Browser, and it starts when Jihad Browser is finished.
Memory: `global-jihad-project.md`. The workspace `CLAUDE.md` carries the pointer and the corrected
target line (both webOS 3.0.5 and CE 3.1.0).
## 2026-11-03 (evening) — T-505 item 3 DONE: the parts' pictures by part, the express second frame back; item 4 next (READ FIRST)
**State:** main = this commit (on the owner's `ffd6aae2`, pulled from Gitea); the engine fork `jihad/uxp-RB_20261027` at
`adfaa7ff30` (patch 0073; `patch-queue.sh --check` and `check-engine-pins.sh` pass with the gitlink of this commit).
**Not pushed:** the owner's push, `bash .cavekit/handoff-2026-11-03/push.sh` (the engine branch first, then main to Gitea;
both read back). Codex was out of credits until Nov 4th, 01:42: the review gate was ONE ck:inspector.
**Item 3 (the owner's "cheaper part pictures"), built, reviewed, measured, verified — behind two switches, the defaults
the owner's call:**
- Engine 0073: each rect of a frame's parts list carries a signature of its own (its outermost fixed or sticky layer's
subtree; every subtree layer's last paint as the stamp of one counter that only goes up — every compositable update
stamps its layer, part or not; the part's scale, quarter-pixel phase, place inside its rect and size);
`jihad_offscreen_compose_post3`/`start3` (`overview_ok` 4) take the signatures the daemon holds and the pass reads
only the rects not in the list; the poll's 54 ints carry each rect's signature and the rects read.
- Daemon: eight part pictures by signature; `JIHAD_GPU_PARTS_PER=1` (or `<state>/parts-per`) sends the list — without
it E2's rule (every part when the whole signature moved) with the pictures still by part. `PartsReady()` needs an
engine that answers 4 (a level-3 libxul puts the parts back on the GPU).
- The express lane's second frame on the first in flight is back when both are without their parts
(`JIHAD_GPU_EXPRESS_SECOND_PARTS=1` or `<state>/express-second-parts`): the second's dress puts back what lay beneath
the first's parts AND beneath the first's base's parts (slot records keep their base's serial and the rows read; a
slot's previous record kept aside), never on a first that is itself two deep. `JIHAD_GPU_VERIFY=1` now verifies the
express lane's frames (the reference composed BEFORE the job), and `JIHAD_GPU_TEST_DRESS_HOLD=<pre|mid>:<ms>` holds
a first frame's dress so the second's copy really catches it.
- By finger (fps while frames flow): desktop Wikipedia 10.8 (whole) → 11.4 (per part) → 11.4 (+ the second frame); the
long page 19.2 / 19.4 / 18.1. Wikipedia's parts are its two sidebars and the one that changes (the contents panel's
highlight) is 310 of the 340 KB: the lever is bounded by the changing part's size; the passes are ~5 % of the time
either way; the compositor job and the card's own redraw bound the rate. Verify after the pan mark: 0 wrong rows in
every run, 142 two-deep frames among them, 55-80 with the first's dress held. `docs/rounds/t505-2026-11-03/notes.txt`.
- The inspector (review 3, `context/impl/impl-review-findings-t505-cpuparts.md`): REVISE, 3 P1 / 1 P2 / 3 P3, all fixed
(a third frame on a two-deep first; a paint count that could return to an old value; the signature blind to a part's
place and phase; the verify hiding the copy-vs-dress race; a store evicting this frame's picture; equal fallback
signatures; a leaked verify buffer). The verify then found a LATENT main-lane fault from review 2's F4 (2026-11-02):
a second job whose in-flight base the parts rule refused built on the newest finished frame but told the engine it
was the previous composite, so the frame was recorded 316 rows off and the next own-slot frames copied wrong rows
(5 wrong frames in one run) — fixed (`srcInFlight` in gpuJobStart; 0 wrong in three runs after).
- Desktop: the engine built clean, the adapter round-trip and headless-daemon tests pass; `check-engine-api.py` 0
FAIL; `check-engine-calls.sh` ok.
**Decisions for the owner (multiple choice, in the handoff prompt):** (1) ship `JIHAD_GPU_PARTS_PER` on by default? (2)
ship `JIHAD_GPU_EXPRESS_SECOND_PARTS` on by default? (3) the next lever on this road — reading only a part's DAMAGED
rows (the contents panel's highlight is one line of 544) and patching the picture held — or item 4 first.
**Open, in order:** item 4 — the scroll-only bands composed on the CPU from the tiles' CPU-side pixels, the GPU only for
tree changes (the owner's step 2 of "both, parts first"); then the Tier 91 kits (the webOS SDK T-548..T-550 with its
sudo step for the owner, the Atlas references T-545..T-547, the low-power levers T-555..T-560).
**Traps from this session (not in the source):** the host had 4 GB available of 32 with the zram swap 97 % full — 12 GB of
closed Claude sessions' scratch in `/tmp/claude-1000` (the permission classifier refused deleting it: the owner's
`rm -rf /tmp/claude-1000/-home-*`), and earlyoom's daemon is not running (only its runsv): engine builds ran at 2 jobs
(`build-goanna-arm.sh build 2`; `build/desktop/build-goanna.sh build 2` takes the argument since today). The desktop
README's podman line lacks the `/jihad` mount the components step needs: `-v "$PWD:/jihad"` (DESKTOP-POC.md has it). A
backgrounded build command once ran with its relative log path lost: use absolute paths. A verify round is judged by
the lines after the `inject pan` mark (memory `verify-counts-after-the-pan-mark`): Wikipedia's load gives 14 "wrong"
frames before it (tiles arriving between the job and its reference). `pan-run.sh` leaves the hand daemon running and
the Enyo job stopped: `gpu-spike-collect-stop.sh` after the last run. `finger-pan-device.sh`'s CPU ticks do not catch
the daemon (its command line is cut before its name).
**Device state:** the three variants run engine 0073 (libxul md5 `a597f8a2`, stripped from `adfaa7ff30`) and the daemon
(`340aeb0a`) by `push-engine-update.sh enyo mochi mojo` (md5-verified, each job restarted; the `.ipk`s NOT rebuilt for
this head; the adapters still the packages of `62af6e82`). default-check passes in all three (the GPU path with the
express lane; faults 0, GPU off 0, express off 0; the Enyo screenshot looked at). No dev copies, `.orig`, gate files
(`parts-per`, `express-second-parts`, `no-cpu-parts`) or adapter gates; Default look, No proxy; the sensors running;
`/tmp/finger` on the device; the round dumps removed; `/var` 28 %.
## 2026-11-03 (later) — the engine on UXP `RB_20261027` (T-551..T-554 DONE); the device on it; the owner's push owed (READ FIRST)
**State:** main `62af6e82` + this PICKUP commit; the engine fork's NEW branch `jihad/uxp-RB_20261027` at `52ad9ab66e`
@@ -0,0 +1,9 @@
--- CPU ticks over the drags (pid, before, after, command)
10929 9919 9926 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
12315 9752 9761 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
30698 1056 1706 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
18912 4977 5171 /usr/bin/LunaSysMgr -s
18982 103600 104314 /usr/bin/LunaSysMgr -s
--- /proc/stat before and after
cpu 31254352 2588849 29375979 127762581 2320981 5552 62962 0 0 0
cpu 31255179 2589561 29376741 127764152 2321393 5552 62962 0 0 0
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,9 @@
--- CPU ticks over the drags (pid, before, after, command)
10929 9869 9877 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
12315 9696 9706 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
26975 3810 5817 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
18912 4497 4705 /usr/bin/LunaSysMgr -s
18982 101966 102779 /usr/bin/LunaSysMgr -s
--- /proc/stat before and after
cpu 31248892 2587133 29372529 127754559 2320319 5552 62956 0 0 0
cpu 31251057 2587954 29373343 127754912 2320523 5552 62958 0 0 0
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,9 @@
--- CPU ticks over the drags (pid, before, after, command)
10929 11986 11993 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.riv
12315 11862 11869 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.riv
2495 1130 1771 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.river
18912 10660 10850 /usr/bin/LunaSysMgr -s
18982 172565 173280 /usr/bin/LunaSysMgr -s
--- /proc/stat before and after
cpu 31380187 2633300 29484641 128011139 2380112 5559 63114 0 0 0
cpu 31380952 2634033 29485358 128012767 2380550 5559 63114 0 0 0
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,9 @@
--- CPU ticks over the drags (pid, before, after, command)
10929 11922 11930 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.riv
12315 11796 11805 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.riv
31205 3840 5843 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
18912 10158 10367 /usr/bin/LunaSysMgr -s
18982 171086 171908 /usr/bin/LunaSysMgr -s
--- /proc/stat before and after
cpu 31374280 2631779 29480631 128003122 2379381 5559 63106 0 0 0
cpu 31376447 2632605 29481456 128003479 2379580 5559 63109 0 0 0
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,9 @@
--- CPU ticks over the drags (pid, before, after, command)
10929 10011 10023 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.riv
12315 9854 9864 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
5728 1083 1711 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.river
18912 5967 6159 /usr/bin/LunaSysMgr -s
18982 106572 107282 /usr/bin/LunaSysMgr -s
--- /proc/stat before and after
cpu 31267233 2591840 29383259 127777972 2322570 5552 62975 0 0 0
cpu 31268027 2592544 29384012 127779556 2323005 5552 62975 0 0 0
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,9 @@
--- CPU ticks over the drags (pid, before, after, command)
10929 9962 9969 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
12315 9798 9806 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
1957 4154 6160 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.river
18912 5462 5676 /usr/bin/LunaSysMgr -s
18982 105077 105863 /usr/bin/LunaSysMgr -s
--- /proc/stat before and after
cpu 31261273 2590346 29379379 127769817 2321900 5552 62967 0 0 0
cpu 31263504 2591124 29380216 127770158 2322101 5552 62968 0 0 0
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,9 @@
--- CPU ticks over the drags (pid, before, after, command)
10082 1075 1716 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
10929 12105 12113 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.riv
12315 11966 11975 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.riv
18912 11656 11848 /usr/bin/LunaSysMgr -s
18982 175621 176340 /usr/bin/LunaSysMgr -s
--- /proc/stat before and after
cpu 31393565 2636418 29492064 128026571 2381769 5559 63118 0 0 0
cpu 31394356 2637135 29492874 128028149 2382167 5559 63120 0 0 0
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,9 @@
--- CPU ticks over the drags (pid, before, after, command)
10929 12034 12043 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.riv
12315 11909 11918 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.riv
6225 4343 6344 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.river
18912 11109 11316 /usr/bin/LunaSysMgr -s
18982 173944 174767 /usr/bin/LunaSysMgr -s
--- /proc/stat before and after
cpu 31387626 2634728 29488185 128018430 2381042 5559 63115 0 0 0
cpu 31389831 2635530 29489020 128018798 2381242 5559 63116 0 0 0
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,9 @@
--- CPU ticks over the drags (pid, before, after, command)
10929 9821 9830 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
12315 9646 9654 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
23104 1106 1718 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
18912 4068 4262 /usr/bin/LunaSysMgr -s
18982 100639 101332 /usr/bin/LunaSysMgr -s
--- /proc/stat before and after
cpu 31242137 2585795 29368836 127747566 2319344 5552 62954 0 0 0
cpu 31242902 2586501 29369550 127749172 2319781 5552 62955 0 0 0
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,9 @@
--- CPU ticks over the drags (pid, before, after, command)
10929 9754 9764 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
12315 9587 9595 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
19315 3785 5849 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
18912 3514 3725 /usr/bin/LunaSysMgr -s
18982 99161 99961 /usr/bin/LunaSysMgr -s
--- /proc/stat before and after
cpu 31236094 2584211 29364828 127739263 2318658 5551 62947 0 0 0
cpu 31238314 2585031 29365651 127739670 2318863 5551 62949 0 0 0
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,9 @@
--- CPU ticks over the drags (pid, before, after, command)
10929 11868 11877 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.riv
12315 11744 11752 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.riv
27524 1089 1707 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
18912 9703 9896 /usr/bin/LunaSysMgr -s
18982 169742 170442 /usr/bin/LunaSysMgr -s
--- /proc/stat before and after
cpu 31367192 2630421 29476562 127996168 2378528 5559 63100 0 0 0
cpu 31367980 2631145 29477290 127997800 2378916 5559 63102 0 0 0
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,9 @@
--- CPU ticks over the drags (pid, before, after, command)
10929 11815 11824 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.riv
12315 11680 11688 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.riv
23894 4330 6370 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
18912 9200 9409 /usr/bin/LunaSysMgr -s
18982 168243 169036 /usr/bin/LunaSysMgr -s
--- /proc/stat before and after
cpu 31361432 2628888 29472740 127987759 2377796 5559 63097 0 0 0
cpu 31363620 2629683 29473607 127988097 2378096 5559 63098 0 0 0
File diff suppressed because it is too large. Load diff
+130
View File
@@ -0,0 +1,130 @@
T-505, cheaper part pictures (the owner's item 3, 2026-11-03): the parts by part, and the express lane's second
frame on the first back (review C1)
====================================================================================================================
The engine on UXP RB_20261027 (jihad/uxp-RB_20261027 + the per-part commit), the daemon of this head, on the Enyo
variant (push-engine-update.sh enyo: libxul 7fa99738, daemon 9dfb38e5); the spike daemon by hand with the timeline
and the compose phases (finger-pan.sh), eight emulated-finger drags (480,900 -> 480,250 in 1 s, a second apart),
sensors running. Pages: desktop Wikipedia (HP_TouchPad), jihad-long-static.html (the long page, from the host on
port 8077). Logs fp-<round>-daemon-gpu.log, the CPU ticks fp-<round>-cpu.txt; tools/host/flow-fps.py and
express-jobs.py.
What changed
------------
Engine: each rect of a frame's parts list carries a signature of its own (JihadPartsSig: the outermost fixed or
sticky layer's subtree as before, plus the paints under THAT layer — CompositableTransactionParent notes each paint
by its outermost part, sJihadPartPaint — and the rect's size; a rect no part's rect meets takes every part's). The
whole signature is the rects' and the global paint count (the one-signature daemon reads every part, as E2 did).
compose_post3/start3 (overview_ok 4) take the list of signatures the daemon holds; the pass composes and reads only
the rects not in it, packed in rect order, and the poll's 54 ints carry each rect's signature and the rects read.
Daemon: the pictures kept by part (eight), each frame dressed from the picture of each of its rects' signature;
JIHAD_GPU_PARTS_PER=1 sends the engine the signatures held (the whole mode without it: post2 with the whole
signature, every part read when it moved). JIHAD_GPU_EXPRESS_SECOND_PARTS=1: the express lane's second frame on the
first in flight again when both are without their parts; the second's dress puts back what lay beneath the first's
parts AND beneath the first's base's parts (the slot records keep their base's serial and the rows read; a slot's
previous record is kept aside for a frame built on its own slot), so the copy may catch the first's dress at any
point. JIHAD_GPU_VERIFY=1 now verifies the express lane's frames too (the reference composed right before the job,
the compare shared with the main lane: "verify express " / "verify express (two deep) " lines).
By finger (fps while frames flow, flow-fps.py; one round each; the spread between rounds of one build is ~1-2 fps)
--------------------------------------------------------------------------------------------------------------
round Wikipedia the long page
pw: this build, the whole mode (E2's rule) 10.4 (fp-pw-wiki) 18.5 (fp-pw-long)
pp: per part 11.4 (fp-pp-wiki) 18.3 (fp-pp-long)
ps: per part + the express second frame 11.5 (fp-ps-wiki) 18.0 (fp-ps-long)
Whole-log rates (express-jobs.py, the pauses between drags in): 8.6 / 10.2 / 9.9 and 13.9 / 13.5 / 13.5.
Wikipedia's parts at this layout are its two sidebars: part 0 the contents panel (146x544, 310 KB), part 1 the
right-hand panel (138x311, 167 KB); no header rect. The contents panel's highlight moves as the page scrolls, so
it is the part that changes — and the big one: the per-part pass reads 310 of 340 KB. The passes: 21 in each
Wikipedia round (about 2.6 a drag), masks whole mode 21x both; per part 6x the panel alone, 1x the right panel
alone, 14x both (a drag's first pass after the pictures aged out of the eight, or both moved). The passes' cost
over the round: 1151 ms (pw), 954 (pp), 1317 (ps) of ~20 s — a pass's read waits for the GPU behind the card's
redraw (8-57 ms for 310 KB), the compose 9-11 ms. The long page: 1-3 passes a round (its header, 173 KB), none
during the drags.
What it says: the per-part pass saves a little compose and read on Wikipedia (the right panel, 30 KB-167 KB, is no
longer read when the contents panel moves) and nothing on a page with one part; the lever is bounded by the
changing part's own size. The next step on that road would be reading only the DAMAGED rows of a part (the
contents panel's highlight is one line of 544) and patching the picture held: the engine's compositable updates
carry the painted region per layer; not built (the owner's call).
The express second frame: no "the second frame cannot build on the first" notes in the ps rounds (42 / 30 in the
pw / pp Wikipedia rounds, 4 / 2 on the long page): every second frame the lane asked for was built on the first.
The rate did not move for it (11.4 -> 11.5, 18.3 -> 18.0): the lane's idle notes are "its frame is being composed"
and "the card is served" — the compositor job and the card's own redraw bound it, not the lane's depth.
Verify (pan-run.sh "pan 0 3000 15000", JIHAD_GPU_VERIFY=1 JIHAD_GPU_VERIFY_DUMP_WRONG=1 JIHAD_GPU_PARTS_PER=1; the
card moving; Wikipedia after PAN_DELAY=20)
-------------------------------------------------------------------------------------------------------------------
round verified shifted wrong structural express two deep
v-pp-long (the main lane alone) 79 78 0 0 0 0
v-pp-wiki 44 43 0 0 0 0
v-ps-long (both lanes, the second frame) 56 53 0 0 41 1
v-ps-wiki 23 23 0 0 23 2
(the frames after the "inject pan" mark; the whole logs hold 85 / 74 / 64 / 47 verified). Before the mark, during
Wikipedia's load, 14-15 frames a run differ from their reference by the whole article (782-785 rows "wrong", the
sidebar drawn and the text column white in the frame, the text in the reference: vw-slot / vw-whole looked at):
the page's tiles arrive between the job's composite and its reference's, the class the 2026-10-26 round saw once
(it had no PAN_DELAY: 39 frames against 74). None after the pan begins; the far pixels of those frames sit in the
article column, outside the parts. The express verify: the reference is composed right BEFORE the job (posted
after it, its token would bar the second frame on the first), the compare the main lane's.
Frames looked at: pan-v-pp-wiki-frame-60.jpg (both sidebars in place, the article's text continuous),
pan-v-ps-long-frame-120.jpg (the fixed header at its place, paragraphs 41-58 in order, 50 highlighted),
pan-v-pp-long-frame-60.jpg, pan-v-ps-long-frame-60.jpg.
Two-deep frames are rare under verify (the lane seldom gets ahead of a first frame that costs two composites):
a second pair with JIHAD_GPU_EXPRESS_EARLY=1 (the second frame posted right behind the first) follows (v-pe-*).
v-pe-long (both lanes, the second frame EARLY) 70 - 0 0 61 33
v-pe-wiki 42 - 0 0 39 22
(JIHAD_GPU_EXPRESS_EARLY=1: the second frame posted right behind the first, before it is composed — the most frames
built on a first in flight the lane can make; every one of the 55 two-deep frames dressed by the double restore
compared equal to its reference but for the resampling class.) Frames looked at: pan-v-pe-wiki-frame-60.jpg,
pan-v-pe-long-frame-120.jpg.
The review (one ck:inspector; Codex out of credits until Nov 4th) and the second build
---------------------------------------------------------------------------------------
REVISE: three P1 (F1 a third frame on a two-deep first reaches past the two records; F2 a per-part paint count that
could return to an old value after the map's clear, and paints of a layer before it was a part not its own; F3 the
signature carried a part's size but not where it sits — a header wider than the frame moving sideways, a part moving
inside a merged rect, a zoom rounding to the same size), one P2 (F4 under verify the reference's whole read parts the
first frame's read from the second's copy, so the copy never caught the dress the double restore exists for), three P3
(F5 a store could evict a picture the same frame draws; F6 two fallback rects of one size shared a signature; F7 a
verify buffer leaked on the !mPage path). All fixed (context/impl/impl-review-findings-t505-cpuparts.md, review 3):
no second frame on a two-deep first; every layer's last paint stamped from one counter that only goes up and each
subtree layer's stamp in its part's signature; a part's scale, quarter-pixel phase, place inside its rect and size in
the signature; JIHAD_GPU_TEST_DRESS_HOLD=<pre|mid>:<ms> holds a first frame's dress so the second's copy catches it
(pre: before the restores; mid: between the record and the draw); this frame's pictures made newest before a store;
the fallback mixes the rect's index and place. The engine commit amended (adfaa7ff30), libxul a597f8a2, daemon
5fedb62f, pushed to Enyo; the rounds below are on that build.
The second build (the review's fixes; libxul a597f8a2, daemon 5fedb62f then 340aeb0a)
---------------------------------------------------------------------------------------
By finger (fps while frames flow): Wikipedia the long page
pw2: the whole mode 10.8 19.2
pp2: per part 11.4 19.4
ps2: per part + the express second frame 11.4 18.1
Wikipedia's passes 21 a round again; per part 12 of them the contents panel alone (6 before: the signature now
carries each part's place and phase, so the right panel's own changes are told apart from the contents panel's).
Verify after the pan mark (pan-run.sh "pan 0 3000 15000", JIHAD_GPU_PARTS_PER=1; v2-* on daemon 5fedb62f):
round verified wrong structural express two deep
v2-pp-long (the main lane alone) 76 0 0 0 0
v2-pp-wiki 52 5 0 0 0 <- see below
v2-pe-long (both lanes, the second frame EARLY) 66 0 0 59 29
v2-pe-wiki 35 0 0 35 13
v2-ph-long (EARLY + the first's dress held 80 ms 55 0 0 49 24
v2-ph-wiki before its restores) 34 1 0 33 13
v2-pm-long (EARLY + held between its record 61 0 0 54 27
v2-pm-wiki and its draw) 34 1 0 34 16
The two single frames in the hold rounds: an express frame at the load (y -141, a 12-row band, 1 row) and a
two-deep frame at the pan's start (y 82, 2 rows at 24..35) — both in rows THOSE jobs read fresh (the band
23..62), none in restored rows: the reference composed before the job met the page's load still changing.
With the first frame's dress held, the second's copy caught it before its restores (pre) and between its record
and its draw (mid) on 24-27 two-deep frames a round on the long page and 13-16 on Wikipedia: 0 wrong rows in
the restored rows of every one. Frames looked at: pan-v2-pm-wiki-frame-60.jpg, pan-v2-ph-long-frame-60.jpg.
v2-pp-wiki's five wrong frames (the main lane): a frame recorded at a position 316 rows off (the dress log's
"y 569, frameY 253/1"; 316 = the previous job's shift), then own-slot frames built on it copying from the wrong
rows (687, 703, 474, 138, 60 rows wrong, healing as the shifts moved the rows off the top). Cause: a second
main-lane job whose in-flight base was refused (the parts on: review 2's F4, 2026-11-02) builds on the newest
finished frame, which is NOT the previous composite, but still told the engine the base was (engineShift), so the
engine added its diff shift — against the in-flight composite — to that base's top. Latent since 2026-11-02, exposed
by a run with two main-lane jobs in flight; the first build's run had the same two frameY-valid frames with the
flag right by luck. Fixed in the daemon (the flag follows the base actually taken; 340aeb0a, pushed daemon-only).
Verify again, the main lane alone (v3-*): Wikipedia 55 and 52 verified, the long page 80 — 0 wrong, 0 structurally
wrong.
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+309 -142
View File
@@ -1950,6 +1950,34 @@ bool BrowserPageGoanna::cpuPartsFor() const {
return cpuPartsOn() && jihadNowMs() >= mPartsOffUntil;
}
// T-505 (per part): with JIHAD_GPU_PARTS_PER=1 (or the gate file <state>/parts-per, read once) the engine is sent the
// signatures of the part pictures held and reads only the rects it lacks; without it every part is read when the whole
// signature moved (E2's rule). The pictures are kept by part either way
bool BrowserPageGoanna::partsPerRect() const {
static const bool sOn = [] {
const char* e = getenv("JIHAD_GPU_PARTS_PER");
if (e) return strcmp(e, "0") != 0;
const std::string sd = jihad::RuntimeStateDir();
return !sd.empty() && access((sd + "/parts-per").c_str(), F_OK) == 0;
}();
return sOn;
}
int BrowserPageGoanna::partsHaveList(uint32_t* out, int cap) const {
if (!partsPerRect()) return -1;
if (mPicForce) return 0; // a frame found no picture of a part: every rect read
int n = 0;
for (int k = 0; k < kPartPics && n < cap; k++) if (mPartPics[k].ok) out[n++] = mPartPics[k].sig;
return n;
}
const BrowserPageGoanna::SlotParts* BrowserPageGoanna::slotRecord(int slot, uint32_t serial) const {
if (slot < 0 || slot >= 3 || !serial) return nullptr;
if (mSlotParts[slot].kind == 1 && mSlotParts[slot].serial == serial) return &mSlotParts[slot];
if (mSlotPrev[slot].kind == 1 && mSlotPrev[slot].serial == serial) return &mSlotPrev[slot];
return nullptr;
}
// the parts buffers (E2): one lent to each job without its parts, by token
unsigned char* BrowserPageGoanna::partsBufTake() {
const int64_t now = jihadNowMs();
@@ -1989,15 +2017,21 @@ void BrowserPageGoanna::partsBufsBury() {
mPartsLent.clear();
}
// A frame composed without its parts, in its slot: a picture its job read goes into the cache; the picture of the
// frame's own signature drawn over it rect by rect (premultiplied: out = part + out x (1 - alpha)); what lay beneath the
// parts of the frame it was built on back where they landed (outside the rows the job read: those are new); what lies
// beneath this frame's parts kept. The outcome says why a frame was not dressed (it is then not shown).
// A frame composed without its parts, in its slot: the pictures its job read (the rects the pass read, packed in rect
// order) go into the cache by their signature; each of the frame's parts is drawn over it from the picture of that
// part's own signature (premultiplied: out = part + out x (1 - alpha)); what lay beneath the parts of the frame it was
// built on back where they landed (outside the rows the job read: those are new); twoDeep (the express lane's second
// frame on the first in flight, review C1): what lay beneath the parts of THAT frame's base too, where they landed,
// outside the rows either job read — the first frame's pixels were copied while this thread dressed them, so every
// pixel its dress touches is put back from the records, whatever the copy caught. What lies beneath this frame's
// parts is kept. The outcome says why a frame was not dressed (it is then not shown).
__attribute__((optimize("O2"))) int BrowserPageGoanna::cpuPartsDress(int s, unsigned char* pixels, const GoannaRenderPage::ComposeResult& cr,
int H, int w, int src, long srcY, int jobY, const unsigned char* buf) {
int H, int w, int src, long srcY, int jobY, const unsigned char* buf,
bool twoDeep) {
if (cr.partsFlags & 1) return kPartsCovered;
if (cr.partsFlags & 2) return kPartsTooBig;
// this frame's parts (the engine's list, its order and sizes the signature's)
if (!cr.partsSigKnown) return kPartsNoPic; // an engine without each rect's signature
// this frame's parts (the engine's list, its order and sizes)
int nf = 0, fr[4][4];
size_t need = 0;
for (int i = 0; i < cr.nStuck && i < 4; i++) {
@@ -2006,53 +2040,99 @@ __attribute__((optimize("O2"))) int BrowserPageGoanna::cpuPartsDress(int s, unsi
fr[nf][0] = x0; fr[nf][1] = y0; fr[nf][2] = x1; fr[nf][3] = y1; nf++;
need += (size_t)(x1 - x0) * (y1 - y0) * 4;
}
// the picture its own job read, into the cache (the least used one makes room)
if (cr.partsBytes > 0 && buf && (size_t)cr.partsBytes == need && need <= (size_t)kPartsCap) {
int at = 0;
for (int k = 0; k < 3; k++) {
if (mPics[k].ok && mPics[k].sig == cr.partsGen) { at = k; break; }
if (!mPics[k].ok) { at = k; break; }
if (mPics[k].used < mPics[at].used) at = k;
}
PartsPic& np = mPics[at];
np.sig = cr.partsGen; np.n = nf; np.ok = true;
size_t o = 0;
for (int k = 0; k < nf; k++) {
np.wh[k][0] = fr[k][2] - fr[k][0]; np.wh[k][1] = fr[k][3] - fr[k][1]; np.off[k] = o;
o += (size_t)np.wh[k][0] * np.wh[k][1] * 4;
}
np.px.assign(buf, buf + need);
np.used = ++mPicClock;
mPicNewest = np.sig; mPicHave = true; mPicForce = false;
static uint32_t sPicN = 0;
if (sPicN++ < 20 || (sPicN % 50) == 0)
JIHAD_GPU_LOG("[jihad-gpu] cpu parts: a picture of %d parts (%zu KB, signature %08x) came with the frame\n", nf, need / 1024, np.sig);
}
const PartsPic* p = nullptr; // (no parts in the frame: nothing to draw, only what lay beneath the base's put back)
for (int k = 0; k < 3 && nf > 0; k++) if (mPics[k].ok && mPics[k].sig == cr.partsGen) p = &mPics[k];
if (nf > 0 && (!p || p->n != nf)) return kPartsNoPic;
// (review F5) the pictures this frame holds already are the newest now, before the store makes room: a store must
// not evict a picture this very frame draws
for (int k = 0; k < nf; k++)
if (p->wh[k][0] != fr[k][2] - fr[k][0] || p->wh[k][1] != fr[k][3] - fr[k][1]) return kPartsNoPic;
for (int q = 0; q < kPartPics; q++)
if (mPartPics[q].ok && mPartPics[q].sig == cr.partsSig[k]) mPartPics[q].used = ++mPicClock;
// JIHAD_GPU_TEST_DRESS_HOLD=<pre|mid>:<ms> (device rounds, review F4): a first frame's dress held that long before
// its restores (pre) or between its record and its draw (mid), so a second frame's copy of its slot catches the
// dress where the double restore must cover it (under verify the reference's read parts the two otherwise)
static const char* sHold = getenv("JIHAD_GPU_TEST_DRESS_HOLD");
static const int sHoldMs = sHold && strchr(sHold, ':') ? atoi(strchr(sHold, ':') + 1) : 0;
static const bool sHoldPre = sHold && !strncmp(sHold, "pre", 3), sHoldMid = sHold && !strncmp(sHold, "mid", 3);
if (sHoldMs > 0 && sHoldPre && !twoDeep) usleep(sHoldMs * 1000);
// the pictures its own job read, into the cache (a part's own slot, else an empty one, else the least used)
if (cr.partsBytes > 0 && buf) {
size_t readNeed = 0;
for (int k = 0; k < nf; k++)
if (cr.partsRead & (1 << k)) readNeed += (size_t)(fr[k][2] - fr[k][0]) * (fr[k][3] - fr[k][1]) * 4;
if ((size_t)cr.partsBytes == readNeed && readNeed <= (size_t)kPartsCap) {
size_t o = 0;
for (int k = 0; k < nf; k++) {
if (!(cr.partsRead & (1 << k))) continue;
const int pw = fr[k][2] - fr[k][0], ph = fr[k][3] - fr[k][1];
const size_t bytes = (size_t)pw * ph * 4;
int at = -1;
for (int q = 0; q < kPartPics && at < 0; q++) if (mPartPics[q].ok && mPartPics[q].sig == cr.partsSig[k]) at = q;
if (at < 0) {
at = 0;
for (int q = 0; q < kPartPics; q++) {
if (!mPartPics[q].ok) { at = q; break; }
if (mPartPics[q].used < mPartPics[at].used) at = q;
}
}
PartPic& np = mPartPics[at];
np.sig = cr.partsSig[k]; np.w = pw; np.h = ph; np.ok = true;
np.px.assign(buf + o, buf + o + bytes);
np.used = ++mPicClock;
o += bytes;
static uint32_t sPicN = 0;
if (sPicN++ < 40 || (sPicN % 50) == 0)
JIHAD_GPU_LOG("[jihad-gpu] cpu parts: a picture of part %d (%dx%d, %zu KB, signature %08x) came with the frame (mask %x of %d parts)\n",
k, pw, ph, bytes / 1024, np.sig, cr.partsRead, nf);
}
} else {
static uint32_t sBadN = 0;
if (sBadN++ < 20) JIHAD_GPU_LOG("[jihad-gpu] cpu parts: the pass's %d bytes are not the read rects' %zu (mask %x): not kept\n", cr.partsBytes, readNeed, cr.partsRead);
}
}
// each part's picture, by its signature and size
const PartPic* pics[4] = { nullptr, nullptr, nullptr, nullptr };
for (int k = 0; k < nf; k++) {
for (int q = 0; q < kPartPics && !pics[k]; q++) {
const PartPic& p = mPartPics[q];
if (p.ok && p.sig == cr.partsSig[k] && p.w == fr[k][2] - fr[k][0] && p.h == fr[k][3] - fr[k][1]) pics[k] = &p;
}
if (!pics[k]) return kPartsNoPic;
}
const long y = cr.frameYValid ? cr.frameY : jobY;
auto readRow = [&](long r) { return (r >= cr.readLo && r < cr.readHi) || (r >= cr.read2Lo && r < cr.read2Hi); };
// what lay beneath the old frame's parts, where they landed, outside the rows read
int putBack = 0, baseN = 0, baseR[4] = { 0, 0, 0, 0 };
uint32_t baseProbe = 0;
int putBack = 0, putBack2 = 0, baseN = 0, baseR[4] = { 0, 0, 0, 0 };
uint32_t baseProbe = 0, usedSerial = 0;
long d = 0;
size_t baseBytes = 0;
if (src >= 0) {
const SlotParts& sp = mSlotParts[src];
if (sp.kind != 1) return kPartsNoBase;
d = (long)(cr.frameYValid ? cr.frameY : jobY) - srcY;
baseN = sp.n; baseBytes = sp.beneath.size();
d = y - srcY;
baseN = sp.n; baseBytes = sp.beneath.size(); usedSerial = sp.serial;
if (sp.n > 0) for (int c = 0; c < 4; c++) baseR[c] = sp.r[0][c];
if (sp.n > 0 && sp.beneath.size() >= (size_t)(sp.r[0][2] - sp.r[0][0]) * 4 * 2) // a pixel of the record's second row (what lay beneath the part's top)
memcpy(&baseProbe, sp.beneath.data() + (size_t)(sp.r[0][2] - sp.r[0][0]) * 4 + 100 * 4, 4);
auto readRow = [&](long y) { return (y >= cr.readLo && y < cr.readHi) || (y >= cr.read2Lo && y < cr.read2Hi); };
if (twoDeep && sp.baseSlot >= 0) {
// the base's base: its record (by serial: a newer frame may have taken its slot) and the shift the base was built with
const SlotParts* xp = slotRecord(sp.baseSlot, sp.baseSerial);
if (!xp) return kPartsNoBase;
const long dA = sp.y - sp.baseY, d2 = d + dA;
auto readRowA = [&](long r) { return (r >= sp.readLo && r < sp.readHi) || (r >= sp.read2Lo && r < sp.read2Hi); }; // in the base's frame
for (int k = xp->n - 1; k >= 0; k--) {
const int x0 = xp->r[k][0], x1 = xp->r[k][2], rw = x1 - x0;
for (int yy = xp->r[k][1]; yy < xp->r[k][3]; yy++) {
const long ny = yy - d2;
if (ny < 0 || ny >= H || readRow(ny) || readRowA(ny + d)) continue;
memcpy(pixels + ((size_t)ny * w + x0) * 4, xp->beneath.data() + xp->off[k] + (size_t)(yy - xp->r[k][1]) * rw * 4, (size_t)rw * 4);
putBack2++;
}
}
}
for (int k = sp.n - 1; k >= 0; k--) { // (review F3) the first recorded last: where rects overlap, its record is the oldest
const int x0 = sp.r[k][0], x1 = sp.r[k][2], rw = x1 - x0;
for (int y = sp.r[k][1]; y < sp.r[k][3]; y++) {
const long ny = y - d;
for (int yy = sp.r[k][1]; yy < sp.r[k][3]; yy++) {
const long ny = yy - d;
if (ny < 0 || ny >= H || readRow(ny)) continue;
memcpy(pixels + ((size_t)ny * w + x0) * 4, sp.beneath.data() + sp.off[k] + (size_t)(y - sp.r[k][1]) * rw * 4, (size_t)rw * 4);
memcpy(pixels + ((size_t)ny * w + x0) * 4, sp.beneath.data() + sp.off[k] + (size_t)(yy - sp.r[k][1]) * rw * 4, (size_t)rw * 4);
putBack++;
}
}
@@ -2061,28 +2141,34 @@ __attribute__((optimize("O2"))) int BrowserPageGoanna::cpuPartsDress(int s, unsi
// the restore, frame by frame (the long page's first shifted frame kept the base's header where it landed, 2026-11-02)
static uint32_t sDressN = 0;
if (sDressN++ < 80 || (sDressN % 50) == 0)
JIHAD_GPU_LOG("[jihad-gpu] cpu parts: slot %d (y %ld, frameY %ld/%d) dressed on slot %d (y %ld; %d parts, first %d,%d..%d,%d, %zu B beneath, (100,1)=%08x): d %ld, read %ld..%ld %ld..%ld, %d rows put back; its own %d parts, first %d,%d..%d,%d\n",
JIHAD_GPU_LOG("[jihad-gpu] cpu parts: slot %d (y %ld, frameY %ld/%d) dressed on slot %d (y %ld; %d parts, first %d,%d..%d,%d, %zu B beneath, (100,1)=%08x): d %ld, read %ld..%ld %ld..%ld, %d rows put back%s%d; its own %d parts, first %d,%d..%d,%d (sig %08x)\n",
s, (long)jobY, (long)cr.frameY, (int)cr.frameYValid, src, srcY, baseN, baseR[0], baseR[1], baseR[2], baseR[3], baseBytes, baseProbe, d,
cr.readLo, cr.readHi, cr.read2Lo, cr.read2Hi, putBack, nf, nf > 0 ? fr[0][0] : 0, nf > 0 ? fr[0][1] : 0, nf > 0 ? fr[0][2] : 0, nf > 0 ? fr[0][3] : 0);
cr.readLo, cr.readHi, cr.read2Lo, cr.read2Hi, putBack, twoDeep ? ", two deep: its base's " : " (", twoDeep ? putBack2 : 0,
nf, nf > 0 ? fr[0][0] : 0, nf > 0 ? fr[0][1] : 0, nf > 0 ? fr[0][2] : 0, nf > 0 ? fr[0][3] : 0, nf > 0 ? cr.partsSig[0] : 0u);
}
if (p) const_cast<PartsPic*>(p)->used = ++mPicClock;
// this frame's: what lies beneath kept (every part's before any is drawn: where two rects overlap, the second's
// record would hold the first's drawn pixels, review F3), then the parts drawn over it
for (int k = 0; k < nf; k++) const_cast<PartPic*>(pics[k])->used = ++mPicClock;
// this frame's record (the slot's last one kept aside: a frame built on this one in flight restores from it too);
// what lies beneath kept (every part's before any is drawn: where two rects overlap, the second's record would hold
// the first's drawn pixels, review F3), then the parts drawn over it
mSlotPrev[s] = std::move(mSlotParts[s]);
SlotParts& dp = mSlotParts[s];
dp = SlotParts();
dp.beneath.resize(need);
size_t at = 0;
for (int i = 0; i < nf; i++) {
const int x0 = fr[i][0], rw = fr[i][2] - x0;
dp.off[i] = at;
for (int c = 0; c < 4; c++) dp.r[i][c] = fr[i][c];
for (int y = fr[i][1]; y < fr[i][3]; y++, at += (size_t)rw * 4)
memcpy(dp.beneath.data() + at, pixels + ((size_t)y * w + x0) * 4, (size_t)rw * 4);
for (int yy = fr[i][1]; yy < fr[i][3]; yy++, at += (size_t)rw * 4)
memcpy(dp.beneath.data() + at, pixels + ((size_t)yy * w + x0) * 4, (size_t)rw * 4);
}
if (sHoldMs > 0 && sHoldMid && !twoDeep) usleep(sHoldMs * 1000); // (review F4) between the record and the draw
for (int i = 0; i < nf; i++) {
const int x0 = fr[i][0], rw = fr[i][2] - x0;
for (int y = fr[i][1]; y < fr[i][3]; y++) {
uint32_t* row = (uint32_t*)(pixels + ((size_t)y * w + x0) * 4);
const uint32_t* pp = (const uint32_t*)(p->px.data() + p->off[i]) + (size_t)(y - fr[i][1]) * rw;
const unsigned char* ppx = pics[i]->px.data();
for (int yy = fr[i][1]; yy < fr[i][3]; yy++) {
uint32_t* row = (uint32_t*)(pixels + ((size_t)yy * w + x0) * 4);
const uint32_t* pp = (const uint32_t*)ppx + (size_t)(yy - fr[i][1]) * rw;
for (int x = 0; x < rw; x++) {
const uint32_t sv = pp[x], a = sv >> 24;
if (a == 255) { row[x] = sv; continue; }
@@ -2095,6 +2181,11 @@ __attribute__((optimize("O2"))) int BrowserPageGoanna::cpuPartsDress(int s, unsi
}
}
dp.n = nf; dp.kind = 1;
dp.y = y; dp.baseSlot = src; dp.baseY = srcY; dp.baseSerial = usedSerial;
dp.readLo = cr.readLo; dp.readHi = cr.readHi; dp.read2Lo = cr.read2Lo; dp.read2Hi = cr.read2Hi;
dp.serial = ++mSlotSerial;
if (!dp.serial) dp.serial = ++mSlotSerial;
mPicNewest = cr.partsGen; mPicHave = true; mPicForce = false;
mPartsMissRun = 0;
return kPartsDressed;
}
@@ -2129,10 +2220,14 @@ void BrowserPageGoanna::cpuPartsRefused(int outcome, int nStuck) {
}
void BrowserPageGoanna::cpuPartsForget() {
for (int k = 0; k < 3; k++) { mPics[k] = PartsPic(); }
for (int k = 0; k < kPartPics; k++) { mPartPics[k] = PartPic(); }
mPicHave = false; mPicForce = false;
// a slot holding parts drawn on the CPU is no base for either kind any more
for (int i = 0; i < 3; i++) { if (mSlotParts[i].kind == 1) mSlotParts[i].kind = -1; std::vector<unsigned char>().swap(mSlotParts[i].beneath); }
for (int i = 0; i < 3; i++) {
if (mSlotParts[i].kind == 1) mSlotParts[i].kind = -1;
std::vector<unsigned char>().swap(mSlotParts[i].beneath);
mSlotPrev[i] = SlotParts();
}
}
// The inject pan (JihadBrowserServer) used to send the card's scroll commands to the daemon itself; the card's
@@ -4863,11 +4958,13 @@ bool BrowserPageGoanna::gpuJobStart(int slot, int key, unsigned char* pixels, si
// compositor thread may be copying it for this one)
// (review F4) and not on a frame without its parts either: the parts turned off between the two posts (a cover
// refusal, the document's three misses) would copy a frame this thread dresses at its poll
bool srcInFlight = false; // the base is the job in flight (the previous composite): the engine's diff shift is exact
if (!co && !sWhole && mGpuNextSecond && mGpuNextBaseKey && mGpuNextBaseKey != key && mGpuFixedHi >= mGpuFixedLo && !mGpuNextBaseContentOnly) {
// round 130: the job in flight is the base (its frame is complete when this job runs)
const long dy = (long)y - mGpuNextBaseY;
unsigned char* bb = (dy > -h && dy < h) ? attachShm(mGpuNextBaseKey) : nullptr;
if (bb) {
srcInFlight = true;
src = bb + sizeof(BrowserOffscreenInfo) + (size_t)mGpuNextBaseWin * stride; // T-517: its window
srcYForEngine = mGpuNextBaseY;
shift = dy;
@@ -5038,17 +5135,22 @@ bool BrowserPageGoanna::gpuJobStart(int slot, int key, unsigned char* pixels, si
}
// the diff's shift is exact when the base is the previous composite: the newest finished frame
// for a first job, the job in flight for a second (round 130)
const bool baseIsPrevious = src != nullptr && (!mGpuNextSecond || mGpuNextBaseKey != 0);
// (2026-11-03) a second job whose in-flight base was refused (the parts on: review 2's F4) builds on the newest
// finished frame, which is NOT the previous composite: the engine's diff shift (against the in-flight composite)
// would be added to that base's top and the frame recorded 316 rows off, the next own-slot frames copying from the
// wrong rows (v2-pp-wiki, 5 wrong frames); only a base taken from the job in flight is the previous composite
const bool baseIsPrevious = src != nullptr && (!mGpuNextSecond || srcInFlight);
// T-515: the layers painted already (the main lane's paint window, the frame lock let go of): the
// compose posted on its own
unsigned char* const winPixels = pixels + (size_t)win * stride; // T-517
const size_t winBytes = bytes - (size_t)win * stride;
unsigned char* const pbuf = co ? partsBufTake() : nullptr; // T-505 (E2): where its job reads the parts' picture
uint32_t have[16]; const int nHave = co ? partsHaveList(have, 16) : -1; // (per part) the pictures held
const uint32_t token = mGpuPainted
? mPage->ComposePost(winPixels, winBytes, h, sLo, sHi, s2Lo, s2Hi, shift, src, srcYForEngine, baseIsPrevious, nullptr, colX0, colX1, winMoved,
false, false, false, false, co, pbuf, kPartsCap, partsHave())
false, false, false, false, co, pbuf, kPartsCap, partsHave(), have, nHave)
: mPage->ComposeStart(winPixels, winBytes, sLo, sHi, s2Lo, s2Hi, shift, src, srcYForEngine, baseIsPrevious, false, colX0, colX1, winMoved,
co, pbuf, kPartsCap, partsHave());
co, pbuf, kPartsCap, partsHave(), have, nHave);
partsBufLend(pbuf, token);
if (token) mXLastPostToken = token;
if (token && slot >= 0) mGpuSlotValid[slot] = false; // T-517 (review): being written; its publish records it again
@@ -5403,7 +5505,21 @@ bool BrowserPageGoanna::xStart(int64_t now, double Z, const XWant& want, int64_t
// lay beneath them kept), and a frame of the other kind on its own. Not a second frame on the first in flight
// without the parts (review C1): the first's pixels are dressed on this thread while the compositor copies them
const bool co = cpuPartsFor();
if (base && (aBase ? (co || aBase->contentOnly) : mSlotParts[b].kind != (co ? 1 : 0))) base = false;
// (2026-11-03) the second frame on the first without its parts again, behind JIHAD_GPU_EXPRESS_SECOND_PARTS=1 (or
// <state>/express-second-parts): its dress puts back what lay beneath the first's parts AND beneath the first's
// base's parts (cpuPartsDress, twoDeep), so the copy may catch the first's dress anywhere; both frames of one kind
static const bool sSecondParts = [] {
const char* e = getenv("JIHAD_GPU_EXPRESS_SECOND_PARTS");
if (e) return strcmp(e, "0") != 0;
const std::string sd = jihad::RuntimeStateDir();
return !sd.empty() && access((sd + "/express-second-parts").c_str(), F_OK) == 0;
}();
if (base) {
// (review F1) never on a first frame that is itself two deep: its copy may hold ITS base's base's parts, which a
// restore two records deep would not reach (the chain would grow without bound)
if (aBase) { if (co != aBase->contentOnly || (co && (!sSecondParts || aBase->twoDeep))) base = false; }
else if (mSlotParts[b].kind != (co ? 1 : 0)) base = false;
}
const long dy = base ? (long)y - baseY : 0;
if (base && !(dy > -H && dy < H)) base = false;
if (aBase && !base) return false; // the second frame only builds on the first
@@ -5580,12 +5696,27 @@ bool BrowserPageGoanna::xStart(int64_t now, double Z, const XWant& want, int64_t
const bool viewOnly = sViewFirst && !aBase && !base && !own && (mXPub.slot < 0 || fabs(mXPub.zoom - Z) > 1e-9) &&
os > 0 && os + S.h <= H;
if (viewOnly) { lo = os; hi = os + S.h; lo2 = hi2 = 0; }
// JIHAD_GPU_VERIFY (device rounds): the whole frame composed right BEFORE this job, on the same tree at the same view
// (a verify composite leaves the diff baseline alone; posted after, as the main lane's, its token would bar the second
// frame on this one: aBase->token == mXLastPostToken); compared at the poll (verifyCompare)
static const bool sVerify = getenv("JIHAD_GPU_VERIFY") != nullptr;
uint32_t vToken = 0; int vBuf = -1;
if (sVerify && !viewOnly && (hi - lo < H || (own ? ownDy : (base ? dy : 0)) != 0)) {
const size_t refBytes = (size_t)w * H * 4;
vBuf = verifyRefAcquire(refBytes);
if (vBuf >= 0) {
vToken = mPage->ComposePost(sVerifyRef[vBuf].buf.data(), refBytes, H, 0, H, 0, 0, 0, nullptr, 0, false, view, 0, -1, false, true);
if (!vToken) { verifyRefRelease(vBuf, false); vBuf = -1; }
}
}
unsigned char* const pbuf = (co && !viewOnly) ? partsBufTake() : nullptr; // T-505 (E2)
uint32_t have[16]; const int nHave = pbuf ? partsHaveList(have, 16) : -1; // (per part) the pictures held
const uint32_t token = mPage->ComposePost(pixels, S.seg[slot] - S.hdr - (size_t)xWin * xStride, H, lo, hi, lo2, hi2,
own ? ownDy : (base ? dy : 0), src, srcY, base && !own, view, 0, -1, xMoved,
false, viewOnly, false, false, co && !viewOnly, pbuf, kPartsCap, partsHave());
false, viewOnly, false, false, co && !viewOnly, pbuf, kPartsCap, partsHave(), have, nHave);
partsBufLend(pbuf, token);
if (!token) {
if (vToken) { mPage->ComposeForget(vToken); verifyRefRelease(vBuf, true); }
if (++mXFailures >= 3) fprintf(stderr, "[jihad-gpu] express lane off for this page: the compositor took no frame three times\n");
return false;
}
@@ -5602,11 +5733,110 @@ bool BrowserPageGoanna::xStart(int64_t now, double Z, const XWant& want, int64_t
j.contentOnly = co && !viewOnly; // T-505
j.srcSlot = own ? slot : (base ? b : -1);
j.srcY = own ? mGpuSlotY[slot] : (base ? baseY : 0);
j.twoDeep = aBase != nullptr && j.contentOnly; // the first's dress may run while the compositor copies it
j.vToken = vToken; j.vBuf = vBuf; j.askLo = lo; j.askHi = hi; j.ask2Lo = lo2; j.ask2Hi = hi2;
return true;
}
// JIHAD_GPU_VERIFY: the frame against its whole composite (the main lane's T-503/T-513 compare, out of its poll on
// 2026-11-03 so the express lane's frames — the second frame on the first in flight — are checked the same way)
void BrowserPageGoanna::verifyCompare(const char* lane, int slot, int y, long shift, long askLo, long askHi, long ask2Lo, long ask2Hi,
const unsigned char* pixels, const unsigned char* refPx, int w, int h, int pageLo, int pageHi) {
const size_t bytes = (size_t)w * h * 4;
std::string runs; int nRuns = 0, bad = 0;
for (int r = pageLo; r < pageHi; ) { // T-505: the page's rows (the rest is filled white)
if (memcmp(pixels + (size_t)r * w * 4, refPx + (size_t)r * w * 4, (size_t)w * 4) == 0) { ++r; continue; }
int r0 = r;
while (r < pageHi && memcmp(pixels + (size_t)r * w * 4, refPx + (size_t)r * w * 4, (size_t)w * 4) != 0) ++r;
bad += r - r0;
if (nRuns++ < 8) { char b[48]; snprintf(b, sizeof b, " %d..%d", r0, r); runs += b; }
}
// T-503: rows that are WRONG, not just resampled. A scroll by a fractional device pixel
// (the card's zoom) samples the text at another phase than the shifted frame: up to
// ~24 levels on glyph edges and single pixels at tile seams. A wrong shift, a missing
// band or a doubled header puts many pixels far off in a row.
std::string wrongRuns; int nWrong = 0, wrongRows = 0;
auto rowWrong = [&](int r) {
const unsigned char* a = pixels + (size_t)r * w * 4;
const unsigned char* b = refPx + (size_t)r * w * 4;
int far = 0;
for (int x = 0; x < w && far <= 4; x++, a += 4, b += 4) {
if (abs(a[0] - b[0]) > 64 || abs(a[1] - b[1]) > 64 || abs(a[2] - b[2]) > 64) far++;
}
return far > 4;
};
for (int r = pageLo; r < pageHi; ) {
if (!rowWrong(r)) { ++r; continue; }
int r0 = r;
while (r < pageHi && rowWrong(r)) ++r;
wrongRows += r - r0;
if (nWrong++ < 8) { char b[48]; snprintf(b, sizeof b, " %d..%d", r0, r); wrongRuns += b; }
}
{
// T-503: the first frame with many wrong rows, dumped with its whole composite
static bool sDumpedWrong = false;
if (wrongRows > 50 && !sDumpedWrong && getenv("JIHAD_GPU_VERIFY_DUMP_WRONG")) {
sDumpedWrong = true;
FILE* f1 = fopen("/tmp/vw-slot.raw", "wb"); if (f1) { fwrite(pixels, 1, bytes, f1); fclose(f1); }
FILE* f2 = fopen("/tmp/vw-whole.raw", "wb"); if (f2) { fwrite(refPx, 1, bytes, f2); fclose(f2); }
JIHAD_GPU_LOG("[jihad-gpu] verify: the first wrong frame dumped (/tmp/vw-slot.raw, /tmp/vw-whole.raw)\n");
}
}
// Structural errors, by blocks of 16 rows: a block whose content sits better at another
// row offset (-3..3) than at its own (misplaced: a wrong shift, a doubled header), or
// that differs heavily at every offset (missing or stale content). A sharp page at a
// fractional zoom resamples text by a sub-pixel phase between frames: best at offset
// 0, a mean difference of a few levels.
int structural = 0;
std::string structRuns;
auto blockMean = [&](int r0, int r1, int off) {
long long sum = 0, n = 0;
for (int r = r0; r < r1; r++) {
const int rr = r + off;
if (rr < 0 || rr >= h) continue;
const unsigned char* a = pixels + (size_t)r * w * 4;
const unsigned char* b = refPx + (size_t)rr * w * 4;
for (int x = 0; x < w; x++, a += 4, b += 4) sum += abs(a[0] - b[0]) + abs(a[1] - b[1]) + abs(a[2] - b[2]);
n += w * 3;
}
return n ? (double)sum / n : 0.0;
};
for (int b0 = pageLo; b0 < pageHi; b0 += 16) {
const int b1 = std::min(pageHi, b0 + 16);
bool any = false;
for (int r = b0; r < b1 && !any; r++) any = rowWrong(r);
if (!any) continue;
const double m0 = blockMean(b0, b1, 0);
double best = m0;
for (int off = -3; off <= 3; off++) if (off) best = std::min(best, blockMean(b0, b1, off));
if ((best < 0.6 * m0 && m0 > 2.0) || best > 24.0) {
structural += b1 - b0;
if (structRuns.size() < 80) { char t[32]; snprintf(t, sizeof t, " %d..%d", b0, b1); structRuns += t; }
}
}
{
static bool sDumpedStruct = false;
if (structural > 0 && !sDumpedStruct && getenv("JIHAD_GPU_VERIFY_DUMP_WRONG")) {
sDumpedStruct = true;
FILE* f1 = fopen("/tmp/vs-slot.raw", "wb"); if (f1) { fwrite(pixels, 1, bytes, f1); fclose(f1); }
FILE* f2 = fopen("/tmp/vs-whole.raw", "wb"); if (f2) { fwrite(refPx, 1, bytes, f2); fclose(f2); }
JIHAD_GPU_LOG("[jihad-gpu] verify: the first structurally wrong frame dumped (/tmp/vs-slot.raw, /tmp/vs-whole.raw)\n");
}
}
JIHAD_GPU_LOG("[jihad-gpu] verify %sslot %d (y %d, shift %ld, bands %ld..%ld %ld..%ld): %d rows differ in %d runs:%s%s; %d rows wrong:%s%s; %d rows structurally wrong:%s\n",
lane, slot, y, shift, askLo, askHi, ask2Lo, ask2Hi, bad, nRuns, runs.c_str(), nRuns > 8 ? " ..." : "",
wrongRows, wrongRuns.c_str(), nWrong > 8 ? " ..." : "", structural, structRuns.c_str());
}
void BrowserPageGoanna::xVerifyDrop(XJob& j) {
if (!j.vToken) return;
if (mPage) mPage->ComposeForget(j.vToken);
if (j.vBuf >= 0) verifyRefRelease(j.vBuf, true);
j.vToken = 0; j.vBuf = -1;
}
bool BrowserPageGoanna::xPoll(int64_t now) {
if (!mPage) { mXJob.on = false; return false; }
if (!mPage) { xVerifyDrop(mXJob); mXJob.on = false; return false; }
const bool genOk = mXJob.gen == mXGeomGen && mPage->GpuPath();
GoannaRenderPage::ComposeResult cr;
const int r = mPage->ComposePoll(mXJob.token, genOk ? mXJob.pixels : nullptr, &cr);
@@ -5629,6 +5859,7 @@ bool BrowserPageGoanna::xPoll(int64_t now) {
// the buffers or the geometry changed under it, or it failed: dropped, and the one built on it
pubDirtyAll(); // T-527
const bool adopted = mXJob.adopted;
xVerifyDrop(mXJob);
mXJob = XJob(); xForget(mXJob2);
gpuSlotsLost(); // T-517: its change is in no slot's record
// T-527 (review): an adopted main-lane job's failure is the main lane's (its count of three gives the
@@ -5657,9 +5888,10 @@ bool BrowserPageGoanna::xPoll(int64_t now) {
}
// T-505: a frame without its parts: drawn over it on the CPU from the picture of its own signature, or not shown
if (mXJob.contentOnly) {
const int outcome = cpuPartsDress(s, pixels, cr, H, w, mXJob.srcSlot, mXJob.srcY, mXJob.y, partsBufOf(mXJob.token));
const int outcome = cpuPartsDress(s, pixels, cr, H, w, mXJob.srcSlot, mXJob.srcY, mXJob.y, partsBufOf(mXJob.token), mXJob.twoDeep);
if (outcome != kPartsDressed) {
cpuPartsRefused(outcome, cr.nStuck);
xVerifyDrop(mXJob);
mXJob = XJob(); xForget(mXJob2);
gpuSlotsLost(); // every slot's kind unknown (this one's pixels are half made)
return true;
@@ -5668,6 +5900,23 @@ bool BrowserPageGoanna::xPoll(int64_t now) {
} else {
mSlotParts[s].kind = 0;
}
if (mXJob.vToken) { // JIHAD_GPU_VERIFY: the frame as published against its whole composite
unsigned char* ref = sVerifyRef[mXJob.vBuf].buf.data();
GoannaRenderPage::ComposeResult vr;
int r2 = -1;
const int64_t t0 = jihadNowMs();
while ((r2 = mPage->ComposePoll(mXJob.vToken, ref, &vr)) <= -2 && jihadNowMs() - t0 < 800) usleep(1000);
if (r2 >= 0) {
const int pageLo = (int)above, pageHi = (int)std::max(above, std::min((long)H, pageEnd));
verifyCompare(mXJob.twoDeep ? "express (two deep) " : "express ", s, mXJob.y, mXJob.shift, mXJob.askLo, mXJob.askHi, mXJob.ask2Lo, mXJob.ask2Hi,
pixels, ref, w, H, pageLo, pageHi);
} else {
JIHAD_GPU_LOG("[jihad-gpu] verify: the express reference did not finish (%d)\n", r2);
if (r2 <= -2) mPage->ComposeForget(mXJob.vToken);
}
verifyRefRelease(mXJob.vBuf, r2 <= -2);
mXJob.vToken = 0; mXJob.vBuf = -1;
}
BrowserOffscreenInfo* oi = (BrowserOffscreenInfo*)mXSnap.base[s]; // T-517: the segment's start, not the window's
oi->bufferWidth = w; oi->bufferHeight = H;
oi->contentZoom = mXJob.zoom;
@@ -6797,89 +7046,7 @@ void BrowserPageGoanna::paintToSharedBuffer() {
mGpuViewRow = viewRowNow; gpuSetView(vz, mGpuViewRX, mGpuViewRY);
}
if (r2 >= 0) {
std::string runs; int nRuns = 0, bad = 0;
for (int r = pageLo; r < pageHi; ) { // T-505: the page's rows (the rest is filled white)
if (memcmp(pixels + (size_t)r * w * 4, refPx + (size_t)r * w * 4, (size_t)w * 4) == 0) { ++r; continue; }
int r0 = r;
while (r < pageHi && memcmp(pixels + (size_t)r * w * 4, refPx + (size_t)r * w * 4, (size_t)w * 4) != 0) ++r;
bad += r - r0;
if (nRuns++ < 8) { char b[48]; snprintf(b, sizeof b, " %d..%d", r0, r); runs += b; }
}
// T-503: rows that are WRONG, not just resampled. A scroll by a fractional device pixel
// (the card's zoom) samples the text at another phase than the shifted frame: up to
// ~24 levels on glyph edges and single pixels at tile seams. A wrong shift, a missing
// band or a doubled header puts many pixels far off in a row.
std::string wrongRuns; int nWrong = 0, wrongRows = 0;
auto rowWrong = [&](int r) {
const unsigned char* a = pixels + (size_t)r * w * 4;
const unsigned char* b = refPx + (size_t)r * w * 4;
int far = 0;
for (int x = 0; x < w && far <= 4; x++, a += 4, b += 4) {
if (abs(a[0] - b[0]) > 64 || abs(a[1] - b[1]) > 64 || abs(a[2] - b[2]) > 64) far++;
}
return far > 4;
};
for (int r = pageLo; r < pageHi; ) {
if (!rowWrong(r)) { ++r; continue; }
int r0 = r;
while (r < pageHi && rowWrong(r)) ++r;
wrongRows += r - r0;
if (nWrong++ < 8) { char b[48]; snprintf(b, sizeof b, " %d..%d", r0, r); wrongRuns += b; }
}
{
// T-503: the first frame with many wrong rows, dumped with its whole composite
static bool sDumpedWrong = false;
if (wrongRows > 50 && !sDumpedWrong && getenv("JIHAD_GPU_VERIFY_DUMP_WRONG")) {
sDumpedWrong = true;
FILE* f1 = fopen("/tmp/vw-slot.raw", "wb"); if (f1) { fwrite(pixels, 1, bytes, f1); fclose(f1); }
FILE* f2 = fopen("/tmp/vw-whole.raw", "wb"); if (f2) { fwrite(refPx, 1, bytes, f2); fclose(f2); }
JIHAD_GPU_LOG("[jihad-gpu] verify: the first wrong frame dumped (/tmp/vw-slot.raw, /tmp/vw-whole.raw)\n");
}
}
// Structural errors, by blocks of 16 rows: a block whose content sits better at another
// row offset (-3..3) than at its own (misplaced: a wrong shift, a doubled header), or
// that differs heavily at every offset (missing or stale content). A sharp page at a
// fractional zoom resamples text by a sub-pixel phase between frames: best at offset
// 0, a mean difference of a few levels.
int structural = 0;
std::string structRuns;
auto blockMean = [&](int r0, int r1, int off) {
long long sum = 0, n = 0;
for (int r = r0; r < r1; r++) {
const int rr = r + off;
if (rr < 0 || rr >= h) continue;
const unsigned char* a = pixels + (size_t)r * w * 4;
const unsigned char* b = refPx + (size_t)rr * w * 4;
for (int x = 0; x < w; x++, a += 4, b += 4) sum += abs(a[0] - b[0]) + abs(a[1] - b[1]) + abs(a[2] - b[2]);
n += w * 3;
}
return n ? (double)sum / n : 0.0;
};
for (int b0 = pageLo; b0 < pageHi; b0 += 16) {
const int b1 = std::min(pageHi, b0 + 16);
bool any = false;
for (int r = b0; r < b1 && !any; r++) any = rowWrong(r);
if (!any) continue;
const double m0 = blockMean(b0, b1, 0);
double best = m0;
for (int off = -3; off <= 3; off++) if (off) best = std::min(best, blockMean(b0, b1, off));
if ((best < 0.6 * m0 && m0 > 2.0) || best > 24.0) {
structural += b1 - b0;
if (structRuns.size() < 80) { char t[32]; snprintf(t, sizeof t, " %d..%d", b0, b1); structRuns += t; }
}
}
{
static bool sDumpedStruct = false;
if (structural > 0 && !sDumpedStruct && getenv("JIHAD_GPU_VERIFY_DUMP_WRONG")) {
sDumpedStruct = true;
FILE* f1 = fopen("/tmp/vs-slot.raw", "wb"); if (f1) { fwrite(pixels, 1, bytes, f1); fclose(f1); }
FILE* f2 = fopen("/tmp/vs-whole.raw", "wb"); if (f2) { fwrite(refPx, 1, bytes, f2); fclose(f2); }
JIHAD_GPU_LOG("[jihad-gpu] verify: the first structurally wrong frame dumped (/tmp/vs-slot.raw, /tmp/vs-whole.raw)\n");
}
}
JIHAD_GPU_LOG("[jihad-gpu] verify slot %d (y %d, shift %ld, bands %ld..%ld %ld..%ld): %d rows differ in %d runs:%s%s; %d rows wrong:%s%s; %d rows structurally wrong:%s\n",
gslot, oldest.y, oldest.shift, oldest.askLo, oldest.askHi, oldest.ask2Lo, oldest.ask2Hi, bad, nRuns, runs.c_str(), nRuns > 8 ? " ..." : "",
wrongRows, wrongRuns.c_str(), nWrong > 8 ? " ..." : "", structural, structRuns.c_str());
verifyCompare("", gslot, oldest.y, oldest.shift, oldest.askLo, oldest.askHi, oldest.ask2Lo, oldest.ask2Hi, pixels, refPx, w, h, pageLo, pageHi);
} else {
JIHAD_GPU_LOG("[jihad-gpu] verify: the whole composite did not finish (%d)\n", r2);
}
+31 -7
View File
@@ -616,11 +616,17 @@ private:
// A slot whose frame had parts drawn keeps what lay beneath them; a frame built on it (its window moved, or copied
// from it) gets that back where the old parts landed, outside the rows the job read. Frames of that kind build only
// on their kind, and the others only on theirs.
struct PartsPic { uint32_t sig = 0; bool ok = false; int n = 0; int wh[4][2] = {}; size_t off[4] = {};
std::vector<unsigned char> px; uint64_t used = 0; }; // px: B8G8R8A8 premultiplied, rect by rect
PartsPic mPics[3];
// (per part, 2026-11-03): each rect of a frame's parts list has a signature of its own (its parts' layers, their paints
// and its size); the pictures are kept by part, eight at most. With JIHAD_GPU_PARTS_PER=1 (or <state>/parts-per) the
// engine is sent the signatures held, so a pass reads only the rects it lacks; without it, as E2, every part when
// the whole signature moved (the pictures still kept by part)
struct PartPic { uint32_t sig = 0; int w = 0, h = 0; bool ok = false; std::vector<unsigned char> px; uint64_t used = 0; }; // B8G8R8A8 premultiplied
static const int kPartPics = 8;
PartPic mPartPics[kPartPics];
uint64_t mPicClock = 0;
uint32_t mPicNewest = 0; bool mPicHave = false, mPicForce = false;
uint32_t mPicNewest = 0; bool mPicHave = false, mPicForce = false; // the whole signature of the last frame dressed
bool partsPerRect() const;
int partsHaveList(uint32_t* out, int cap) const; // the signatures held, for the engine (-1: the whole mode)
static const int kPartsCap = 1 << 20; // a frame's parts buffer: more parts than that are the engine's
// the parts buffers lent to jobs (by token: returned at the poll; one forgotten in flight reclaimed 10 s on, when
// the compositor is surely done with it)
@@ -635,15 +641,23 @@ private:
uint32_t partsHave() const { return (mPicHave && !mPicForce) ? mPicNewest : 0; }
// kind: 0 a GPU frame with its parts (the engine's), 1 its parts drawn on the CPU (beneath: what lies under them),
// -1 neither known (a lost record, a frame not composed): no frame builds on it
struct SlotParts { int kind = 0; int n = 0; int r[4][4] = {}; size_t off[4] = {}; std::vector<unsigned char> beneath; };
struct SlotParts { int kind = 0; int n = 0; int r[4][4] = {}; size_t off[4] = {}; std::vector<unsigned char> beneath;
// (the express lane's second frame on the first, 2026-11-03) the frame's top, the slot and top it
// was built on and that record's serial, the rows its job read; its own serial
long y = 0; int baseSlot = -1; long baseY = 0; uint32_t serial = 0, baseSerial = 0;
long readLo = 0, readHi = 0, read2Lo = 0, read2Hi = 0; };
SlotParts mSlotParts[3];
SlotParts mSlotPrev[3]; // each slot's record before its last dress (a frame built on its own slot's frame leaves it here)
uint32_t mSlotSerial = 0;
const SlotParts* slotRecord(int slot, uint32_t serial) const; // the record with that serial: the slot's current or previous
int mPartsMissRun = 0; bool mPartsOff = false; // frames in a row with no picture of their parts; off for the document
int mPartsCovers = 0; int64_t mPartsOffUntil = 0; // a content layer above a part: the engine's parts for a while
bool cpuPartsOn() const;
bool cpuPartsFor() const; // the next frames without their parts
enum { kPartsDressed = 0, kPartsNoPic, kPartsNoBase, kPartsCovered, kPartsTooBig };
int cpuPartsDress(int s, unsigned char* pixels, const GoannaRenderPage::ComposeResult& cr, int H, int w,
int src, long srcY, int jobY, const unsigned char* buf); // src: the slot it was built on (-1: none)
int src, long srcY, int jobY, const unsigned char* buf, // src: the slot it was built on (-1: none)
bool twoDeep = false); // built on a frame in flight: what lay beneath ITS base's parts put back too
void cpuPartsRefused(int outcome, int nStuck); // a frame not dressed (not shown): why, and what follows
void cpuPartsForget(); // the pictures and every slot's record (the frame lock)
private:
@@ -690,6 +704,10 @@ private:
bool viewOnly = false; // T-516: the first frame at a new zoom, the card's view read alone (the overscan white)
bool contentOnly = false; // T-505: composed without its parts (JIHAD_GPU_CPU_PARTS): they are drawn on the CPU
int srcSlot = -1, srcY = 0; // T-505: the frame it was built on (its window moved, or copied from it), and its top
bool twoDeep = false; // T-505: the second frame on the first in flight, both without their parts (JIHAD_GPU_EXPRESS_SECOND_PARTS)
// JIHAD_GPU_VERIFY (2026-11-03): the whole frame composed right before this job (sVerifyRef's buffer), compared
// at the poll as the main lane's is; the bands the job asked for, for the log
uint32_t vToken = 0; int vBuf = -1; long askLo = 0, askHi = 0, ask2Lo = 0, ask2Hi = 0;
};
XJob mXJob; // the express lane's frame in flight (the older one)
// T-517: the express lane's frame time on its slot's own frame and copied from the base (running
@@ -708,7 +726,13 @@ private:
void xInvalidate() { ++mXGeomGen; mXSnap.ok = false; mXPub = XPub(); mXMainPubMs = 0; }
bool xStart(int64_t now, double zoom, const XWant& want, int64_t waitSince, const XJob* aBase = nullptr);
// T-517: a forgotten job may have been composed: its change is in no slot's record
void xForget(XJob& j) { if (j.on && mPage) { mPage->ComposeForget(j.token); gpuSlotsLost(); pubDirtyAll(); } j = XJob(); }
void xForget(XJob& j) { xVerifyDrop(j); if (j.on && mPage) { mPage->ComposeForget(j.token); gpuSlotsLost(); pubDirtyAll(); } j = XJob(); }
void xVerifyDrop(XJob& j); // its verify reference (if any) forgotten and its buffer back to the pool
// JIHAD_GPU_VERIFY: a published frame against the whole composite of the same tree at the same view: the rows that
// differ, the rows wrong (far pixels), the rows structurally wrong (blocks that fit better at another offset); the
// first wrong and the first structurally wrong frame dumped with JIHAD_GPU_VERIFY_DUMP_WRONG=1. Both lanes' frames
void verifyCompare(const char* lane, int slot, int y, long shift, long askLo, long askHi, long ask2Lo, long ask2Hi,
const unsigned char* pixels, const unsigned char* refPx, int w, int h, int pageLo, int pageHi);
bool xLeads(int64_t now); // the card is moving: the express lane makes the frames (the frame lock held)
int64_t mXMainPaintMs = 0; // the main lane's last layer paint while the express lane leads
bool xPoll(int64_t now);
+17
View File
@@ -103,6 +103,21 @@ extern "C" {
const void* aSrc, int aSrcY, int aEngineShift,
int aColX0, int aColX1, void* aPartsDest, int aPartsCap,
uint32_t aPartsHave) __attribute__((weak));
// OPTIONAL (T-505, per part; jihad_offscreen_overview_ok answers 4): post and start with the list of signatures the
// daemon holds part pictures of (aNHave of them, 16 at most): the pass reads only the rects whose signature is not in
// it; the poll then takes 54 ints (each rect's signature, the rects read as a mask)
uint32_t jihad_offscreen_compose_post3(nsIWidget* aWidget, void* aDest, int aStride, int aWidth, int aHeight,
int aRowLo, int aRowHi, int aRow2Lo, int aRow2Hi, int aShift,
const void* aSrc, int aSrcY, int aFlags, int aUseView, double aScale,
double aOffX, double aOffY, double aScrollX, double aScrollY,
int aScrollValid, int aColX0, int aColX1, void* aPartsDest, int aPartsCap,
const uint32_t* aHave, int aNHave) __attribute__((weak));
uint32_t jihad_offscreen_compose_start3(nsIWidget* aWidget, nsIDocShell* aDocShell, void* aDest,
int aStride, int aWidth, int aHeight, int aRowLo,
int aRowHi, int aRow2Lo, int aRow2Hi, int aShift,
const void* aSrc, int aSrcY, int aEngineShift,
int aColX0, int aColX1, void* aPartsDest, int aPartsCap,
const uint32_t* aHave, int aNHave) __attribute__((weak));
// OPTIONAL (T-502): the card's zoom and the visual viewport's residual offset (CSS px, what the
// engine scroll could not reach), applied by the compositor to the whole layer tree.
void jihad_offscreen_set_view(nsIWidget* aWidget, double aScale, double aOffsetX,
@@ -177,6 +192,8 @@ inline void ReportEngineExports() {
{"jihad_offscreen_compose_post", jihad_offscreen_compose_post != nullptr},
{"jihad_offscreen_compose_post2", jihad_offscreen_compose_post2 != nullptr},
{"jihad_offscreen_compose_start2", jihad_offscreen_compose_start2 != nullptr},
{"jihad_offscreen_compose_post3", jihad_offscreen_compose_post3 != nullptr},
{"jihad_offscreen_compose_start3", jihad_offscreen_compose_start3 != nullptr},
{"jihad_offscreen_compose_peek", jihad_offscreen_compose_peek != nullptr},
{"jihad_offscreen_overview_ok", jihad_offscreen_overview_ok != nullptr},
{"jihad_offscreen_compositor_seq", jihad_offscreen_compositor_seq != nullptr},
+29 -10
View File
@@ -4989,7 +4989,7 @@ bool GoannaRenderPage::ComposeAsync() const {
uint32_t GoannaRenderPage::ComposeStart(unsigned char* dst, size_t dstBytes, long rowLo, long rowHi, long row2Lo, long row2Hi, long shift,
const unsigned char* src, long srcY, bool engineShift, bool verify, long colX0, long colX1,
bool windowMoved, bool contentOnly, unsigned char* partsDest, int partsCap,
uint32_t partsHave) {
uint32_t partsHave, const uint32_t* partsHaveList, int nPartsHave) {
if (!mOffscreen || !mWidget || !ComposeAsync()) return 0;
const int w = mWidth, h = GpuFrameHeight(); // T-505: the frame's rows, overscan included
if (w <= 0 || h <= 0 || dstBytes < (size_t)w * h * 4) return 0;
@@ -4999,9 +4999,14 @@ uint32_t GoannaRenderPage::ComposeStart(unsigned char* dst, size_t dstBytes, lon
if (colX1 < 0) { colX0 = 0; colX1 = w; } // whole rows
// flag 4 (T-511): the column arguments are given, and the poll takes twelve ints back
const int flags = (engineShift ? 1 : 0) | (verify ? 2 : 0) | 4 | (windowMoved ? 8 : 0) | (contentOnly && PartsReady() ? 128 : 0);
if (contentOnly && PartsReady()) // T-505 (E2): with its parts pass
if (contentOnly && PartsReady()) { // T-505 (E2): with its parts pass; (per part) with the signatures held
if (partsHaveList && nPartsHave >= 0)
return jihad_offscreen_compose_start3(mWidget, ds, dst, w * 4, w, h, (int)rowLo, (int)rowHi, (int)row2Lo, (int)row2Hi, (int)shift,
src, (int)srcY, flags, (int)colX0, (int)colX1, partsDest, partsDest ? partsCap : 0,
partsHaveList, nPartsHave);
return jihad_offscreen_compose_start2(mWidget, ds, dst, w * 4, w, h, (int)rowLo, (int)rowHi, (int)row2Lo, (int)row2Hi, (int)shift,
src, (int)srcY, flags, (int)colX0, (int)colX1, partsDest, partsDest ? partsCap : 0, partsHave);
}
const uint32_t token = jihad_offscreen_compose_start(mWidget, ds, dst, w * 4, w, h, (int)rowLo, (int)rowHi, (int)row2Lo, (int)row2Hi, (int)shift, src, (int)srcY,
flags, (int)colX0, (int)colX1);
return token;
@@ -5030,9 +5035,10 @@ bool GoannaRenderPage::OverviewReady() const {
}
bool GoannaRenderPage::PartsReady() const {
// T-505 (E2): the frame's parts pass in its own job (an engine that answers 3)
return ExpressReady() && jihad_offscreen_overview_ok && jihad_offscreen_overview_ok() >= 3 && jihad_offscreen_compose_poll2 &&
jihad_offscreen_compose_post2 && jihad_offscreen_compose_start2;
// T-505 (E2): the frame's parts pass in its own job; (per part) each rect's signature in the poll and the post with
// the signatures held: an engine that answers 4 (the daemon keeps its pictures by part, so 3 is not enough)
return ExpressReady() && jihad_offscreen_overview_ok && jihad_offscreen_overview_ok() >= 4 && jihad_offscreen_compose_poll2 &&
jihad_offscreen_compose_post2 && jihad_offscreen_compose_start2 && jihad_offscreen_compose_post3 && jihad_offscreen_compose_start3;
}
int GoannaRenderPage::ComposePeek(uint32_t token) {
@@ -5049,14 +5055,23 @@ uint32_t GoannaRenderPage::ComposePost(unsigned char* dst, size_t dstBytes, int
long row2Hi, long shift, const unsigned char* src, long srcY, bool engineShift,
const double* aView, long colX0, long colX1, bool windowMoved, bool verify,
bool bandsOnly, bool overview, bool overviewParts, bool contentOnly,
unsigned char* partsDest, int partsCap, uint32_t partsHave) {
unsigned char* partsDest, int partsCap, uint32_t partsHave,
const uint32_t* partsHaveList, int nPartsHave) {
if (!ExpressReady() || !mWidget || !dst) return 0;
const int w = mWidth;
// T-504: a job that reads one band alone, from no base and with no shift, writes no row past it
const long rows = (bandsOnly && row2Hi <= row2Lo && !shift && !src && rowHi > 0 && rowHi <= frameH) ? rowHi : frameH;
if (w <= 0 || frameH <= 0 || dstBytes < (size_t)w * rows * 4) return 0;
if (colX1 < 0) { colX0 = 0; colX1 = w; } // whole rows
if (contentOnly && PartsReady() && !overviewParts) { // T-505 (E2): with its parts pass
if (contentOnly && PartsReady() && !overviewParts) { // T-505 (E2): with its parts pass; (per part) with the signatures held
if (partsHaveList && nPartsHave >= 0)
return jihad_offscreen_compose_post3(mWidget, dst, w * 4, w, frameH, (int)rowLo, (int)rowHi, (int)row2Lo, (int)row2Hi,
(int)shift, src, (int)srcY,
(engineShift ? 1 : 0) | (verify ? 2 : 0) | 4 | (windowMoved ? 8 : 0) | (bandsOnly ? 16 : 0) | 128,
aView ? 1 : 0,
aView ? aView[0] : 1.0, aView ? aView[1] : 0.0, aView ? aView[2] : 0.0,
aView ? aView[3] : 0.0, aView ? aView[4] : 0.0, aView ? 1 : 0, (int)colX0, (int)colX1,
partsDest, partsDest ? partsCap : 0, partsHaveList, nPartsHave);
return jihad_offscreen_compose_post2(mWidget, dst, w * 4, w, frameH, (int)rowLo, (int)rowHi, (int)row2Lo, (int)row2Hi,
(int)shift, src, (int)srcY,
(engineShift ? 1 : 0) | (verify ? 2 : 0) | 4 | (windowMoved ? 8 : 0) | (bandsOnly ? 16 : 0) | 128,
@@ -5101,10 +5116,10 @@ void GoannaRenderPage::SetGpuOverscan(int rows, double zoom) {
int GoannaRenderPage::ComposePoll(uint32_t token, unsigned char* dst, ComposeResult* out) {
if (!mWidget || !ComposeAsync()) return -1;
int o[49] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1}; // T-511: o[10] stays -1 from an engine that reports rows only
int o[54] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1}; // T-511: o[10] stays -1 from an engine that reports rows only
int scrollOnly = 0;
// T-505 (E2): 49 ints from an engine with the parts pass (it writes no more than it is asked for)
const int nOut = PartsReady() ? 49 : 29;
// T-505 (E2): 49 ints from an engine with the parts pass, 54 with each rect's signature (it writes no more than asked)
const int nOut = PartsReady() ? 54 : 29;
const int r = jihad_offscreen_compose_poll2 ? jihad_offscreen_compose_poll2(mWidget, token, o, nOut, &scrollOnly) // T-544
: jihad_offscreen_compose_poll(mWidget, token, o, &scrollOnly);
if (r == 0) return -2; // pending
@@ -5132,6 +5147,10 @@ int GoannaRenderPage::ComposePoll(uint32_t token, unsigned char* dst, ComposeRes
for (int k = 0; k < out->nHold; k++)
for (int c = 0; c < 4; c++) out->hold[k][c] = o[33 + 4 * k + c];
}
if (nOut >= 54) { // (per part)
for (int k = 0; k < 4; k++) out->partsSig[k] = (uint32_t)o[49 + k];
out->partsRead = o[53]; out->partsSigKnown = true;
}
for (int k = 0; k < out->nStuck; k++)
for (int c = 0; c < 4; c++) out->stuck[k][c] = o[13 + 4 * k + c];
}
+9 -4
View File
@@ -387,7 +387,8 @@ public:
const unsigned char* src = nullptr, long srcY = 0, bool engineShift = false, bool verify = false,
long colX0 = 0, long colX1 = -1, bool windowMoved = false,
bool contentOnly = false, // T-505: the frame without its parts (PartsReady)
unsigned char* partsDest = nullptr, int partsCap = 0, uint32_t partsHave = 0); // (E2)
unsigned char* partsDest = nullptr, int partsCap = 0, uint32_t partsHave = 0, // (E2)
const uint32_t* partsHaveList = nullptr, int nPartsHave = -1); // (per part) the signatures held
void CleanRows(unsigned char* dst, long lo, long hi); // the cache clean of rows another thread wrote
// T-515: ComposeStart's halves. PaintLayers (the main thread) paints the layers; ComposePost (any
// thread, the frame lock held) composes the tree as it stands into a frameH-row frame, at aView
@@ -401,10 +402,11 @@ public:
bool overview = false, // T-504: an overview window (OverviewReady)
bool overviewParts = false, // T-504: the overview's fixed and stuck parts alone
bool contentOnly = false, // T-505: the frame without its parts (PartsReady)
unsigned char* partsDest = nullptr, int partsCap = 0, uint32_t partsHave = 0); // (E2)
unsigned char* partsDest = nullptr, int partsCap = 0, uint32_t partsHave = 0, // (E2)
const uint32_t* partsHaveList = nullptr, int nPartsHave = -1); // (per part) the signatures held
bool ExpressReady() const;
bool OverviewReady() const; // T-504: ExpressReady and the engine composes overview windows
bool PartsReady() const; // T-505: and frames without their parts (flag 128), with the parts' generation
bool PartsReady() const; // T-505: and frames without their parts (flag 128) with their parts pass, per part (level 4)
int ComposePeek(uint32_t token); // 1 done, 2 composed (being read), 0 pending, -1 unknown
// askLo/askHi: the band that job was started with (what was read is it joined with the change)
// out (may be null): the rows the tree changed (inv), the rows fixed layers cover (fixed),
@@ -417,7 +419,10 @@ public:
uint32_t partsGen = 0; bool partsGenKnown = false; // T-505: the parts' signature
// T-505 (E2): the bytes the job's parts pass wrote into its parts buffer (0: none), its flags (1 a
// content layer above a part, 2 the parts too big), the card's hold set (fixed and stuck layers)
int partsBytes = 0, partsFlags = 0; bool holdKnown = false; int nHold = 0; int hold[4][4] = {}; };
int partsBytes = 0, partsFlags = 0; bool holdKnown = false; int nHold = 0; int hold[4][4] = {};
// T-505 (per part): each stuck rect's own signature, and the rects the pass read (bit k; the
// buffer holds those, packed in rect order)
uint32_t partsSig[4] = {}; int partsRead = 0; bool partsSigKnown = false; };
int ComposePoll(uint32_t token, unsigned char* dst, ComposeResult* out);
void ComposeForget(uint32_t token);
// Identifies the document now in the window (its inner window id; 0 if unknown). Two
+1 -1
+15
View File
@@ -244,6 +244,21 @@ The inject channel's `cardscroll <x> <y>` tells the card to scroll there (msgScr
and `rect` prints the engine's and the card's scroll with the rect (`scroll=`, `card=`): the finger walks use them
(`tools/device/finger/prefs-finger-walk.sh`).
T-505, the parts by part (2026-11-03): the engine signs each rect of a frame's parts list on its own (the parts it
holds, their paints, its size; `jihad_offscreen_overview_ok` 4, the poll's 54 ints) and the daemon keeps the last
eight part pictures by signature. `JIHAD_GPU_PARTS_PER=1` (or the gate file `<state>/parts-per`) sends the engine the
signatures held (`compose_post3`/`start3`), so a pass composes and reads only the rects the daemon lacks — on desktop
Wikipedia the contents panel's highlight alone, not the header and both sidebars; without it every part is read when
the whole signature moved (E2's rule). `JIHAD_GPU_EXPRESS_SECOND_PARTS=1` (or `<state>/express-second-parts`) lets the
express lane build its second frame on the first in flight again when both are without their parts (review C1 had
barred it): the second's dress puts back what lay beneath the first's parts AND beneath the first's base's parts, from
the slot records (each keeps its base's serial and the rows it read; the slot's previous record is kept aside), so the
copy may catch the first's dress at any point. `JIHAD_GPU_VERIFY=1` now checks the express lane's frames too (the
reference composed right before the job; `verify express ` / `verify express (two deep) ` lines), with the main lane's.
Under verify the reference's whole read sits between the first frame's read and the second's copy, so the copy never
catches the first's dress; `JIHAD_GPU_TEST_DRESS_HOLD=pre:80` (or `mid:80`) holds a first frame's dress 80 ms before
its restores (or between its record and its draw) so the copy does, and the double restore is what the verify tests.
Older device-round switches still in the code (T-502), for completeness: `JIHAD_GPU_DUMP2=<path>` (the 5th
frame's buffer as the adapter reads it), `JIHAD_GPU_DUMP_SEQ=<path>` (the first 12 content frames, every
4th pixel, appended to one file, with their content extents in the log), `JIHAD_GPU_FORCE_FPS=1` (a frame