WebGL Ripper

WebGL Ripper против оригинала 0.6: замеры

English · Русский · WebGL Ripper на GitHub

Проект начинался как доработка WebGL Ripper 0.6 от Rilshrink, а закончился переписыванием. Чтобы показать разницу на деле, обе версии установлены как настоящие расширения в один и тот же браузер и запущены на одинаковых тестовых страницах. В оригинале ничего не менялось, ему выставлены его лучшие настройки.

Итог

  Оригинал 0.6 Эта версия (1.1.2)
Сцена three.js с тенями и пост-обработкой неправильно: все меши в начале координат, без нормалей, чужая текстура на всех мешах, сохранена в 16 раз больше правильно: меши на местах, нормали, цвета, настоящая текстура
Время до файла, сцена three.js 0,56–0,82 с 0,17 с (GLB), 0,24–0,42 с (OBJ ZIP)
Зависание страницы при экспорте, сцена three.js 385–585 мс 41–80 мс
3000 вызовов отрисовки за 3 минуты файла нет, страница зависла, +1,3 ГБ 0,28 с (GLB) / 0,6–0,8 с (OBJ ZIP), +42–78 МБ
250 000 вершин, две текстуры 4096² 3,9 с, зависание 1,5 с, +816 МБ, ZIP 60 МБ, модель не на месте 1,6 с (GLB) / 2,7 с, зависание меньше 0,2 с, +177–253 МБ, ZIP 5,6 МБ, на месте
Буферы в стиле Unity / Emscripten мусор точно
WebGL создаётся во время загрузки страницы не перехватывается (3 из 7 тестовых страниц) перехватывается
Страницы, которые не вызывают gl.clear() никогда не заканчивает перехватываются
Время кадра, 3000 вызовов отрисовки, без захвата +0,36–0,53 мс (≈ в 2 раза дольше) ±0,1 мс (погрешность)
Время кадра, 2000 загрузок буферов WebGL 1 +0,85–1,1 мс +0,5–1,1 мс

Методика

Абсолютное время зависит от компьютера; важно соотношение двух версий. Сцены и скрипт лежат в tests/compare/, см. Как повторить.

Тестовые страницы

Страница Что это Зачем
three.html three.js r170: текстурированный куб, тор-узел, икосаэдр и пол; тени от всех мешей, пост-обработка EffectComposer, оси-гизмо ViewHelper типичный современный просмотрщик
bench.html WebGL 2: 3000 мелких текстурированных вызовов отрисовки за кадр из одного общего буфера, 8 текстур; WebGL 1: N вызовов bufferSubData за кадр много объектов; цена каждого кадра
heavy-late.html один меш на 250 000 вершин, сдвинутый матрицей модели в (1, 2, 3), две текстуры 4096×4096 большая модель, как из фотограмметрии или генерации ИИ
heap-late.html WebGL 1, буферы загружаются срезами одной большой «кучи», которая сразу перезаписывается так загружают данные Unity и приложения на Emscripten
early.html, heap.html, heavy.html контекст создаётся, пока страница ещё загружается момент запуска самого расширения

Страницы -late создают WebGL через полсекунды после загрузки. Оригинал подгружает свой код асинхронно и пропускает контексты, созданные раньше; варианты -late позволяют измерить его на том, что он может захватить.

Результаты

Время кадра, когда ничего не захватывается

Сколько расширение стоит странице всё время — просто тем, что установлено.

Сцена Без расширения Оригинал 0.6 Эта версия
3000 вызовов отрисовки за кадр (WebGL 2) 0,40–0,62 мс 0,88–1,00 мс 0,39–0,71 мс
2000 загрузок буферов за кадр (WebGL 1) 0,70–0,78 мс 1,55–1,83 мс 1,22–1,79 мс

Оригинал оборачивает 24 функции WebGL на каждом контексте и ведёт собственную копию состояния GL в JavaScript, поэтому за это платит каждый вызов отрисовки, привязки и указателя. Эта версия перехватывает отрисовку только пока записывается кадр; остаётся лишь копирование, без которого не обойтись для загрузок WebGL 1 (WebGL 1 не умеет читать буферы обратно).

Сцена three.js

Объект (где он на самом деле) Оригинал 0.6 Эта версия
Текстурированный куб в (−2; 0,7; 0) 24 вершины в (0; 0; 0) 8 вершин в (−2; 0,7; 0), с нормалями
Тор-узел в (0,4; 1,2; 0) 4025 вершин в (0,1; 0; 0) 3840 вершин в (0,42; 1,2; −0,02), с нормалями
Икосаэдр в (2,6; 0,8; −0,5) 240 несвязанных вершин в (0; 0; 0) 42 сваренные вершины в (2,6; 0,8; −0,5), с нормалями
Пол 12 × 12 в начале координат стоит вертикально (12 × 12 × 0) лежит горизонтально (12 × 0 × 12)
Нормали нет есть
Цвета материалов нет Kd / baseColorFactor из шейдера
Текстура клетка куба 256×256 сохранена как 4096×4096 и назначена всем 4 мешам 256×256, только у куба
Файл ZIP 0,95 МБ ZIP 0,13 МБ / GLB 0,14 МБ
Время / самый долгий кадр 0,56–0,82 с / 385–585 мс 0,24–0,42 с (OBJ), 0,17 с (GLB) / 41–80 мс

