gpu compositor: the parts' picture by part — each rect its own signature, a pass reads only the rects the daemon lacks

T-505 (the owner's "cheaper part pictures", 2026-11-03). A frame without its parts (flag 128) used to carry
one signature of all its parts; when it moved, the parts pass composed and read every part. Now each rect of
the parts list has a signature of its own: the outermost fixed or sticky layer's subtree as before, every
subtree layer's last paint as the stamp of one counter that only goes up (CompositableTransactionParent stamps
each compositable update's layer, part or not: a layer that becomes a part later carries it — review F2), the
part's scale and the sub-pixel phase of its position, where it sits inside the rect and its size (review F3: a
part wider than the frame moving sideways, or moving inside a merged rect, keeps the rect's size), and the
rect's size; a rect no part's rect meets (a nested part's own) takes every part's with its index and place
(review F6). The whole signature is the rects' and the global paint count, so a daemon with the one signature
behaves as before (every part read when any moved).

jihad_offscreen_compose_post3/start3 (jihad_offscreen_overview_ok answers 4) take the list of signatures the
daemon holds pictures of; the pass packs and reads only the rects whose signature is not in it, in rect order,
and the poll's 54 ints carry each rect's signature (49..52) and the rects read as a mask (53). post2/start2
keep their meaning.

Measured by finger on desktop Wikipedia (its two sidebars; the contents panel, 310 of 340 KB, is the one that
changes): the pass reads the panel alone, 10.4 -> 11.4 fps flowing; the long page unchanged. Verify 0 wrong
rows over the scripted pans on both (docs/rounds/t505-2026-11-03/notes.txt).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
NotAlexNoyleandClaude Fable 5.1 committed 2026-11-03 12:47:32 -05:00
1 parent 52ad9ab66e
commit adfaa7ff30
6 files changed
+264 -98

No files matched your search

@@ -31,6 +31,7 @@ namespace mozilla {
namespace layers {
extern std::atomic<uint32_t> gJihadPartsGen; // Jihad T-505, CompositorBridgeParent.cpp
void JihadNotePartPaint(Layer* aLayer); // Jihad T-505 (per part): that layer painted (its paint stamp)
void JihadReqNote(const char* aSrc); // Jihad T-505 diagnostic, CompositorBridgeParent.cpp
class ClientTiledLayerBuffer;
@@ -75,8 +76,13 @@ CompositableParentManager::ReceiveCompositableUpdate(const CompositableOperation
}
// Jihad T-505: an update to a layer under a fixed or sticky one moves the parts' generation (the daemon composes
// the page's fixed and sticky parts alone again when it moves: they changed)
for (Layer* l = compositable->GetLayer(); l; l = l->GetParent()) {
if (l->GetIsFixedPosition() || l->GetIsStickyPosition()) { ++gJihadPartsGen; break; }
{
// (per part, review F2) every layer's paint is stamped, part or not: a layer that becomes a part later carries it
Layer* painted = compositable->GetLayer();
if (painted) JihadNotePartPaint(painted);
for (Layer* l = painted; l; l = l->GetParent()) {
if (l->GetIsFixedPosition() || l->GetIsStickyPosition()) { ++gJihadPartsGen; break; }
}
}
switch (aEdit.detail().type()) {
+185 -83
View File
@@ -8,6 +8,7 @@
#include <unistd.h> // Jihad T-509: sleep() for JIHAD_GPU_TEST_STALL
#include <stdint.h> // for uint64_t
#include <map> // for _Rb_tree_iterator, etc
#include <mutex> // Jihad T-505 (per part): sJihadPartPaintLock
#include <utility> // for pair
#include "LayerTransactionParent.h" // for LayerTransactionParent
#include "RenderTrace.h" // for RenderTraceLayers
@@ -30,6 +31,20 @@ static bool sJihadContentOnly = false; // Jihad T-505: the job is a frame withou
// sticky layer counts as a part, none of the fixed rows' handling applies
namespace mozilla { namespace layers {
std::atomic<uint32_t> gJihadPartsGen(0); // Jihad T-505: updates to layers under a fixed or sticky one
// Jihad T-505 (per part): every layer's last paint, as the stamp of ONE counter that only goes up (review F2: a count
// per part that started again after a clear could return to a value a held picture was signed with; a layer painted
// while it was no part keeps its stamp for when it becomes one). CompositableTransactionParent notes each update; an
// ImageBridge update may come on its own thread. Past 4096 layers the map is cleared: the stamps lost make every
// part's signature differ once (a pass), and the next paints take new stamps, so a lost stamp never matches
static std::mutex sJihadPaintStampLock;
static uint32_t sJihadPaintStamp = 0;
static std::map<Layer*, uint32_t> sJihadLayerPaint;
void JihadNotePartPaint(Layer* aLayer)
{
std::lock_guard<std::mutex> g(sJihadPaintStampLock);
if (sJihadLayerPaint.size() > 4096) sJihadLayerPaint.clear();
sJihadLayerPaint[aLayer] = ++sJihadPaintStamp;
}
} } // (CompositableTransactionParent.cpp)
#include <cmath> // Jihad T-502: floor, ceil
#ifdef MOZ_WIDGET_GTK
@@ -1167,87 +1182,152 @@ CompositorBridgeParent::JihadHideForJob()
});
}
// T-505 (E2): FNV-1a over what makes the parts' pixels: each layer in a fixed or sticky subtree (its identity and
// type, opacity, content flags, mask, visible region; inside a part its transform and clip too, so an inner scroll or a
// move inside the part counts; a colour layer's colour; an animation: every composite), the paints of layers under a
// part (gJihadPartsGen) and the parts' sizes on the frame (a part crossing the frame's edge is new pixels). A content
// leaf drawn after a part (above it, in paint order) whose pixels meet it is a cover: a popup over a fixed bar, a page
// over a fixed background
// T-505 (E2, per part): FNV-1a per outermost fixed or sticky layer over what makes its pixels: each layer of its subtree
// (identity and type, opacity, content flags, mask, visible region; inside the part its transform and clip too, so an
// inner scroll or a move inside the part counts; a colour layer's colour; an animation: every composite), the paints
// under it (sJihadPartPaint) and, cut by the frame's top or bottom edge, the slice shown. Each rect of aRects takes the
// signatures of the parts whose rects meet it and its own size (aRectSig; a rect no part's rect meets, a nested
// part's own, takes every part's). The whole is the rects' signatures, their count and the global paint count
// (gJihadPartsGen: a daemon with the one signature reads every part when any paint happened under one, as before).
// A content leaf drawn after a part (above it, in paint order) whose pixels meet it is a cover: a popup over a fixed
// bar, a page over a fixed background
uint32_t
CompositorBridgeParent::JihadPartsSig(const gfx::IntRect* aRects, int aN, bool* aCovered, int aWidth, int aHeight)
CompositorBridgeParent::JihadPartsSig(const gfx::IntRect* aRects, int aN, bool* aCovered, int aWidth, int aHeight, uint32_t* aRectSig)
{
uint32_t h = 2166136261u;
const uint32_t kBasis = 2166136261u;
uint32_t h = kBasis; // the part being walked
auto mix = [&](uint32_t v) { h = (h ^ v) * 16777619u; };
auto mixf = [&](float f) { uint32_t u; memcpy(&u, &f, sizeof u); mix(u); };
auto mixr = [&](const gfx::IntRect& r) { mix(r.x); mix(r.y); mix(r.width); mix(r.height); };
mix((uint32_t)gJihadPartsGen);
for (int i = 0; i < aN; i++) { mix(aRects[i].width); mix(aRects[i].height); }
struct TopPart { uint32_t sig; gfx::IntRect ir; gfx::IntRect tb; }; // tb: its bounds on the frame, not inflated
TopPart tops[32];
int nTop = 0;
gfx::IntRect topIr, topTb;
std::map<Layer*, uint32_t> stamps; // the layers' paint stamps, taken once under the lock
{
std::lock_guard<std::mutex> g(sJihadPaintStampLock);
stamps = sJihadLayerPaint;
}
auto stamp = [&](Layer* l) { auto it = stamps.find(l); return it != stamps.end() ? it->second : 0u; };
const gfx::IntRect frame(0, 0, aWidth, aHeight);
*aCovered = false;
Layer* root = mLayerManager ? mLayerManager->GetRoot() : nullptr;
if (!root) return h;
static uint32_t sAnimTick = 0;
static uint32_t sCoverLog = 0;
int depth = 0;
nsIntRegion partsSoFar;
auto onFrame = [&](Layer* x, nsIntRegion& aOut) {
gfx::Matrix m;
if (!x->GetEffectiveTransform().Is2D(&m) || !m.PreservesAxisAlignedRectangles()) {
const gfx::IntRect vb = x->GetLocalVisibleRegion().GetBounds().ToUnknownRect();
aOut.OrWith(gfx::RoundedOut(x->GetEffectiveTransform().TransformBounds(gfx::Rect(vb.x, vb.y, vb.width, vb.height))));
return;
}
const nsIntRegion visible = x->GetLocalVisibleRegion().ToUnknownRegion();
for (auto it = visible.RectIter(); !it.Done(); it.Next()) {
const gfx::IntRect& q = it.Get();
aOut.OrWith(gfx::RoundedOut(x->GetEffectiveTransform().TransformBounds(gfx::Rect(q.x, q.y, q.width, q.height))));
}
};
ForEachNode<ForwardIterator>(root,
[&](Layer* l) {
const bool part = l->GetIsFixedPosition() || l->GetIsStickyPosition();
if (part || depth > 0) {
mix((uint32_t)(uintptr_t)l); mix((uint32_t)l->GetType()); mixf(l->GetOpacity());
mix(l->GetContentFlags());
if (Layer* mk = l->GetMaskLayer()) { mix((uint32_t)(uintptr_t)mk); mixr(mk->GetVisibleRegion().GetBounds().ToUnknownRect()); } // (review F5)
mixr(l->GetVisibleRegion().GetBounds().ToUnknownRect());
if (l->GetClipRect()) mixr(l->GetClipRect()->ToUnknownRect()); // (review F5) a top-level part's clip too
if (depth > 0) {
const gfx::Matrix4x4 m = l->GetBaseTransform();
for (int k = 0; k < 16; k++) mixf(m.components[k]);
}
if (l->GetMixBlendMode() != gfx::CompositionOp::OP_OVER) *aCovered = true; // (review F5) not drawable by "over" on the CPU
if (part && depth == 0) {
// (review F5) a part cut by the frame's top or bottom edge: its rect keeps its size while the slice shown
// moves with it (a sticky element not yet stuck, taller than the frame or half off it): where it starts
const gfx::IntRect vb = l->GetLocalVisibleRegion().GetBounds().ToUnknownRect();
const gfx::IntRect tb = gfx::RoundedOut(l->GetEffectiveTransform().TransformBounds(gfx::Rect(vb.x, vb.y, vb.width, vb.height)));
if (tb.y < 0 || tb.YMost() > aHeight) mix((uint32_t)tb.y);
}
if (l->GetType() == Layer::TYPE_COLOR) {
const gfx::Color c = static_cast<ColorLayer*>(l)->GetColor();
mixf(c.r); mixf(c.g); mixf(c.b); mixf(c.a);
}
if (!l->GetAnimations().IsEmpty()) mix(++sAnimTick);
if (part && depth == 0) onFrame(l, partsSoFar);
} else if (!l->GetFirstChild() && !partsSoFar.IsEmpty() && l->GetEffectiveOpacity() > 0.0f) {
const Layer::LayerType t = l->GetType();
if (t == Layer::TYPE_PAINTED || t == Layer::TYPE_COLOR || t == Layer::TYPE_IMAGE || t == Layer::TYPE_CANVAS) {
nsIntRegion mine;
onFrame(l, mine);
mine.AndWith(partsSoFar);
if (!mine.IsEmpty()) {
if (!*aCovered && sCoverLog++ < 20) {
const gfx::IntRect b = mine.GetBounds();
JIHAD_GPU_LOG("[jihad-gpu] parts: layer %p (type %d) drawn above a part at %d,%d %dx%d\n", l, (int)t, b.x, b.y, b.width, b.height);
if (root) {
static uint32_t sAnimTick = 0;
static uint32_t sCoverLog = 0;
int depth = 0;
nsIntRegion partsSoFar;
auto onFrame = [&](Layer* x, nsIntRegion& aOut) {
gfx::Matrix m;
if (!x->GetEffectiveTransform().Is2D(&m) || !m.PreservesAxisAlignedRectangles()) {
const gfx::IntRect vb = x->GetLocalVisibleRegion().GetBounds().ToUnknownRect();
aOut.OrWith(gfx::RoundedOut(x->GetEffectiveTransform().TransformBounds(gfx::Rect(vb.x, vb.y, vb.width, vb.height))));
return;
}
const nsIntRegion visible = x->GetLocalVisibleRegion().ToUnknownRegion();
for (auto it = visible.RectIter(); !it.Done(); it.Next()) {
const gfx::IntRect& q = it.Get();
aOut.OrWith(gfx::RoundedOut(x->GetEffectiveTransform().TransformBounds(gfx::Rect(q.x, q.y, q.width, q.height))));
}
};
ForEachNode<ForwardIterator>(root,
[&](Layer* l) {
const bool part = l->GetIsFixedPosition() || l->GetIsStickyPosition();
if (part || depth > 0) {
if (part && depth == 0) {
// a new part: its own hash; its rect on the frame as JihadFixedRectsNow makes it, and its bounds; (review F3)
// its scale and the sub-pixel phase of its position (a quarter pixel): a picture made at another zoom
// or phase is other pixels
h = kBasis;
topIr = topTb = gfx::IntRect();
const gfx::IntRect vb = l->GetLocalVisibleRegion().GetBounds().ToUnknownRect();
if (!vb.IsEmpty()) {
topTb = gfx::RoundedOut(l->GetEffectiveTransform().TransformBounds(gfx::Rect(vb.x, vb.y, vb.width, vb.height)));
topIr = topTb;
topIr.Inflate(1);
topIr = topIr.Intersect(frame);
}
gfx::Matrix m2;
if (l->GetEffectiveTransform().Is2D(&m2)) {
mixf(m2._11); mixf(m2._22); mixf(m2._12); mixf(m2._21);
mix((uint32_t)lroundf((m2._31 - floorf(m2._31)) * 4.0f)); mix((uint32_t)lroundf((m2._32 - floorf(m2._32)) * 4.0f));
} else {
mix(0x3d3d3d3du);
}
}
mix((uint32_t)(uintptr_t)l); mix((uint32_t)l->GetType()); mixf(l->GetOpacity());
mix(stamp(l)); // its last paint (every layer of the subtree: review F2)
mix(l->GetContentFlags());
if (Layer* mk = l->GetMaskLayer()) { mix((uint32_t)(uintptr_t)mk); mixr(mk->GetVisibleRegion().GetBounds().ToUnknownRect()); } // (review F5)
mixr(l->GetVisibleRegion().GetBounds().ToUnknownRect());
if (l->GetClipRect()) mixr(l->GetClipRect()->ToUnknownRect()); // (review F5) a top-level part's clip too
if (depth > 0) {
const gfx::Matrix4x4 m = l->GetBaseTransform();
for (int k = 0; k < 16; k++) mixf(m.components[k]);
}
if (l->GetMixBlendMode() != gfx::CompositionOp::OP_OVER) *aCovered = true; // (review F5) not drawable by "over" on the CPU
if (part && depth == 0) {
// (review F5) a part cut by the frame's top or bottom edge: its rect keeps its size while the slice shown
// moves with it (a sticky element not yet stuck, taller than the frame or half off it): where it starts
const gfx::IntRect vb = l->GetLocalVisibleRegion().GetBounds().ToUnknownRect();
const gfx::IntRect tb = gfx::RoundedOut(l->GetEffectiveTransform().TransformBounds(gfx::Rect(vb.x, vb.y, vb.width, vb.height)));
if (tb.y < 0 || tb.YMost() > aHeight) mix((uint32_t)tb.y);
}
if (l->GetType() == Layer::TYPE_COLOR) {
const gfx::Color c = static_cast<ColorLayer*>(l)->GetColor();
mixf(c.r); mixf(c.g); mixf(c.b); mixf(c.a);
}
if (!l->GetAnimations().IsEmpty()) mix(++sAnimTick);
if (part && depth == 0) onFrame(l, partsSoFar);
} else if (!l->GetFirstChild() && !partsSoFar.IsEmpty() && l->GetEffectiveOpacity() > 0.0f) {
const Layer::LayerType t = l->GetType();
if (t == Layer::TYPE_PAINTED || t == Layer::TYPE_COLOR || t == Layer::TYPE_IMAGE || t == Layer::TYPE_CANVAS) {
nsIntRegion mine;
onFrame(l, mine);
mine.AndWith(partsSoFar);
if (!mine.IsEmpty()) {
if (!*aCovered && sCoverLog++ < 20) {
const gfx::IntRect b = mine.GetBounds();
JIHAD_GPU_LOG("[jihad-gpu] parts: layer %p (type %d) drawn above a part at %d,%d %dx%d\n", l, (int)t, b.x, b.y, b.width, b.height);
}
*aCovered = true;
}
*aCovered = true;
}
}
}
if (part) depth++;
},
[&](Layer* l) { if (l->GetIsFixedPosition() || l->GetIsStickyPosition()) depth--; });
return h;
if (part) depth++;
},
[&](Layer* l) {
if (!(l->GetIsFixedPosition() || l->GetIsStickyPosition())) return;
if (--depth > 0) return;
// the part walked: its signature, rect and bounds kept (past 32 parts, folded into the last)
if (nTop < 32) { tops[nTop].sig = h; tops[nTop].ir = topIr; tops[nTop].tb = topTb; nTop++; }
else { tops[31].sig = (tops[31].sig ^ h) * 16777619u; tops[31].ir = tops[31].ir.Union(topIr); tops[31].tb = tops[31].tb.Union(topTb); }
});
}
// each rect's signature, and the whole
uint32_t whole = kBasis;
auto mixw = [&](uint32_t v) { whole = (whole ^ v) * 16777619u; };
mixw((uint32_t)gJihadPartsGen); mixw((uint32_t)aN);
for (int k = 0; k < aN; k++) {
h = kBasis;
mix((uint32_t)aRects[k].width); mix((uint32_t)aRects[k].height);
bool any = false;
for (int i = 0; i < nTop; i++) {
if (tops[i].ir.IsEmpty() || !aRects[k].Intersects(tops[i].ir)) continue;
mix(tops[i].sig); any = true;
// (review F3) where the part sits inside the rect, and its size: a part wider than the frame moving sideways
// under a cut rect, a part moving inside a merged rect, keep the rect's size and would keep its picture
mix((uint32_t)(tops[i].tb.x - aRects[k].x)); mix((uint32_t)(tops[i].tb.y - aRects[k].y));
mix((uint32_t)tops[i].tb.width); mix((uint32_t)tops[i].tb.height);
}
if (!any) { // (review F6) a rect no part's rect meets: every part's, and its own index and place
for (int i = 0; i < nTop; i++) mix(tops[i].sig);
mix((uint32_t)k); mix((uint32_t)aRects[k].x); mix((uint32_t)aRects[k].y);
}
if (aRectSig) aRectSig[k] = h;
mixw(h);
}
return whole;
}
void
@@ -1483,11 +1563,12 @@ JihadFixedCols()
// T-544: the frame's fixed and stuck rects (the last JihadUpdateLastFixed's) out to the daemon
static void
JihadStuckOut(JihadFrameRequest* aReq, const gfx::IntRect* aRects, int aN)
JihadStuckOut(JihadFrameRequest* aReq, const gfx::IntRect* aRects, int aN, const uint32_t* aSig = nullptr)
{
aReq->nStuck = 0;
for (int k = 0; k < aN && aReq->nStuck < 4; k++) {
if (aRects[k].IsEmpty()) continue;
aReq->partsSig[aReq->nStuck] = aSig ? aSig[k] : 0; // T-505 (per part): the rect's own signature
int* o = aReq->stuck[aReq->nStuck++];
o[0] = aRects[k].x; o[1] = aRects[k].y; o[2] = aRects[k].XMost(); o[3] = aRects[k].YMost();
}
@@ -2032,7 +2113,8 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
mLayerManager->JihadPrepareOnly();
const int partN = JihadFixedRectsNow(aWidth, aHeight, parts, pk); // every fixed and sticky layer, whole rects
bool covered = false;
aReq->partsGen = JihadPartsSig(parts, partN, &covered, aWidth, aHeight);
uint32_t rectSig[16] = {};
aReq->partsGen = JihadPartsSig(parts, partN, &covered, aWidth, aHeight, rectSig);
if (covered) aReq->partsFlags |= 1;
{
sJihadContentOnly = false; // the card's hold set keeps T-544's meaning: fixed layers and stuck ones
@@ -2047,11 +2129,25 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
}
}
mLayerManager->JihadEndTransactionNoComposite();
JihadStuckOut(aReq, parts, partN);
if (!covered && aReq->partsDest && aReq->nStuck > 0 && aReq->partsGen != aReq->partsHave) {
JihadStuckOut(aReq, parts, partN, rectSig);
// T-505 (per part): the rects to read — with the list of signatures the daemon holds, the ones not in it; with
// the one signature, every rect when the whole differs
int want = 0;
if (!covered && aReq->partsDest && aReq->nStuck > 0) {
if (aReq->nPartsHave >= 0) {
for (int k = 0; k < aReq->nStuck; k++) {
bool have = false;
for (int j = 0; j < aReq->nPartsHave && j < 16 && !have; j++) have = aReq->partsHaveList[j] == aReq->partsSig[k];
if (!have) want |= 1 << k;
}
} else if (aReq->partsGen != aReq->partsHave) {
want = (1 << aReq->nStuck) - 1;
}
}
if (want) {
size_t need = 0;
for (int k = 0; k < aReq->nStuck; k++)
need += (size_t)(aReq->stuck[k][2] - aReq->stuck[k][0]) * (aReq->stuck[k][3] - aReq->stuck[k][1]) * 4;
if (want & (1 << k)) need += (size_t)(aReq->stuck[k][2] - aReq->stuck[k][0]) * (aReq->stuck[k][3] - aReq->stuck[k][1]) * 4;
// the rects cut into pieces no taller than the surface, on shelves across its width (12 regions at most)
struct Piece { int k, x, y, w, h, atX, atY; } pieces[12];
int np = 0, used = 0;
@@ -2059,6 +2155,7 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
np = 0; used = 0;
int shelfY = 0, shelfH = 0, cursorX = 0;
for (int k = 0; k < aReq->nStuck; k++) {
if (!(want & (1 << k))) continue; // a rect the daemon has
const int x0 = aReq->stuck[k][0], w = aReq->stuck[k][2] - x0;
for (int y = aReq->stuck[k][1]; y < aReq->stuck[k][3];) {
const int hgt = std::min(aCap, aReq->stuck[k][3] - y);
@@ -2105,9 +2202,11 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
mLayerManager->JihadSetRegions(nullptr, nullptr, 0);
const TimeStamp pr0 = TimeStamp::Now();
bool ok = true;
size_t off[4] = {};
for (int k = 1; k < aReq->nStuck; k++)
off[k] = off[k - 1] + (size_t)(aReq->stuck[k - 1][2] - aReq->stuck[k - 1][0]) * (aReq->stuck[k - 1][3] - aReq->stuck[k - 1][1]) * 4;
size_t off[4] = {}; // each read rect's place in the buffer: the read ones packed in rect order
for (int k = 0, o = 0; k < aReq->nStuck; k++) {
off[k] = (size_t)o;
if (want & (1 << k)) o += (aReq->stuck[k][2] - aReq->stuck[k][0]) * (aReq->stuck[k][3] - aReq->stuck[k][1]) * 4;
}
for (int i = 0; i < np && ok; i++) {
if (aReq->cancelled) { ok = false; break; } // (review F6)
const int k = pieces[i].k, rw = aReq->stuck[k][2] - aReq->stuck[k][0];
@@ -2117,12 +2216,15 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal
ogl->JihadEndBandPass();
ogl->JihadSetClearTransparent(false);
sJihadOverview = 0;
if (ok) aReq->partsBytes = (int)need;
if (ok) { aReq->partsBytes = (int)need; aReq->partsRead = want; }
static uint32_t sPassLog = 0;
if (sPassLog++ < 20 || (sPassLog % 50) == 0)
JIHAD_GPU_LOG("[jihad-gpu] parts pass: %d parts, %d pieces on %d rows, %zu KB: compose %.1f ms, read %.1f ms%s\n",
aReq->nStuck, np, used, need / 1024, (pr0 - pc0).ToMilliseconds(), (TimeStamp::Now() - pr0).ToMilliseconds(),
if (sPassLog++ < 20 || (sPassLog % 50) == 0) {
int nRead = 0;
for (int k = 0; k < aReq->nStuck; k++) if (want & (1 << k)) nRead++;
JIHAD_GPU_LOG("[jihad-gpu] parts pass: %d of %d parts (mask %x), %d pieces on %d rows, %zu KB: compose %.1f ms, read %.1f ms%s\n",
nRead, aReq->nStuck, want, np, used, need / 1024, (pr0 - pc0).ToMilliseconds(), (TimeStamp::Now() - pr0).ToMilliseconds(),
ok ? "" : " (failed)");
}
}
mLayerManager->JihadIgnoreDiff(false);
mLayerManager->JihadSetWorldTransform(world);
+4 -3
View File
@@ -546,9 +546,10 @@ public:
gfx::IntRect mJihadLastKeep[4], mJihadPrevKeep[4];
int mJihadPrevKeepN = 0;
void JihadHideForJob(); // Jihad T-504/T-505: the overview's and a content-only frame's layers hidden
// Jihad T-505 (E2): the parts' signature (their layers, content, attributes and sizes); *aCovered when a content
// layer is drawn above a part (the daemon cannot draw that part over the frame)
uint32_t JihadPartsSig(const gfx::IntRect* aRects, int aN, bool* aCovered, int aWidth, int aHeight);
// Jihad T-505 (E2): the parts' signature (their layers, content, attributes and sizes), and each rect's own
// (aRectSig, aN of them: per part); *aCovered when a content layer is drawn above a part (the daemon cannot draw
// that part over the frame)
uint32_t JihadPartsSig(const gfx::IntRect* aRects, int aN, bool* aCovered, int aWidth, int aHeight, uint32_t* aRectSig);
int JihadFixedRectsNow(int aWidth, int aHeight, gfx::IntRect* aOut, gfx::IntRect* aKeep);
void JihadUpdateLastFixed(int aWidth, int aHeight);
bool mJihadSeqKnown = false;
+7
View File
@@ -185,6 +185,13 @@ struct JihadFrameRequest {
uint8_t* partsDest = nullptr;
int partsCap = 0;
uint32_t partsHave = 0;
// T-505 (per part, 2026-11-03): each rect of stuck[] has a signature of its own (partsSig[k]: the parts it holds,
// their paints and its size). A job posted with the list of signatures the daemon holds pictures of (partsHaveList,
// nPartsHave >= 0: compose_post3/start3) reads only the rects whose signature is not in it, packed in rect order;
// partsRead says which (bit k). One posted with the one signature (post2/start2, nPartsHave -1) reads every rect
// when the whole differs, as before
uint32_t partsHaveList[16] = {}; int nPartsHave = -1;
uint32_t partsSig[4] = {}; int partsRead = 0; // out
int partsBytes = 0; // out: the bytes the job's parts pass wrote (0: no pass)
int partsFlags = 0; // out: 1 a content layer above a part (the frame cannot be dressed), 2 too big
int hold[4][4] = {}; // out: the card's hold set (x0, y0, x1, y1) of a frame without its parts
+54 -7
View File
@@ -853,6 +853,7 @@ struct JihadReadJob {
bool overviewParts = false; // T-504 (flag 64): the overview's fixed and stuck parts alone
bool contentOnly = false; // T-505 (flag 128): the frame without its fixed and sticky parts
uint8_t* partsDest = nullptr; int partsCap = 0; uint32_t partsHave = 0; // T-505 (E2): its parts pass
uint32_t partsHaveList[16] = {}; int nPartsHave = -1; // T-505 (per part): the signatures the daemon holds (post3/start3)
int invLo, invHi; // the rows the composite changed
float scrollX = 0.0f, scrollY = 0.0f; bool scrollValid = false; // the card's scroll for the compositor
uint32_t token;
@@ -905,6 +906,8 @@ void JihadReadOnCompositorThread(JihadReadJob* job)
job->req.overviewParts = job->overviewParts;
job->req.contentOnly = job->contentOnly;
job->req.partsDest = job->partsDest; job->req.partsCap = job->partsCap; job->req.partsHave = job->partsHave;
job->req.nPartsHave = job->nPartsHave;
for (int i = 0; i < 16; i++) job->req.partsHaveList[i] = job->partsHaveList[i];
job->req.scrollX = job->scrollX; job->req.scrollY = job->scrollY; job->req.scrollValid = job->scrollValid;
job->req.done = &JihadJobDone; job->req.cookie = job;
// Once done() has run the daemon may forget (delete) the job at any moment (round 108's wake
@@ -941,7 +944,8 @@ uint32_t
PuppetWidget::JihadComposeStart(nsIDocShell* aDocShell, void* aDest, int aStride,
int aWidth, int aHeight, int aRowLo, int aRowHi, int aRow2Lo, int aRow2Hi, int aShift,
const void* aSrc, int aSrcY, int aFlags, int aColX0, int aColX1,
void* aPartsDest, int aPartsCap, uint32_t aPartsHave)
void* aPartsDest, int aPartsCap, uint32_t aPartsHave,
const uint32_t* aPartsHaveList, int aNPartsHave)
{
if (!aDocShell || !aDest || aWidth <= 0 || aHeight <= 0 || aStride < aWidth * 4) {
return 0;
@@ -950,7 +954,7 @@ PuppetWidget::JihadComposeStart(nsIDocShell* aDocShell, void* aDest, int aStride
return 0;
}
return JihadPostJob(aDest, aStride, aWidth, aHeight, aRowLo, aRowHi, aRow2Lo, aRow2Hi, aShift, aSrc, aSrcY, aFlags,
nullptr, false, aColX0, aColX1, aPartsDest, aPartsCap, aPartsHave);
nullptr, false, aColX0, aColX1, aPartsDest, aPartsCap, aPartsHave, aPartsHaveList, aNPartsHave);
}
bool
@@ -1042,7 +1046,8 @@ uint32_t
PuppetWidget::JihadPostJob(void* aDest, int aStride, int aWidth, int aHeight, int aRowLo, int aRowHi, int aRow2Lo,
int aRow2Hi, int aShift, const void* aSrc, int aSrcY, int aFlags,
const float* aView, bool aScrollValid, int aColX0, int aColX1,
void* aPartsDest, int aPartsCap, uint32_t aPartsHave)
void* aPartsDest, int aPartsCap, uint32_t aPartsHave,
const uint32_t* aPartsHaveList, int aNPartsHave)
{
mozilla::layers::CompositorBridgeParent* bridge = JihadBridge();
MessageLoop* loop = mozilla::layers::CompositorThreadHolder::Loop();
@@ -1069,6 +1074,10 @@ PuppetWidget::JihadPostJob(void* aDest, int aStride, int aWidth, int aHeight, in
job->contentOnly = (aFlags & 128) != 0; // T-505
if (job->contentOnly && aPartsDest && aPartsCap > 0) { // T-505 (E2)
job->partsDest = static_cast<uint8_t*>(aPartsDest); job->partsCap = aPartsCap; job->partsHave = aPartsHave;
if (aPartsHaveList && aNPartsHave >= 0) { // (per part) the signatures the daemon holds pictures of
job->nPartsHave = std::min(aNPartsHave, 16);
for (int i = 0; i < job->nPartsHave; i++) job->partsHaveList[i] = aPartsHaveList[i];
}
}
if (job->colsKnown) {
job->colX0 = std::max(0, std::min(aColX0, aWidth)); job->colX1 = std::max(0, std::min(aColX1, aWidth));
@@ -1083,13 +1092,14 @@ uint32_t
PuppetWidget::JihadComposePost(void* aDest, int aStride, int aWidth, int aHeight, int aRowLo, int aRowHi,
int aRow2Lo, int aRow2Hi, int aShift, const void* aSrc, int aSrcY, int aFlags,
const float* aView, bool aScrollValid, int aColX0, int aColX1,
void* aPartsDest, int aPartsCap, uint32_t aPartsHave)
void* aPartsDest, int aPartsCap, uint32_t aPartsHave,
const uint32_t* aPartsHaveList, int aNPartsHave)
{
if (!aDest || aWidth <= 0 || aHeight <= 0 || aStride < aWidth * 4) {
return 0;
}
return JihadPostJob(aDest, aStride, aWidth, aHeight, aRowLo, aRowHi, aRow2Lo, aRow2Hi, aShift, aSrc, aSrcY, aFlags,
aView, aScrollValid, aColX0, aColX1, aPartsDest, aPartsCap, aPartsHave);
aView, aScrollValid, aColX0, aColX1, aPartsDest, aPartsCap, aPartsHave, aPartsHaveList, aNPartsHave);
}
int
@@ -1158,6 +1168,10 @@ PuppetWidget::JihadComposePoll(uint32_t aToken, bool aWait, int* aOut, bool* aSc
for (int k = 0; k < 4; k++)
for (int c = 0; c < 4; c++) aOut[33 + 4 * k + c] = k < job->req.nHold ? job->req.hold[k][c] : 0;
}
if (aOutLen >= 54) { // T-505 (per part): each rect's signature, and the rects the pass read (bit k)
for (int k = 0; k < 4; k++) aOut[49 + k] = (int)job->req.partsSig[k];
aOut[53] = job->req.partsRead;
}
}
if (aScrollOnly) *aScrollOnly = job->req.scrollOnly;
} else if (!aWait && job->req.composed) {
@@ -1535,8 +1549,9 @@ MOZ_EXPORT int
jihad_offscreen_overview_ok()
{
static const bool sPass = getenv("JIHAD_GPU_PASS") != nullptr;
return sPass ? 0 : 3; // 2 (T-505): flag 128 too, and the poll's 30th int; 3 (E2): compose_post2/start2's parts
// pass and the poll's 49 ints
return sPass ? 0 : 4; // 2 (T-505): flag 128 too, and the poll's 30th int; 3 (E2): compose_post2/start2's parts
// pass and the poll's 49 ints; 4 (per part): post3/start3 with the signatures the daemon
// holds, the poll's 54 ints (each rect's signature, the rects read)
}
MOZ_EXPORT int
jihad_offscreen_compose_peek(nsIWidget* aWidget, uint32_t aToken)
@@ -1608,6 +1623,38 @@ jihad_offscreen_compose_start2(nsIWidget* aWidget, nsIDocShell* aDocShell, void*
(aEngineShift & 4) ? aColX0 : 0, (aEngineShift & 4) ? aColX1 : 0,
aPartsDest, aPartsCap, aPartsHave);
}
// T-505 (per part): post and start with the list of signatures the daemon holds part pictures of (aNHave of them, 16 at
// most; 0 is a list: every part read): the pass reads only the rects whose signature is not in it
MOZ_EXPORT uint32_t
jihad_offscreen_compose_post3(nsIWidget* aWidget, void* aDest, int aStride, int aWidth, int aHeight,
int aRowLo, int aRowHi, int aRow2Lo, int aRow2Hi, int aShift,
const void* aSrc, int aSrcY, int aFlags, int aUseView, double aScale,
double aOffX, double aOffY, double aScrollX, double aScrollY, int aScrollValid,
int aColX0, int aColX1, void* aPartsDest, int aPartsCap, const uint32_t* aHave, int aNHave)
{
if (!aWidget) {
return 0;
}
const float view[5] = { (float)aScale, (float)aOffX, (float)aOffY, (float)aScrollX, (float)aScrollY };
return static_cast<PuppetWidget*>(aWidget)->JihadComposePost(aDest, aStride, aWidth, aHeight, aRowLo, aRowHi,
aRow2Lo, aRow2Hi, aShift, aSrc, aSrcY, aFlags,
aUseView ? view : nullptr, aScrollValid != 0,
(aFlags & 4) ? aColX0 : 0, (aFlags & 4) ? aColX1 : 0,
aPartsDest, aPartsCap, 0, aHave, aHave ? std::max(aNHave, 0) : -1);
}
MOZ_EXPORT uint32_t
jihad_offscreen_compose_start3(nsIWidget* aWidget, nsIDocShell* aDocShell, void* aDest, int aStride,
int aWidth, int aHeight, int aRowLo, int aRowHi, int aRow2Lo, int aRow2Hi, int aShift,
const void* aSrc, int aSrcY, int aEngineShift, int aColX0, int aColX1,
void* aPartsDest, int aPartsCap, const uint32_t* aHave, int aNHave)
{
if (!aWidget) {
return 0;
}
return static_cast<PuppetWidget*>(aWidget)->JihadComposeStart(aDocShell, aDest, aStride, aWidth, aHeight, aRowLo, aRowHi, aRow2Lo, aRow2Hi, aShift, aSrc, aSrcY, aEngineShift,
(aEngineShift & 4) ? aColX0 : 0, (aEngineShift & 4) ? aColX1 : 0,
aPartsDest, aPartsCap, 0, aHave, aHave ? std::max(aNHave, 0) : -1);
}
MOZ_EXPORT int
jihad_offscreen_compose_poll(nsIWidget* aWidget, uint32_t aToken, int* aOut, int* aScrollOnly)
{
+6 -3
View File
@@ -293,7 +293,8 @@ public:
int aWidth, int aHeight, int aRowLo, int aRowHi, int aRow2Lo = 0, int aRow2Hi = 0, int aShift = 0,
const void* aSrc = nullptr, int aSrcY = 0, int aFlags = 0, // aFlags: 1 engine shift, 2 verify, 4 columns
int aColX0 = 0, int aColX1 = 0, // T-511: the bands' stale columns (flag 4)
void* aPartsDest = nullptr, int aPartsCap = 0, uint32_t aPartsHave = 0); // T-505 (E2)
void* aPartsDest = nullptr, int aPartsCap = 0, uint32_t aPartsHave = 0, // T-505 (E2)
const uint32_t* aPartsHaveList = nullptr, int aNPartsHave = -1); // T-505 (per part)
// aOut (10 ints, may be null): the rows the tree changed, the rows fixed layers cover, the two
// bands read, the frame's position and its valid flag; 12 for a job posted with flag 4 (T-511):
// the columns the composite changed; aScrollOnly: the composite's only change was the card's scroll
@@ -307,7 +308,8 @@ public:
uint32_t JihadComposePost(void* aDest, int aStride, int aWidth, int aHeight, int aRowLo, int aRowHi,
int aRow2Lo, int aRow2Hi, int aShift, const void* aSrc, int aSrcY, int aFlags,
const float* aView, bool aScrollValid, int aColX0 = 0, int aColX1 = 0,
void* aPartsDest = nullptr, int aPartsCap = 0, uint32_t aPartsHave = 0); // T-505 (E2)
void* aPartsDest = nullptr, int aPartsCap = 0, uint32_t aPartsHave = 0, // T-505 (E2)
const uint32_t* aPartsHaveList = nullptr, int aNPartsHave = -1); // T-505 (per part)
void JihadComposeForget(uint32_t aToken);
// T-515: a job's state without taking its result: 1 done, 2 composed (being read), 0 pending, -1 unknown
int JihadComposePeek(uint32_t aToken);
@@ -471,7 +473,8 @@ private:
uint32_t JihadPostJob(void* aDest, int aStride, int aWidth, int aHeight, int aRowLo, int aRowHi, int aRow2Lo,
int aRow2Hi, int aShift, const void* aSrc, int aSrcY, int aFlags,
const float* aView = nullptr, bool aScrollValid = false, int aColX0 = 0, int aColX1 = 0,
void* aPartsDest = nullptr, int aPartsCap = 0, uint32_t aPartsHave = 0);
void* aPartsDest = nullptr, int aPartsCap = 0, uint32_t aPartsHave = 0,
const uint32_t* aPartsHaveList = nullptr, int aNPartsHave = -1); // T-505 (per part)
uint32_t mJihadNextToken = 1;
float mJihadScrollX = 0.0f, mJihadScrollY = 0.0f; // T-502: the card's scroll for the compositor
bool mJihadScrollValid = false;