Туторіал · WebGL · Налагодження · Інструменти
📅 Липень 2026 ⏱ ≈ 25 хв 🎯 Середній рівень

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));
}
Чому це важливо для баг-репортів: «чорний екран у деяких користувачів» майже завжди специфічний для GPU/драйвера. Логуйте 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.

Також варто знати: власна панель WebGL Inspector у Chrome DevTools (через меню «More tools» у нещодавніх версіях Chrome, або класичне окреме розширення «WebGL Inspector» для старіших робочих процесів) пропонує подібну перевірку списку викликів прямо в браузері, без встановлення чогось у бандл вашої сторінки.

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 векторів для платформи.

Three.js вже робить це за вас: якщо ви використовуєте 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);
});
Все на боці GPU зникає після втрати — кожен буфер, текстуру й скомпільовану програму шейдерів потрібно перестворити з нуля всередині обробника 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 для особливостей точності та драйверів, специфічних для телефонів і планшетів, або з довідником WebGL Extensions для повного списку діагностичних розширень окрім двох використаних тут.

Часті запитання

Чого я навчуся в цьому уроці?

Практичний набір інструментів для налагодження WebGL: читання chrome://gpu, захоплення кадрів через Spector.js, правильна перевірка помилок компіляції/лінковки шейдерів, обробка втрати контексту та чекліст найпоширеніших тихих збоїв.

Які теми розглядаються в цьому уроці?

Цей урок охоплює такі теми: Чому WebGL ламається тихо, chrome://gpu — статус драйвера й функцій, Пошук реального GPU за ANGLE, Покадрове захоплення через Spector.js, Правильна перевірка помилок компіляції/лінковки шейдерів, Обробка втрати контексту WebGL, Чекліст: типові підозрювані.

Скільки часу займає цей урок?

Цей урок займає приблизно 25 хв.

Які попередні знання потрібні?

Це урок рівня «Середній рівень» — окрема попередня підготовка, крім базового JavaScript, не потрібна.