diff --git a/build/desktop/patches/0063-gpu-compositor-a-tall-band-surface-for-a-change-that.patch b/build/desktop/patches/0063-gpu-compositor-a-tall-band-surface-for-a-change-that.patch new file mode 100644 index 0000000..f84da71 --- /dev/null +++ b/build/desktop/patches/0063-gpu-compositor-a-tall-band-surface-for-a-change-that.patch @@ -0,0 +1,276 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NotAlexNoyle +Date: Sun, 25 Oct 2026 22:02:16 -0400 +Subject: [PATCH] gpu-compositor: a tall band surface for a change that does + not fit the band surface + +cavekit-gpu-rendering.md R8 / T-527. A frame whose change does not fit the 256-row band surface took the +whole path: the frame-sized target (768x1224) cleared, drawn and resolved on the Adreno 220 every frame, and +the read waited for all of it. A 480 px WebGL canvas changes 354-482 rows a frame, so every frame of the +spinning cube went that way (the job ~45 ms, the card 16.6-22.0 fps while the page drew 22-24). A second, +taller band surface (JIHAD_GPU_BAND_TALL rows, default 512, 0 off; its own pbuffer and target, freed with +the context like the band surface's) takes such a frame; frames are packed for the band surface first, so +one that fits it never pays the tall one (desktop Wikipedia's chunked pan frames stay where they were). The +cube's job 15-30 ms; with the daemon's partial invalidation the card shows 25.3-28.0 fps at 480 px. + +Co-Authored-By: Claude Opus 5.5 +--- + gfx/gl/GLContextEGL.h | 6 +++- + gfx/gl/GLContextProviderEGL.cpp | 33 +++++++++++-------- + gfx/layers/ipc/CompositorBridgeParent.cpp | 28 ++++++++++++++-- + gfx/layers/opengl/CompositorOGL.cpp | 40 +++++++++++++++-------- + gfx/layers/opengl/CompositorOGL.h | 19 +++++++++-- + 5 files changed, 92 insertions(+), 34 deletions(-) + +diff --git a/gfx/gl/GLContextEGL.h b/gfx/gl/GLContextEGL.h +index 13459d1f5b..7d1268821e 100644 +--- a/gfx/gl/GLContextEGL.h ++++ b/gfx/gl/GLContextEGL.h +@@ -96,10 +96,14 @@ public: + EGLSurface mJihadPbOverride = nullptr; + // Round 106: a band-sized pbuffer made current for the band composites of a scroll-only + // frame, then the frame's pbuffer again. false when the band pbuffer cannot be made. +- bool JihadUseBandPBuffer(int aWidth, int aHeight); ++ // T-527: aTall picks a second pbuffer of its own (the tall band surface), so a frame that needs ++ // the tall one and the next that needs the short one do not remake either ++ bool JihadUseBandPBuffer(int aWidth, int aHeight, bool aTall = false); + void JihadUseFramePBuffer(); + EGLSurface mJihadBandPb = nullptr; + int mJihadBandPbW = 0, mJihadBandPbH = 0; ++ EGLSurface mJihadBandPbTall = nullptr; ++ int mJihadBandPbTallW = 0, mJihadBandPbTallH = 0; + + virtual bool MakeCurrentImpl(bool aForce) override; + +diff --git a/gfx/gl/GLContextProviderEGL.cpp b/gfx/gl/GLContextProviderEGL.cpp +index eb00bc01f0..58329c0236 100644 +--- a/gfx/gl/GLContextProviderEGL.cpp ++++ b/gfx/gl/GLContextProviderEGL.cpp +@@ -261,8 +261,10 @@ GLContextEGL::~GLContextEGL() + + // Jihad (T-513 review): the frame-sized and the band pbuffers made for this context + if (mJihadBandPb && mJihadBandPb != mSurface) DestroySurface(mJihadBandPb); +- if (mJihadPbOverride && mJihadPbOverride != mSurface && mJihadPbOverride != mJihadBandPb) DestroySurface(mJihadPbOverride); +- mJihadBandPb = mJihadPbOverride = nullptr; ++ if (mJihadBandPbTall && mJihadBandPbTall != mSurface) DestroySurface(mJihadBandPbTall); // T-527 ++ if (mJihadPbOverride && mJihadPbOverride != mSurface && mJihadPbOverride != mJihadBandPb && ++ mJihadPbOverride != mJihadBandPbTall) DestroySurface(mJihadPbOverride); ++ mJihadBandPb = mJihadBandPbTall = mJihadPbOverride = nullptr; + DestroySurface(mSurface); + } + +@@ -373,25 +375,28 @@ GLContextEGL::JihadUsePBufferOfSize(int aWidth, int aHeight) + } + + bool +-GLContextEGL::JihadUseBandPBuffer(int aWidth, int aHeight) ++GLContextEGL::JihadUseBandPBuffer(int aWidth, int aHeight, bool aTall) + { +- if (!mJihadBandPb || mJihadBandPbW != aWidth || mJihadBandPbH != aHeight) { +- if (mJihadBandPb) { +- if (mSurfaceOverride == mJihadBandPb) JihadUseFramePBuffer(); +- sEGLLibrary.fDestroySurface(EGL_DISPLAY(), mJihadBandPb); +- mJihadBandPb = nullptr; ++ EGLSurface& pb = aTall ? mJihadBandPbTall : mJihadBandPb; ++ int& pbW = aTall ? mJihadBandPbTallW : mJihadBandPbW; ++ int& pbH = aTall ? mJihadBandPbTallH : mJihadBandPbH; ++ if (!pb || pbW != aWidth || pbH != aHeight) { ++ if (pb) { ++ if (mSurfaceOverride == pb) JihadUseFramePBuffer(); ++ sEGLLibrary.fDestroySurface(EGL_DISPLAY(), pb); ++ pb = nullptr; + } + const EGLint attrs[] = { LOCAL_EGL_WIDTH, aWidth, LOCAL_EGL_HEIGHT, aHeight, LOCAL_EGL_NONE }; +- mJihadBandPb = sEGLLibrary.fCreatePbufferSurface(EGL_DISPLAY(), mConfig, attrs); +- if (!mJihadBandPb) { ++ pb = sEGLLibrary.fCreatePbufferSurface(EGL_DISPLAY(), mConfig, attrs); ++ if (!pb) { + fprintf(stderr, "[jihad-gpu] a %dx%d band pbuffer could not be made (0x%x)\n", aWidth, aHeight, sEGLLibrary.fGetError()); + return false; + } +- mJihadBandPbW = aWidth; mJihadBandPbH = aHeight; +- JIHAD_GPU_LOG("[jihad-gpu] band pbuffer %dx%d made\n", aWidth, aHeight); ++ pbW = aWidth; pbH = aHeight; ++ JIHAD_GPU_LOG("[jihad-gpu] band pbuffer %dx%d made%s\n", aWidth, aHeight, aTall ? " (the tall one)" : ""); + } +- if (mSurfaceOverride != mJihadBandPb) { +- mSurfaceOverride = mJihadBandPb; ++ if (mSurfaceOverride != pb) { ++ mSurfaceOverride = pb; + MakeCurrent(true); + } + return true; +diff --git a/gfx/layers/ipc/CompositorBridgeParent.cpp b/gfx/layers/ipc/CompositorBridgeParent.cpp +index 4650113654..7478575b87 100644 +--- a/gfx/layers/ipc/CompositorBridgeParent.cpp ++++ b/gfx/layers/ipc/CompositorBridgeParent.cpp +@@ -1921,7 +1921,19 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal + int dfNChunks = 0; + dfSpan = JihadPackColumns(dfCols, dfNCols, total, ogl->JihadBandRows(), aWidth, dfChunks, &dfNChunks); // round 142 + } +- if (total > 0 && dfSpan >= 0 && total + dfSpan <= ogl->JihadBandRows()) { ++ // T-527: packed for the band surface first; a frame that does not fit it takes the tall one ++ bool dfFits = total > 0 && dfSpan >= 0 && total + dfSpan <= ogl->JihadBandRows(); ++ const int dfTall = ogl->JihadBandTallRows(); ++ if (!dfFits && dfTall > 0 && total > 0) { ++ int span2 = 0; ++ if (JihadFixedCols()) { ++ JihadChunk dfChunks2[12]; ++ int dfNChunks2 = 0; ++ span2 = JihadPackColumns(dfCols, dfNCols, total, dfTall, aWidth, dfChunks2, &dfNChunks2); ++ } ++ if (span2 >= 0 && total + span2 <= dfTall) { dfSpan = span2; dfFits = true; } ++ } ++ if (dfFits) { + aReq->lo = iv[0][0]; aReq->hi = iv[0][1]; + aReq->lo2 = n > 1 ? iv[1][0] : 0; aReq->hi2 = n > 1 ? iv[1][1] : 0; + aReq->nCols = dfNCols; +@@ -1968,7 +1980,17 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal + const int fullAsked = (b1 ? std::min(aHeight, aReq->hi) - std::max(0, aReq->lo) : 0) + (b2 ? std::min(aHeight, aReq->hi2) - std::max(0, aReq->lo2) : 0); + JihadChunk chunks[12]; + int nChunks = 0; +- const int chunkRows = JihadPackColumns(aReq->cols, aReq->nCols, fullAsked, ogl->JihadBandRows(), aWidth, chunks, &nChunks); ++ int chunkRows = JihadPackColumns(aReq->cols, aReq->nCols, fullAsked, ogl->JihadBandRows(), aWidth, chunks, &nChunks); ++ // T-527: packed for the band surface first; the tall one's rows only when that does not fit ++ if (!(chunkRows >= 0 && fullAsked + chunkRows <= ogl->JihadBandRows()) && ogl->JihadBandTallRows() > 0) { ++ JihadChunk chunks2[12]; ++ int nChunks2 = 0; ++ const int cr2 = JihadPackColumns(aReq->cols, aReq->nCols, fullAsked, ogl->JihadBandTallRows(), aWidth, chunks2, &nChunks2); ++ if (cr2 >= 0 && fullAsked + cr2 <= ogl->JihadBandTallRows()) { ++ chunkRows = cr2; nChunks = nChunks2; ++ for (int c = 0; c < nChunks2; c++) chunks[c] = chunks2[c]; ++ } ++ } + // T-513 (review): the regions of one composite are at most 12, bands included: chunks past that + // would be read without being composed, so such a frame does not fit + const bool fits = chunkRows >= 0 && fullAsked + chunkRows <= bandMax && nChunks + (b1 ? 1 : 0) + (b2 ? 1 : 0) <= 12; +@@ -1985,7 +2007,7 @@ CompositorBridgeParent::JihadComposeAndRead(JihadFrameRequest* aReq, float aScal + // the band pass: its own small surface and target when available (round 106), else the + // stack at the top of the frame-sized target (round 105) + const TimeStamp tp0 = TimeStamp::Now(); +- const int passRows = (top > 0 && top <= ogl->JihadBandRows()) ? ogl->JihadBeginBandPass(aWidth) : 0; ++ const int passRows = top > 0 ? ogl->JihadBeginBandPass(aWidth, top) : 0; // T-527: the band surface or the tall one + if (!passRows) { + // T-513 (review): stacked at the top of the frame-sized target, so the frame's own surface + // current (a band frame before this one left the band pbuffer current, and on the Adreno 220 +diff --git a/gfx/layers/opengl/CompositorOGL.cpp b/gfx/layers/opengl/CompositorOGL.cpp +index 21dd27b6fe..ed16534208 100644 +--- a/gfx/layers/opengl/CompositorOGL.cpp ++++ b/gfx/layers/opengl/CompositorOGL.cpp +@@ -261,6 +261,9 @@ CompositorOGL::CleanupResources() + if (mJihadBandFBO) mGLContext->fDeleteFramebuffers(1, &mJihadBandFBO); + if (mJihadBandRB) mGLContext->fDeleteRenderbuffers(1, &mJihadBandRB); + mJihadBandFBO = mJihadBandRB = 0; ++ if (mJihadBandTallFBO) mGLContext->fDeleteFramebuffers(1, &mJihadBandTallFBO); // T-527 ++ if (mJihadBandTallRB) mGLContext->fDeleteRenderbuffers(1, &mJihadBandTallRB); ++ mJihadBandTallFBO = mJihadBandTallRB = 0; + if (mJihadReadFBO) mGLContext->fDeleteFramebuffers(1, &mJihadReadFBO); + if (mJihadReadRB) mGLContext->fDeleteRenderbuffers(1, &mJihadReadRB); + mJihadReadFBO = mJihadReadRB = 0; +@@ -2329,35 +2332,44 @@ CompositorOGL::JihadEnsureTargetSize(int aWidth, int aHeight) + } + + int +-CompositorOGL::JihadBeginBandPass(int aWidth) ++CompositorOGL::JihadBeginBandPass(int aWidth, int aRows) + { + static const bool sOff = getenv("JIHAD_GPU_BAND_SURFACE") && !strcmp(getenv("JIHAD_GPU_BAND_SURFACE"), "0"); + if (sOff || aWidth <= 0 || mGLContext->GetContextType() != GLContextType::EGL) { + return 0; + } ++ // T-527: the band surface, or the tall one when the pass needs more rows than the band surface has ++ const bool tall = aRows > JihadBandRows(); ++ const int rows = tall ? JihadBandTallRows() : JihadBandRows(); ++ if (rows <= 0 || aRows > rows) { ++ return 0; ++ } ++ GLuint& rb = tall ? mJihadBandTallRB : mJihadBandRB; ++ GLuint& fbo = tall ? mJihadBandTallFBO : mJihadBandFBO; ++ int& w = tall ? mJihadBandTallW : mJihadBandW; + GLContextEGL* egl = static_cast(mGLContext.get()); +- if (!egl->JihadUseBandPBuffer(aWidth, JihadBandRows())) { ++ if (!egl->JihadUseBandPBuffer(aWidth, rows, tall)) { + return 0; + } +- if (!mJihadBandFBO || mJihadBandW != aWidth) { +- if (!mJihadBandRB) mGLContext->fGenRenderbuffers(1, &mJihadBandRB); +- mGLContext->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, mJihadBandRB); +- mGLContext->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER, 0x8058 /* GL_RGBA8_OES */, aWidth, JihadBandRows()); ++ if (!fbo || w != aWidth) { ++ if (!rb) mGLContext->fGenRenderbuffers(1, &rb); ++ mGLContext->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, rb); ++ mGLContext->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER, 0x8058 /* GL_RGBA8_OES */, aWidth, rows); + mGLContext->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, 0); +- if (!mJihadBandFBO) mGLContext->fGenFramebuffers(1, &mJihadBandFBO); +- mGLContext->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mJihadBandFBO); +- mGLContext->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_RENDERBUFFER, mJihadBandRB); ++ if (!fbo) mGLContext->fGenFramebuffers(1, &fbo); ++ mGLContext->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, fbo); ++ mGLContext->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_RENDERBUFFER, rb); + const GLenum st = mGLContext->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); + if (st != LOCAL_GL_FRAMEBUFFER_COMPLETE) { +- fprintf(stderr, "[jihad-gpu] band target %dx%d incomplete (0x%x)\n", aWidth, JihadBandRows(), st); ++ fprintf(stderr, "[jihad-gpu] band target %dx%d incomplete (0x%x)\n", aWidth, rows, st); + egl->JihadUseFramePBuffer(); + return 0; + } +- mJihadBandW = aWidth; +- JIHAD_GPU_LOG("[jihad-gpu] band target %dx%d: FBO %u on its own pbuffer\n", aWidth, JihadBandRows(), mJihadBandFBO); ++ w = aWidth; ++ JIHAD_GPU_LOG("[jihad-gpu] band target %dx%d: FBO %u on its own pbuffer%s\n", aWidth, rows, fbo, tall ? " (the tall one)" : ""); + } +- mJihadWindowFBO = mJihadBandFBO; +- return JihadBandRows(); ++ mJihadWindowFBO = fbo; ++ return rows; + } + + void +diff --git a/gfx/layers/opengl/CompositorOGL.h b/gfx/layers/opengl/CompositorOGL.h +index a4c738dc47..0c27bb890a 100644 +--- a/gfx/layers/opengl/CompositorOGL.h ++++ b/gfx/layers/opengl/CompositorOGL.h +@@ -242,12 +242,13 @@ public: + // A separate band-sized FBO broke on the Adreno 220: the driver bins an FBO render by the EGL + // surface's size, so any target or viewport smaller than the surface lost rows (round 104). + // 0 rows when off (JIHAD_GPU_NO_BAND_TARGET=1). +- int JihadBandMaxRows() const { static const bool sOff = getenv("JIHAD_GPU_NO_BAND_TARGET") != nullptr; return sOff ? 0 : (JihadBandRows() > 320 ? JihadBandRows() : 320); } ++ int JihadBandMaxRows() const { static const bool sOff = getenv("JIHAD_GPU_NO_BAND_TARGET") != nullptr; const int m = JihadBandRows() > JihadBandTallRows() ? JihadBandRows() : JihadBandTallRows(); return sOff ? 0 : (m > 320 ? m : 320); } + // Round 106: the band pass on its own surface (JIHAD_GPU_BAND_SURFACE=0 keeps the stack in the + // frame-sized target): a 256-row pbuffer and an FBO of the same size are made current, so the + // composite costs the band, not the frame; JihadEndBandPass puts the frame's surface and + // target back. Returns the band target's rows, 0 when the band pass is not available. +- int JihadBeginBandPass(int aWidth); ++ // T-527: aRows the pass needs: the band surface when they fit it, else the tall one when they fit that ++ int JihadBeginBandPass(int aWidth, int aRows); + void JihadEndBandPass(); + void JihadUseFrameSurface(); // the frame's pbuffer current again (before a whole composite) + // T-513 (review): the reads that follow belong to a request the daemon may give up while the GPU +@@ -255,6 +256,8 @@ public: + void JihadSetCancel(const mozilla::Atomic* aCancel) { mJihadCancel = aCancel; } + GLuint mJihadBandRB = 0, mJihadBandFBO = 0; + int mJihadBandW = 0; ++ GLuint mJihadBandTallRB = 0, mJihadBandTallFBO = 0; // T-527: the tall band surface's target ++ int mJihadBandTallW = 0; + // the band surface's rows: JIHAD_GPU_BAND_ROWS (64..942, default 256; round 137) + static int JihadBandRows() { + static const int sRows = [] { +@@ -264,6 +267,18 @@ public: + }(); + return sRows; + } ++ // T-527: a second, taller band surface for a frame whose change does not fit the band surface (a ++ // 480 px WebGL canvas changes 354-482 rows a frame and took the whole frame-sized target, 768x1224 ++ // cleared, drawn and resolved every frame): JIHAD_GPU_BAND_TALL (default 512, 0 off). Frames are ++ // packed for the band surface first, so a frame that fits it never pays the tall one. ++ static int JihadBandTallRows() { ++ static const int sRows = [] { ++ const char* e = getenv("JIHAD_GPU_BAND_TALL"); ++ const int r = e ? atoi(e) : 512; ++ return r <= JihadBandRows() ? 0 : (r > 942 ? 942 : r); ++ }(); ++ return sRows; ++ } + void JihadShiftRowsNow(JihadFrameRequest* aReq); + uint8_t* JihadBandScratch(size_t aBytes); // the stacked bands' read, at least aBytes + bool JihadReadAsync(JihadFrameRequest* aReq); diff --git a/build/engine/engine-version.env b/build/engine/engine-version.env index 84cb64a..019f87d 100644 --- a/build/engine/engine-version.env +++ b/build/engine/engine-version.env @@ -9,7 +9,7 @@ UXP_BASE=5f290d1097a8350b56ce2092aa9d5fc9147c6661 # The tip of our engine branch (NotAlexNoyle/UXP-Jihad, jihad/uxp-: 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=63c20e39deda564931abc70cc08da7c54cfcb750 +UXP_PIN=bda3141209f1e97571e6e0879d85c51bb1386df1 # What the user agent and the start pages say: Goanna version and the short UPSTREAM base. GOANNA_VERSION=6.9 UXP_SHORT=5f290d1 diff --git a/build/webos-oe/recipes-jihad/goanna/goanna_1.0.bb b/build/webos-oe/recipes-jihad/goanna/goanna_1.0.bb index f6a1596..f6530fa 100644 --- a/build/webos-oe/recipes-jihad/goanna/goanna_1.0.bb +++ b/build/webos-oe/recipes-jihad/goanna/goanna_1.0.bb @@ -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=" SRC_URI = "git://${JIHAD_REPO}/third_party/uxp;protocol=file;nobranch=1" -SRCREV = "63c20e39deda564931abc70cc08da7c54cfcb750" +SRCREV = "bda3141209f1e97571e6e0879d85c51bb1386df1" S = "${WORKDIR}/git" # Modern C++14 toolchain (device-build R1) — the stock dylan/device gcc cannot build UXP. diff --git a/context/impl/impl-gpu-rendering.md b/context/impl/impl-gpu-rendering.md index e164495..80eb920 100644 --- a/context/impl/impl-gpu-rendering.md +++ b/context/impl/impl-gpu-rendering.md @@ -26,3 +26,4 @@ Build site: context/plans/build-site.md | T-513 | DONE | 2026-10-19/24. Two inspectors (Codex out of credits) on Tier 87, twelve findings fixed; Codex by direct invocation on the fixes, T-499 and T-487 on 2026-10-24, nine more fixed: `context/impl/impl-review-findings-gpu-2.md`. Engine 0058 (`gfx/layers/opengl/CompositorOGL.{h,cpp}`, `GLContextProviderEGL.cpp`, `AsyncCompositionManager.{h,cpp}`, `CompositorBridgeParent.cpp`, `TextureHostOGL.cpp`, `PuppetWidget.cpp`); the daemon's `BrowserPageGoanna.{h,cpp}` (the shown buffer never composed into, the express jobs forgotten on a fallback, a job of another frame height given up, the verify reference posted behind its job from a static pool), `GoannaRenderPage.{h,cpp}` (`DocumentKey`/`ResetLayoutSize`), `JihadBrowserServer.{h,cpp}` (inject `target focus|last`, the inject threads reaped), `render/adapter/BrowserOffscreen.cpp` (the window bound). R2's fast flick and two cards (40 + 22 verified frames, 0 rows wrong), R10's twenty card cycles (+76 KB). Round notes `docs/rounds/t513-2026-10-19/`. | | T-519 | DONE | 2026-10-24. `docs/rounds/t519-2026-10-24/` (14 pan logs, the dumps). New `render/goanna/test/jihad-fixed-both.html`; `tools/device/t502/pan-run.sh` (`ROUND_DIR`), `tools/device/t502/README.md` (the verify mode's two limits: an animating layer's rows, and its frame rates). Kit: R2 lines 2-3 and R4's four `[x]`, R2's first `[~]` (the animated block under verify), R3's GPU-against-software `[x]`; the table in `docs/RENDERING-OPTIONS.md`. Open: the verify reference's animation time pinned to its job's (engine). | | T-527 | PARTIAL | 2026-10-25 night: the zoom's sharp frame. `render/goanna/GoannaRenderPage.{h,cpp}` (`SetGpuDpCritical`, `GpuResolutionFor`/`GpuResolutionChanges`/`GpuResolutionNow`, `JIHAD_GPU_CRIT_MARGIN`), `render/goanna/BrowserPageGoanna.{h,cpp}` (`gpuCriticalEnd`, the express lane publishing a main-lane frame composed while the main thread is away, the express thread kicked by a main-lane post, the hold ended at the express post, `mXTreeRes`: no frame drawing the tiles at under half their size, `JIHAD_GPU_ZOOM_DUMP_SEQ`), engine 0062 (`63c20e39`: the root's displayport base kept by the paint), `tools/device/t502/finger-zoom.sh`. First painted frame at a new zoom 513-669 ms (was 746-2212); pans unchanged or better; verify 0 structurally wrong. `docs/rounds/t527-2026-10-25/notes.txt` | +| T-527 | PARTIAL | 2026-10-25 later: R8 met. `render/browserserver/Src/JihadFrameWindow.h` (the dirty words), `render/goanna/BrowserPageGoanna.{h,cpp}` (`pubDirtyAdd`/`pubDirtyTake`, `publishWindow` writes the rect changed since the frame before or none; `jihadXNote`: the express lane's idle reasons on the timeline), `render/adapter/BrowserOffscreen.{h,cpp}` (`frameSeq`, `dirtyRect`), `render/adapter/BrowserAdapter.{h,cpp}` (msgPainted invalidates only the rect when it shows the base frame at the same geometry, zoom and scroll), `render/browserserver/Yap/YapProxy.{h,cpp}` (the PDK build: no member initializer), engine 0063 (`bda31412`: the tall band surface), `tools/host/gap-trace.py`, `tools/device/t502/default-check.sh` (the log rotation), `gpu-spike-collect.sh` (dumps removed). The 480 px cube 25.3-28.0 fps on the card | diff --git a/context/impl/loop-log.md b/context/impl/loop-log.md index c972169..f73fa94 100644 --- a/context/impl/loop-log.md +++ b/context/impl/loop-log.md @@ -296,3 +296,4 @@ Build site: context/plans/build-site.md ### Iteration 19 — 2026-10-25 (night) - T-527: the zoom's sharp frame (R6) — PARTIAL. Critical displayport (what the card shows first, margins after), express lane publishes main-lane frames composed while the main thread is away, hold ends at the express post, no minified frame for a big zoom out, engine 0062 (root displayport base kept by the paint: PaintFrame's DecideScrollableLayer replaced it every paint). First painted frame 513-669 ms by finger on desktop Wikipedia (was 746-2212). Files: render/goanna/{BrowserPageGoanna,GoannaRenderPage}.{h,cpp}, engine 0062, tools/device/t502/finger-zoom*.sh, docs/rounds/t527-2026-10-25/. Build P, patch-queue --check P (62), pans P (long 29.5-35.4, wiki 22.3-28.5), verify 0 structural. Next: T-527 — the pan's 56 ms+ gaps, WebGL 24 fps, pool size. +- T-527 (same night): R8 met — dirty rect through the frame trailer + adapter partial invalidation, tall band surface (engine 0063); 480 px cube 25.3-28.0 fps card (was 16.6-22.0); PDK adapter build fixed (YapProxy m_sendFailed, broken since T-540); pan gaps traced (gap-trace.py, express idle notes): 24 in 10 s, compositor-job bound. T-511 DONE. Build P, patch-queue P (63), Enyo default-check P, verify 0 wrong. Next: T-527 — the read/copy floor, Wikipedia 24 fps with sensors, dtap-out first frame. diff --git a/context/kits/cavekit-gpu-rendering.md b/context/kits/cavekit-gpu-rendering.md index 6e20e3b..9be275c 100644 --- a/context/kits/cavekit-gpu-rendering.md +++ b/context/kits/cavekit-gpu-rendering.md @@ -59,7 +59,7 @@ Hacker News 36-37, desktop Wikipedia 13-14 (software 9-12.4). **Acceptance Criteria:** - [~] The scripted pan (0 → 3000 card px in 10 s, `tools/device/t502/pan-run.sh`) runs at 24 fps or better on `jihad-long-static.html` and on desktop en.wikipedia.org after its load *(2026-10-24, T-527, the owner's lever 3b: the bench's `sensord` takes ~30% of a core at idle (load 3.5). With it and `atlas-sensord` stopped the Wikipedia pan ran 25.0 / 27.5 / 27.4 fps against 21.4 / 20.9 / 22.4 with them running, the long page 33-36.5 against 30-35; both restored after. The floor is met on the bench without the sensors; with them it is 21-22, so the compositor thread stays the product's lever. 2026-10-19, T-515: the test page 30-35 fps; desktop Wikipedia 19-21 fps in 2 s windows, median frame gap 45 ms, with the frames made off the main thread. The page's script no longer gaps them (its 2.2 s restyle ran under frames every ~45 ms); the floor is the compositor thread's ~45 ms a Wikipedia frame, whichever lane asks for it: T-517. A page whose script holds the main thread 600 ms in every 700 pans at 26-32 fps (2.4-3.8 without the express lane).)* - [x] On every page measured, the GPU path's pan is at least as fast as the software path's on the same pan (`NOGPU=1`), and the table in `docs/RENDERING-OPTIONS.md` says so per page *(2026-10-24, T-519, the same 10 s pan on each page both ways on engine 0058, the pan's 2 s windows: long static 30.4-33.3 against 10.8-12.5 fps, fixed header and footer 26.4-27.9 against 10.9-12.7, sticky-mix 16.5-19.0 against 8.6-10.0, the animation page panned 14.5-24.4 against 11.4-23.6, the self-scrolling page 22.8-27.7 against 10.5-11.4, Hacker News 37.5-48.0 against 6.3-10.2 (the pan overshoots its 1249 px, the last frames are past the page), desktop Wikipedia 18.5-25.9 against 2.7-2.9 (the 18.5 window is the first scroll's restyle). The table is in `docs/RENDERING-OPTIONS.md`; logs `docs/rounds/t519-2026-10-24/pan-*-gpu2-*` and `pan-*-sw-*`.)* -- [ ] Frame gaps of 56 ms or more during a pan are accounted for (traced to their source) and the avoidable ones removed *(2026-10-18, traced: desktop Wikipedia's pan has 3-13 a 2 s window. The stall sampler resolves them to the page's own restyle on the first scroll (955 ms, 36% selector matching: its sticky header), the layer paints of the displayport moves (58-121 ms: display list, glyphs, regions), and short misc events. None is the daemon's own work; the anchor distance does not change them (0.125 and 0.4 heights measured). What would remove them from the frames is the frames made off the main thread: T-515. 2026-10-19: done (T-515, the express lane): the main thread's stalls are covered by frames composed and published from a thread of their own; the gaps left are the compositor's own (T-517).)* +- [ ] Frame gaps of 56 ms or more during a pan are accounted for (traced to their source) and the avoidable ones removed *(2026-10-25, T-527: traced per gap with `tools/host/gap-trace.py` on the timeline (the express lane now notes why it made no frame): desktop Wikipedia's 10 s pan, 24 gaps in 252 frames — an express job of 50-100 ms (a frame whose tree changed: a whole read; the copy from the base with a stuck header) while the two-deep pipeline waits, a job queued 25-88 ms behind the page's tile-upload transaction, once all three buffers held by the card; none is the main thread any more. Removing them is the compositor job's floor (the read, the copy), still open. 2026-10-18, traced: desktop Wikipedia's pan has 3-13 a 2 s window. The stall sampler resolves them to the page's own restyle on the first scroll (955 ms, 36% selector matching: its sticky header), the layer paints of the displayport moves (58-121 ms: display list, glyphs, regions), and short misc events. None is the daemon's own work; the anchor distance does not change them (0.125 and 0.4 heights measured). What would remove them from the frames is the frames made off the main thread: T-515. 2026-10-19: done (T-515, the express lane): the main thread's stalls are covered by frames composed and published from a thread of their own; the gaps left are the compositor's own (T-517).)* - [x] During a pan the card has content past the view's edges, as the software path's frames have: a gap between frames or a busy main thread shows the page, not grey *(added 2026-10-18 from the owner's "areas turn grey": the GPU frame carries 0.15 of the card's height above and below the view (engine 0046, `JIHAD_GPU_OVERSCAN`); verify 0 structurally wrong rows with it on the test page, sticky-mix, desktop Wikipedia and Hacker News; frame dumps looked at)* **Dependencies:** R2 @@ -101,7 +101,7 @@ Hacker News 36-37, desktop Wikipedia 13-14 (software 9-12.4). **Description:** WebGL 1 contexts are created on the device's GPU and composited without a CPU round trip per WebGL frame. **Acceptance Criteria:** - [x] On the GPU path `canvas.getContext("webgl")` succeeds on the bench; on the software path WebGL stays off *(2026-10-19, T-511, engine 0051: WebGL asks EGL on the GPU path and is not blocklisted there; `NOGPU=1`: getContext null, "Refused to create native OpenGL context" (rounds 281-313, `docs/rounds/t502-2026-10-17/webgl-r281-313.txt`))* -- [ ] A WebGL test page (a spinning textured cube, `render/goanna/test/`) renders correctly and runs at 24 fps or better *(2026-10-24, the emulated finger (T-524): the cube stays drawn and sharp through a pinch out and a drag over the canvas and after the card is closed and opened three times, the page's counter 19.7-21.8 fps (14.8 at 360 px). 2026-10-19, T-511, `render/goanna/test/jihad-webgl-cube.html`: renders correctly (screenshots r288, r305, r310). On the card: a 240 px cube 26.5-26.9 fps, 360 px 21.5-22.8, 480 px 18-20.5 (the page's own rate 21.5-25 at 480). The canvas reaches the compositor with no copy (one share group); what is left is the compositor job's ~45 ms for 482 rows, the same floor as Wikipedia's pan frames: carried by T-517)* +- [x] A WebGL test page (a spinning textured cube, `render/goanna/test/`) renders correctly and runs at 24 fps or better *(2026-10-25, T-527, the sensors running (`docs/rounds/t527-2026-10-25/notes.txt`): the 480 px cube 25.3-28.0 fps on the card (the page 28.5-30), 360 px 28.9-29.9, 240 px 29.4-30.0; verify with a ticker beside the canvas and the cube stopped 0 rows wrong over 24 frames, the screenshot shows the cube and the ticker. What it took: the card redraws only the rect that changed (the daemon's dirty words in the frame trailer, the adapter's partial invalidation) and a change that does not fit the 256-row band surface takes a 512-row one (engine 0063) instead of the frame-sized target. 2026-10-24, the emulated finger (T-524): the cube stays drawn and sharp through a pinch out and a drag over the canvas and after the card is closed and opened three times, the page's counter 19.7-21.8 fps (14.8 at 360 px). 2026-10-19, T-511, `render/goanna/test/jihad-webgl-cube.html`: renders correctly (screenshots r288, r305, r310). On the card: a 240 px cube 26.5-26.9 fps, 360 px 21.5-22.8, 480 px 18-20.5 (the page's own rate 21.5-25 at 480). The canvas reaches the compositor with no copy (one share group); what is left is the compositor job's ~45 ms for 482 rows, the same floor as Wikipedia's pan frames: carried by T-517)* - [x] Context loss (the card closed, the GPU reset) is handled without a daemon crash; a WebGL page cannot wedge the GPU path (R1's bound) *(2026-10-19, T-511: the second WebGL page closed faulted in libGLESv2 until a context was released from its thread before its destruction (engine 0051); 8 open/close cycles of the card on the cube, no fault; `WEBGL_lose_context` lose and restore (`?lose=1`): the page rebuilds and draws on; the cube's compose jobs run under R1's job bound like any other)* - [x] WebGL conformance spot checks (the basic ones a page needs: textures, framebuffers, shaders) pass, and what does not is listed *(2026-10-19, T-511: the test page's checks: shaders, a texture upload, a framebuffer object, readPixels of what was drawn: ok; MAX_TEXTURE_SIZE 4096; 14 extensions; missing against desktop WebGL 1: ANGLE_instanced_arrays, WEBGL_compressed_texture_s3tc, EXT_blend_minmax, EXT_shader_texture_lod (not offered by the GLES 2.0 driver))* **Dependencies:** R1 @@ -150,3 +150,4 @@ Hacker News 36-37, desktop Wikipedia 13-14 (software 9-12.4). - 2026-10-18: R6 gains the sharpness criterion (the zoom's resolution) after T-504's rounds 162-186. - 2026-10-18: R3 gains the overscan criterion (content past the view's edges during a pan) after T-505's rounds 187-201. - 2026-10-25: R6's zoom criterion annotated with T-527's first round (the critical displayport, the express lane publishing the main lane's frame, engine 0062). +- 2026-10-25: R8's 24 fps criterion met (the partial redraw through the frame trailer, the tall band surface, engine 0063); R3's gap criterion annotated with the trace. diff --git a/context/plans/build-site.md b/context/plans/build-site.md index 9da0411..d48a963 100644 --- a/context/plans/build-site.md +++ b/context/plans/build-site.md @@ -520,7 +520,7 @@ Codex was out of usage (468k tokens of grepping, no report); the path was read f | T-524 | The finger round, part 1: the GPU path on real pages in the three interfaces under the timeline and the dumps; cavekit-gpu-rendering R6 and R12 closed; T-514's items | cavekit-finger-emulation.md | R3 | T-523 | yes | L | DONE 2026-10-24 (the scrolling and gesture items of `docs/FINGER-TESTS.md` run in all three with the emulated finger, `docs/rounds/finger-2026-10-24/zoom-grey.txt`: the owner's grey found and fixed — the smart zoom's block rule and the viewport's default maximum (daemon), the root's displayport base and clip divided by the resolution (engine c79d16f8, patch 0059), the GPU-path hit test's mapping, the resolution set before the displayport, the displayport re-set at a zoom; a GPU maximum-scale of 4000 / the declared viewport's larger side. Dispositioned to T-527: the flick's coast gap (blank lower card and header for ~0.5 s), the first frame at a new zoom (1.6-2.9 s on desktop Wikipedia), the clip above ~2.9 on a wide page and the untagged page's maximum. Dev copies on the device: libxul 4d753b8f, daemon 7c29035a on all three, originals as .orig) | | T-525 | The finger round, part 2: chrome and shells (settings and personas flicks, addons lists, the long-press menus, the padlock, Mojo pinch; rotation a person's) | cavekit-finger-emulation.md | R3 | T-523 | yes | L | DONE 2026-10-24 (the chrome buttons of all three, the long-press menus of all three, the drop-down and the certificate dialog, the preferences tabs and controls, Mojo's settings values and address search, a Persona worn and taken off, the add-ons panes and plugin menu, the add-on site's nav and search — each on its item in `docs/FINGER-TESTS.md`. Open from it: Mojo's link hold offers the text commands only (mojo-ui R6), the extension row's buttons take no tap at the fit zoom (chrome-look R2), the about:addons search filter not judged. Rotation stays a person's.) | | T-526 | The finger round, part 3: keyboard and text, plugins and media; what stays a person's said so | cavekit-finger-emulation.md | R3 | T-523 | yes | L | DONE 2026-10-24 (every keyboard, text, plugin and media item a finger reaches run — see `docs/FINGER-TESTS.md`; Mochi's Paste found and fixed (the sheet blurred the field; a click first), Mojo's link hold fixed with it (T-525); a person's: hearing (Flash and HTML audio), a physical keyboard, a framed field (no test page), another app's link; open: the about:addons extension row's buttons at the fit zoom) | -| T-527 | The GPU floor with the owner's levers: the bench without `sensord`/`atlas-sensord`, then the compositor-thread floor in the engine (T-505/T-511/T-517 re-opened under it) | cavekit-gpu-rendering.md | R3, R8 | T-524 | yes | XL | IN PROGRESS 2026-10-25 (the sensor bench done: desktop Wikipedia 21-22 fps with sensord/atlas-sensord, 25-27.5 without; the long page 30-35 / 33-37; sensors restored. 2026-10-25 night, the zoom's sharp frame (R6): the first frame painted at a new zoom's resolution 513-669 ms on desktop Wikipedia by finger, was 746-2212 — the critical displayport, the express lane publishing the main lane's frame, the hold ended at the express post, no minified frame for a big zoom out, engine 0062 (the root's displayport base kept by the paint); pans unchanged or better, verify 0 structurally wrong (`docs/rounds/t527-2026-10-25/notes.txt`). Next: the pan's 56 ms+ gaps traced, the WebGL cube at 24 fps, the tile texture pool size (160 cut a zoom's transaction by 70-110 ms)) | +| T-527 | The GPU floor with the owner's levers: the bench without `sensord`/`atlas-sensord`, then the compositor-thread floor in the engine (T-505/T-511/T-517 re-opened under it) | cavekit-gpu-rendering.md | R3, R8 | T-524 | yes | XL | IN PROGRESS 2026-10-25 (the sensor bench done: desktop Wikipedia 21-22 fps with sensord/atlas-sensord, 25-27.5 without; the long page 30-35 / 33-37; sensors restored. 2026-10-25 night, the zoom's sharp frame (R6): the first frame painted at a new zoom's resolution 513-669 ms on desktop Wikipedia by finger, was 746-2212 — the critical displayport, the express lane publishing the main lane's frame, the hold ended at the express post, no minified frame for a big zoom out, engine 0062 (the root's displayport base kept by the paint); pans unchanged or better, verify 0 structurally wrong (`docs/rounds/t527-2026-10-25/notes.txt`). 2026-10-25 later: R8 met — the 480 px WebGL cube 25.3-28.0 fps on the card (was 16.6-22.0): the daemon writes the rect changed since the frame before into the frame trailer and the adapter redraws only that, and a change too tall for the band surface takes a 512-row one (engine 0063); the PDK adapter builds again (YapProxy's member initializer, T-540); the pan's 56 ms+ gaps traced (`tools/host/gap-trace.py`, the express lane's idle notes). Next: the gaps' sources (the read, the copy from the base), desktop Wikipedia ≥ 24 fps with the sensors, the double tap out's first frame, the tile texture pool size) | ## Tier 88 — Closing the kits' open criteria on the current build (2026-10-24; added to the site, not a regeneration) @@ -573,7 +573,7 @@ the mid-pan dumps, never by a screenshot. No device run while the owner is at th | T-508 | Video and Flash on the GPU path: `