Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 | 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 128x 7834x 128x 7834x | import type { TextureFormatSupport } from './textureSupport';
import { getSupportedTextureFormats } from './textureSupport';
/**
* Bump when the probes (or the meaning of any profile field) change so that
* profiles cached by earlier versions are discarded.
*/
export const RENDERING_CAPABILITIES_PROBE_VERSION = 1;
const STORAGE_KEY = 'cornerstone3D.renderingCapabilities';
const SOFTWARE_RASTERIZER_PATTERN =
/swiftshader|llvmpipe|softpipe|software|microsoft basic render/i;
/**
* The GPU capability profile detected through offscreen WebGL probes.
*
* This is the single source the rendering configuration consults instead of
* scattered per-feature checks: backend selection reads `webgl`/`webgl2`,
* texture-format decisions read the {@link TextureFormatSupport} flags, and
* `renderer`/`softwareRasterizer` let applications surface or log degraded
* environments (e.g. SwiftShader after a driver denylist hit).
*/
export interface RenderingCapabilities extends TextureFormatSupport {
/** Any WebGL context (1 or 2) could be created. */
webgl: boolean;
/** A WebGL2 context could be created. */
webgl2: boolean;
/** MAX_TEXTURE_SIZE of the probed context, 0 when no context exists. */
maxTextureSize: number;
/** Unmasked renderer string when exposed by the browser, '' otherwise. */
renderer: string;
/** True when the renderer string identifies a software rasterizer. */
softwareRasterizer: boolean;
}
interface WebGLContextInfo {
webgl: boolean;
webgl2: boolean;
maxTextureSize: number;
renderer: string;
}
interface CachedCapabilities {
probeVersion: number;
renderer: string;
webgl2: boolean;
formats: TextureFormatSupport;
}
const NO_GPU_FORMATS: TextureFormatSupport = {
norm16: false,
norm16Linear: false,
float: false,
floatLinear: false,
halfFloat: false,
halfFloatLinear: false,
};
let cachedCapabilities: RenderingCapabilities | null = null;
function getWebGLContextInfo(): WebGLContextInfo {
const info: WebGLContextInfo = {
webgl: false,
webgl2: false,
maxTextureSize: 0,
renderer: '',
};
Iif (typeof document === 'undefined') {
return info;
}
try {
const canvas = document.createElement('canvas');
const gl2 = canvas.getContext('webgl2');
const gl =
gl2 ||
(canvas.getContext('webgl') as WebGLRenderingContext | null) ||
(canvas.getContext('experimental-webgl') as WebGLRenderingContext | null);
Iif (!gl) {
return info;
}
info.webgl = true;
info.webgl2 = !!gl2;
info.maxTextureSize = Number(gl.getParameter(gl.MAX_TEXTURE_SIZE)) || 0;
// Modern browsers expose the unmasked renderer through RENDERER directly;
// older ones require the WEBGL_debug_renderer_info extension.
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
const renderer = debugInfo
? gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL)
: gl.getParameter(gl.RENDERER);
info.renderer = typeof renderer === 'string' ? renderer : '';
const loseContext = gl.getExtension('WEBGL_lose_context');
Eif (loseContext) {
loseContext.loseContext();
}
} catch {
// A throwing context factory is equivalent to "no GPU".
}
return info;
}
function readCachedFormats(
renderer: string,
webgl2: boolean
): TextureFormatSupport | null {
try {
const raw = window.localStorage?.getItem(STORAGE_KEY);
Eif (!raw) {
return null;
}
const parsed = JSON.parse(raw) as CachedCapabilities;
// The texture probes require WebGL2, so a profile cached on a WebGL1-only
// run is all-false; invalidate it when WebGL2 availability changes for
// the same renderer (browser update/flag) instead of pinning it forever.
if (
parsed?.probeVersion !== RENDERING_CAPABILITIES_PROBE_VERSION ||
parsed?.renderer !== renderer ||
parsed?.webgl2 !== webgl2 ||
typeof parsed?.formats !== 'object' ||
parsed?.formats === null
) {
return null;
}
return { ...NO_GPU_FORMATS, ...parsed.formats };
} catch {
return null;
}
}
function writeCachedFormats(
renderer: string,
webgl2: boolean,
formats: TextureFormatSupport
): void {
try {
const payload: CachedCapabilities = {
probeVersion: RENDERING_CAPABILITIES_PROBE_VERSION,
renderer,
webgl2,
formats,
};
window.localStorage?.setItem(STORAGE_KEY, JSON.stringify(payload));
} catch {
// Storage may be unavailable (privacy mode, quota); probing every load is
// the acceptable fallback.
}
}
/**
* Runs the capability detection: one cheap context to gather renderer string,
* WebGL level and MAX_TEXTURE_SIZE, then the texture-format probes.
*
* Probe results are cached in localStorage keyed by renderer string, WebGL2
* availability, and probe version, so repeat page loads on the same GPU skip
* the probe contexts entirely. Pass `useCache: false` to force a fresh probe
* run (also refreshes the stored cache).
*/
export function detectRenderingCapabilities({
useCache = true,
}: { useCache?: boolean } = {}): RenderingCapabilities {
const contextInfo = getWebGLContextInfo();
Iif (!contextInfo.webgl) {
return {
...contextInfo,
...NO_GPU_FORMATS,
softwareRasterizer: false,
};
}
let formats = useCache
? readCachedFormats(contextInfo.renderer, contextInfo.webgl2)
: null;
Eif (!formats) {
const probed = getSupportedTextureFormats();
// A null probe result means the probes could not run (context limit hit,
// GPU process restarting, context lost mid-probe) — report no format
// support for this load but do NOT persist it, so a transient bad run
// costs one re-probe next load instead of poisoning the cache under an
// otherwise valid renderer key.
Eif (probed) {
writeCachedFormats(contextInfo.renderer, contextInfo.webgl2, probed);
}
formats = probed ?? { ...NO_GPU_FORMATS };
}
return {
...contextInfo,
...formats,
softwareRasterizer: SOFTWARE_RASTERIZER_PATTERN.test(contextInfo.renderer),
};
}
/**
* Returns the memoized capability profile, detecting it on first access.
* This is the accessor the rest of the library (backend resolution, texture
* format selection) reads.
*/
export function getRenderingCapabilities(): RenderingCapabilities {
if (!cachedCapabilities) {
cachedCapabilities = detectRenderingCapabilities();
}
return cachedCapabilities;
}
/**
* Drops the in-memory profile (and optionally the persisted cache) so the
* next {@link getRenderingCapabilities} call re-detects. Intended for tests
* and for applications that want to re-probe after a GPU change.
*/
export function resetRenderingCapabilities({
clearStorage = false,
}: { clearStorage?: boolean } = {}): void {
cachedCapabilities = null;
if (clearStorage) {
try {
window.localStorage?.removeItem(STORAGE_KEY);
} catch {
// Ignore storage failures on reset.
}
}
}
|