T-505: the parts' picture in the frame's own job (E2, engine 0072); the review's fixes

Two inspector reviews of the parts on the CPU (Codex was out of credits) returned REVISE, and the
owner's question on the stale-picture trade was answered by a third design, now built:
- the engine reads the parts' picture in the frame's own job when the parts' signature (their
  layers' content, attributes, structure, sizes) is not the one the daemon holds; the daemon keeps
  the last three pictures by signature and dresses each frame with its own, rect by rect: exact,
  no separate picture job, no matching, no stale switch;
- a content layer above a part: the frame not shown, the engine's parts for 2 s (the document
  after three); parts over 1 MB: the engine's; the card's hold set reported apart (T-544's meaning);
- review fixes: the express lane's second frame no longer copies a frame being dressed (C1); the
  kind recorded with the slot (C7); the out-of-page rows white before dressing (C10); misses that
  are not misses not counted (C8); parts buffers lent by token, returned at the poll, reclaimed
  10 s after a forgotten job; CPU parts on unless JIHAD_GPU_CPU_PARTS=0 or <state>/no-cpu-parts.

Verify (main lane only, scripted Wikipedia pan): 0 wrong rows in 27 shifted frames; without the
parts the same run showed 2 frames with wrong rows. Scripted pan: 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). Frames looked
at. The findings and their status: context/impl/impl-review-findings-t505-cpuparts.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XtfxSpM1w2uiVKeLKoV3g3
This commit is contained in:
NotAlexNoyleandClaude Opus 5.5 committed 2026-10-26 14:43:50 -04:00
1 parent 2bd3e6f1ae
commit 81f56bef6d
24 files changed
+18263 -235

No files matched your search

