diff --git a/packages/producer/src/services/distributed/renderChunk.ts b/packages/producer/src/services/distributed/renderChunk.ts index c3127cccd6..a9e60f0c71 100644 --- a/packages/producer/src/services/distributed/renderChunk.ts +++ b/packages/producer/src/services/distributed/renderChunk.ts @@ -483,7 +483,6 @@ export async function renderChunk( const videoCaptureBeyondViewport = resolveVideoCaptureBeyondViewport( planVideos?.videos.length ?? 0, - "software", ); // ── Per-chunk work + frames directories ── diff --git a/packages/producer/src/services/render/captureBeyondViewport.test.ts b/packages/producer/src/services/render/captureBeyondViewport.test.ts index 7b43c3c143..d97bd02577 100644 --- a/packages/producer/src/services/render/captureBeyondViewport.test.ts +++ b/packages/producer/src/services/render/captureBeyondViewport.test.ts @@ -3,15 +3,14 @@ import { resolveVideoCaptureBeyondViewport } from "./captureBeyondViewport.js"; describe("resolveVideoCaptureBeyondViewport", () => { it("leaves no-video renders on the engine default", () => { - expect(resolveVideoCaptureBeyondViewport(0, "software")).toBeUndefined(); - expect(resolveVideoCaptureBeyondViewport(0, "hardware")).toBeUndefined(); + expect(resolveVideoCaptureBeyondViewport(0)).toBeUndefined(); }); - it("keeps video renders on the fast viewport-bound path under software rendering", () => { - expect(resolveVideoCaptureBeyondViewport(1, "software")).toBe(false); - }); - - it("preserves the beyond-viewport video workaround under hardware rendering", () => { - expect(resolveVideoCaptureBeyondViewport(1, "hardware")).toBe(true); + it("forces beyond-viewport for any video render so the bottom edge is not clipped", () => { + // Regression: software hosts (including every distributed chunk render, + // which resolves as "software") previously returned false here and clipped + // ~87 bottom rows to black on video comps. + expect(resolveVideoCaptureBeyondViewport(1)).toBe(true); + expect(resolveVideoCaptureBeyondViewport(5)).toBe(true); }); }); diff --git a/packages/producer/src/services/render/captureBeyondViewport.ts b/packages/producer/src/services/render/captureBeyondViewport.ts index eaa7a3bdae..6e709df8ce 100644 --- a/packages/producer/src/services/render/captureBeyondViewport.ts +++ b/packages/producer/src/services/render/captureBeyondViewport.ts @@ -1,14 +1,23 @@ -export type ResolvedBrowserGpuMode = "software" | "hardware"; - /** - * Native video surfaces can need Chrome's beyond-viewport compositor on - * hardware-accelerated captures, but that path is a full-surface software - * re-rasterization tax on SwiftShader/CPU render hosts. + * Native video surfaces need Chrome's beyond-viewport screenshot path or the + * viewport-bound capture clips the bottom edge of the frame — the same + * #1094 tall-portrait guard the alpha capture paths already hardcode + * (`captureScreenshotWithAlpha` / `captureAlphaPng`). + * + * This used to be gated to hardware-GPU captures to skip the full-surface + * software re-rasterization tax on SwiftShader/CPU render hosts. But that left + * software hosts clipping ~87 bottom rows to black on video comps — and every + * distributed chunk render resolves as "software", so the entire distributed + * fleet shipped video renders with a black bottom band. Correct output wins + * over the software perf optimization: enable beyond-viewport for any render + * that has a native video surface, regardless of GPU mode. + * + * ponytail: blanket-on for video. If the software re-raster tax proves + * significant on the distributed fleet, narrow it back to comps whose content + * actually reaches the bottom edge — but that needs a reliable clip predictor + * first, and a black band is unshippable in the meantime. */ -export function resolveVideoCaptureBeyondViewport( - videoCount: number, - browserGpuMode: ResolvedBrowserGpuMode, -): boolean | undefined { +export function resolveVideoCaptureBeyondViewport(videoCount: number): boolean | undefined { if (videoCount <= 0) return undefined; - return browserGpuMode === "hardware"; + return true; } diff --git a/packages/producer/src/services/renderOrchestrator.ts b/packages/producer/src/services/renderOrchestrator.ts index 74dcb3d17b..018c35d270 100644 --- a/packages/producer/src/services/renderOrchestrator.ts +++ b/packages/producer/src/services/renderOrchestrator.ts @@ -1899,10 +1899,7 @@ export async function executeRenderJob( browserTimeout: cfg.browserTimeout, }); updateCaptureObservability({ browserGpuMode: resolvedBrowserGpuMode }); - const videoCaptureBeyondViewport = resolveVideoCaptureBeyondViewport( - composition.videos.length, - resolvedBrowserGpuMode, - ); + const videoCaptureBeyondViewport = resolveVideoCaptureBeyondViewport(composition.videos.length); const captureOptions: CaptureOptions = { width,