Debugging WebGL: chrome://gpu, Spector.js та помилки шейдерів
Чорний canvas без жодної помилки в консолі — найпоширеніший баг-репорт щодо WebGL. WebGL спроєктований як тонкий, швидкий шар над драйвером GPU — а це означає, що здебільшого він ламається тихо, а не кидає виключення. Цей туторіал — набір інструментів, щоб дійсно побачити, що робить GPU: діагностика драйвера, захоплення кадрів, правильні перевірки помилок шейдерів і відновлення після втрати контексту.
1. Чому WebGL ламається тихо
На відміну від більшості JavaScript API, виклик функції WebGL з
неправильним станом рідко кидає виключення, яке можна перехопити.
Шейдер, що не скомпілювався, все одно повертає об'єкт шейдера,
який виглядає валідним (але непридатний до використання).
Текстура, прив'язана до неправильного юніта, не видає помилку —
вона просто семплить чорний колір, сміття або неправильне
зображення. Оскільки WebGL відображає дизайн C-API OpenGL,
інформація про помилку зберігається у внутрішньому прапорці
помилки, який потрібно опитувати
(gl.getError()), а не у виключенні, яке можна
перехопити.
Практичний наслідок: «нічого не рендериться» або «неправильний колір» можуть мати десяток різних першопричин без жодного stack trace, що вказує на будь-яку з них. Інструменти нижче існують саме для того, щоб зробити цей невидимий стан знову видимим.
{ failIfMajorPerformanceCaveat: true }
під час розробки. Він одразу кидає виключення, якщо WebGL
перейшов би на повільний, непришвидшений програмний шлях —
перетворюючи тонкий баг продуктивності на гучний, ранній збій.
2. chrome://gpu — статус драйвера й функцій
Перейдіть на chrome://gpu у Chrome (або
about:support у Firefox) для повного діагностичного
звіту ще до написання жодного рядка коду для налагодження.
Таблиця «Graphics Feature Status» — перше, що варто перевірити —
кожен рядок має бути Hardware accelerated:
| Рядок функції | Якщо написано «Software only» / «Disabled» |
|---|---|
| WebGL / WebGL2 | Весь рендеринг йде на CPU через SwiftShader — очікуйте час кадру у 10-50 разів повільніший, це не баг рендерингу у вашому коді. |
| Canvas | Операції 2D canvas (зчитування, завантаження текстури з canvas) теж стають повільнішими. |
| Rasterization / Compositing |
Вказує на запис у чорному списку драйверів — часто
виправляється оновленням драйверів GPU або зняттям
прапорця на кшталт --disable-gpu.
|
Нижче таблиці функцій розділ «Problems Detected» перелічує саме ті помилки драйвера, які Chrome заблокував для вашої конкретної комбінації GPU/драйвер — корисно, щоб відрізнити «мій шейдер неправильний» від «у цього драйвера є відома помилка з цим конкретним розширенням».
3. Пошук реального GPU за ANGLE
gl.getParameter(gl.RENDERER) у Chrome зазвичай
повертає загальний рядок на кшталт
"ANGLE (Google, Vulkan 1.3.0...)" — не дуже
корисно, щоб зрозуміти, який фізичний GPU насправді виконує ваші
шейдери. Розширення WEBGL_debug_renderer_info
відкриває реальні значення:
const gl = canvas.getContext('webgl2');
const ext = gl.getExtension('WEBGL_debug_renderer_info');
if (ext) {
console.log('Vendor:', gl.getParameter(ext.UNMASKED_VENDOR_WEBGL));
console.log('Renderer:', gl.getParameter(ext.UNMASKED_RENDERER_WEBGL));
} else {
// Розширення недоступне — часто заблоковане налаштуванням приватності чи старим браузером
console.log('Masked renderer:', gl.getParameter(gl.RENDERER));
}
UNMASKED_RENDERER_WEBGL разом
зі своїми звітами про помилки, і часто побачите закономірність
(конкретний вбудований GPU, конкретний мобільний SoC), а не
баг коду, що впливає на всіх однаково.
4. Покадрове захоплення через Spector.js
Spector.js (розширення браузера або npm-пакет) записує кожен виклик WebGL, зроблений протягом одного кадру, і дозволяє пройти по них крок за кроком: прив'язані текстури, активний шейдер, значення uniform-змінних, розкладку вершинних атрибутів і фактичний вміст framebuffer після кожного draw call.
// npm install --save-dev spectorjs
import { Spector } from 'spectorjs';
const spector = new Spector();
spector.displayUI(); // додає плаваючу кнопку захоплення
// або запустіть захоплення програмно:
spector.captureCanvas(canvas);
Типова сесія налагодження: захопіть один кадр, потім
скануйте список викликів у пошуку draw call, який має
рендерити відсутній об'єкт. Spector.js показує точний стан GL
на цьому виклику — якщо мініатюра прив'язаної текстури порожня,
це баг завантаження текстури; якщо кількість вершин 0 — це баг
bufferData/налаштування атрибутів; якщо шейдер
показує іконку помилки компіляції — переходьте одразу до
розділу 5.
5. Правильна перевірка помилок компіляції/лінковки шейдерів
Це найцінніший фікс, якого бракує більшості кодових баз WebGL:
gl.compileShader() та gl.linkProgram()
ніколи не кидають виключення й нічого не логують за
замовчуванням. Зламаний шейдер тихо створює непридатний об'єкт,
якщо ви явно не перевірите це:
function compileShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
const log = gl.getShaderInfoLog(shader);
gl.deleteShader(shader);
throw new Error(`Shader compile failed:\n${log}`);
}
return shader;
}
function linkProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
const log = gl.getProgramInfoLog(program);
gl.deleteProgram(program);
throw new Error(`Program link failed:\n${log}`);
}
return program;
}
Помилки лінковки — окремий, легко пропустити тип збою:
компіляція може успішно пройти для обох шейдерів окремо, поки
лінковка все одно провалюється — найчастіше через
невідповідність varying-змінних (змінна
in у фрагментному шейдері без відповідної
out у вершинному шейдері з тією ж назвою й типом),
або перевищення максимальної кількості varying/uniform векторів
для платформи.
ShaderMaterial Three.js, помилки
компіляції/лінковки автоматично з'являються в консолі у
збірках для розробки через
renderer.debug.checkShaderErrors. Написання
сирого WebGL (як у коді вище) — саме те місце, де цю перевірку
найчастіше пропускають.
6. Обробка втрати контексту WebGL
Драйвер GPU може відібрати контекст WebGL у будь-який момент — ноутбук перемикає GPU, інша вкладка вичерпує VRAM, ОС відновлюється після краху драйвера. Без обробника це залишає назавжди чорний canvas без жодної помилки:
canvas.addEventListener('webglcontextlost', (e) => {
e.preventDefault(); // обов'язково — повідомляє браузеру про намір відновити контекст
cancelAnimationFrame(rafId);
console.warn('WebGL context lost — pausing render loop');
});
canvas.addEventListener('webglcontextrestored', () => {
console.warn('WebGL context restored — reinitialising GL resources');
initGLResources(); // буфери, текстури й програми потрібно перестворити повністю
requestAnimationFrame(renderLoop);
});
webglcontextrestored. Тримайте вихідні дані
(масиви геометрії, об'єкти зображень, рядки шейдерного коду) у
звичайній пам'яті JS, а не лише на GPU, спеціально для того,
щоб цьому обробнику було з чого відновлюватися.
Ви можете примусово викликати втрату контексту для тестування
через розширення WEBGL_lose_context:
gl.getExtension('WEBGL_lose_context').loseContext() —
необхідно, щоб перевірити, чи справді працює ваш шлях
відновлення, перш ніж це станеться з реальним користувачем.
7. Чекліст: типові підозрювані
| Симптом | Ймовірна причина |
|---|---|
| Нічого не рендериться, помилок немає | Увімкнено depth test, а нічого не очищає буфер глибини, або ближня/дальня площини камери виключають всю сцену |
| Об'єкт рендериться суцільно чорним | Відсутній/непризначений текстурний юніт, uniform-семплер вказує на неправильний індекс текстурного юніта, або збій компіляції шейдера тихо повертає програму за замовчуванням |
| Геометрія виглядає пошкодженою/спотвореною |
Невідповідність size/type/stride
атрибута фактичному розкладу буфера, або невідповідність
типу буфера індексів Int16Array проти
Uint16Array
|
| Прозорі краї виглядають неправильно / артефакти-ореоли | Невідповідність premultiplied та звичайного (straight) альфа-каналу між джерелом текстури та функцією змішування |
| Працює на десктопі, ламається на мобільному |
Переповнення точності шейдера mediump/lowp,
або використання функції, доступної лише в WebGL2, без
запасного варіанта
|
| Мерехтіння між двома об'єктами на однаковій глибині | Z-fighting через ближню площину відсікання, встановлену занадто близько до 0 відносно дальньої площини |
Часті запитання
Чого я навчуся в цьому уроці?
Практичний набір інструментів для налагодження WebGL: читання chrome://gpu, захоплення кадрів через Spector.js, правильна перевірка помилок компіляції/лінковки шейдерів, обробка втрати контексту та чекліст найпоширеніших тихих збоїв.
Які теми розглядаються в цьому уроці?
Цей урок охоплює такі теми: Чому WebGL ламається тихо, chrome://gpu — статус драйвера й функцій, Пошук реального GPU за ANGLE, Покадрове захоплення через Spector.js, Правильна перевірка помилок компіляції/лінковки шейдерів, Обробка втрати контексту WebGL, Чекліст: типові підозрювані.
Скільки часу займає цей урок?
Цей урок займає приблизно 25 хв.
Які попередні знання потрібні?
Це урок рівня «Середній рівень» — окрема попередня підготовка, крім базового JavaScript, не потрібна.