@@ -0,0 +1,597 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: NotAlexNoyle <alexnoyle@icloud.com>
Date: Mon, 26 Oct 2026 14:42:39 -0400
Subject: [PATCH] gpu-compositor: the parts' picture in the frame's own job,
from the same tree state (E2)
T-505, after two adversarial reviews of the parts on the CPU (the inspector's design E2): a frame
composed without its parts (flag 128) and posted with compose_post2/start2 (overview_ok answers 3)
carries a parts buffer and the signature of the picture the daemon holds.
- In the job, before the frame's compose: the parts' list (every fixed and sticky layer, whole
rects), their signature (each part subtree's layers: identity, type, opacity, content flags,
mask, visible region; inside a part its transform and clip; a colour layer's colour; an
animation every composite; the paints under a part; the parts' sizes), the card's hold set
(fixed and stuck layers, T-544's meaning) and a cover flag (a content leaf drawn above a part
whose pixels meet it). When the signature is not the daemon's, the parts alone are composed on
the band surface, packed in shelves, and read with their alpha into the buffer (a verify guard
holds the diff baseline, the probe, the last scroll and view). Flag 2: the parts too big.
- The poll's 49 ints: the signature, the pass's bytes, the flags, the hold set.
- Review fixes: no column reads in a frame without its parts (the daemon restores what lay beneath
them by whole rows); 0071's own-slot compensation only without the parts or with no fixed rects.
- LayerManagerComposite::JihadPrepareOnly (visible regions and effective transforms without the
diff), CompositorOGL::JihadReadPacked (a packed read that keeps the alpha).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XtfxSpM1w2uiVKeLKoV3g3
---
.../composite/LayerManagerComposite.cpp | 12 ++
gfx/layers/composite/LayerManagerComposite.h | 3 +
gfx/layers/ipc/CompositorBridgeParent.cpp | 204 +++++++++++++++++-
gfx/layers/ipc/CompositorBridgeParent.h | 3 +
gfx/layers/opengl/CompositorOGL.cpp | 33 +++
gfx/layers/opengl/CompositorOGL.h | 17 +-
widget/PuppetWidget.cpp | 60 +++++-
widget/PuppetWidget.h | 9 +-
8 files changed, 323 insertions(+), 18 deletions(-)
diff --git a/gfx/layers/composite/LayerManagerComposite.cpp b/gfx/layers/composite/LayerManagerComposite.cpp
index 124b20c3fa..280b884634 100644
--- a/gfx/layers/composite/LayerManagerComposite.cpp
+++ b/gfx/layers/composite/LayerManagerComposite.cpp
@@ -417,6 +417,18 @@ LayerManagerComposite::EndTransaction(const TimeStamp& aTimeStamp,
#endif
}
+void
+LayerManagerComposite::JihadPrepareOnly()
+{
+ if (!mRoot) {
+ return;
+ }
+ nsIntRegion opaque;
+ LayerIntRegion visible;
+ PostProcessLayers(mRoot, opaque, visible, Nothing());
+ mRoot->ComputeEffectiveTransforms(getenv("JIHAD_GPU_NO_WORLD_ENGINE") ? gfx::Matrix4x4() : mJihadWorld);
+}
+
nsIntRegion
LayerManagerComposite::JihadDiffOnly()
{
diff --git a/gfx/layers/composite/LayerManagerComposite.h b/gfx/layers/composite/LayerManagerComposite.h
index e8f22dc1ab..c49ecb73a6 100644
--- a/gfx/layers/composite/LayerManagerComposite.h
+++ b/gfx/layers/composite/LayerManagerComposite.h
@@ -146,6 +146,9 @@ public:
// round 119: the tree diff now, without a render (the stored tree refreshed as a composite
// would), in frame pixels; the caller has begun a transaction and applied the async transforms
nsIntRegion JihadDiffOnly();
+ // T-505: the visible regions and effective transforms as a composite would make them, without the diff (the
+ // stored tree untouched); the caller has begun a transaction and applied the async transforms
+ void JihadPrepareOnly();
void JihadEndTransactionNoComposite() { mInTransaction = false; }
bool JihadPartial() const { return mJihadPartialOn; }
diff --git a/gfx/layers/ipc/CompositorBridgeParent.cpp b/gfx/layers/ipc/CompositorBridgeParent.cpp
index 177fc4c51d..420a0c7cbf 100644
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -1167,6 +1167,80 @@ 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
+uint32_t
+CompositorBridgeParent::JihadPartsSig(const gfx::IntRect* aRects, int aN, bool* aCovered)
+{
+ uint32_t h = 2166136261u;
+ 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); }
+ *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()); mix(l->GetMaskLayer() ? 1u : 0u);
+ mixr(l->GetVisibleRegion().GetBounds().ToUnknownRect());
+ if (depth > 0) {
+ const gfx::Matrix4x4 m = l->GetBaseTransform();
+ for (int k = 0; k < 16; k++) mixf(m.components[k]);
+ if (l->GetClipRect()) mixr(l->GetClipRect()->ToUnknownRect());
+ }
+ 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;
+ }
+ }
+ }
+ if (part) depth++;
+ },
+ [&](Layer* l) { if (l->GetIsFixedPosition() || l->GetIsStickyPosition()) depth--; });
+ return h;
+}
+
void
CompositorBridgeParent::CompositeToTarget(DrawTarget* aTarget, const gfx::IntRect* aRect)
{
@@ -1880,8 +1954,10 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
// transaction during a pan was the whole frame changed for it (the content's translation), and a whole read
// with the other two slots' frames made stale whole. Only the card's scroll: the shift the diff measured must
// be the card's since the last composite (a scroll the page made itself is then the whole frame changed)
+ // (review: not for a frame with the engine's parts while fixed or stuck rects exist: its keeps and columns are
+ // judged against the last composite, not the older base)
const bool ownComp = aReq->src == nullptr && aReq->scrollValid && mJihadLastScrollValid &&
- aReq->scrollX == mJihadLastScrollX;
+ aReq->scrollX == mJihadLastScrollX && (sJihadContentOnly || mJihadLastFixedN == 0);
const float ownExpectRows = (aReq->scrollY - mJihadLastScrollY) * (aScale > 0.0f ? aScale : 1.0f);
auto ownShiftOk = [&]() {
float sdx = 0.0f, sdy = 0.0f;
@@ -1907,6 +1983,119 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
const bool treeChanged = !mJihadSeqKnown || seqNow != mJihadSeqAtLast;
mJihadSeqAtLast = seqNow; mJihadSeqKnown = true;
const bool scrollOnly = scrollChanged && !viewChanged && !treeChanged;
+ // T-505 (E2): a frame without its parts: their list, signature and the card's hold set from this job's tree state,
+ // and their picture read here when the daemon holds none of this signature (CompositorOGL.h, partsDest)
+ bool jPartsPrepped = false;
+ if (aReq->contentOnly && !aReq->overviewParts && !aReq->verify && mCompositionManager && mLayerManager->GetRoot()) {
+ jPartsPrepped = true;
+ gfx::IntRect parts[4], pk[4];
+ mLayerManager->BeginTransaction();
+ SetShadowProperties(mLayerManager->GetRoot());
+ mCompositionManager->ComputeRotation();
+ {
+ TimeStamp time = mIsTesting ? mTestTime : mCompositorScheduler->GetLastComposeTime();
+ mCompositionManager->TransformShadowTree(time, mVsyncRate);
+ }
+ 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);
+ if (covered) aReq->partsFlags |= 1;
+ {
+ sJihadContentOnly = false; // the card's hold set keeps T-544's meaning: fixed layers and stuck ones
+ gfx::IntRect hr[4], hk[4];
+ const int hn = JihadFixedRectsNow(aWidth, aHeight, hr, hk);
+ sJihadContentOnly = true;
+ aReq->nHold = 0;
+ for (int k = 0; k < hn && aReq->nHold < 4; k++) {
+ if (hr[k].IsEmpty()) continue;
+ int* o = aReq->hold[aReq->nHold++];
+ o[0] = hr[k].x; o[1] = hr[k].y; o[2] = hr[k].XMost(); o[3] = hr[k].YMost();
+ }
+ }
+ mLayerManager->JihadEndTransactionNoComposite();
+ JihadStuckOut(aReq, parts, partN);
+ if (!covered && aReq->partsDest && aReq->nStuck > 0 && aReq->partsGen != aReq->partsHave) {
+ 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;
+ // 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;
+ auto pack = [&](int aCap) {
+ np = 0; used = 0;
+ int shelfY = 0, shelfH = 0, cursorX = 0;
+ for (int k = 0; k < aReq->nStuck; k++) {
+ 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);
+ if (cursorX + w > aWidth) { shelfY += shelfH; shelfH = 0; cursorX = 0; }
+ if (w > aWidth || shelfY + hgt > aCap || np == 12) return false;
+ pieces[np++] = { k, x0, y, w, hgt, cursorX, shelfY };
+ cursorX += w; shelfH = std::max(shelfH, hgt); y += hgt;
+ }
+ }
+ used = shelfY + shelfH;
+ return true;
+ };
+ const bool fits = need <= (size_t)aReq->partsCap &&
+ (pack(ogl->JihadBandRows()) || (ogl->JihadBandTallRows() > 0 && pack(ogl->JihadBandTallRows())));
+ if (!fits) {
+ aReq->partsFlags |= 2;
+ } else {
+ JihadVerifyGuard partsGuard(this, true); // the baseline, the probe, the last scroll and view held
+ const int passRows = ogl->JihadBeginBandPass(aWidth, used);
+ if (!passRows) {
+ aReq->partsFlags |= 2;
+ } else {
+ sJihadOverview = 2; // the parts alone (JihadHideForJob)
+ ogl->JihadSetClearTransparent(true);
+ gfx::Matrix4x4 worlds[12];
+ gfx::IntRect rects[12];
+ for (int i = 0; i < np; i++) {
+ worlds[i] = world;
+ worlds[i].PostTranslate((float)(pieces[i].atX - pieces[i].x), (float)(pieces[i].atY - pieces[i].y), 0.0f);
+ rects[i] = gfx::IntRect(pieces[i].atX, pieces[i].atY, pieces[i].w, pieces[i].h);
+ }
+ mLayerManager->JihadSetWorldTransform(worlds[0]);
+ mLayerManager->UpdateRenderBounds(gfx::IntRect(0, 0, aWidth, passRows));
+ mLayerManager->JihadIgnoreDiff(true);
+ mLayerManager->JihadResetLastInvalid();
+ mLayerManager->JihadSetBand(gfx::IntRect(), gfx::IntRect());
+ mLayerManager->JihadSetRegions(worlds, rects, np);
+ const TimeStamp pc0 = TimeStamp::Now();
+ sJihadInCompose = true;
+ ForceComposeToTarget(nullptr, nullptr);
+ sJihadInCompose = false;
+ 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;
+ for (int i = 0; i < np && ok; i++) {
+ const int k = pieces[i].k, rw = aReq->stuck[k][2] - aReq->stuck[k][0];
+ uint8_t* d = aReq->partsDest + off[k] + (size_t)(pieces[i].y - aReq->stuck[k][1]) * rw * 4;
+ ok = ogl->JihadReadPacked(d, rw * 4, passRows, pieces[i].atX, pieces[i].w, pieces[i].atY, pieces[i].h);
+ }
+ ogl->JihadEndBandPass();
+ ogl->JihadSetClearTransparent(false);
+ sJihadOverview = 0;
+ if (ok) aReq->partsBytes = (int)need;
+ 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(),
+ ok ? "" : " (failed)");
+ }
+ mLayerManager->JihadIgnoreDiff(false);
+ mLayerManager->JihadSetWorldTransform(world);
+ mLayerManager->UpdateRenderBounds(whole);
+ mLayerManager->JihadSetBand(gfx::IntRect(), gfx::IntRect());
+ ogl->JihadSelectTarget();
+ }
+ }
+ }
// Round 119: a frame whose tree changed takes the diff first, without a render. When what it
// changed, the bands the daemon asked for, the entering band of a scroll the page made and the
// fixed rows fit the band pass, the frame is composed there like a scroll-only one (the band
@@ -2190,7 +2379,8 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
ogl->JihadSetCancel(&aReq->cancelled);
// T-511: one band and no column chunks: the band read as the column the change spans
int cx0 = 0, cx1 = 0;
- const bool colRead = ok && diffFirst && hs[0] > 0 && !hs[1] && nChunks == 0 &&
+ // (T-505 review: not without the parts: the daemon puts back what lay beneath them by whole rows)
+ const bool colRead = ok && diffFirst && !sJihadContentOnly && hs[0] > 0 && !hs[1] && nChunks == 0 &&
JihadColumnRead(aReq, dfInvX0, dfInvX1, dfInvLo, dfInvHi, aWidth, &cx0, &cx1);
if (colRead) {
TimeStamp r0 = TimeStamp::Now();
@@ -2256,8 +2446,8 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
});
if (fHi > fLo) { mJihadLastFixedLo = fLo; mJihadLastFixedHi = fHi; } else { mJihadLastFixedLo = mJihadLastFixedHi = 0; }
if (sJihadContentOnly) {
- gfx::IntRect pr[4], pk[4]; // T-505: where the parts are, for the daemon to draw them
- JihadStuckOut(aReq, pr, JihadFixedRectsNow(aWidth, aHeight, pr, pk));
+ gfx::IntRect pr[4], pk[4]; // T-505: where the parts are, for the daemon to draw them (E2: the prep's)
+ if (!jPartsPrepped) JihadStuckOut(aReq, pr, JihadFixedRectsNow(aWidth, aHeight, pr, pk));
} else {
JihadUpdateLastFixed(aWidth, aHeight); // rounds 138/141
JihadStuckOut(aReq, mJihadLastFixedRects, mJihadLastFixedN);
@@ -2390,8 +2580,8 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
gfx::IntRect colRects[8], colKeeps[8];
int colRectsN = 0;
if (sJihadContentOnly) {
- gfx::IntRect pr[4], pk[4]; // T-505: where the parts are, for the daemon to draw them
- JihadStuckOut(aReq, pr, JihadFixedRectsNow(aWidth, aHeight, pr, pk));
+ gfx::IntRect pr[4], pk[4]; // T-505: where the parts are, for the daemon to draw them (E2: the prep's)
+ if (!jPartsPrepped) JihadStuckOut(aReq, pr, JihadFixedRectsNow(aWidth, aHeight, pr, pk));
}
if (JihadFixedCols()) {
gfx::IntRect lastR[4];
@@ -2473,7 +2663,7 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
aReq->invX0 = invX0; aReq->invX1 = invX1;
{
int cx0 = 0, cx1 = 0; // T-511: the bands read as one column (JihadReadAsync)
- if (!aReq->bandsOnly && JihadColumnRead(aReq, invX0, invX1, invLo, invHi, aWidth, &cx0, &cx1)) { aReq->readX0 = cx0; aReq->readX1 = cx1; }
+ if (!aReq->bandsOnly && !sJihadContentOnly && JihadColumnRead(aReq, invX0, invX1, invLo, invHi, aWidth, &cx0, &cx1)) { aReq->readX0 = cx0; aReq->readX1 = cx1; }
}
// the log's values now: once the read hands the frame over, aReq belongs to the daemon
const int logLo2 = aReq->lo2, logHi2 = aReq->hi2, logFixedLo = aReq->fixedLo, logFixedHi = aReq->fixedHi, logCols = aReq->nCols, logKeep = aReq->nKeep;
diff --git a/gfx/layers/ipc/CompositorBridgeParent.h b/gfx/layers/ipc/CompositorBridgeParent.h
index 7b021a883d..5776ec30c4 100644
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -546,6 +546,9 @@ 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 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.cpp b/gfx/layers/opengl/CompositorOGL.cpp
index 5f3bc77030..d16d74dcfc 100644
--- a/gfx/layers/opengl/CompositorOGL.cpp
+++ b/gfx/layers/opengl/CompositorOGL.cpp
@@ -2759,6 +2759,39 @@ CompositorOGL::JihadReadFrame(uint8_t* aDest, int aStride, int aWidth, int aHeig
return true;
}
+bool
+CompositorOGL::JihadReadPacked(uint8_t* aDest, int aDestStride, int aSurfaceH, int aX0, int aW, int aSrcRowLo, int aRows)
+{
+ if (!mGLContext || !aDest || aW <= 0 || aRows <= 0 || aSrcRowLo < 0 || aSrcRowLo + aRows > aSurfaceH || aX0 < 0 ||
+ aDestStride < aW * 4) {
+ return false;
+ }
+ static uint8_t* sBuf = nullptr;
+ static size_t sCap = 0;
+ const size_t bytes = (size_t)aW * 4 * aRows;
+ if (sCap < bytes) {
+ free(sBuf);
+ sBuf = (uint8_t*)malloc(bytes);
+ sCap = sBuf ? bytes : 0;
+ if (!sBuf) return false;
+ }
+ mGLContext->MakeCurrent();
+ mGLContext->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mJihadWindowFBO);
+ {
+ ScopedPackState safePackState(mGLContext);
+ mGLContext->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 4);
+ mGLContext->fReadPixels(aX0, aSurfaceH - (aSrcRowLo + aRows), aW, aRows, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, sBuf);
+ }
+ if (mJihadCancel && *mJihadCancel) {
+ return false;
+ }
+ JihadSwizzleAndFlipRows(sBuf, aW * 4, aW, aRows, false); // the parts keep their alpha
+ for (int r = 0; r < aRows; r++) {
+ memcpy(aDest + (size_t)r * aDestStride, sBuf + (size_t)r * aW * 4, (size_t)aW * 4);
+ }
+ return true;
+}
+
bool
CompositorOGL::JihadReadColumns(uint8_t* aDest, int aStride, int aSurfaceH, int aX0, int aX1, int aSrcRowLo, int aRows, int aDestRowLo,
int aDestX)
diff --git a/gfx/layers/opengl/CompositorOGL.h b/gfx/layers/opengl/CompositorOGL.h
index 12ef1ecb53..f33ebe4908 100644
--- a/gfx/layers/opengl/CompositorOGL.h
+++ b/gfx/layers/opengl/CompositorOGL.h
@@ -176,7 +176,19 @@ struct JihadFrameRequest {
// layer at opacity 0 (stuck or not), no fixed rows, keeps or columns; the
// parts' rects still reported (the daemon draws them on the CPU). With flag
// 64, the parts alone with the same rule
- uint32_t partsGen = 0; // T-505, out: the count of updates to layers under a fixed or sticky one
+ uint32_t partsGen = 0; // T-505, out: the parts' signature (the poll's thirtieth int; see partsHave)
+ // T-505 (E2, the inspector's design): a frame without its parts reads the parts' picture in its own job when their
+ // signature (their layers' content, attributes, structure and sizes) is not the one the daemon holds a picture of:
+ // the parts alone, cleared transparent, packed on the band surface and read with their alpha into partsDest (each
+ // rect of stuck[] in turn, its rows one after the other, B8G8R8A8 premultiplied), from the same tree state as the
+ // frame. stuck[] is then the parts' list (every fixed and sticky layer), hold[] the card's (fixed and stuck ones)
+ uint8_t* partsDest = nullptr;
+ int partsCap = 0;
+ uint32_t partsHave = 0;
+ 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
+ int nHold = 0;
int fixedLo = 0, fixedHi = 0; // out: the rows fixed and sticky layers cover (0,0 = none)
int cols[4][4] = {}; // round 138: column reads after the bands (x0, x1, y0, y1 on the
int nCols = 0; // frame): the fixed and stuck layers' rects, widened by the shift
@@ -242,6 +254,9 @@ public:
// target aSurfaceH rows tall) into the frame's rows aDestRowLo.. at the same columns
bool JihadReadColumns(uint8_t* aDest, int aStride, int aSurfaceH, int aX0, int aX1, int aSrcRowLo, int aRows, int aDestRowLo,
int aDestX = -1); // round 142: the frame's column when packed elsewhere on the target
+ // T-505: aW columns from aX0 of the target's rows aSrcRowLo.. (aRows, top-down, on a target aSurfaceH rows tall)
+ // into aDest (aDestStride a row), the alpha kept
+ bool JihadReadPacked(uint8_t* aDest, int aDestStride, int aSurfaceH, int aX0, int aW, int aSrcRowLo, int aRows);
// The asynchronous frame: SelectTarget before the compose picks the free window target,
// ReadAsync after it fences the compose and hands the target to the read thread, which reads
// rows [lo, hi) into dest and calls done (on the read thread). When no read thread could be
diff --git a/widget/PuppetWidget.cpp b/widget/PuppetWidget.cpp
index 6535e3e649..5a45a2f1f2 100644
--- a/widget/PuppetWidget.cpp
+++ b/widget/PuppetWidget.cpp
@@ -852,6 +852,7 @@ struct JihadReadJob {
bool overview = false; // T-504 (flag 32): a window of the daemon's overview
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
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;
@@ -903,6 +904,7 @@ void JihadReadOnCompositorThread(JihadReadJob* job)
job->req.overview = job->overview;
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.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
@@ -938,7 +940,8 @@ PuppetWidget::JihadBridge()
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)
+ const void* aSrc, int aSrcY, int aFlags, int aColX0, int aColX1,
+ void* aPartsDest, int aPartsCap, uint32_t aPartsHave)
{
if (!aDocShell || !aDest || aWidth <= 0 || aHeight <= 0 || aStride < aWidth * 4) {
return 0;
@@ -947,7 +950,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);
+ nullptr, false, aColX0, aColX1, aPartsDest, aPartsCap, aPartsHave);
}
bool
@@ -1038,7 +1041,8 @@ PuppetWidget::JihadPaintLayers(nsIDocShell* aDocShell)
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)
+ const float* aView, bool aScrollValid, int aColX0, int aColX1,
+ void* aPartsDest, int aPartsCap, uint32_t aPartsHave)
{
mozilla::layers::CompositorBridgeParent* bridge = JihadBridge();
MessageLoop* loop = mozilla::layers::CompositorThreadHolder::Loop();
@@ -1063,6 +1067,9 @@ PuppetWidget::JihadPostJob(void* aDest, int aStride, int aWidth, int aHeight, in
job->overview = (aFlags & 32) != 0; // T-504
job->overviewParts = (aFlags & 64) != 0;
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 (job->colsKnown) {
job->colX0 = std::max(0, std::min(aColX0, aWidth)); job->colX1 = std::max(0, std::min(aColX1, aWidth));
}
@@ -1075,13 +1082,14 @@ PuppetWidget::JihadPostJob(void* aDest, int aStride, int aWidth, int aHeight, in
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)
+ const float* aView, bool aScrollValid, int aColX0, int aColX1,
+ void* aPartsDest, int aPartsCap, uint32_t aPartsHave)
{
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);
+ aView, aScrollValid, aColX0, aColX1, aPartsDest, aPartsCap, aPartsHave);
}
int
@@ -1144,7 +1152,12 @@ PuppetWidget::JihadComposePoll(uint32_t aToken, bool aWait, int* aOut, bool* aSc
for (int k = 0; k < job->req.nStuck && k < 4; k++)
for (int c = 0; c < 4; c++) aOut[13 + 4 * k + c] = job->req.stuck[k][c];
}
- if (aOutLen >= 30) aOut[29] = (int)job->req.partsGen; // T-505
+ if (aOutLen >= 30) aOut[29] = (int)job->req.partsGen; // T-505: the parts' signature
+ if (aOutLen >= 49) { // T-505 (E2): the parts pass's bytes, the flags, the card's hold set
+ aOut[30] = job->req.partsBytes; aOut[31] = job->req.partsFlags; aOut[32] = job->req.nHold;
+ 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 (aScrollOnly) *aScrollOnly = job->req.scrollOnly;
} else if (!aWait && job->req.composed) {
@@ -1522,7 +1535,8 @@ MOZ_EXPORT int
jihad_offscreen_overview_ok()
{
static const bool sPass = getenv("JIHAD_GPU_PASS") != nullptr;
- return sPass ? 0 : 2; // 2 (T-505): flag 128 too, and the poll's 30th int (the parts' generation)
+ 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
}
MOZ_EXPORT int
jihad_offscreen_compose_peek(nsIWidget* aWidget, uint32_t aToken)
@@ -1562,6 +1576,38 @@ jihad_offscreen_compose_start(nsIWidget* aWidget, nsIDocShell* aDocShell, void*
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);
}
+// T-505 (E2): post and start with a frame without its parts' pass arguments: where its parts' picture goes (aPartsCap
+// bytes), and the signature of the picture the daemon holds (a pass only when this frame's differs)
+MOZ_EXPORT uint32_t
+jihad_offscreen_compose_post2(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, uint32_t aPartsHave)
+{
+ 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, aPartsHave);
+}
+MOZ_EXPORT uint32_t
+jihad_offscreen_compose_start2(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, uint32_t aPartsHave)
+{
+ 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, aPartsHave);
+}
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 c0331d09a7..5378b5afa0 100644
--- a/widget/PuppetWidget.h
+++ b/widget/PuppetWidget.h
@@ -292,7 +292,8 @@ public:
uint32_t JihadComposeStart(nsIDocShell* aDocShell, void* aDest, int aStride,
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)
+ 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)
// 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
@@ -305,7 +306,8 @@ public:
bool JihadPaintLayers(nsIDocShell* aDocShell);
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);
+ 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 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);
@@ -468,7 +470,8 @@ private:
mozilla::Mutex mJihadJobsLock{"jihad-compose-jobs"}; // T-515: the list from the main thread and the express lane
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);
+ const float* aView = nullptr, bool aScrollValid = false, int aColX0 = 0, int aColX1 = 0,
+ void* aPartsDest = nullptr, int aPartsCap = 0, uint32_t aPartsHave = 0);
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=5f290d1097a8350b56ce2092aa9d5fc9147c6661
# 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=ffb0cb8ae1b78695545721b10d77691df2b71aef
UXP_PIN=446b22790a48c38c0ae4d523d1603e63beb88497
# What the user agent and the start pages say: Goanna version and the short UPSTREAM base.
GOANNA_VERSION=6.9
UXP_SHORT=5f290d1
@@ -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 = "ffb0cb8ae1b78695545721b10d77691df2b71aef"
SRCREV = "446b22790a48c38c0ae4d523d1603e63beb88497"
S = "${WORKDIR}/git"
# Modern C++14 toolchain (device-build R1) — the stock dylan/device gcc cannot build UXP.
@@ -0,0 +1,51 @@
# Review findings — T-505 the parts on the CPU (2026-10-26, evening)
Two reviews of daemon `2bd3e6f1` and engine `d14fbe3f68` (0070) / `ffb0cb8ae1` (0071), by the ck:inspector (Codex
was out of credits until 14:59; the inspector is Cavekit 3.1's backup): a code review, and the adversarial design
review the owner asked for on the stale-picture trade ("adversarial review with cavekit 3.1 backup inspector to
address this"). Verdict of both: REVISE. The owner had chosen CPU parts on by default once the findings are fixed;
until they are, the default stays off.
## The trade (design review)
Neither option is exact: without the switch a frame posted before a change but composed after it is still dressed
with the old picture (no check at the poll), and "at most 600 ms" is not bounded (a picture that arrives stale
restarts the clock). Recommended third option (**E2**): the parts' picture composed and read **inside the frame's
own compositor job** whenever the parts changed since the picture the daemon holds — the parts alone, cleared
transparent, only the parts' rects read (packed as chunks, one read) — so every frame is dressed from the same tree
state; the daemon keeps the last 2-3 pictures keyed by generation and dresses each frame with its own. Exact, and
expected at least as fast as the stale switch (the separate picture job, 55-250 ms in the queue, and the express
lane's lost base after it both go). Prerequisite: a change signal that covers more than paints (a parts signature).
Rejected: cheap kind changes (a kind-0 band frame on Wikipedia costs about a whole read), a per-layer hide set (high
effort, E2 with per-part signatures does it cheaper), dropping frames newer than the picture (stalls).
## Findings
| # | sev | where | finding | fix | status |
|---|---|---|---|---|---|
| C1 | high | daemon xStart (express second frame) | with parts on, the express lane's second frame copies the first frame's slot while the express thread dresses it: a ghost strip of a part | no second frame on the first without parts (as the main lane) | |
| C2/D1 | medium | daemon cpuPartsApply | the picture is checked at the post, used at the poll: another zoom's picture, or one older than the frame's generation, can dress it | stamp the job with the picture's serial; at the poll a frame whose generation is not the picture's is not shown (not counted a miss) | |
| D3 | medium | daemon partsGenSeen | the generation can move backwards (an older poll after a newer one) and mark a stale picture current | ignore an older generation | |
| D2 | medium | daemon partsGenSeen / cpuPartsStep | the stale bound restarts with each stale picture | (moot with E2; the switch goes) | |
| D4 | high | engine JihadColumnRead; daemon restore | a column read in a frame without its parts: the restore of what lay beneath treats the band as read whole rows; old part pixels kept (a part blended twice, a ghost strip later) | no column reads without the parts | |
| D5 | medium | daemon cpuPartsStep | the picture's post sets mXLastPostToken: the express lane loses its base after every picture | (moot with E2) | |
| C3/D6 | medium | engine CompositableTransactionParent | the generation moves only on paints: opacity/transform/colour/clip set in a transaction, compositor animations, structure changes inside a part are missed (a fading button stays as pictured) | a parts signature computed in the composite (E2) | |
| C4 | medium | engine | content stacked above a part (a popup over a fixed bar) is drawn under the CPU-drawn part | the engine flags a non-part leaf above and over a part; such a frame is composed with the engine's parts | |
| C5 | medium | engine stuck block | in a frame without its parts the card's hold set (T-544) is every sticky layer, stuck or not, merged: a not-yet-stuck one held still between frames | report the hold set (fixed + stuck) apart from the parts | |
| C6 | medium | engine 0071 | in frames WITH the engine's parts and fixed layers, an own-slot frame's diff-first plan keeps rects judged against the last composite, not the older base | the compensation only without parts or with no fixed/stuck rects | |
| D7 | medium | daemon match | parts matched by columns, height and nearest y: same-size sticky headers in one column (GitHub's file headers) can take each other's pixels | (E2: the frame's own rects and picture come from one composite) | |
| C7 | low | daemon main poll | the slot's kind recorded at the publish, not with the record: the blank-suppression return leaves a valid slot with the old kind and beneath | record the kind at adoption | |
| C8 | low | daemon cpuPartsApply | a gone base record (after a forget or a loss) counted as a miss: old-document jobs can turn parts off for a new page | a separate outcome, not counted | |
| C9 | low | daemon cpuPartsStep | the clip flags read cr.stuck with the compacted index | the original index | |
| D8 | low | daemon cpuPartsApply | a part cut at both frame edges takes the wrong rows | (E2) | |
| C10/D9 | low | daemon main poll | the main lane dresses before its out-of-page white fill (the express lane after) | the same order | |
| — | — | daemon cpuPartsApply | (found while waiting) a frame's part 2 px off the picture's read past the part's block or before its row | rows and columns both have only | fixed 2026-10-26 |
Checked and fine (code review): the restore offset for every kind of base; the scratch's graves; `ownNotWhole`
(speed only); the poll's thirtieth int and flag 128 only with an engine that takes them; the main lane's round-130
exclusion; dropped, missed and adopted jobs' records; 0071's compensation for content (diff in current coordinates,
the verify guard's state, a page-made scroll is whole). No path publishes a frame without its parts undressed.
Proposed criterion (cavekit-gpu-rendering.md): 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; `JIHAD_GPU_VERIFY=1` with the parts on shows zero wrong rows over a scripted pan on Wikipedia and the long page.
+1
View File
@@ -71,6 +71,7 @@ Hacker News 36-37, desktop Wikipedia 13-14 (software 9-12.4).
- [x] A page with a fixed header and a fixed footer reads two bands, not the whole frame *(2026-10-24, T-519: `jihad-fixed-both.html` (an opaque fixed header and footer, nothing else fixed): every band frame reads two bands, `rows 139..141 and 1033..1224`, `rows 130..171 and 1024..1224`, with the header and footer kept.)*
- [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.)*
- [~] 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`); the long page's verify run still owed)*
**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-10-26 (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 second core comes up for a pan, T-516) — earlier: PARTIAL 2026-10-18 (rounds 187-201: the per-page table against `NOGPU=1` (the GPU path ahead on every page: the test page 30-35 against 2.7-3.2 fps, sticky-mix 17-19 against 2.4-3.1, desktop Wikipedia 19-26 against 2.7-2.9, Hacker News 31-37 against 6.4-10.4; the software path pans inside its painted region and publishes few frames); the 56 ms+ gaps traced to the page's restyle, the displayport moves' paints and misc events (stall sampler); overscan, 0.15 of the height above and below the view (engine 0046): grey-free pans, verify clean on four pages, at 10% of the rate (Wikipedia 16.5-24, the test page 25-32). Open: Wikipedia ≥ 24 fps, which needs the frames made off the main thread (T-515)); 2026-10-19, T-515: the main thread's stalls covered (19-21 fps, median gap 45 ms); the rest is the compositor thread's ~45 ms a Wikipedia frame: T-517; 2026-10-19 evening (rounds 349-357): the 10 s pan 23.2-24.1 fps; the page's tile uploads, partial uploads, deferred uploads, a topped-up pool and the express lane on its own slot measured and not kept; the band frame is GPU-bound (r357). BLOCKED by the circuit breaker: the owner's finger round (T-514) and the bench's sensord load (the owner's call) decide) |
| 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-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 second core comes up for a pan, T-516) — earlier: PARTIAL 2026-10-18 (rounds 187-201: the per-page table against `NOGPU=1` (the GPU path ahead on every page: the test page 30-35 against 2.7-3.2 fps, sticky-mix 17-19 against 2.4-3.1, desktop Wikipedia 19-26 against 2.7-2.9, Hacker News 31-37 against 6.4-10.4; the software path pans inside its painted region and publishes few frames); the 56 ms+ gaps traced to the page's restyle, the displayport moves' paints and misc events (stall sampler); overscan, 0.15 of the height above and below the view (engine 0046): grey-free pans, verify clean on four pages, at 10% of the rate (Wikipedia 16.5-24, the test page 25-32). Open: Wikipedia ≥ 24 fps, which needs the frames made off the main thread (T-515)); 2026-10-19, T-515: the main thread's stalls covered (19-21 fps, median gap 45 ms); the rest is the compositor thread's ~45 ms a Wikipedia frame: T-517; 2026-10-19 evening (rounds 349-357): the 10 s pan 23.2-24.1 fps; the page's tile uploads, partial upLine 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
@@ -94,6 +94,7 @@ modifications; the same changes are the commits of `jihad/uxp-RB_20261006`, one
| `0069-gpu-compositor-the-overview-s-windows-and-parts-the-.patch` | `widget/PuppetWidget.cpp`, `gfx/layers/opengl/CompositorOGL.{h,cpp}`, `gfx/layers/ipc/CompositorBridgeParent.cpp`, `gfx/layers/composite/AsyncCompositionManager.{h,cpp}` | T-504: compose flags 32 and 64 (`jihad_offscreen_overview_ok`) — an overview window (fixed and stuck layers at opacity 0, cleared transparent, read with its alpha, so rows no tile covered read alpha 0) and the overview's parts (only the fixed and stuck layers, with their alpha); a fixed sticky probe for both, and the verify guard keeps the probe's state; the daemon stitches a half-size picture of the page around a zoom-in point for a double tap out after a pan |
| `0070-gpu-compositor-a-frame-without-its-parts-flag-128-an.patch` | `widget/PuppetWidget.cpp`, `gfx/layers/opengl/CompositorOGL.h`, `gfx/layers/ipc/CompositorBridgeParent.{h,cpp}`, `gfx/layers/ipc/CompositableTransactionParent.cpp` | T-505: compose flag 128 (`jihad_offscreen_overview_ok` answers 2) — a frame without its parts: every fixed and sticky layer at opacity 0 in the composite and the diff, no fixed rows, keeps or columns, each part reported as its layer's whole rect; the parts' generation (compositable updates under a fixed or sticky layer) as the poll's thirtieth int; `JIHAD_GPU_REQLOG=1` names each composite request's source |
| `0071-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 |
| `0072-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 |
| `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 |
@@ -0,0 +1,9 @@
--- CPU ticks over the drags (pid, before, after, command)
29552 12398 12409 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.riv
31196 11243 11251 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.riv
31867 1056 1708 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
6451 16773 16966 /usr/bin/LunaSysMgr -s
6538 146217 146933 /usr/bin/LunaSysMgr -s
--- /proc/stat before and after
cpu 26424481 2409970 24162426 97913466 2130759 4062 50159 0 0 0
cpu 26425300 2410711 24163221 97915204 2131097 4062 50159 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)
28094 3313 4483 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
29552 12332 12342 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.riv
31196 11182 11189 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.riv
6451 16258 16457 /usr/bin/LunaSysMgr -s
6538 144818 145590 /usr/bin/LunaSysMgr -s
--- /proc/stat before and after
cpu 26419497 2408524 24158206 97903616 2130106 4061 50153 0 0 0
cpu 26420882 2409264 24158961 97905690 2130351 4061 50153 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)
29552 12457 12467 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.riv
31196 11296 11305 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.riv
3415 3052 4234 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.river
6451 17247 17450 /usr/bin/LunaSysMgr -s
6538 147566 148348 /usr/bin/LunaSysMgr -s
--- /proc/stat before and after
cpu 26430869 2411377 24166382 97922312 2131520 4062 50163 0 0 0
cpu 26432233 2412147 24167177 97923508 2131794 4062 50163 0 0 0
File diff suppressed because it is too large. Load diff
+30
View File
@@ -145,3 +145,33 @@ By finger (eight drags; fps while frames flow; one round each, the spread betwee
Mid-pan frames looked at (Wikipedia with and without the stale switch, the long page): each sidebar and the fixed
header once, sharp, in place; the content scrolled beneath; no ghost. Rows read by finger stay above the baseline
(44k against 34k): the pictures (about one a drag, ~736 rows each).
The parts' picture in the frame's own job (E2, the inspector's design; 2026-10-26, late afternoon)
--------------------------------------------------------------------------------------------------
Two inspector reviews (Codex was out of credits; context/impl/impl-review-findings-t505-cpuparts.md): REVISE. The
stale trade was not exact either way (no check at the poll; the bound not bounded), and the generation missed
attribute changes, content above a part was drawn under it, the card's hold set changed meaning, column reads broke
the beneath record, the express lane's second frame raced the first's dressing. The design that replaced the
separate picture job (engine 0072):
- every frame without its parts carries a parts buffer and the signature of the newest picture the daemon holds; the
engine computes the parts' signature in the frame's own job (each part subtree's layers: identity, type, opacity,
content flags, mask, visible region; inside a part its transform and clip; a colour layer's colour; an animation
every composite; the paints under a part; the parts' sizes on the frame) and, when it differs, composes the parts
alone on the band surface (packed in shelves, at most 12 pieces) and reads them with their alpha into the buffer,
in the same task as the frame (no transaction between them; the verify guard holds the diff baseline);
- the daemon keeps the last three pictures by signature and dresses each frame with its own signature's, rect by rect
(same parts, same order, same sizes): no matching, no stale picture;
- a content leaf drawn above a part (in paint order) whose pixels meet it: the frame is not shown, the engine's parts
for 2 s (for the document after three); parts over 1 MB: the engine's for the document;
- the card's hold set is reported apart (fixed and stuck layers, T-544's meaning); no column reads without the parts;
0071's compensation only without the parts or without fixed rects; the express lane builds no second frame on the
first in flight when either is without its parts; the main lane records the kind with the slot and fills the
out-of-page rows before dressing.
Verify (JIHAD_GPU_VERIFY=1, main lane only so every frame is checked, scripted Wikipedia pan; pan-v1-wiki): 39 frames,
27 of them shifted, 0 wrong rows during the pan (one frame during the load, in rows its job read while tiles arrived);
the same run without the parts (pan-v0-wiki): 2 shifted frames with wrong rows. A parts pass: 7-78 ms compose, 7-66 ms
read, 340 KB, about one a drag on Wikipedia.
Scripted pan, the card moving (pan-e2-*): desktop Wikipedia 12.8 fps (9.4 without; 14.2 the stale switch), the long
page 23.6 (18.8; 24.7). By finger (fp-e2-*): Wikipedia 12.3 fps against 9.5 without (fp-e2b-wiki, this build) and 12.3
with the stale switch; the long page 18.3 (15.7 without; 17.5 stale). Frames looked at: parts once, in place.
The stale switch is gone: exact and as fast by finger.
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
+164 -190
View File
@@ -488,7 +488,7 @@ static void jihadExpressThread() {
if (now - w0 >= 30 && jihadTimelineOn()) // device rounds: the main thread held the frame lock this long
fprintf(stderr, "[jihad-tl] t=%lld express waited %ld ms for the frame lock\n", (long long)now, (long)(now - w0));
if (gXPages) for (BrowserPageGoanna* p : *gXPages) {
soon = p->expressStep(now) || soon; soon = p->overviewStep(now) || soon; soon = p->cpuPartsStep(now) || soon;
soon = p->expressStep(now) || soon; soon = p->overviewStep(now) || soon;
}
jihadBoostFrameThreads();
}
@@ -718,12 +718,16 @@ void BrowserPageGoanna::publishWindow(int key, int windowRows, const long* dirty
// T-544: the frame's fixed and stuck rects for the card (JIHAD_GPU_NO_HOLD=1: none, as before)
static const bool sNoHold = getenv("JIHAD_GPU_NO_HOLD") != nullptr;
sb->magic = 0;
if (stuck && stuck->nStuck > 0 && !sNoHold) {
// T-505 (review): a frame without its parts lists every fixed and sticky layer as a part; the card's hold set is
// the engine's hold list (fixed and stuck ones), as T-544 meant
const int nHeld = stuck ? (stuck->holdKnown ? stuck->nHold : stuck->nStuck) : 0;
const int (*held)[4] = stuck ? (stuck->holdKnown ? stuck->hold : stuck->stuck) : nullptr;
if (nHeld > 0 && !sNoHold) {
sb->seq = mPubSeq;
sb->viewX = 0; sb->viewY = viewRow;
sb->count = std::min(stuck->nStuck, kJihadStuckMax);
sb->count = std::min(nHeld, kJihadStuckMax);
for (int k = 0; k < sb->count; k++)
for (int c = 0; c < 4; c++) sb->rects[k][c] = stuck->stuck[k][c];
for (int c = 0; c < 4; c++) sb->rects[k][c] = held[k][c];
sb->magic = kJihadStuckMagic;
static uint32_t sN = 0;
if (jihadTimelineOn() && (sN++ % 25) == 0)
@@ -836,6 +840,7 @@ BrowserPageGoanna::~BrowserPageGoanna() {
if (gXPages) gXPages->erase(this);
overviewForget(); // T-504
cpuPartsForget(); // T-505
partsBufsBury();
// T-512: the lanes' count, one line a page (the [jihad-gpu] lines are off by default; the device
// rounds' default checks read this one)
if (mPage && mPage->GpuPath())
@@ -1931,170 +1936,107 @@ void BrowserPageGoanna::overviewForget() {
// T-505 (the owner's CPU/GPU split): the parts on the CPU (see BrowserPageGoanna.h, PartsPic)
bool BrowserPageGoanna::cpuPartsOn() const {
static const bool sOn = getenv("JIHAD_GPU_CPU_PARTS") != nullptr;
// on (the owner, 2026-10-26); JIHAD_GPU_CPU_PARTS=0 or the gate file <state>/no-cpu-parts (read once) turns it off
static const bool sOn = [] {
const char* e = getenv("JIHAD_GPU_CPU_PARTS");
if (e && !strcmp(e, "0")) return false;
const std::string sd = jihad::RuntimeStateDir();
return sd.empty() || access((sd + "/no-cpu-parts").c_str(), F_OK) != 0;
}();
return sOn && !mPartsOff && mPage && mPage->GpuPath() && mPage->PartsReady() && !mGpuRootless;
}
// JIHAD_GPU_CPU_PARTS_STALE (a measurement for the owner): while a changed part's picture is made, the frames keep the
// one before it (at most 600 ms behind) instead of going back to the engine's parts for those frames and back again
static bool jihadPartsStaleOk() { static const bool s = getenv("JIHAD_GPU_CPU_PARTS_STALE") != nullptr; return s; }
bool BrowserPageGoanna::cpuPartsFor(double Z, int x, int w, int H, int os) const {
const PartsPic& p = mPP;
const bool genOk = !mPartsGenKnown || p.gen == mPartsGenNow ||
(jihadPartsStaleOk() && mPartsStaleSince && jihadNowMs() - mPartsStaleSince < 600);
// the frame's height and overscan row need not be the picture's: each frame says where its parts are, and a part's
// pixels do not depend on where it sits (the fling lead moves the overscan row at every drag's end)
(void)H; (void)os;
return cpuPartsOn() && p.ok && !p.miss && fabs(p.zoom - Z) < 1e-6 && p.x == x && p.w == w && genOk;
bool BrowserPageGoanna::cpuPartsFor() const {
return cpuPartsOn() && jihadNowMs() >= mPartsOffUntil;
}
// the parts' generation a composite saw: the picture older than it since when
void BrowserPageGoanna::partsGenSeen(uint32_t g) {
mPartsGenNow = g; mPartsGenKnown = true;
if (!mPP.ok || mPP.gen == g) mPartsStaleSince = 0;
else if (!mPartsStaleSince) mPartsStaleSince = jihadNowMs();
}
// The picture's job, on the express thread after its step (the frame lock held): the parts alone (flags 64 and 128, a
// verify composite: the lanes' next frames see no change from it) at the card's newest position, read with their alpha;
// each part's rect cropped out of it. True while it is in flight.
__attribute__((optimize("O2"))) bool BrowserPageGoanna::cpuPartsStep(int64_t now) {
if (!cpuPartsOn()) return false;
PartsPic& p = mPP;
const XSnap& S = mXSnap;
if (p.building) {
GoannaRenderPage::ComposeResult cr;
const int r = mPage->ComposePoll(p.token, nullptr, &cr);
if (r == -2 || r == -3) {
if (now - p.tPost > 3000) { JIHAD_GPU_LOG("[jihad-gpu] cpu parts: the picture not composed in 3 s\n"); cpuPartsForget(); }
return p.building;
}
p.building = false; p.token = 0;
if (r >= 0 && cr.partsGenKnown) partsGenSeen(cr.partsGen);
// (its own geometry is what it was composed at: only another width makes it useless)
if (r < 0 || !S.ok || S.w != p.bW) { p.scratch.reset(); p.scratchBytes = 0; return false; }
// each part's rows as read, with their alpha (frame px of the job's frame)
const int W = p.bW, H = p.bH;
int n = 0, rr[4][4]; size_t bytes = 0;
for (int i = 0; i < cr.nStuck && i < 4; i++) {
const int x0 = std::max(0, cr.stuck[i][0]), x1 = std::min(W, cr.stuck[i][2]);
const int y0 = std::max(0, cr.stuck[i][1]), y1 = std::min(H, cr.stuck[i][3]);
if (x1 <= x0 || y1 <= y0) continue;
rr[n][0] = x0; rr[n][1] = y0; rr[n][2] = x1; rr[n][3] = y1; bytes += (size_t)(x1 - x0) * (y1 - y0) * 4; n++;
}
// read around the last picture's parts only: a part outside those rows is not in it; the next picture whole
for (int i = 0; i < n; i++) {
if (rr[i][1] < p.bLo || rr[i][3] > p.bHi) {
JIHAD_GPU_LOG("[jihad-gpu] cpu parts: a part (rows %d..%d) outside the rows read (%d..%d): the picture made whole\n",
rr[i][1], rr[i][3], p.bLo, p.bHi);
p.wholeNext = true; p.ok = false; p.scratch.reset(); p.scratchBytes = 0;
return false;
}
}
p.wholeNext = false;
p.px.resize(bytes);
size_t at = 0;
for (int i = 0; i < n; i++) {
const size_t rowBytes = (size_t)(rr[i][2] - rr[i][0]) * 4;
p.off[i] = at;
for (int y = rr[i][1]; y < rr[i][3]; y++, at += rowBytes)
memcpy(p.px.data() + at, p.scratch.get() + ((size_t)y * W + rr[i][0]) * 4, rowBytes);
for (int c = 0; c < 4; c++) p.r[i][c] = rr[i][c];
p.clipTop[i] = cr.stuck[i][1] <= 0; p.clipBottom[i] = cr.stuck[i][3] >= H;
}
p.n = n; p.zoom = p.bZoom; p.x = p.bX; p.w = W; p.h = H; p.os = p.bOs;
p.gen = cr.partsGen; p.ok = true; p.miss = false;
p.scratch.reset(); p.scratchBytes = 0;
mPartsStaleSince = (!mPartsGenKnown || p.gen == mPartsGenNow) ? 0 : jihadNowMs();
char rs[160] = "";
for (int i = 0, k = 0; i < n && k < (int)sizeof(rs) - 32; i++)
k += snprintf(rs + k, sizeof(rs) - k, " %d,%d..%d,%d", p.r[i][0], p.r[i][1], p.r[i][2], p.r[i][3]);
JIHAD_GPU_LOG("[jihad-gpu] cpu parts: the picture in %ld ms, %d parts (%zu KB, generation %u, rows %d..%d read):%s\n",
(long)(now - p.tPost), n, p.px.size() / 1024, p.gen, p.bLo, p.bHi, rs);
return false;
// the parts buffers (E2): one lent to each job without its parts, by token
unsigned char* BrowserPageGoanna::partsBufTake() {
const int64_t now = jihadNowMs();
for (size_t i = 0; i < mPartsLent.size();) { // a job forgotten in flight: its buffer back after 10 s
if (now - mPartsLent[i].since > 10000) { mPartsFree.push_back(mPartsLent[i].buf); mPartsLent[i] = mPartsLent.back(); mPartsLent.pop_back(); }
else ++i;
}
if (!S.ok || S.popups || mFrozen || mXFailures >= 3) return false;
XWant want;
{ std::lock_guard<std::mutex> l(jihadWantLock()); want = mXWant; }
if (!want.seq) return false;
const double Z = (want.zoom >= 0.99 && want.zoom <= 1.01) ? 1.0 : want.zoom;
// wanted: none yet, a part a frame had and it lacks, another generation (a part changed), another geometry
const bool fits = p.ok && fabs(p.zoom - Z) < 1e-6 && p.x == want.x && p.w == S.w; // (cpuPartsFor)
if (fits && !p.miss && (!mPartsGenKnown || p.gen == mPartsGenNow)) return false;
if (mXTreeRes > 1.0005 && GoannaRenderPage::GpuResolutionFor(Z) <= 1.0005f) return false; // a zoom out's paint first
const int H = S.frameH, W = S.w, os = S.os;
p.scratchBytes = (size_t)W * 4 * H;
p.scratch.reset(new unsigned char[p.scratchBytes]);
const double view[5] = { Z, 0.0, -(double)os / Z, (double)want.x / Z, (double)want.y / Z };
// the rows: around the last picture's parts at this geometry (the parts are mostly stuck: the same rows), else whole
int lo = 0, hi = H;
if (fits && !p.wholeNext && p.n > 0) {
lo = H; hi = 0;
for (int i = 0; i < p.n; i++) { lo = std::min(lo, p.r[i][1]); hi = std::max(hi, p.r[i][3]); }
lo += os - p.os; hi += os - p.os; // the stuck parts move with the overscan row
lo = std::max(0, lo - 96); hi = std::min(H, hi + 96);
if (hi <= lo) { lo = 0; hi = H; }
}
p.bLo = lo; p.bHi = hi;
const uint32_t token = mPage->ComposePost(p.scratch.get(), p.scratchBytes, H, lo, hi, 0, 0, 0, nullptr, 0, false, view,
0, -1, false, true, true, false, true, true);
if (!token) { p.scratch.reset(); p.scratchBytes = 0; return false; }
mXLastPostToken = token; // the newest composite is not a published frame
p.building = true; p.token = token; p.tPost = now;
p.bZoom = Z; p.bX = want.x; p.bW = W; p.bH = H; p.bOs = os;
return true;
if (!mPartsFree.empty()) { unsigned char* b = mPartsFree.back(); mPartsFree.pop_back(); return b; }
return new (std::nothrow) unsigned char[kPartsCap];
}
// A frame composed without its parts, in its slot: what lay beneath the frame it was built on's parts back where they
// landed (outside the rows the job read: those are new), what lies beneath this frame's parts kept, the parts drawn
// over it (premultiplied: out = part + out x (1 - alpha)). False: a part the picture lacks (the frame is not shown).
__attribute__((optimize("O2"))) bool BrowserPageGoanna::cpuPartsApply(int s, unsigned char* pixels, const GoannaRenderPage::ComposeResult& cr,
int H, int w, int src, long srcY, int jobY) {
PartsPic& p = mPP;
// this frame's parts in the picture: the same columns and height, one to one, the nearest in y; a part cut by the
// frame's top or bottom edge is the matching end of a whole one
int nf = 0, fr[4][4], pick[4], from[4];
void BrowserPageGoanna::partsBufLend(unsigned char* buf, uint32_t token) {
if (!buf) return;
if (!token) { mPartsFree.push_back(buf); return; }
mPartsLent.push_back(PartsLend{ buf, token, jihadNowMs() });
}
unsigned char* BrowserPageGoanna::partsBufOf(uint32_t token) {
for (const PartsLend& l : mPartsLent) if (l.token == token) return l.buf;
return nullptr;
}
void BrowserPageGoanna::partsBufReturn(uint32_t token) {
for (size_t i = 0; i < mPartsLent.size(); i++) {
if (mPartsLent[i].token != token) continue;
if (mPartsFree.size() < 4) mPartsFree.push_back(mPartsLent[i].buf); else delete[] mPartsLent[i].buf;
mPartsLent[i] = mPartsLent.back(); mPartsLent.pop_back();
return;
}
}
void BrowserPageGoanna::partsBufsBury() {
for (unsigned char* b : mPartsFree) delete[] b;
mPartsFree.clear();
if (!mPartsLent.empty() && !sOvGraves) sOvGraves = new std::vector<JihadOvGrave>();
for (const PartsLend& l : mPartsLent) sOvGraves->push_back(JihadOvGrave{ l.buf, jihadNowMs() + 10000 }); // a late pass may still write
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).
__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) {
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)
int nf = 0, fr[4][4];
size_t need = 0;
for (int i = 0; i < cr.nStuck && i < 4; i++) {
const int x0 = std::max(0, cr.stuck[i][0]), x1 = std::min(w, cr.stuck[i][2]);
const int y0 = std::max(0, cr.stuck[i][1]), y1 = std::min(H, cr.stuck[i][3]);
if (x1 <= x0 || y1 <= y0) continue;
const int x0 = cr.stuck[i][0], y0 = cr.stuck[i][1], x1 = cr.stuck[i][2], y1 = cr.stuck[i][3];
if (x0 < 0 || y0 < 0 || x1 > w || y1 > H || x1 <= x0 || y1 <= y0) return kPartsNoPic; // not as the engine packs them
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;
}
bool used[4] = {};
for (int i = 0; i < nf; i++) {
const int fh = fr[i][3] - fr[i][1];
int best = -1, bd = 1 << 30;
for (int j = 0; j < p.n; j++) {
if (used[j] || abs(p.r[j][0] - fr[i][0]) > 2 || abs(p.r[j][2] - fr[i][2]) > 2) continue;
const int ph = p.r[j][3] - p.r[j][1];
const bool cut = (fr[i][1] == 0 || fr[i][3] == H) && fh < ph && !p.clipTop[j] && !p.clipBottom[j];
if (abs(ph - fh) > 2 && !cut) continue;
const int d = abs(p.r[j][1] - fr[i][1]);
if (d < bd) { bd = d; best = j; }
// 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;
}
if (best < 0) {
p.miss = true;
static uint32_t sMissLog = 0;
if (sMissLog++ < 12) {
char fs[160] = "", ps[160] = "";
for (int k = 0, q = 0; k < nf && q < (int)sizeof(fs) - 32; k++)
q += snprintf(fs + q, sizeof(fs) - q, " %d,%d..%d,%d", fr[k][0], fr[k][1], fr[k][2], fr[k][3]);
for (int k = 0, q = 0; k < p.n && q < (int)sizeof(ps) - 32; k++)
q += snprintf(ps + q, sizeof(ps) - q, " %d,%d..%d,%d", p.r[k][0], p.r[k][1], p.r[k][2], p.r[k][3]);
JIHAD_GPU_LOG("[jihad-gpu] cpu parts: the frame's part %d unmatched; the frame's:%s; the picture's (generation %u):%s\n",
i, fs, p.gen, ps);
}
return false;
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;
}
used[best] = true; pick[i] = best;
const int ph = p.r[best][3] - p.r[best][1];
from[i] = (fr[i][1] == 0 && fh < ph) ? ph - fh : 0; // cut at the top: its lower rows
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;
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;
// what lay beneath the old frame's parts, where they landed, outside the rows read
if (src >= 0) {
const SlotParts& sp = mSlotParts[src];
if (sp.kind != 1) return false;
if (sp.kind != 1) return kPartsNoBase;
const long d = (long)(cr.frameYValid ? cr.frameY : jobY) - srcY;
auto readRow = [&](long y) { return (y >= cr.readLo && y < cr.readHi) || (y >= cr.read2Lo && y < cr.read2Hi); };
for (int k = 0; k < sp.n; k++) {
@@ -2106,22 +2048,20 @@ __attribute__((optimize("O2"))) bool BrowserPageGoanna::cpuPartsApply(int s, uns
}
}
}
if (p) const_cast<PartsPic*>(p)->used = ++mPicClock;
// this frame's: what lies beneath kept, the parts drawn over it
SlotParts& dp = mSlotParts[s];
size_t bytes = 0;
for (int i = 0; i < nf; i++) bytes += (size_t)(fr[i][2] - fr[i][0]) * (fr[i][3] - fr[i][1]) * 4;
dp.beneath.resize(bytes);
dp.beneath.resize(need);
size_t at = 0;
for (int i = 0; i < nf; i++) {
const int x0 = fr[i][0], x1 = fr[i][2], rw = x1 - x0, pw = p.r[pick[i]][2] - p.r[pick[i]][0];
const int px0 = x0 - p.r[pick[i]][0];
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) {
uint32_t* row = (uint32_t*)(pixels + ((size_t)y * w + x0) * 4);
memcpy(dp.beneath.data() + at, row, (size_t)rw * 4);
const uint32_t* pp = (const uint32_t*)(p.px.data() + p.off[pick[i]]) + (size_t)(from[i] + y - fr[i][1]) * pw + px0;
for (int x = 0; x < rw && x + px0 < pw; x++) {
const uint32_t* pp = (const uint32_t*)(p->px.data() + p->off[i]) + (size_t)(y - 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; }
if (a == 0) continue;
@@ -2134,29 +2074,41 @@ __attribute__((optimize("O2"))) bool BrowserPageGoanna::cpuPartsApply(int s, uns
}
dp.n = nf; dp.kind = 1;
mPartsMissRun = 0;
return true;
return kPartsDressed;
}
void BrowserPageGoanna::cpuPartsMissed(int nStuck) {
static uint32_t sMissN = 0;
if (sMissN++ < 20) JIHAD_GPU_LOG("[jihad-gpu] cpu parts: a part the picture lacks (%d in the frame): the frame not shown\n", nStuck);
// a picture made again that still lacks it would be made forever: three in a row and the parts are the GPU's
// again for this document
if (++mPartsMissRun >= 3 && !mPartsOff) {
// A frame not dressed is not shown. No picture of its parts: the next job asks for one (and three in a row turn the
// parts back to the engine's for the document); a content layer above a part (a popup over a fixed bar, a page over a
// fixed background): the engine's parts for 2 s, for the document after three; the parts too big: the engine's
void BrowserPageGoanna::cpuPartsRefused(int outcome, int nStuck) {
static uint32_t sN = 0;
const char* why = outcome == kPartsNoPic ? "no picture of its parts" : outcome == kPartsNoBase ? "its base's record gone"
: outcome == kPartsCovered ? "a content layer above a part" : "the parts too big";
if (sN++ < 20 || (sN % 50) == 0) JIHAD_GPU_LOG("[jihad-gpu] cpu parts: the frame not shown (%s; %d parts)\n", why, nStuck);
if (outcome == kPartsNoPic) {
mPicForce = true;
if (++mPartsMissRun >= 3 && !mPartsOff) {
mPartsOff = true;
JIHAD_GPU_LOG("[jihad-gpu] cpu parts off for this page: three frames in a row without a picture of their parts\n");
cpuPartsForget();
}
} else if (outcome == kPartsCovered) {
mPartsOffUntil = jihadNowMs() + 2000;
if (++mPartsCovers >= 3 && !mPartsOff) {
mPartsOff = true;
JIHAD_GPU_LOG("[jihad-gpu] cpu parts off for this page: content drawn above its parts three times\n");
cpuPartsForget();
}
} else if (outcome == kPartsTooBig && !mPartsOff) {
mPartsOff = true;
JIHAD_GPU_LOG("[jihad-gpu] cpu parts off for this page: three frames in a row had a part the picture lacks\n");
JIHAD_GPU_LOG("[jihad-gpu] cpu parts off for this page: the parts are too big\n");
cpuPartsForget();
}
}
void BrowserPageGoanna::cpuPartsForget() {
if (mPP.building && mPage) {
mPage->ComposeForget(mPP.token);
if (!sOvGraves) sOvGraves = new std::vector<JihadOvGrave>();
sOvGraves->push_back(JihadOvGrave{ mPP.scratch.release(), jihadNowMs() + 10000 }); // a late read may still write
}
mPP = PartsPic();
mPartsStaleSince = 0;
for (int k = 0; k < 3; k++) { mPics[k] = PartsPic(); }
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); }
}
@@ -4857,9 +4809,9 @@ void verifyRefRelease(int i, bool givenUp) {
bool BrowserPageGoanna::gpuJobStart(int slot, int key, unsigned char* pixels, size_t bytes, int x, int y, double zoom, int w, int h) {
gpuSetView(zoom, mGpuViewRX, mGpuViewRY); // T-505: the view's row in this frame (the compositor takes it at the start)
// T-505: a frame without its parts when the parts' picture fits (as the express lane's): every lane's frames of one
// kind, built only on frames of that kind; the frame it is built on, for what lay beneath that frame's parts
const bool co = cpuPartsFor(zoom, x, w, h, mGpuViewRow);
// T-505: a frame without its parts (as the express lane's): every lane's frames of one kind, built only on frames of
// that kind; the frame it is built on, for what lay beneath that frame's parts
const bool co = cpuPartsFor();
const int kindNow = co ? 1 : 0;
int baseSlot = -1; long baseY = 0;
long sLo = 0, sHi = h, s2Lo = 0, s2Hi = 0, shift = 0; // whole, unless the slot's frame is still good
@@ -5067,10 +5019,13 @@ bool BrowserPageGoanna::gpuJobStart(int slot, int key, unsigned char* pixels, si
// 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
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)
: mPage->ComposeStart(winPixels, winBytes, sLo, sHi, s2Lo, s2Hi, shift, src, srcYForEngine, baseIsPrevious, false, colX0, colX1, winMoved, co);
false, false, false, false, co, pbuf, kPartsCap, partsHave())
: mPage->ComposeStart(winPixels, winBytes, sLo, sHi, s2Lo, s2Hi, shift, src, srcYForEngine, baseIsPrevious, false, colX0, colX1, winMoved,
co, pbuf, kPartsCap, partsHave());
partsBufLend(pbuf, token);
if (token) mXLastPostToken = token;
if (token && slot >= 0) mGpuSlotValid[slot] = false; // T-517 (review): being written; its publish records it again
if (!token) {
@@ -5420,10 +5375,11 @@ bool BrowserPageGoanna::xStart(int64_t now, double Z, const XWant& want, int64_t
base = b >= 0 && b < 3 && mXPub.token && mXPub.token == mXLastPostToken && mXPub.x == x && mXPub.zoom == Z &&
mXPub.w == w && mXPub.h == H && mXPub.fixedHi >= mXPub.fixedLo && S.base[b] && mXPub.key == S.keys[b];
}
// T-505: a frame without its parts (JIHAD_GPU_CPU_PARTS) when their picture fits it; it builds only on a frame of
// its kind (the parts drawn over it on the CPU, what lay beneath them kept), and a frame of the other kind on its own
const bool co = cpuPartsFor(Z, x, w, H, os);
if (base && (aBase ? aBase->contentOnly != co : mSlotParts[b].kind != (co ? 1 : 0))) base = false;
// T-505: a frame without its parts; it builds only on a frame of its kind (the parts drawn over it on the CPU, what
// 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;
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
@@ -5600,9 +5556,11 @@ 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; }
unsigned char* const pbuf = (co && !viewOnly) ? partsBufTake() : nullptr; // T-505 (E2)
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);
false, viewOnly, false, false, co && !viewOnly, pbuf, kPartsCap, partsHave());
partsBufLend(pbuf, token);
if (!token) {
if (++mXFailures >= 3) fprintf(stderr, "[jihad-gpu] express lane off for this page: the compositor took no frame three times\n");
return false;
@@ -5641,6 +5599,8 @@ bool BrowserPageGoanna::xPoll(int64_t now) {
}
return true;
}
// T-505 (E2): done or failed, its parts buffer back to the pool once this poll is through with it
struct PartsBack { BrowserPageGoanna* p; uint32_t t; ~PartsBack() { p->partsBufReturn(t); } } partsBack{this, mXJob.token};
if (!genOk || r < 0) {
// the buffers or the geometry changed under it, or it failed: dropped, and the one built on it
pubDirtyAll(); // T-527
@@ -5656,7 +5616,7 @@ bool BrowserPageGoanna::xPoll(int64_t now) {
if (mXJob.adopted) mGpuRefusals = 0; else mXFailures = 0;
const int s = mXJob.slot, H = mXJob.h, w = mXJob.w;
unsigned char* pixels = mXJob.pixels;
if (cr.partsGenKnown) partsGenSeen(cr.partsGen); // T-505
if (!mXJob.contentOnly) cr.holdKnown = false; // T-505: the hold set is a frame without its parts' only
const int frameY = cr.frameYValid ? (int)cr.frameY : mXJob.y; // round 115: where the engine composed it
if (mXJob.shift) mPage->CleanRows(pixels, 0, H); // the shifted rows: the compositor thread's writes
// the frame's rows outside the page white, as the main lane does (T-505)
@@ -5671,10 +5631,11 @@ bool BrowserPageGoanna::xPoll(int64_t now) {
if (vhi < H) memset(pixels + (size_t)vhi * w * 4, 0xff, (size_t)(H - vhi) * w * 4);
pubDirtyAll();
}
// T-505: a frame without its parts: drawn over it on the CPU, or (a part the picture lacks) not shown at all
// 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) {
if (!cpuPartsApply(s, pixels, cr, H, w, mXJob.srcSlot, mXJob.srcY, mXJob.y)) {
cpuPartsMissed(cr.nStuck);
const int outcome = cpuPartsDress(s, pixels, cr, H, w, mXJob.srcSlot, mXJob.srcY, mXJob.y, partsBufOf(mXJob.token));
if (outcome != kPartsDressed) {
cpuPartsRefused(outcome, cr.nStuck);
mXJob = XJob(); xForget(mXJob2);
gpuSlotsLost(); // every slot's kind unknown (this one's pixels are half made)
return true;
@@ -6641,7 +6602,9 @@ void BrowserPageGoanna::paintToSharedBuffer() {
// composite the newest; this one is dropped (its slot's record with it)
const bool xDropped = r >= 0 && oldest.xFrames != mXFrames;
// T-527: its composite's change, into the change since the last published frame
if (r >= 0 && cr.partsGenKnown) partsGenSeen(cr.partsGen); // T-505
// T-505 (E2): its parts buffer back to the pool once this poll is through; the hold set its kind's only
struct PartsBack { BrowserPageGoanna* p; uint32_t t; ~PartsBack() { p->partsBufReturn(t); } } partsBack{this, oldest.token};
if (!oldest.contentOnly) cr.holdKnown = false;
if (r >= 0) pubDirtyAdd(cr.invLo, cr.invHi, cr.invX0, cr.invX1, cr.invXKnown, oldest.x,
cr.frameYValid ? (int)cr.frameY : oldest.y, oldest.w, oldest.h, oldest.zoom);
else pubDirtyAll();
@@ -6687,11 +6650,21 @@ void BrowserPageGoanna::paintToSharedBuffer() {
// one: its change into the other slots' records)
bool partsMiss = false;
if (r >= 0 && gslot >= 0 && !xDropped && oldest.contentOnly) {
if (cpuPartsApply(gslot, pixels, cr, h, w, oldest.srcSlot, oldest.srcY, oldest.y)) {
{
// (review C10) the rows outside the page white first, as the express lane does, so what lies beneath the
// parts is kept as shown (the adoption below fills them again: the same pixels)
const int fy = cr.frameYValid ? (int)cr.frameY : oldest.y;
const long pe = (mLastContentH > 0) ? (long)((double)mLastContentH * oldest.zoom) - fy : (long)h;
const long ab = std::min((long)h, std::max(0L, -(long)fy));
if (ab > 0) memset(pixels, 0xff, (size_t)ab * w * 4);
if (pe < h && pe >= 0) memset(pixels + (size_t)pe * w * 4, 0xff, (size_t)(h - pe) * w * 4);
}
const int outcome = cpuPartsDress(gslot, pixels, cr, h, w, oldest.srcSlot, oldest.srcY, oldest.y, partsBufOf(oldest.token));
if (outcome == kPartsDressed) {
gpuPartsCpu = true;
} else {
partsMiss = true;
cpuPartsMissed(cr.nStuck);
cpuPartsRefused(outcome, cr.nStuck);
mGpuSlotValid[gslot] = false; mSlotParts[gslot].kind = -1;
gpuStaleAdd(gslot, il, ih, cr, cr.frameYValid ? (int)cr.frameY : oldest.y, oldest.x, oldest.zoom, w, h);
nb = -1;
@@ -6701,6 +6674,7 @@ void BrowserPageGoanna::paintToSharedBuffer() {
if (xDropped && gslot >= 0 && oldest.contentOnly) mSlotParts[gslot].kind = -1; // its pixels lack the parts
if (r >= 0 && gslot >= 0 && !xDropped && !partsMiss) {
mGpuSlotValid[gslot] = true;
if (!gpuPartsCpu) mSlotParts[gslot].kind = 0; // (review C7) the kind with the record, not at the publish
// round 115: the position the engine composed it at, when a scroll of the page's own moved it
const int frameY = cr.frameYValid ? (int)cr.frameY : oldest.y;
mGpuSlotX[gslot] = oldest.x; mGpuSlotY[gslot] = frameY; mGpuSlotZoom[gslot] = oldest.zoom;
+35 -30
View File
@@ -605,41 +605,46 @@ private:
int64_t t0 = 0, tPost = 0;
};
Overview mOv;
// T-505 (the owner's CPU/GPU split, 2026-10-26): JIHAD_GPU_CPU_PARTS=1. Under a finger the GPU is the contended
// resource (LunaSysMgr redraws the card on it) while the CPUs are a quarter idle. A pan's frames are composed on the
// GPU WITHOUT the page's fixed and sticky parts (engine flag 128: no sidebar columns, no kept rects, no fixed rows) and
// the parts are drawn over each frame on the CPU from their picture (flags 64|128, the parts alone with their alpha,
// composed again when the engine's parts generation moves: a part's content changed). 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. A part the picture does not have: the frame is not published, and the frames are composed as
// before until the picture has it.
struct PartsPic {
bool ok = false, miss = false; uint32_t gen = 0; double zoom = 0.0; int x = 0, w = 0, h = 0, os = 0;
int n = 0; int r[4][4] = {}; bool clipTop[4] = {}, clipBottom[4] = {}; size_t off[4] = {};
std::vector<unsigned char> px; // B8G8R8A8 premultiplied, each rect's rows one after the other
bool building = false; uint32_t token = 0; int64_t tPost = 0;
double bZoom = 0.0; int bX = 0, bW = 0, bH = 0, bOs = 0; // the job in flight's geometry
int bLo = 0, bHi = 0; bool wholeNext = false; // its rows read; a part fell outside the last ones
std::unique_ptr<unsigned char[]> scratch; size_t scratchBytes = 0;
};
PartsPic mPP;
// T-505 (the owner's CPU/GPU split, 2026-10-26; on, JIHAD_GPU_CPU_PARTS=0 or <state>/no-cpu-parts off). Under a
// finger the GPU is the contended resource (LunaSysMgr redraws the card on it) while the CPUs are a quarter idle. A
// pan's frames are composed on the GPU WITHOUT the page's fixed and sticky parts (engine flag 128: no sidebar
// columns, no kept rects, no fixed rows) and the parts are drawn over each frame on the CPU. E2 (the inspector's
// design, 2026-10-26): the engine reads the parts' picture in the frame's own job, from the same tree state, when
// their signature (content, attributes, structure, sizes) is not one the daemon holds a picture of (partsHave); each
// frame is dressed with the picture of its own signature, rect by rect (the same parts, in the same order and sizes).
// 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];
uint64_t mPicClock = 0;
uint32_t mPicNewest = 0; bool mPicHave = false, mPicForce = false;
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)
struct PartsLend { unsigned char* buf; uint32_t token; int64_t since; };
std::vector<PartsLend> mPartsLent;
std::vector<unsigned char*> mPartsFree;
unsigned char* partsBufTake();
void partsBufLend(unsigned char* buf, uint32_t token); // token 0: the post failed, the buffer back
unsigned char* partsBufOf(uint32_t token);
void partsBufReturn(uint32_t token);
void partsBufsBury(); // the page going: the free ones freed, the lent ones freed 10 s later (sOvGraves)
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; };
SlotParts mSlotParts[3];
uint32_t mPartsGenNow = 0; bool mPartsGenKnown = false;
int mPartsMissRun = 0; bool mPartsOff = false; // frames in a row a picture could not dress; off for the document
int64_t mPartsStaleSince = 0; // the picture older than the parts since (0: current)
void partsGenSeen(uint32_t g);
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(double Z, int x, int w, int H, int os) const; // the picture fits a frame of this geometry
bool cpuPartsApply(int s, unsigned char* pixels, const GoannaRenderPage::ComposeResult& cr, int H, int w,
int src, long srcY, int jobY); // src: the slot it was built on (-1: none), at srcY
void cpuPartsMissed(int nStuck); // a frame a picture could not dress (three in a row: off for the document)
void cpuPartsForget(); // the picture and every slot's record (the frame lock)
public:
bool cpuPartsStep(int64_t now); // T-505: the express thread (the frame lock): the picture's job
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)
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:
uint32_t mOvDueSeq = 0; int64_t mOvDueMs = 0; // the kept frame an overview has been due for, since
std::unique_ptr<unsigned char[]> mOvScratch; // the jobs' reads while they are in flight (the parts' first, a frame;
+17
View File
@@ -88,6 +88,21 @@ extern "C" {
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) __attribute__((weak));
// OPTIONAL (T-505, E2; jihad_offscreen_overview_ok answers 3): post and start with a frame without its parts' pass
// arguments: where the parts' picture goes (aPartsCap bytes) and the signature of the picture the daemon holds; the
// poll then takes 49 ints (the parts' signature, the pass's bytes, the flags, the card's hold set)
uint32_t jihad_offscreen_compose_post2(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,
uint32_t aPartsHave) __attribute__((weak));
uint32_t jihad_offscreen_compose_start2(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,
uint32_t aPartsHave) __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,
@@ -160,6 +175,8 @@ inline void ReportEngineExports() {
{"jihad_offscreen_compose_forget", jihad_offscreen_compose_forget != nullptr},
{"jihad_offscreen_paint_layers", jihad_offscreen_paint_layers != nullptr},
{"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_peek", jihad_offscreen_compose_peek != nullptr},
{"jihad_offscreen_overview_ok", jihad_offscreen_overview_ok != nullptr},
{"jihad_offscreen_compositor_seq", jihad_offscreen_compositor_seq != nullptr},
+30 -8
View File
@@ -4988,7 +4988,8 @@ 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) {
bool windowMoved, bool contentOnly, unsigned char* partsDest, int partsCap,
uint32_t partsHave) {
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;
@@ -4997,9 +4998,12 @@ uint32_t GoannaRenderPage::ComposeStart(unsigned char* dst, size_t dstBytes, lon
if (!ds) return 0;
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
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,
(engineShift ? 1 : 0) | (verify ? 2 : 0) | 4 | (windowMoved ? 8 : 0) |
(contentOnly && PartsReady() ? 128 : 0), (int)colX0, (int)colX1);
flags, (int)colX0, (int)colX1);
return token;
}
@@ -5026,7 +5030,9 @@ bool GoannaRenderPage::OverviewReady() const {
}
bool GoannaRenderPage::PartsReady() const {
return ExpressReady() && jihad_offscreen_overview_ok && jihad_offscreen_overview_ok() >= 2 && jihad_offscreen_compose_poll2;
// 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;
}
int GoannaRenderPage::ComposePeek(uint32_t token) {
@@ -5042,13 +5048,23 @@ bool GoannaRenderPage::PaintLayers() {
uint32_t GoannaRenderPage::ComposePost(unsigned char* dst, size_t dstBytes, int frameH, long rowLo, long rowHi, long row2Lo,
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) {
bool bandsOnly, bool overview, bool overviewParts, bool contentOnly,
unsigned char* partsDest, int partsCap, uint32_t partsHave) {
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
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,
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, partsHave);
}
return jihad_offscreen_compose_post(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) |
@@ -5085,10 +5101,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[30] = {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[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 scrollOnly = 0;
// T-505: thirty ints from an engine with the parts' generation (it writes no more than it is asked for)
const int nOut = PartsReady() ? 30 : 29;
// 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;
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
@@ -5110,6 +5126,12 @@ int GoannaRenderPage::ComposePoll(uint32_t token, unsigned char* dst, ComposeRes
out->frameY = o[8]; out->frameYValid = o[9] != 0;
out->nStuck = (o[12] > 0 && o[12] <= 4) ? o[12] : 0; // T-544
out->partsGen = (uint32_t)o[29]; out->partsGenKnown = nOut >= 30; // T-505
if (nOut >= 49) { // (E2)
out->partsBytes = o[30]; out->partsFlags = o[31]; out->holdKnown = true;
out->nHold = (o[32] > 0 && o[32] <= 4) ? o[32] : 0;
for (int k = 0; k < out->nHold; k++)
for (int c = 0; c < 4; c++) out->hold[k][c] = o[33 + 4 * k + c];
}
for (int k = 0; k < out->nStuck; k++)
for (int c = 0; c < 4; c++) out->stuck[k][c] = o[13 + 4 * k + c];
}
+8 -3
View File
@@ -386,7 +386,8 @@ public:
uint32_t ComposeStart(unsigned char* dst, size_t dstBytes, long rowLo, long rowHi, long row2Lo = 0, long row2Hi = 0, long shift = 0,
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)
bool contentOnly = false, // T-505: the frame without its parts (PartsReady)
unsigned char* partsDest = nullptr, int partsCap = 0, uint32_t partsHave = 0); // (E2)
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
@@ -399,7 +400,8 @@ public:
bool bandsOnly = false, // T-516: read the bands alone, not the tree's change
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)
bool contentOnly = false, // T-505: the frame without its parts (PartsReady)
unsigned char* partsDest = nullptr, int partsCap = 0, uint32_t partsHave = 0); // (E2)
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
@@ -412,7 +414,10 @@ public:
struct ComposeResult { long invLo = 0, invHi = 0, fixedLo = 0, fixedHi = 0, readLo = 0, readHi = 0, read2Lo = 0, read2Hi = 0; bool scrollOnly = false;
long frameY = 0; bool frameYValid = false; long invX0 = 0, invX1 = 0; bool invXKnown = false;
int nStuck = 0; int stuck[4][4] = {}; // T-544: the fixed and stuck rects (x0, y0, x1, y1)
uint32_t partsGen = 0; bool partsGenKnown = false; }; // T-505: updates under fixed or sticky layers
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 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