Skip to content

Commit cfddbc8

Browse files
chore: max px ratio, check WebGL support
1 parent 9d71ba0 commit cfddbc8

5 files changed

Lines changed: 69 additions & 23 deletions

File tree

src/features/Overview/ShredsProgression/WebGl/Chart.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,11 @@ export default function ShredsChart({
101101
) {
102102
lastRedrawRef.current = time;
103103
if (!rendererRef.current) {
104-
const result = setUpRenderer(width, height);
105-
if (!result) return;
106-
rendererRef.current = result;
107-
containerRef.current?.replaceChildren(result.renderer.domElement);
104+
const rendererObj = setUpRenderer(width, height);
105+
if (!rendererObj) return;
106+
107+
rendererRef.current = rendererObj;
108+
containerRef.current?.replaceChildren(rendererObj.renderer.domElement);
108109
} else {
109110
draw(
110111
chartId,

src/features/Overview/ShredsProgression/WebGl/chartUtils.ts

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
liveShredsDataAtom,
1313
liveShredsPostStartupRangeAtom,
1414
minDirtySlotByChartAtom,
15+
isWebgl2SupportedAtom,
1516
} from "../atoms";
1617
import { shredEventDescPriorities } from "../const";
1718
import { updateLabels } from "../shredsProgressionPlugin";
@@ -55,6 +56,8 @@ export type RendererObj = {
5556
worldTsRange: TsRange;
5657
};
5758

59+
const MAX_PIXEL_RATIO = 2;
60+
5861
const colors = {
5962
skipped: convertToWebGlColor(shredSkippedColor),
6063
repairRequested: convertToWebGlColor(shredRepairRequestedColor),
@@ -85,23 +88,29 @@ export function setUpRenderer(canvasWidth: number, canvasHeight: number) {
8588
const camera = new THREE.OrthographicCamera(0, 0, 0, 0, 0.5, 10);
8689
camera.position.z = 1;
8790

88-
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
89-
renderer.setPixelRatio(window.devicePixelRatio);
90-
renderer.setSize(canvasWidth, canvasHeight);
91-
renderer.setClearColor(0x000000, 0);
92-
93-
const meshes = new Map<number, SlotMesh>();
94-
const availableMeshes: SlotMesh[] = [];
95-
renderer.render(scene, camera);
96-
97-
return {
98-
renderer,
99-
camera,
100-
scene,
101-
meshes,
102-
availableMeshes,
103-
worldTsRange,
104-
};
91+
try {
92+
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
93+
renderer.setPixelRatio(Math.min(window.devicePixelRatio, MAX_PIXEL_RATIO));
94+
renderer.setSize(canvasWidth, canvasHeight);
95+
renderer.setClearColor(0x000000, 0);
96+
97+
const meshes = new Map<number, SlotMesh>();
98+
const availableMeshes: SlotMesh[] = [];
99+
renderer.render(scene, camera);
100+
101+
return {
102+
renderer,
103+
camera,
104+
scene,
105+
meshes,
106+
availableMeshes,
107+
worldTsRange,
108+
};
109+
} catch {
110+
// context creation can still fail despite the probe (e.g. too many live
111+
// contexts, driver crash). Mark as unsupported to trigger fallback to canvas chart
112+
store.set(isWebgl2SupportedAtom, false);
113+
}
105114
}
106115

107116
export function draw(

src/features/Overview/ShredsProgression/atoms.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { nsPerMs, slotsPerLeader } from "../../../consts";
66
import { getSlotGroupLeader } from "../../../utils";
77
import { serverTimeMsAtom, skippedClusterSlotsAtom } from "../../../atoms";
88
import { slotCaughtUpAtom } from "../../../api/atoms";
9+
import { isWebGl2Available } from "./webglSupport";
910

1011
type ShredEventTsDeltaMs = number | undefined;
1112
/**
@@ -292,6 +293,13 @@ export const liveShredsPostStartupRangeAtom = atom((get) =>
292293
get(shredsAtoms.rangeAfterStartup),
293294
);
294295

296+
/**
297+
* Whether WebGL2 is available.
298+
* Will be set to false if renderer setup fails at runtime
299+
* (e.g. because of context-limit / driver failure).
300+
*/
301+
export const isWebgl2SupportedAtom = atom(isWebGl2Available());
302+
295303
export const minDirtySlotByChartAtom = atom<Map<string, number>>(new Map());
296304

297305
/*

src/features/Overview/ShredsProgression/index.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
import { lazy, Suspense } from "react";
22
import { Box, Flex } from "@radix-ui/themes";
3+
import { useAtomValue } from "jotai";
34
import Card from "../../../components/Card";
45
import CardHeader from "../../../components/CardHeader";
56
import { ShredsChartLegend } from "./ShredsChartLegend";
67
import { isFrankendancer } from "../../../client";
8+
import { isWebgl2SupportedAtom } from "./atoms";
79

810
// Lazy-load so Three.js is split into its own chunk, downloaded only when the
9-
// shreds chart mounts.
11+
// WebGL shreds chart mounts.
1012
const ShredsChartWebGl = lazy(() => import("./WebGl/Chart"));
13+
// Canvas (uPlot) fallback
14+
const ShredsChartCanvas = lazy(() => import("./ShredsChart"));
1115

1216
export default function ShredsProgression() {
17+
const webgl2Supported = useAtomValue(isWebgl2SupportedAtom);
18+
1319
if (isFrankendancer) return;
1420

1521
return (
@@ -21,7 +27,15 @@ export default function ShredsProgression() {
2127
<ShredsChartLegend />
2228
</Flex>
2329
<Suspense fallback={<Box height="400px" />}>
24-
<ShredsChartWebGl height="400px" chartId="overview-shreds-chart" />
30+
{webgl2Supported ? (
31+
<ShredsChartWebGl height="400px" chartId="overview-shreds-chart" />
32+
) : (
33+
<ShredsChartCanvas
34+
height="400px"
35+
chartId="overview-shreds-chart"
36+
isOnStartupScreen={false}
37+
/>
38+
)}
2539
</Suspense>
2640
</Flex>
2741
</Card>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* Detect whether a WebGL2 context can be created. Three.js (>= r163) is
3+
* WebGL2-only, and `new WebGLRenderer()` throws if the context can't be
4+
* created. Some browsers / GPUs report support but still fail, so probe
5+
* a canvas.
6+
*/
7+
export function isWebGl2Available(): boolean {
8+
try {
9+
const canvas = document.createElement("canvas");
10+
return !!canvas.getContext("webgl2");
11+
} catch {
12+
return false;
13+
}
14+
}

0 commit comments

Comments
 (0)