perf(movimento): vitrine e 3D mais leves, WebGL por software lembrado na sessão e suporte ao WebKit

This commit is contained in:
Evandro Fonseca Junior committed 2026-10-14 22:54:27 -03:00
1 parent 9fd1570426
commit 8903f3c57e
28 files changed
+854 -406

No files matched your search

@@ -1,4 +1,5 @@
import { act, render } from "@testing-library/react";
import { isKnownSoftwareWebGL } from "@/lib/device";
import HeroFieldScene from "./HeroFieldScene";
/*
@@ -15,6 +16,8 @@ type RendererOptions = {
};
const mockRenderers: MockRenderer[] = [];
let mockFailToCreate = false;
/** O nome do renderizador que o contexto de teste conta (WEBGL_debug_renderer_info); `null` = a extensão não existe. */
let mockRendererName: string | null = null;
/** Quando o teste quer segurar a compilação do shader: a promessa que `compileAsync` devolve. */
let mockCompile: () => Promise<unknown> = () => Promise.resolve();
@@ -31,6 +34,14 @@ class MockRenderer {
this.pixelRatio = value;
};
getPixelRatio = () => this.pixelRatio;
getContext = () => ({
getExtension: (name: string) =>
name === "WEBGL_debug_renderer_info" && mockRendererName !== null
? { UNMASKED_RENDERER_WEBGL: 37446 }
: null,
getParameter: (parameter: number) =>
parameter === 37446 ? mockRendererName : null,
});
constructor(options: RendererOptions) {
if (mockFailToCreate) throw new Error("sem WebGL");
this.options = options;
@@ -64,6 +75,7 @@ beforeEach(() => {
jest.useFakeTimers();
mockRenderers.length = 0;
mockFailToCreate = false;
mockRendererName = null;
mockCompile = () => Promise.resolve();
setDevicePixelRatio(1);
});
@@ -406,3 +418,54 @@ describe("HeroFieldScene (ambient 3D)", () => {
});
});
});
describe("HeroFieldScene: WebGL por software (o teste é o contexto da própria cena)", () => {
beforeEach(() => sessionStorage.clear());
afterEach(() => sessionStorage.clear());
it.each([
"ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
"llvmpipe (LLVM 15.0.7, 256 bits)",
"ANGLE (Microsoft, Microsoft Basic Render Driver (0x0000008C) Direct3D11 vs_5_0 ps_5_0, D3D11)",
])(
"draws on the CPU (%s): no canvas, the page is told once, the context is freed and the session remembers it",
async (name) => {
mockRendererName = name;
const scene = props();
const { container } = await mount(<HeroFieldScene {...scene} />);
// um só contexto: o do teste é o que a cena abriria para desenhar, sem um segundo
expect(mockRenderers).toHaveLength(1);
expect(mockRenderers[0].dispose).toHaveBeenCalledTimes(1);
expect(mockRenderers[0].forceContextLoss).toHaveBeenCalledTimes(1);
expect(mockRenderers[0].compileAsync).not.toHaveBeenCalled();
expect(container.querySelector("canvas")).toBeNull();
expect(scene.onError).toHaveBeenCalledTimes(1);
expect(scene.onReady).not.toHaveBeenCalled();
expect(isKnownSoftwareWebGL()).toBe(true);
},
);
it("draws on the GPU: the canvas stays and the session is not marked", async () => {
mockRendererName =
"ANGLE (NVIDIA, NVIDIA GeForce RTX 3070 Direct3D11 vs_5_0 ps_5_0, D3D11)";
const scene = props();
const { container } = await mount(<HeroFieldScene {...scene} />);
expect(container.querySelector("canvas")).toBe(mockRenderers[0].domElement);
expect(scene.onError).not.toHaveBeenCalled();
expect(isKnownSoftwareWebGL()).toBe(false);
});
it("does not report an unknown renderer (no debug extension) as software", async () => {
mockRendererName = null;
const scene = props();
await mount(<HeroFieldScene {...scene} />);
expect(scene.onError).not.toHaveBeenCalled();
expect(isKnownSoftwareWebGL()).toBe(false);
});
});