Оригинал записывает от одного gl.clear() до следующего. В этой сцене это, по всем признакам, проход карты теней (отрисовка только глубины, без нормалей): никакой знакомой ему трансформации не применено, поэтому все объекты оказались в начале координат, а текстура — та, что осталась привязанной. Эта версия записывает весь кадр и оставляет проход, который доходит до экрана.

3000 вызовов отрисовки

  Оригинал 0.6 Эта версия
Результат за 3 минуты файла нет; страница перестала отвечать 3000 мешей на своих местах, 8 текстур
Время — 0,28 с (GLB), 0,62–0,76 с (OBJ ZIP, 3011 файлов)
Самый долгий кадр страница перестала отвечать 68–112 мс
Дополнительная память +1335 МБ (+197 МБ в GPU) +42–78 МБ

На каждый вызов отрисовки оригинал дважды перебирает все активные uniform-переменные программы и читает матрицы и сэмплеры через getUniform — синхронные запросы к процессу GPU, — а каждую текстуру читает в угаданном размере 4096×4096 через 2D canvas. Если в кадре нет gl.clear(), он вообще не заканчивает.

Большая модель: 250 000 вершин, две 4K-текстуры

  Оригинал 0.6 Эта версия
Результат число вершин верное, но в (0; 0; 0) вместо (1; 2; 3) на месте, в (1; 2; 3)
Время 3,9 с 1,6 с (GLB), 2,7 с (OBJ ZIP)
Самый долгий кадр 1539 мс 134–186 мс
Дополнительная память +590 МБ (+226 МБ в GPU) +169–245 МБ (+8 МБ в GPU)
Файл ZIP 60 МБ (без сжатия) ZIP 5,6 МБ / GLB 14 МБ

Когда та же страница создавала контекст во время загрузки (heavy.html), оригинал не захватил ничего.

Загрузка в стиле Unity / Emscripten

  Оригинал 0.6 Эта версия
Контекст создан после загрузки OBJ на 1,1 МБ с одной вершиной в (3,4·10³⁸; 3,4·10³⁸; 3,4·10³⁸) квадрат, точно
Контекст создан во время загрузки ничего квадрат, точно

Оригинал хранит ссылку на загруженный срез вместо копии; к моменту чтения «куча» уже переиспользована и содержит другие данные (здесь байты 0x7F7F7F7F, что как float равно 3,4·10³⁸).

Контекст, созданный во время загрузки страницы

early.html создаёт контекст в <script> в заголовке страницы — так часто делают небольшие демо и загрузчики движков. Код оригинала приходит позже и этот контекст не видит: файла нет. Эта версия запускается раньше скриптов страницы и сохраняет треугольник точно.

Всё остальное, чем они отличаются

  Оригинал 0.6 Эта версия
Форматы OBJ + MTL + PNG GLB (glTF 2.0) и/или OBJ + MTL + PNG
Выбор, что сохранить — 3D-превью на странице
Сохранение одного объекта — клик по нему (режим выбора)
Проходы теней, гизмо, пост-обработка, дубликаты сохраняются, если попали в записанный проход убираются
Vertex array objects (WebGL 2) не учитываются читаются у WebGL
Размер текстур известен только для загрузок из картинки/canvas, иначе 4096×4096 отслеживается для всех способов загрузки
Сжатые, float, luminance-текстуры не читаются читаются через проход отрисовки
Цвет под прозрачными пикселями теряется (toDataURL домножает на альфу) сохраняется, PNG байт в байт
Сварка вершин, недостающие нормали — есть
Скачивание несжатый ZIP или по файлу с паузами 0,5 с один сжатый ZIP или один GLB, потоково
Как настройки попадают в страницу через скрытый <div> в странице из хранилища расширения, сразу
Кнопка, статус, сочетания клавиш только клавиша Insert значок, окно, настраиваемые клавиши, сочетания браузера
Firefox инструкция в его README пакет для Firefox 128+, проверен тестами
Тесты — тесты движка, three.js, большого экспорта и расширения целиком в Chrome и Firefox

Как повторить

Нужны Node.js 22+, Chrome и распакованный в папку оригинал.

node tests/compare/compare.mjs --original=<папка WebGL Ripper 0.6>
node tests/compare/compare.mjs --original=<папка> --scenes=three,heavy-late --engines=orig,new,new-glb

Скрипт собирает пакет релиза, запускает свежий headless Chrome на каждую версию, печатает по строке на страницу и оставляет скачанные файлы и results.json во временной папке системы (webglripper-compare). Память замеряется только в Windows. Тяжёлым страницам нужно несколько ГБ свободной памяти; скрипт останавливает прогон, если её становится мало.