T-505: the sticky parts on the CPU (behind a switch); own-slot frames diffed without the scroll (engine 0070, 0071)

The owner's CPU/GPU split, "both, parts first". With JIHAD_GPU_CPU_PARTS=1 every GPU frame is
composed without the page's fixed and sticky parts (engine 0070, flag 128) and the daemon draws
them over it from a picture of the parts alone (flags 64|128, remade when the parts' generation
moves); what lay beneath the parts is kept per slot so frames still build on each other. Both
lanes make frames of the same kind (the main lane's are dressed at its poll, under the frame lock).

Faults found and fixed on the way (docs/rounds/t505-2026-10-26/notes.txt):
- the picture never matched a frame (the engine's keep split; engine 0070 reports whole layer
  rects); three misses in a row now turn the parts back to the engine's for the document;
- frames built on their slot's own frame had no scroll compensation in the diff (engine 0071):
  every layer transaction during a pan was a whole read;
- T-511's own-slot rule took a slot stale whole after a one-off whole change (each drag's
  settle): never a slot stale whole now (both lanes' default behaviour);
- the picture's geometry no longer has to match the frame's overscan row or height; it reads only
  the rows around the last picture's parts.

JIHAD_GPU_CPU_PARTS_STALE=1 (the owner's trade, off): frames keep the previous picture while a
changed part's is made (at most 600 ms).

Scripted pan, the card moving: desktop Wikipedia 9.4 fps without, 11.2 with the parts on the
CPU, 14.2 with the stale switch too; the long page 18.8 -> 24.7 (parts + stale). By finger the
rounds spread 11-13 fps on Wikipedia against ~10 without. Frames looked at: parts once, in place.

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 13:44:33 -04:00
1 parent 4586bf6731
commit 2bd3e6f1ae
27 files changed
+30638 -32

No files matched your search

@@ -0,0 +1,367 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: NotAlexNoyle <alexnoyle@icloud.com>
Date: Mon, 26 Oct 2026 13:43:37 -0400
Subject: [PATCH] gpu-compositor: a frame without its parts (flag 128) and the
parts' generation
T-505, the owner's CPU/GPU split ("both, parts first"): the daemon draws a page's fixed and sticky parts
over each frame on the CPU, from a picture of the parts alone, so the GPU composes and reads only the
scrolled content (no fixed rows, keeps or columns for the see-through sidebars).
- flag 128 of jihad_offscreen_compose_post/start: every fixed and every sticky layer (stuck or not) at
shadow opacity 0 in the composite and in the diff-first step; no fixed rows, keeps or columns; the
parts' rects still reported with the poll (each part its layer's whole rect, no keeps: the same in a
frame, where the parts are at opacity 0, and in the parts' picture, flags 64|128, where they are shown;
the keep split made them differ and the daemon's picture never matched);
- the parts' generation: a count of compositable updates under a fixed or sticky layer, the poll's
thirtieth int (jihad_offscreen_overview_ok answers 2), so the daemon knows when its picture is old;
- JIHAD_GPU_REQLOG=1 (diagnostic): each composite request's source.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XtfxSpM1w2uiVKeLKoV3g3
---
.../ipc/CompositableTransactionParent.cpp | 10 ++
gfx/layers/ipc/CompositorBridgeParent.cpp | 93 ++++++++++++++-----
gfx/layers/ipc/CompositorBridgeParent.h | 1 +
gfx/layers/opengl/CompositorOGL.h | 5 +
widget/PuppetWidget.cpp | 6 +-
5 files changed, 92 insertions(+), 23 deletions(-)
diff --git a/gfx/layers/ipc/CompositableTransactionParent.cpp b/gfx/layers/ipc/CompositableTransactionParent.cpp
index 32671f33d7..6d6edb0043 100644
--- a/gfx/layers/ipc/CompositableTransactionParent.cpp
+++ b/gfx/layers/ipc/CompositableTransactionParent.cpp
@@ -4,6 +4,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "CompositableTransactionParent.h"
+#include <atomic> // Jihad T-505
#include "CompositableHost.h" // for CompositableParent, etc
#include "CompositorBridgeParent.h" // for CompositorBridgeParent
#include "GLContext.h" // for GLContext
@@ -29,6 +30,9 @@
namespace mozilla {
namespace layers {
+extern std::atomic<uint32_t> gJihadPartsGen; // Jihad T-505, CompositorBridgeParent.cpp
+void JihadReqNote(const char* aSrc); // Jihad T-505 diagnostic, CompositorBridgeParent.cpp
+
class ClientTiledLayerBuffer;
class Compositor;
@@ -54,6 +58,7 @@ ScheduleComposition(CompositableHost* aCompositable)
if (!cp) {
return false;
}
+ JihadReqNote("a compositable's update");
cp->ScheduleComposition();
return true;
}
@@ -68,6 +73,11 @@ CompositableParentManager::ReceiveCompositableUpdate(const CompositableOperation
if (compositable->GetCompositor() && !compositable->GetCompositor()->IsValid()) {
return true;
}
+ // Jihad T-505: an update to a layer under a fixed or sticky one moves the parts' generation (the daemon composes
+ // the page's fixed and sticky parts alone again when it moves: they changed)
+ for (Layer* l = compositable->GetLayer(); l; l = l->GetParent()) {
+ if (l->GetIsFixedPosition() || l->GetIsStickyPosition()) { ++gJihadPartsGen; break; }
+ }
switch (aEdit.detail().type()) {
case CompositableOperationDetail::TOpPaintTextureRegion: {
diff --git a/gfx/layers/ipc/CompositorBridgeParent.cpp b/gfx/layers/ipc/CompositorBridgeParent.cpp
index 83033ff3ca..30dd63a609 100644
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -18,6 +18,7 @@
#include "gfxContext.h" // for gfxContext
#include "gfxPlatform.h" // for gfxPlatform
#include "TreeTraversal.h" // for ForEachNode
+#include <atomic> // Jihad T-505
#include "LayerTreeInvalidation.h" // Jihad T-502: JihadDiffScrollShift
#include "mozilla/gfx/JihadGpuLog.h" // Jihad (T-512): JIHAD_GPU_LOG
@@ -25,6 +26,11 @@ namespace mozilla { namespace layers { extern double gJihadPhase[12]; } } // L
static bool sJihadInCompose = false; // Jihad T-502 diagnostic: inside JihadComposeAndRead's composite
static int sJihadOverview = 0; // Jihad T-504: that composite is an overview window (1: fixed and stuck layers not
// drawn) or the overview's parts (2: only fixed and stuck layers drawn)
+static bool sJihadContentOnly = false; // Jihad T-505: the job is a frame without its parts (flag 128): every fixed and every
+ // sticky layer counts as a part, none of the fixed rows' handling applies
+namespace mozilla { namespace layers {
+std::atomic<uint32_t> gJihadPartsGen(0); // Jihad T-505: updates to layers under a fixed or sticky one
+} } // (CompositableTransactionParent.cpp)
#include <cmath> // Jihad T-502: floor, ceil
#ifdef MOZ_WIDGET_GTK
#include "gfxPlatformGtk.h" // for gfxPlatform
@@ -371,6 +377,14 @@ CompositorVsyncScheduler::PostCompositeTask(TimeStamp aCompositeTimestamp)
}
}
+// Jihad T-505 diagnostic (JIHAD_GPU_REQLOG=1): where the composite requests (the daemon's "tree changed") come from
+void
+JihadReqNote(const char* aSrc)
+{
+ static const bool sOn = getenv("JIHAD_GPU_REQLOG") != nullptr;
+ if (sOn) JIHAD_GPU_LOG("[jihad-gpu] composite request: %s\n", aSrc);
+}
+
void
CompositorVsyncScheduler::ScheduleComposition()
{
@@ -1083,6 +1097,7 @@ CompositorBridgeParent::NotifyShadowTreeTransaction(uint64_t aId, bool aIsFirstP
mLayerManager->NotifyShadowTreeTransaction();
}
if (aScheduleComposite) {
+ JihadReqNote("a shadow tree transaction");
ScheduleComposition();
}
}
@@ -1127,6 +1142,31 @@ CompositorBridgeParent::SetShadowProperties(Layer* aLayer)
);
}
+// Jihad T-504/T-505: the overview's windows and a frame without its parts (flag 128, sJihadContentOnly) are the
+// scrolled content alone: their fixed and stuck layers at shadow opacity 0 (set again from the layer at every
+// composite); the overview's parts are the other way round, every leaf layer outside one at 0. A part is a fixed
+// layer or a stuck sticky one (the probe); with flag 128 every sticky layer, stuck or not, so no layer turns from
+// content into a part between two frames of a pan. After TransformShadowTree (the stuck set is this composite's)
+void
+CompositorBridgeParent::JihadHideForJob()
+{
+ Layer* root = mLayerManager ? mLayerManager->GetRoot() : nullptr;
+ if (!root || !mCompositionManager) return;
+ auto part = [&](Layer* l) {
+ return l->GetIsFixedPosition() || (l->GetIsStickyPosition() && (sJihadContentOnly || mCompositionManager->JihadIsStuck(l)));
+ };
+ const bool partsAlone = sJihadOverview == 2;
+ ForEachNode<ForwardIterator>(root, [&](Layer* l) {
+ if (!partsAlone) {
+ if (part(l)) l->AsLayerComposite()->SetShadowOpacity(0.0f);
+ return;
+ }
+ if (l->GetFirstChild()) return;
+ for (Layer* c = l; c; c = c->GetParent()) if (part(c)) return;
+ l->AsLayerComposite()->SetShadowOpacity(0.0f);
+ });
+}
+
void
CompositorBridgeParent::CompositeToTarget(DrawTarget* aTarget, const gfx::IntRect* aRect)
{
@@ -1218,21 +1258,10 @@ CompositorBridgeParent::CompositeToTarget(DrawTarget* aTarget, const gfx::IntRec
// other scrolls into one picture, where fixed and stuck layers would sit once per window. Their shadow
// opacity (set again from the layer at every composite) is 0 for this one. The overview's parts are the
// other way round: every leaf layer outside a fixed or stuck one at 0, so they come back with their alpha
- if (sJihadOverview && sJihadInCompose && mLayerManager->GetRoot()) {
- auto fixedOrStuck = [&](Layer* l) {
- return l->GetIsFixedPosition() || (l->GetIsStickyPosition() && mCompositionManager->JihadIsStuck(l));
- };
- ForEachNode<ForwardIterator>(mLayerManager->GetRoot(), [&](Layer* l) {
- if (sJihadOverview == 1) {
- if (fixedOrStuck(l)) l->AsLayerComposite()->SetShadowOpacity(0.0f);
- return;
- }
- if (l->GetFirstChild()) return;
- for (Layer* c = l; c; c = c->GetParent()) if (fixedOrStuck(c)) return;
- l->AsLayerComposite()->SetShadowOpacity(0.0f);
- });
- }
+ if ((sJihadOverview || sJihadContentOnly) && sJihadInCompose) JihadHideForJob();
if (requestNextFrame) {
+ JihadReqNote(sJihadContentOnly ? "the next frame asked (an animation), a job without its parts"
+ : (sJihadOverview ? "the next frame asked (an animation), an overview job" : "the next frame asked (an animation)"));
ScheduleComposition();
#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
// If we have visible windowed plugins then we need to wait for content (and
@@ -1295,6 +1324,7 @@ CompositorBridgeParent::CompositeToTarget(DrawTarget* aTarget, const gfx::IntRec
// we do "composite until" rather than "composite again at".
if (!mCompositor->GetCompositeUntilTime().IsNull() ||
mLayerManager->DebugOverlayWantsNextFrame()) {
+ JihadReqNote(sJihadContentOnly ? "composite until, a job without its parts" : "composite until");
ScheduleComposition();
}
@@ -1365,7 +1395,7 @@ static bool
JihadFixedCols()
{
static const bool sOn = !getenv("JIHAD_GPU_NO_FIXED_COLS") && !getenv("JIHAD_GPU_READ_THREAD");
- return sOn;
+ return sOn && !sJihadContentOnly; // T-505: a frame without its parts has no fixed pixels to keep or read
}
// T-544: the frame's fixed and stuck rects (the last JihadUpdateLastFixed's) out to the daemon
@@ -1507,7 +1537,7 @@ CompositorBridgeParent::JihadFixedRectsNow(int aWidth, int aHeight, gfx::IntRect
int n = 0, fixedDepth = 0, outerLo = 0, outerHi = 0;
const gfx::IntRect frame(0, 0, aWidth, aHeight);
auto isFixed = [&](Layer* l) {
- return l->GetIsFixedPosition() || (l->GetIsStickyPosition() && mCompositionManager && mCompositionManager->JihadIsStuck(l));
+ return l->GetIsFixedPosition() || (l->GetIsStickyPosition() && (sJihadContentOnly || (mCompositionManager && mCompositionManager->JihadIsStuck(l))));
};
// a layer's visible region on the frame, as the pixels its quads cover; false unless its
// transform keeps rects axis-aligned
@@ -1574,7 +1604,9 @@ CompositorBridgeParent::JihadFixedRectsNow(int aWidth, int aHeight, gfx::IntRect
// or its opaque children cover them) is its own pixels whatever scrolls beneath
gfx::IntRect keep;
nsIntRegion vis, opaque;
- if (!sNoKeep && fixedDepth == 0 && l->GetEffectiveOpacity() >= 0.999f && !l->GetMaskLayer() &&
+ // T-505: a job without its parts keeps nothing; each part is its layer's whole rect, the same whether the
+ // job hid the parts (a frame: their opacity 0 here) or showed them alone (the parts' picture)
+ if (!sNoKeep && !sJihadContentOnly && fixedDepth == 0 && l->GetEffectiveOpacity() >= 0.999f && !l->GetMaskLayer() &&
l->GetAncestorMaskLayerCount() == 0 && addCovered(l, vis)) {
bool ok = true;
if (l->GetContentFlags() & Layer::CONTENT_OPAQUE) {
@@ -1800,6 +1832,12 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
if (b->mLayerManager) b->mLayerManager->JihadHoldBaseline(false);
}
} jihadVerifyGuard(this, aReq && aReq->verify);
+ // T-505: a frame without its parts (flag 128), for this job's whole run
+ struct JihadContentOnlyScope {
+ explicit JihadContentOnlyScope(bool aOn) { sJihadContentOnly = aOn; }
+ ~JihadContentOnlyScope() { sJihadContentOnly = false; }
+ } jihadContentOnlyScope(aReq && aReq->contentOnly);
+ if (aReq) aReq->partsGen = gJihadPartsGen;
MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
if (!aReq || !mCompositor || !mLayerManager) {
return false;
@@ -1889,7 +1927,7 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
{
int fLo = aHeight, fHi = 0;
ForEachNode<ForwardIterator>(mLayerManager->GetRoot(), [&](Layer* l) {
- if (!(l->GetIsFixedPosition() || (l->GetIsStickyPosition() && mCompositionManager && mCompositionManager->JihadIsStuck(l)))) return;
+ if (sJihadContentOnly || !(l->GetIsFixedPosition() || (l->GetIsStickyPosition() && mCompositionManager && mCompositionManager->JihadIsStuck(l)))) return;
const gfx::IntRect vb = l->GetLocalVisibleRegion().GetBounds().ToUnknownRect();
if (vb.IsEmpty()) return;
const gfx::Rect r = l->GetEffectiveTransform().TransformBounds(gfx::Rect(vb.x, vb.y, vb.width, vb.height));
@@ -2188,7 +2226,7 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
static uint32_t sFixN = 0;
const bool logThis = sFixLog && (sFixN++ % 20) == 0;
ForEachNode<ForwardIterator>(root, [&](Layer* l) {
- if (!(l->GetIsFixedPosition() || (l->GetIsStickyPosition() && mCompositionManager && mCompositionManager->JihadIsStuck(l)))) return;
+ if (sJihadContentOnly || !(l->GetIsFixedPosition() || (l->GetIsStickyPosition() && mCompositionManager && mCompositionManager->JihadIsStuck(l)))) return;
const gfx::IntRect vb = l->GetLocalVisibleRegion().GetBounds().ToUnknownRect();
if (vb.IsEmpty()) return;
const gfx::Rect r = l->GetEffectiveTransform().TransformBounds(gfx::Rect(vb.x, vb.y, vb.width, vb.height));
@@ -2203,8 +2241,13 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
if (b > a) { fLo = std::min(fLo, a); fHi = std::max(fHi, b); }
});
if (fHi > fLo) { mJihadLastFixedLo = fLo; mJihadLastFixedHi = fHi; } else { mJihadLastFixedLo = mJihadLastFixedHi = 0; }
- JihadUpdateLastFixed(aWidth, aHeight); // rounds 138/141
- JihadStuckOut(aReq, mJihadLastFixedRects, mJihadLastFixedN);
+ 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));
+ } else {
+ JihadUpdateLastFixed(aWidth, aHeight); // rounds 138/141
+ JihadStuckOut(aReq, mJihadLastFixedRects, mJihadLastFixedN);
+ }
} else if (diffFirst) {
mJihadLastFixedLo = dfFixLo; mJihadLastFixedHi = dfFixHi;
}
@@ -2299,7 +2342,7 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
static uint32_t sFixNW = 0;
const bool logThisW = sFixLogW && (sFixNW++ % 20) == 0;
ForEachNode<ForwardIterator>(root, [&](Layer* l) {
- if (!(l->GetIsFixedPosition() || (l->GetIsStickyPosition() && mCompositionManager && mCompositionManager->JihadIsStuck(l)))) return;
+ if (sJihadContentOnly || !(l->GetIsFixedPosition() || (l->GetIsStickyPosition() && mCompositionManager && mCompositionManager->JihadIsStuck(l)))) return;
const gfx::IntRect vb = l->GetLocalVisibleRegion().GetBounds().ToUnknownRect();
if (vb.IsEmpty()) return;
if (logThisW) {
@@ -2331,6 +2374,10 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
// layers were then (round 141: this composite's keeps, held still since the last, stay put)
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));
+ }
if (JihadFixedCols()) {
gfx::IntRect lastR[4];
const int lastN = mJihadLastFixedN;
@@ -2440,6 +2487,7 @@ CompositorBridgeParent::JihadDiffNow()
mCompositionManager->ComputeRotation();
TimeStamp time = mIsTesting ? mTestTime : mCompositorScheduler->GetLastComposeTime();
mCompositionManager->TransformShadowTree(time, mVsyncRate);
+ if (sJihadContentOnly) JihadHideForJob(); // T-505: as the frame will be composed
nsIntRegion changed = mLayerManager->JihadDiffOnly();
mLayerManager->JihadEndTransactionNoComposite();
return changed;
@@ -2583,6 +2631,7 @@ CompositorBridgeParent::ShadowLayersUpdated(LayerTransactionParent* aLayerTree,
SetShadowProperties(root);
}
if (aScheduleComposite) {
+ JihadReqNote("layers updated");
ScheduleComposition();
if (mPaused) {
TimeStamp now = TimeStamp::Now();
diff --git a/gfx/layers/ipc/CompositorBridgeParent.h b/gfx/layers/ipc/CompositorBridgeParent.h
index 0f497e591e..7b021a883d 100644
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -545,6 +545,7 @@ public:
// of the composite before the last (a keep must have held still)
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
int JihadFixedRectsNow(int aWidth, int aHeight, gfx::IntRect* aOut, gfx::IntRect* aKeep);
void JihadUpdateLastFixed(int aWidth, int aHeight);
bool mJihadSeqKnown = false;
diff --git a/gfx/layers/opengl/CompositorOGL.h b/gfx/layers/opengl/CompositorOGL.h
index 92383da308..12ef1ecb53 100644
--- a/gfx/layers/opengl/CompositorOGL.h
+++ b/gfx/layers/opengl/CompositorOGL.h
@@ -172,6 +172,11 @@ struct JihadFrameRequest {
// rows no layer drew read alpha 0; read with its alpha kept
bool overviewParts = false; // T-504 (flag 64): the fixed and stuck layers alone, cleared transparent,
// read with their alpha (drawn over the daemon's overview)
+ bool contentOnly = false; // T-505 (flag 128): the frame without its parts: every fixed and every sticky
+ // 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
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
diff --git a/widget/PuppetWidget.cpp b/widget/PuppetWidget.cpp
index 0d1789be58..6535e3e649 100644
--- a/widget/PuppetWidget.cpp
+++ b/widget/PuppetWidget.cpp
@@ -851,6 +851,7 @@ struct JihadReadJob {
bool bandsOnly = false; // T-516 (flag 16): only the bands asked for are read
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
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;
@@ -901,6 +902,7 @@ void JihadReadOnCompositorThread(JihadReadJob* job)
job->req.bandsOnly = job->bandsOnly;
job->req.overview = job->overview;
job->req.overviewParts = job->overviewParts;
+ job->req.contentOnly = job->contentOnly;
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
@@ -1060,6 +1062,7 @@ PuppetWidget::JihadPostJob(void* aDest, int aStride, int aWidth, int aHeight, in
job->bandsOnly = (aFlags & 16) != 0; // T-516
job->overview = (aFlags & 32) != 0; // T-504
job->overviewParts = (aFlags & 64) != 0;
+ job->contentOnly = (aFlags & 128) != 0; // T-505
if (job->colsKnown) {
job->colX0 = std::max(0, std::min(aColX0, aWidth)); job->colX1 = std::max(0, std::min(aColX1, aWidth));
}
@@ -1141,6 +1144,7 @@ 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 (aScrollOnly) *aScrollOnly = job->req.scrollOnly;
} else if (!aWait && job->req.composed) {
@@ -1518,7 +1522,7 @@ MOZ_EXPORT int
jihad_offscreen_overview_ok()
{
static const bool sPass = getenv("JIHAD_GPU_PASS") != nullptr;
- return sPass ? 0 : 1;
+ return sPass ? 0 : 2; // 2 (T-505): flag 128 too, and the poll's 30th int (the parts' generation)
}
MOZ_EXPORT int
jihad_offscreen_compose_peek(nsIWidget* aWidget, uint32_t aToken)
@@ -0,0 +1,80 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: NotAlexNoyle <alexnoyle@icloud.com>
Date: Mon, 26 Oct 2026 13:43:37 -0400
Subject: [PATCH] gpu-compositor: the diff leaves the card's scroll out of a
frame built on its own slot
A frame with no base to copy (the daemon's frame built on its slot's own older frame, its window moved or
its rows shifted in place) had no scroll compensation in the diff: compensation was on only for a frame
copied from the newest one (src with engineShift). Any layer transaction during a pan was then the whole
frame changed for it (the scrolled content's translation), a whole read, and the daemon's other two slots
stale whole. Such frames are the rule for a page without fixed layers and for the frames without their
parts (T-505); on desktop Wikipedia with the parts on the CPU, whole tree changes went 30 -> 9 and the rows
read 96k -> 57k a round, the long static page 32 -> 10 and 90k -> 43k.
The compensation is kept only when the shift the diff measured is the card's own since the last composite
(the request's scroll against the last one's): a scroll the page made itself (scroll anchoring) is the whole
frame changed, as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XtfxSpM1w2uiVKeLKoV3g3
---
gfx/layers/ipc/CompositorBridgeParent.cpp | 23 +++++++++++++++++++----
1 file changed, 19 insertions(+), 4 deletions(-)
diff --git a/gfx/layers/ipc/CompositorBridgeParent.cpp b/gfx/layers/ipc/CompositorBridgeParent.cpp
index 30dd63a609..177fc4c51d 100644
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -1875,6 +1875,19 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
}
const bool scrollChanged = aReq->scrollValid != mJihadLastScrollValid ||
aReq->scrollX != mJihadLastScrollX || aReq->scrollY != mJihadLastScrollY;
+ // T-505: a frame with no base to copy (its slot's own frame, its window moved or its rows shifted in place, or
+ // none) has the diff leave out the card's scroll too, as one copied from the newest frame does: a layer
+ // 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)
+ const bool ownComp = aReq->src == nullptr && aReq->scrollValid && mJihadLastScrollValid &&
+ aReq->scrollX == mJihadLastScrollX;
+ const float ownExpectRows = (aReq->scrollY - mJihadLastScrollY) * (aScale > 0.0f ? aScale : 1.0f);
+ auto ownShiftOk = [&]() {
+ float sdx = 0.0f, sdy = 0.0f;
+ if (!JihadDiffScrollShift(&sdx, &sdy)) return true; // nothing was left out
+ return fabsf(-sdy * (aScale > 0.0f ? aScale : 1.0f) - ownExpectRows) < 0.5f;
+ };
mJihadLastScrollX = aReq->scrollX; mJihadLastScrollY = aReq->scrollY; mJihadLastScrollValid = aReq->scrollValid;
const bool viewChanged = !mJihadWorldKnown || !(world == mJihadLastWorld) ||
mJihadLastSize != gfx::IntSize(aWidth, aHeight);
@@ -1906,9 +1919,10 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
if (!aReq->verify && treeChanged && !viewChanged && !sNoDiffFirst && ogl->JihadBandMaxRows() > 0 && aReq->dest &&
aReq->stride == aWidth * 4 && !getenv("JIHAD_GPU_READ_THREAD") && mCompositionManager &&
mLayerManager->GetRoot()) {
- mLayerManager->JihadCompensateScroll(aReq->src != nullptr && aReq->engineShift);
- const nsIntRegion dfRegion = JihadDiffNow();
+ mLayerManager->JihadCompensateScroll((aReq->src != nullptr && aReq->engineShift) || ownComp);
+ nsIntRegion dfRegion = JihadDiffNow();
mLayerManager->JihadCompensateScroll(false);
+ if (ownComp && !ownShiftOk()) dfRegion = nsIntRegion(gfx::IntRect(0, 0, aWidth, aHeight)); // T-505
diffTaken = true;
nsIntRegion dfOnFrame;
dfOnFrame.And(dfRegion, whole); // round 124: clipped to the frame before the bounds
@@ -2291,7 +2305,7 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
aReq->hi2 > aReq->lo2 ? gfx::IntRect(0, aReq->lo2, aWidth, aReq->hi2 - aReq->lo2) : gfx::IntRect(),
colBand);
ogl->JihadSelectTarget();
- mLayerManager->JihadCompensateScroll(!diffTaken && aReq->src != nullptr && aReq->engineShift); // round 115
+ mLayerManager->JihadCompensateScroll(!diffTaken && ((aReq->src != nullptr && aReq->engineShift) || ownComp)); // round 115, T-505
TimeStamp t0 = TimeStamp::Now();
sJihadInCompose = true;
sJihadOverview = aReq->overview ? 1 : (aReq->overviewParts ? 2 : 0); // T-504
@@ -2324,7 +2338,8 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
}
}
}
- const gfx::IntRect changed = mLayerManager->JihadLastChanged(); // what the tree changed
+ gfx::IntRect changed = mLayerManager->JihadLastChanged(); // what the tree changed
+ if (!diffTaken && ownComp && !ownShiftOk()) changed = gfx::IntRect(0, 0, aWidth, aHeight); // T-505 (above)
int invLo = 0, invHi = 0, invX0 = 0, invX1 = 0;
if (!changed.IsEmpty()) {
invLo = std::max(0, changed.y);
+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=c05685f43fd72126976abd78b0de8a7119588e2c
UXP_PIN=ffb0cb8ae1b78695545721b10d77691df2b71aef
# 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 = "c05685f43fd72126976abd78b0de8a7119588e2c"
SRCREV = "ffb0cb8ae1b78695545721b10d77691df2b71aef"
S = "${WORKDIR}/git"
# Modern C++14 toolchain (device-build R1) — the stock dylan/device gcc cannot build UXP.
+2
View File
@@ -92,6 +92,8 @@ modifications; the same changes are the commits of `jihad/uxp-RB_20261006`, one
| `0067-gpu-compositor-a-compose-that-asks-for-its-bands-onl.patch` | `widget/PuppetWidget.cpp`, `gfx/layers/opengl/CompositorOGL.h`, `gfx/layers/ipc/CompositorBridgeParent.cpp` | T-516: compose flag 16 — read only the bands asked for, not the rows the tree changed (still reported); the daemon's first frame at a new zoom reads the card's view alone (942 of 1224 rows): double taps by finger 40/40 within 250 ms, the slowest 227 ms where 276-301 |
| `0068-gpu-compositor-a-frame-s-fixed-and-stuck-rects-go-ba.patch` | `widget/PuppetWidget.{h,cpp}`, `gfx/layers/opengl/CompositorOGL.h`, `gfx/layers/ipc/CompositorBridgeParent.cpp` | T-544: `jihad_offscreen_compose_poll2` returns a frame's fixed and stuck rects with its poll; the daemon passes them to the card (`JihadStuckBlock`), which holds desktop Wikipedia's sticky sidebars still between frames of a pan (they moved with the page and jumped back) |
| `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 |
| `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)
27817 3318 4508 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
29552 8453 8475 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
31196 7567 7574 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
6451 8904 9099 /usr/bin/LunaSysMgr -s
6538 112167 112934 /usr/bin/LunaSysMgr -s
--- /proc/stat before and after
cpu 26250080 2381532 24019674 97308586 2102844 4045 49924 0 0 0
cpu 26251452 2382334 24020398 97309790 2103143 4045 49924 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)
25578 1072 1834 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
29552 5268 5276 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
31196 4529 4547 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
6451 2243 2442 /usr/bin/LunaSysMgr -s
6538 3746 4501 /usr/bin/LunaSysMgr -s
--- /proc/stat before and after
cpu 26106387 2317543 23850222 96956374 1990262 4026 49636 0 0 0
cpu 26107301 2318344 23850977 96957793 1990694 4026 49636 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)
21880 3121 4445 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
29552 5205 5214 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
31196 4472 4481 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
6451 1777 1983 /usr/bin/LunaSysMgr -s
6538 2427 3186 /usr/bin/LunaSysMgr -s
--- /proc/stat before and after
cpu 26101345 2316192 23846155 96947346 1989581 4026 49634 0 0 0
cpu 26102822 2316972 23846966 96948424 1989879 4026 49634 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)
25666 3441 4621 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
29552 9163 9172 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
31196 8218 8227 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
6451 11440 11642 /usr/bin/LunaSysMgr -s
6538 135800 136565 /usr/bin/LunaSysMgr -s
--- /proc/stat before and after
cpu 26301855 2397678 24062240 97386690 2124372 4049 49982 0 0 0
cpu 26303265 2398415 24063064 97388669 2124611 4049 49982 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)
20082 1152 1812 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
29552 8347 8354 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
31196 7463 7471 ./ld-2.23.so --library-path /media/cryptofs/apps/usr/palm/applications/net.rive
6451 7914 8110 /usr/bin/LunaSysMgr -s
6538 108945 109665 /usr/bin/LunaSysMgr -s
--- /proc/stat before and after
cpu 26236255 2378292 24011389 97294212 2101151 4045 49918 0 0 0
cpu 26237145 2379009 24012164 97295738 2101524 4045 49920 0 0 0
File diff suppressed because it is too large. Load diff
+58
View File
@@ -87,3 +87,61 @@ A paint is ~10 ms of LunaSysMgr's CPU, almost all PGContext::bitblt of the view
page), the picture the same (looked at) — and the pan rate unchanged (10.0 / 15.7 fps against 9.7 / 18.5): the
daemon's GPU waits do not come from the card's CPU blit but from what LunaSysMgr does on the GPU with the card's
buffer at each redraw.
The sticky parts on the CPU (the owner's "both, parts first"), first rounds
----------------------------------------------------------------------------
Engine 0070 (flag 128: a frame without its parts — every fixed and every sticky layer at opacity 0 in the composite
and in the diff step, no fixed rows, keeps or columns; the parts' rects still reported; a parts generation counted on
compositable updates under a fixed or sticky layer) and the daemon's JIHAD_GPU_CPU_PARTS=1 (the parts' picture with
flags 64|128; each content-only frame gets back what lay beneath the frame it was built on's parts and has the parts
drawn over it on the CPU; frames build only on their own kind).
- Scripted pan, desktop Wikipedia, the card moving (cp1): 14.0 fps against 8.6; the job 37 ms against 58, band reads
18 against 31, no columns, no copy (own-slot frames). The mid-pan dump looked at: both sidebars once, sharp.
But 10 pictures in the pan (the generation moves as Wikipedia repaints its contents panel) and 7 frames not shown.
- By finger (cp-wiki, cp-long): Wikipedia 10.2 fps against 9.7 (26 pictures, 19 frames not shown, 97 whole reads of
71 ms); the long page 15.8 against 18.5 (1 picture, 57 whole reads). The whole reads are the kind changes: every
picture made again sends the frames back to the engine's parts until it is in, and the main lane's frames between
drags are of the other kind; each change is a whole read. Next: the main lane's frames of the same kind, and a
change of the parts that costs less than a whole read (or the picture kept while the next is made, a correctness
trade for the owner).
The parts on the CPU, made to pay (2026-10-26, afternoon)
---------------------------------------------------------
Four faults found by reading the finger rounds' logs, each fixed and measured:
1. The picture never matched (a loop): a frame without its parts has them at opacity 0, so the engine's keep test
failed and it reported each part as its layer's whole rect; the picture (the parts shown) split the same layer into
its covered pieces. Frame 2 rects, picture 3: every frame "a part the picture lacks", the picture made again at the
same generation, forever. Engine: no keeps in a job without its parts (both report whole layer rects). Daemon: three
misses in a row turn the parts back to the engine's for the document; the miss logs both sets of rects.
2. The lanes made frames of two kinds: the main lane's frames carried the engine's parts, the express lane's the CPU's,
so neither found a base of its kind (68 whole reads in a round). The main lane now makes frames without their parts
too (its base rules by kind, the parts drawn at its poll under the frame lock, a miss dropped as an express-dropped
frame; not its second job on the one in flight, whose frame is dressed on this thread while the compositor copies).
3. A frame built on its slot's own frame had no scroll compensation in the diff (`engineShift` is "copied from the
newest" only): any layer transaction during a pan was the whole frame changed for it, and the other two slots then
stale whole. Without parts those frames are rare on a page with fixed layers (the window path is barred); with parts
they are the rule. Engine: the diff leaves the card's scroll out for a frame with no base to copy, and checks that the
shift it measured is the card's since the last composite (a scroll the page made itself is the whole frame changed).
Whole tree changes in a Wikipedia round 30 -> 9, rows read 96k -> 57k; the long page 32 -> 10, 90k -> 43k.
4. T-511's "own slot is cheap" rule took a slot stale WHOLE after a one-off whole change (each drag's settle, where the
engine takes the scroll over: the recent changes are whole too), a whole read where the newest frame was fresh.
Daemon: never a slot stale whole. Whole reads with no change 20 -> 11.
(Also: the picture's own geometry need not match the frame's overscan row or height; a picture reads only the rows
around the last one's parts, whole again if a part falls outside; JIHAD_GPU_REQLOG=1 names each composite request's
source, which showed the requests equal with and without parts and pointed at the diff.)
The switch JIHAD_GPU_CPU_PARTS_STALE=1 (the owner's trade, not on): while a changed part's picture is made (55-250 ms),
frames keep the picture before it (at most 600 ms behind) instead of going back to the engine's parts and back again.
On Wikipedia the contents panel's highlight changes about once a drag; with the switch its new highlight shows up to
one picture late.
Scripted pan (inject "pan 0 3000 10000", the card moving, every frame of the 10 s counted; logs pan-*3-*):
no parts parts parts + stale
desktop Wikipedia 9.4 11.2 14.2 fps (job median 72.7 / 24.9 / 23.5 ms)
the long page 18.8 - 24.7 fps (job median 23.5 / - / 18.5 ms)
By finger (eight drags; fps while frames flow; one round each, the spread between rounds of one build is 11-13):
desktop Wikipedia: no parts 10.7 (fp-pm-wiki-opaque) and 9.2 (fp-be-wiki, this engine); parts + stale 13.0, 11.2,
12.4, 11.3, 12.3 over the fixes (fp-cpj-wiki the last); the long page 15.7 -> 17.5 (fp-rq2-long).
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).
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+327 -19
View File
@@ -487,7 +487,9 @@ static void jihadExpressThread() {
const int64_t now = jihadNowMs();
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; }
if (gXPages) for (BrowserPageGoanna* p : *gXPages) {
soon = p->expressStep(now) || soon; soon = p->overviewStep(now) || soon; soon = p->cpuPartsStep(now) || soon;
}
jihadBoostFrameThreads();
}
}
@@ -687,7 +689,7 @@ void BrowserPageGoanna::gpuStaleAdd(int except, long il, long ih, const GoannaRe
// T-517: a composite whose change no slot's record took (a job forgotten, or dropped unread): no
// slot's frame can be built on any more
void BrowserPageGoanna::gpuSlotsLost() {
for (int i = 0; i < 3; ++i) mGpuSlotValid[i] = false;
for (int i = 0; i < 3; ++i) { mGpuSlotValid[i] = false; mSlotParts[i].kind = -1; } // T-505: their records too
mGpuLatestSlot = -1;
++mGpuLostN; // T-527: a main-lane job posted before this is not adopted (its slot predates the lost change)
}
@@ -833,6 +835,7 @@ BrowserPageGoanna::~BrowserPageGoanna() {
FrameLockGuard frameLock;
if (gXPages) gXPages->erase(this);
overviewForget(); // T-504
cpuPartsForget(); // T-505
// 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())
@@ -936,6 +939,7 @@ void BrowserPageGoanna::setWindowSize(uint32_t width, uint32_t height) {
FrameLockGuard frameLock; // T-515
xInvalidate();
zoomBackDrop(); // T-527: another size's
cpuPartsForget(); // T-505
mGpuZoom = 0.0; // T-502: the scroll port follows the card size; set again at the next zoom
mGpuAnchorValid = false;
// T-499: a new card size (rotation) starts from a layout viewport equal to it; the same size sent
@@ -1064,6 +1068,7 @@ void BrowserPageGoanna::freeze() {
FrameLockGuard frameLock; // T-515
mFrozen = true;
zoomBackDrop(); // T-527: its memory too
cpuPartsForget(); // T-505
if (mPage) mPage->SetActive(false); // a background card is a background tab: timers clamped, no frames
detachShm();
}
@@ -1152,6 +1157,8 @@ void BrowserPageGoanna::resetScrollForNavigation() {
// twice, until a scroll command reached the main thread)
mGpuRootless = false;
zoomBackDrop(); // another document's
cpuPartsForget(); // T-505
mPartsOff = false; mPartsMissRun = 0; // T-505: a new document's parts get their chance
if (mPage && mPage->GpuPath() && (mGpuViewRX != 0.0 || mGpuViewRY != 0.0)) gpuSetView(mGpuZoom > 0.0 ? mGpuZoom : 1.0, 0.0, 0.0);
if (mPage) { mPage->SetRenderPan(0.0, 0.0); mPage->ScrollTo(0, 0); }
mViewportSent = false; // the new page's meta-viewport is sent afresh
@@ -1359,6 +1366,7 @@ void BrowserPageGoanna::gpuFallBack(const char* why) {
if (jihad_offscreen_gpu_given_up) jihad_offscreen_gpu_given_up(); // T-511 (review): WebGL off too
xForget(mXJob2); xForget(mXJob); // T-513 (review): the express lane's frames too, or one recovering would write into a slot the software path paints
overviewForget(); // T-504
cpuPartsForget(); // T-505
xInvalidate();
for (const GpuJob& j : mGpuJobs) gpuForget(j);
mGpuJobs.clear();
@@ -1513,6 +1521,7 @@ bool BrowserPageGoanna::zoomBackPublish(int64_t now, double Z, const XWant& want
// not a composite: no slot record and no base for either lane; the card redraws it whole, and the
// frame after it too
mGpuSlotValid[slot] = false;
mSlotParts[slot].kind = -1; // T-505: not a composite
if (mGpuLatestSlot == slot) mGpuLatestSlot = -1;
if (mXPub.slot == slot) mXPub = XPub();
mBufFull[slot].valid = false;
@@ -1920,6 +1929,238 @@ void BrowserPageGoanna::overviewForget() {
std::vector<unsigned char>().swap(mOvParts);
}
// 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;
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;
}
// 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;
}
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;
}
// 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];
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;
fr[nf][0] = x0; fr[nf][1] = y0; fr[nf][2] = x1; fr[nf][3] = y1; nf++;
}
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; }
}
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;
}
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
}
// 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;
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++) {
const int x0 = sp.r[k][0], x1 = sp.r[k][2], rw = x1 - x0;
for (int y = sp.r[k][1]; y < sp.r[k][3]; y++) {
const long ny = y - d;
if (ny < 0 || ny >= H || readRow(ny)) continue;
memcpy(pixels + ((size_t)ny * w + x0) * 4, sp.beneath.data() + sp.off[k] + (size_t)(y - sp.r[k][1]) * rw * 4, (size_t)rw * 4);
}
}
}
// 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);
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];
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 sv = pp[x], a = sv >> 24;
if (a == 255) { row[x] = sv; continue; }
if (a == 0) continue;
const uint32_t dv = row[x], ia = 255 - a;
const uint32_t rb = (((dv & 0x00FF00FFu) * ia + 0x00800080u) >> 8) & 0x00FF00FFu;
const uint32_t g = (((dv >> 8) & 0xFFu) * ia + 0x80u) >> 8;
row[x] = 0xFF000000u | (((sv & 0x00FF00FFu) + rb) & 0x00FF00FFu) | ((((sv >> 8) & 0xFFu) + g) << 8);
}
}
}
dp.n = nf; dp.kind = 1;
mPartsMissRun = 0;
return true;
}
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) {
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");
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;
// 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); }
}
// The inject pan (JihadBrowserServer) used to send the card's scroll commands to the daemon itself; the card's
// own view did not move, so a person watching saw the frames for each position drawn into a still view (the
// page's column still, only the sticky sidebars changing: the owner, 2026-10-26). Now it tells the CARD to scroll
@@ -4616,6 +4857,11 @@ 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);
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
long colX0 = 0, colX1 = -1; // T-511: the bands' stale columns (-1: whole rows) for the column read
static const bool sWhole = getenv("JIHAD_GPU_WHOLE") != nullptr; // device rounds: every frame read whole
@@ -4639,7 +4885,9 @@ bool BrowserPageGoanna::gpuJobStart(int slot, int key, unsigned char* pixels, si
// round 138: the engine reads the fixed and stuck layers as their columns (widened by the
// shift) when the bands leave their rows out; JIHAD_GPU_NO_FIXED_COLS=1 asks for the whole rows
static const bool sFixedCols = !getenv("JIHAD_GPU_NO_FIXED_COLS") && !getenv("JIHAD_GPU_READ_THREAD");
if (!sWhole && mGpuNextSecond && mGpuNextBaseKey && mGpuNextBaseKey != key && mGpuFixedHi >= mGpuFixedLo) {
// (T-505: not without the parts: the job in flight's frame gets its parts on this thread when it is polled, while the
// compositor thread may be copying it for this one)
if (!co && !sWhole && mGpuNextSecond && mGpuNextBaseKey && mGpuNextBaseKey != key && mGpuFixedHi >= mGpuFixedLo) {
// round 130: the job in flight is the base (its frame is complete when this job runs)
const long dy = (long)y - mGpuNextBaseY;
unsigned char* bb = (dy > -h && dy < h) ? attachShm(mGpuNextBaseKey) : nullptr;
@@ -4664,6 +4912,7 @@ bool BrowserPageGoanna::gpuJobStart(int slot, int key, unsigned char* pixels, si
}
}
const int L = mGpuLatestSlot;
int latestWhy = 0; // T-505 diagnostic: 1 tried, 2 attached
// the slot's own frame is the cheaper base when it is at this very scroll with little stale (an
// animated page): no copy, only the changed rows read. T-511: little stale BEYOND the rows the
// recent frames changed, which this frame likely reads either way: a 480 px WebGL canvas changes
@@ -4672,24 +4921,30 @@ bool BrowserPageGoanna::gpuJobStart(int slot, int key, unsigned char* pixels, si
// frames' changes: a composite with no change between two of the canvas's frames is common
const long ownStale = slot >= 0 ? (mGpuStaleHi[slot] - mGpuStaleLo[slot]) + (mGpuStale2Hi[slot] - mGpuStale2Lo[slot]) : h;
const long recentInv = std::max(mGpuInvRows[0], std::max(mGpuInvRows[1], mGpuInvRows[2]));
const bool ownCheap = slot >= 0 && mGpuSlotValid[slot] && mGpuSlotX[slot] == x && mGpuSlotZoom[slot] == zoom &&
// T-505: never a slot stale whole: after a one-off whole change (a pan's settle, where the engine takes the scroll
// over) the recent changes are whole too and the rule above called it cheap, a whole read where the newest frame
// (that change's, read whole once) is fresh
const bool ownNotWhole = ownStale < h;
const bool ownCheap = slot >= 0 && mGpuSlotValid[slot] && mSlotParts[slot].kind == kindNow && mGpuSlotX[slot] == x && mGpuSlotZoom[slot] == zoom &&
mGpuSlotW[slot] == w && mGpuSlotH[slot] == h && mGpuSlotY[slot] == y &&
ownStale - std::min(ownStale, recentInv) < h / 3;
ownStale - std::min(ownStale, recentInv) < h / 3 && ownNotWhole;
// T-517: after a scroll too, when the slot's window can move by it: no copy, the entering rows and
// the slot's stale rows read
// (review) not with fixed or stuck layers, now or in the slot's frame: the engine judges kept rects
// and fixed columns against the previous composite, and this base is older
const bool ownWin = winOn && slot >= 0 && mGpuSlotValid[slot] && mGpuSlotX[slot] == x && mGpuSlotZoom[slot] == zoom &&
const bool ownWin = winOn && slot >= 0 && mGpuSlotValid[slot] && mSlotParts[slot].kind == kindNow && mGpuSlotX[slot] == x && mGpuSlotZoom[slot] == zoom &&
mGpuSlotW[slot] == w && mGpuSlotH[slot] == h && mGpuSlotY[slot] != y && labs((long)y - mGpuSlotY[slot]) < h / 2 &&
ownStale - std::min(ownStale, recentInv) < h / 3 && mGpuFixedHi <= mGpuFixedLo && !mGpuSlotFixed[slot];
if (!src && !sWhole && !sNoLatest && !ownCheap && !ownWin && L >= 0 && L != slot && L < 3 && mGpuSlotValid[L] && mGpuSlotX[L] == x &&
ownStale - std::min(ownStale, recentInv) < h / 3 && ownNotWhole && mGpuFixedHi <= mGpuFixedLo && !mGpuSlotFixed[slot];
if (!src && !sWhole && !sNoLatest && !ownCheap && !ownWin && L >= 0 && L != slot && L < 3 && mGpuSlotValid[L] && mSlotParts[L].kind == kindNow && mGpuSlotX[L] == x &&
mGpuSlotZoom[L] == zoom && mGpuSlotW[L] == w && mGpuSlotH[L] == h &&
mGpuStaleHi[L] <= mGpuStaleLo[L] && mGpuStale2Hi[L] <= mGpuStale2Lo[L] && mGpuFixedHi >= mGpuFixedLo) {
const long dy = (long)y - mGpuSlotY[L];
unsigned char* lb = (dy > -h && dy < h) ? attachShm(mGpuLatestKey) : nullptr;
latestWhy = lb ? 3 : 1;
if (lb) {
src = lb + sizeof(BrowserOffscreenInfo) + (size_t)mGpuSlotWin[L] * stride; // T-517: its window
srcYForEngine = mGpuSlotY[L];
baseSlot = L; baseY = mGpuSlotY[L]; // T-505
shift = dy;
long iv[2][2]; int n = 0;
const long eLo = dy > 0 ? h - dy : 0, eHi = dy > 0 ? h : (dy < 0 ? -dy : 0);
@@ -4709,10 +4964,11 @@ bool BrowserPageGoanna::gpuJobStart(int slot, int key, unsigned char* pixels, si
slot, L, mGpuSlotY[L], y, dy, sLo, sHi, s2Lo, s2Hi);
}
}
if (!src && !sWhole && slot >= 0 && mGpuSlotValid[slot] && mGpuSlotX[slot] == x && mGpuSlotZoom[slot] == zoom &&
mGpuSlotW[slot] == w && mGpuSlotH[slot] == h) {
if (!src && !sWhole && slot >= 0 && mGpuSlotValid[slot] && mSlotParts[slot].kind == kindNow && mGpuSlotX[slot] == x &&
mGpuSlotZoom[slot] == zoom && mGpuSlotW[slot] == w && mGpuSlotH[slot] == h) {
const long dy = (long)y - mGpuSlotY[slot]; // the card's scroll since this slot's frame, frame rows
win = winOn ? mGpuSlotWin[slot] : 0; // T-517: the slot's frame where it is
if (dy == 0 || (dy > -h && dy < h && mGpuFixedHi >= mGpuFixedLo)) { baseSlot = slot; baseY = mGpuSlotY[slot]; } // T-505
if (dy == 0) {
sLo = mGpuStaleLo[slot]; sHi = mGpuStaleHi[slot]; s2Lo = mGpuStale2Lo[slot]; s2Hi = mGpuStale2Hi[slot];
// T-511: the columns they are stale in (none asked for: only the change is read)
@@ -4768,10 +5024,17 @@ bool BrowserPageGoanna::gpuJobStart(int slot, int key, unsigned char* pixels, si
{
static int64_t sWholeMs = 0;
if (sHi - sLo >= h && jihadNowMs() - sWholeMs >= 500 && (sWholeMs = jihadNowMs()))
JIHAD_GPU_LOG("[jihad-gpu] slot %d read whole: valid=%d x %d/%d y %d/%d zoom %.4f/%.4f w %d/%d h %d/%d fixed %ld..%ld\n",
JIHAD_GPU_LOG("[jihad-gpu] slot %d read whole: valid=%d x %d/%d y %d/%d zoom %.4f/%.4f w %d/%d h %d/%d fixed %ld..%ld, parts %d/%d, stale %ld..%ld %ld..%ld; newest %d (valid %d, parts %d, y %d, stale %ld..%ld %ld..%ld; taken %d, src %d, own %d%d, zoom %s, key %d)\n",
slot, slot >= 0 ? (int)mGpuSlotValid[slot] : -1, x, slot >= 0 ? mGpuSlotX[slot] : -1, y, slot >= 0 ? mGpuSlotY[slot] : -1,
zoom, slot >= 0 ? mGpuSlotZoom[slot] : 0.0, w, slot >= 0 ? mGpuSlotW[slot] : -1, h, slot >= 0 ? mGpuSlotH[slot] : -1,
mGpuFixedLo, mGpuFixedHi);
mGpuFixedLo, mGpuFixedHi, slot >= 0 ? mSlotParts[slot].kind : -9, kindNow,
slot >= 0 ? mGpuStaleLo[slot] : 0L, slot >= 0 ? mGpuStaleHi[slot] : 0L, slot >= 0 ? mGpuStale2Lo[slot] : 0L, slot >= 0 ? mGpuStale2Hi[slot] : 0L,
mGpuLatestSlot, mGpuLatestSlot >= 0 ? (int)mGpuSlotValid[mGpuLatestSlot] : -1, mGpuLatestSlot >= 0 ? mSlotParts[mGpuLatestSlot].kind : -9,
mGpuLatestSlot >= 0 ? mGpuSlotY[mGpuLatestSlot] : 0,
mGpuLatestSlot >= 0 ? mGpuStaleLo[mGpuLatestSlot] : 0L, mGpuLatestSlot >= 0 ? mGpuStaleHi[mGpuLatestSlot] : 0L,
mGpuLatestSlot >= 0 ? mGpuStale2Lo[mGpuLatestSlot] : 0L, mGpuLatestSlot >= 0 ? mGpuStale2Hi[mGpuLatestSlot] : 0L,
latestWhy, src ? 1 : 0, (int)ownCheap, (int)ownWin,
(mGpuLatestSlot >= 0 && mGpuSlotZoom[mGpuLatestSlot] == zoom) ? "same" : "differs", mGpuLatestKey);
}
static const bool sWhiteLog = getenv("JIHAD_GPU_WHITE") != nullptr;
if (sWhiteLog) JIHAD_GPU_LOG("[jihad-gpu] white before job: slot %d (y %d -> %d, shift %ld, bands %ld..%ld %ld..%ld): rows%s\n",
@@ -4805,8 +5068,9 @@ bool BrowserPageGoanna::gpuJobStart(int slot, int key, unsigned char* pixels, si
unsigned char* const winPixels = pixels + (size_t)win * stride; // T-517
const size_t winBytes = bytes - (size_t)win * stride;
const uint32_t token = mGpuPainted
? mPage->ComposePost(winPixels, winBytes, h, sLo, sHi, s2Lo, s2Hi, shift, src, srcYForEngine, baseIsPrevious, nullptr, colX0, colX1, winMoved)
: mPage->ComposeStart(winPixels, winBytes, sLo, sHi, s2Lo, s2Hi, shift, src, srcYForEngine, baseIsPrevious, false, colX0, colX1, winMoved);
? 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);
if (token) mXLastPostToken = token;
if (token && slot >= 0) mGpuSlotValid[slot] = false; // T-517 (review): being written; its publish records it again
if (!token) {
@@ -4823,6 +5087,7 @@ bool BrowserPageGoanna::gpuJobStart(int slot, int key, unsigned char* pixels, si
j.xFrames = mXFrames;
j.cmdDone = mXCmdDone;
j.lostN = mGpuLostN;
j.contentOnly = co; j.srcSlot = baseSlot; j.srcY = baseY; // T-505
// T-513 (R2's fast flick): JIHAD_GPU_VERIFY's reference, the whole frame composed right behind this
// job, on the same layer tree at the same view. Composed at the job's poll instead, it met a tree
// the pan had moved on from (the displayport's tiles above the view dropped) and called the right
@@ -4934,6 +5199,7 @@ void BrowserPageGoanna::expressDetach() {
FrameLockGuard frameLock;
if (gXPages) gXPages->erase(this);
overviewForget(); // T-504
cpuPartsForget(); // T-505
gpuJobDrain(); // the express lane's frames too: the compositor writes into their slots until done
}
@@ -5070,6 +5336,7 @@ bool BrowserPageGoanna::expressStep(int64_t now) {
j.t0 = gj.t0; j.wantMs = want.ms; j.waitMs = waitSince ? waitSince : now; j.whole = false;
j.pixels = S.base[gj.slot] + S.hdr + (size_t)gj.win * S.w * 4; j.win = gj.win; j.kind = 0; j.cardY = gj.y + gj.viewRow;
j.adopted = true; j.cmdDone = gj.cmdDone;
j.contentOnly = gj.contentOnly; j.srcSlot = gj.srcSlot; j.srcY = gj.srcY; // T-505
mGpuJobs.erase(mGpuJobs.begin());
++mXAdopted;
static uint32_t sTkN = 0;
@@ -5153,6 +5420,10 @@ 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;
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
@@ -5226,7 +5497,8 @@ bool BrowserPageGoanna::xStart(int64_t now, double Z, const XWant& want, int64_t
// yet polled), and with no fixed or stuck layers then or in the slot's frame (the engine judges
// those against the previous composite, not an older base)
if (!aBase && xWinOn && !sNoOwn && mGpuJobs.empty() && mGpuSlotValid[slot] && mGpuSlotX[slot] == x && mGpuSlotZoom[slot] == Z &&
mGpuSlotW[slot] == w && mGpuSlotH[slot] == H && mGpuFixedHi <= mGpuFixedLo && !mGpuSlotFixed[slot]) {
mGpuSlotW[slot] == w && mGpuSlotH[slot] == H && mGpuFixedHi <= mGpuFixedLo && !mGpuSlotFixed[slot] &&
mSlotParts[slot].kind == (co ? 1 : 0)) { // T-505: its own frame of this kind
const long d = (long)y - mGpuSlotY[slot];
const long stale = (mGpuStaleHi[slot] - mGpuStaleLo[slot]) + (mGpuStale2Hi[slot] - mGpuStale2Lo[slot]);
// the faster kind by measurement, when there is a copy to compare with (a base)
@@ -5330,7 +5602,7 @@ bool BrowserPageGoanna::xStart(int64_t now, double Z, const XWant& want, int64_t
if (viewOnly) { lo = os; hi = os + S.h; lo2 = hi2 = 0; }
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, viewOnly, false, false, co && !viewOnly);
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;
@@ -5345,6 +5617,9 @@ bool BrowserPageGoanna::xStart(int64_t now, double Z, const XWant& want, int64_t
j.kind = own ? 1 : (base ? 2 : 0);
j.cardY = want.y;
j.viewOnly = viewOnly;
j.contentOnly = co && !viewOnly; // T-505
j.srcSlot = own ? slot : (base ? b : -1);
j.srcY = own ? mGpuSlotY[slot] : (base ? baseY : 0);
return true;
}
@@ -5381,6 +5656,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
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)
@@ -5395,6 +5671,18 @@ 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
if (mXJob.contentOnly) {
if (!cpuPartsApply(s, pixels, cr, H, w, mXJob.srcSlot, mXJob.srcY, mXJob.y)) {
cpuPartsMissed(cr.nStuck);
mXJob = XJob(); xForget(mXJob2);
gpuSlotsLost(); // every slot's kind unknown (this one's pixels are half made)
return true;
}
pubDirtyAll(); // the parts and what lay beneath them changed on the CPU
} else {
mSlotParts[s].kind = 0;
}
BrowserOffscreenInfo* oi = (BrowserOffscreenInfo*)mXSnap.base[s]; // T-517: the segment's start, not the window's
oi->bufferWidth = w; oi->bufferHeight = H;
oi->contentZoom = mXJob.zoom;
@@ -5832,6 +6120,7 @@ void BrowserPageGoanna::paintToSharedBuffer() {
}
}
mLatestSlot = slot;
{ const int ps = slotForKey(mActiveKey); if (ps >= 0) mSlotParts[ps].kind = -1; } // T-505: a software frame
publishWindow(mActiveKey, 0); // T-517: the software path's frames start at the pixels' start
pubDirtyAll(); mPubZoom = -1.0; // T-527: the next GPU frame is drawn whole
mSink.msgPainted(mActiveKey); noteShown(mActiveKey);
@@ -6076,6 +6365,7 @@ void BrowserPageGoanna::paintToSharedBuffer() {
// classic webOS-era behavior.
long nb = -1;
bool gpuFinished = false; // T-502: this paint publishes a frame started on an earlier tick
bool gpuPartsCpu = false; // T-505: and its parts were drawn on the CPU
int pubWin = 0; // T-517: the published frame's window (0: the software path's frames)
bool gpuRestart = false; // T-502: the compositor asked again while that frame was in flight
int paintedX = (int)bandX, paintedTop = (int)bandY, paintedH = h;
@@ -6351,6 +6641,7 @@ 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
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();
@@ -6385,14 +6676,30 @@ void BrowserPageGoanna::paintToSharedBuffer() {
{
static uint32_t sN = 0;
if (sN++ < 30 || (sN % 50) == 0)
JIHAD_GPU_LOG("[jihad-gpu] job done %ld ms after its start, %d pending polls, read %ld..%ld and %ld..%ld, changed %ld..%ld, fixed %ld..%ld%s, nb=%d, %d still in flight\n",
JIHAD_GPU_LOG("[jihad-gpu] job done %ld ms after its start, %d pending polls, read %ld..%ld and %ld..%ld, changed %ld..%ld, fixed %ld..%ld%s%s, nb=%d, %d still in flight\n",
(long)(mGpuDoneMs - oldest.t0), mGpuJobPolls, cr.readLo, cr.readHi, cr.read2Lo, cr.read2Hi, il, ih, cr.fixedLo, cr.fixedHi,
cr.scrollOnly ? " (scroll only)" : "", r, (int)mGpuJobs.size());
cr.scrollOnly ? " (scroll only)" : "", oldest.contentOnly ? " (without its parts: drawn on the CPU)" : "", r, (int)mGpuJobs.size());
for (int ti = 0; ti < 8; ++ti) mGpuTrace[ti] = 0;
mGpuJobPolls = 0;
paintT0 = oldest.t0; // the painted line's ms= is then start-to-publish
}
if (r >= 0 && gslot >= 0 && !xDropped) {
// T-505: a frame without its parts gets them here; one a part the picture lacks is not adopted (as a dropped
// 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)) {
gpuPartsCpu = true;
} else {
partsMiss = true;
cpuPartsMissed(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;
mNeedsPaint = true;
}
}
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;
// 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;
@@ -6951,6 +7258,7 @@ void BrowserPageGoanna::paintToSharedBuffer() {
const bool stuckOk = gpuFinished && paintedW == w && popupsDrawn == 0;
publishWindow(mActiveKey, pubWin, partial ? dirty : nullptr, stuckOk ? &mGpuJobCr : nullptr, mGpuJobViewRow);
}
{ const int ps = slotForKey(mActiveKey); if (ps >= 0) mSlotParts[ps].kind = gpuFinished ? (gpuPartsCpu ? 1 : 0) : -1; } // T-505: whose parts
if (!(gpuFinished && jihadNoPublish())) { mSink.msgPainted(mActiveKey); noteShown(mActiveKey); }
const int64_t b = jihadNowMs();
jihadTimelinePublish("main", gpuFinished ? paintT0 : 0, gpuFinished ? mGpuJobY : paintedTop, oi->contentZoom);
+39
View File
@@ -467,6 +467,7 @@ private:
int win = 0; // T-517: the frame's window in its slot (rows into the pixels)
uint32_t vToken = 0; int vBuf = -1; // T-513: JIHAD_GPU_VERIFY's reference, composed right behind the job
uint32_t lostN = 0; // T-527: mGpuLostN when it was posted (a composite lost since: not adopted)
bool contentOnly = false; int srcSlot = -1; long srcY = 0; // T-505: without its parts; the frame it was built on
};
void gpuForget(const GpuJob& j); // the job and its verify reference given up
// T-505: the view's row in the GPU frame and the residual offset last asked of the compositor
@@ -604,6 +605,42 @@ 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;
// 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);
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
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;
size_t mOvScratchBytes = 0; // window k half a frame each after it)
@@ -645,6 +682,8 @@ private:
int64_t composedMs = 0; // device rounds: when it was seen composed
bool adopted = false; uint32_t cmdDone = 0; // T-527: a main-lane job the express lane publishes (its main-lane bookkeeping)
bool viewOnly = false; // T-516: the first frame at a new zoom, the card's view read alone (the overscan white)
bool contentOnly = false; // T-505: composed without its parts (JIHAD_GPU_CPU_PARTS): they are drawn on the CPU
int srcSlot = -1, srcY = 0; // T-505: the frame it was built on (its window moved, or copied from it), and its top
};
XJob mXJob; // the express lane's frame in flight (the older one)
// T-517: the express lane's frame time on its slot's own frame and copied from the base (running
+16 -7
View File
@@ -4988,7 +4988,7 @@ bool GoannaRenderPage::ComposeAsync() const {
uint32_t GoannaRenderPage::ComposeStart(unsigned char* dst, size_t dstBytes, long rowLo, long rowHi, long row2Lo, long row2Hi, long shift,
const unsigned char* src, long srcY, bool engineShift, bool verify, long colX0, long colX1,
bool windowMoved) {
bool windowMoved, bool contentOnly) {
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;
@@ -4998,7 +4998,8 @@ uint32_t GoannaRenderPage::ComposeStart(unsigned char* dst, size_t dstBytes, lon
if (colX1 < 0) { colX0 = 0; colX1 = w; } // whole rows
// flag 4 (T-511): the column arguments are given, and the poll takes twelve ints back
const 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), (int)colX0, (int)colX1);
(engineShift ? 1 : 0) | (verify ? 2 : 0) | 4 | (windowMoved ? 8 : 0) |
(contentOnly && PartsReady() ? 128 : 0), (int)colX0, (int)colX1);
return token;
}
@@ -5021,7 +5022,11 @@ bool GoannaRenderPage::ExpressReady() const {
}
bool GoannaRenderPage::OverviewReady() const {
return ExpressReady() && jihad_offscreen_overview_ok && jihad_offscreen_overview_ok() == 1;
return ExpressReady() && jihad_offscreen_overview_ok && jihad_offscreen_overview_ok() >= 1;
}
bool GoannaRenderPage::PartsReady() const {
return ExpressReady() && jihad_offscreen_overview_ok && jihad_offscreen_overview_ok() >= 2 && jihad_offscreen_compose_poll2;
}
int GoannaRenderPage::ComposePeek(uint32_t token) {
@@ -5037,7 +5042,7 @@ 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 bandsOnly, bool overview, bool overviewParts, bool contentOnly) {
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
@@ -5047,7 +5052,8 @@ uint32_t GoannaRenderPage::ComposePost(unsigned char* dst, size_t dstBytes, int
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) |
(overview && jihad_offscreen_overview_ok ? 32 : 0) | (overviewParts && jihad_offscreen_overview_ok ? 64 : 0),
(overview && jihad_offscreen_overview_ok ? 32 : 0) | (overviewParts && jihad_offscreen_overview_ok ? 64 : 0) |
(contentOnly && PartsReady() ? 128 : 0),
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);
@@ -5079,9 +5085,11 @@ 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[29] = {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[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 scrollOnly = 0;
const int r = jihad_offscreen_compose_poll2 ? jihad_offscreen_compose_poll2(mWidget, token, o, 29, &scrollOnly) // T-544
// 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;
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
auto takeCols = [&]() {
@@ -5101,6 +5109,7 @@ int GoannaRenderPage::ComposePoll(uint32_t token, unsigned char* dst, ComposeRes
out->scrollOnly = scrollOnly != 0;
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
for (int k = 0; k < out->nStuck; k++)
for (int c = 0; c < 4; c++) out->stuck[k][c] = o[13 + 4 * k + c];
}
+7 -3
View File
@@ -385,7 +385,8 @@ public:
// windowMoved (T-517): the shift is the daemon's move of the frame's window (the rows in place)
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);
long colX0 = 0, long colX1 = -1, bool windowMoved = false,
bool contentOnly = false); // T-505: the frame without its parts (PartsReady)
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
@@ -397,9 +398,11 @@ public:
long colX0 = 0, long colX1 = -1, bool windowMoved = false, bool verify = false,
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 overviewParts = false, // T-504: the overview's fixed and stuck parts alone
bool contentOnly = false); // T-505: the frame without its parts (PartsReady)
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
int ComposePeek(uint32_t token); // 1 done, 2 composed (being read), 0 pending, -1 unknown
// askLo/askHi: the band that job was started with (what was read is it joined with the change)
// out (may be null): the rows the tree changed (inv), the rows fixed layers cover (fixed),
@@ -408,7 +411,8 @@ public:
// before reports rows only)
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)
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
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