Three.js: Неблокирующие загрузчики активов

Созданный на 11 июл. 2017  ·  53Комментарии  ·  Источник: mrdoob/three.js

Как обсуждалось в https://github.com/mrdoob/three.js/issues/11301, одна из основных проблем WebVR, хотя она и раздражает и не в VR, - это блокировка основного потока при загрузке ресурсов.

С недавней реализацией обхода ссылок в браузере неблокирующая загрузка является обязательной для обеспечения удовлетворительного взаимодействия с пользователем. Если вы перескочите с одной страницы на другую, и целевая страница начнет загружать ресурсы, блокирующие основной поток, она заблокирует функцию рендеринга, поэтому в гарнитуру не будут отправляться кадры, и после небольшого периода отсрочки браузер вышвырнет нас из VR. и пользователю потребуется вынуть гарнитуру, снова щелкнуть «Войти в VR» (для этого требуется жест пользователя) и вернуться к опыту.

В настоящее время мы видим две реализации неблокирующей загрузки файлов OBJ:

  • (1) Использование веб-работников для анализа файла obj и последующего возврата данных обратно в основной поток WWOBJLoader :
    Здесь синтаксический анализ выполняется одновременно, и у вас может быть несколько рабочих одновременно. Главный недостаток заключается в том, что после загрузки данных вам необходимо отправить полезную нагрузку обратно в основной поток, чтобы восстановить ТРИ экземпляра объектов, и эта часть может заблокировать основной поток:
    https://github.com/kaisalmen/WWOBJLoader/blob/master/src/loaders/WWOBJLoader2.js#L312 -L423
  • (2) Обещание основного потока с отложенным синтаксическим анализом с использованием setTimeOut: Oculus ReactVR : этот загрузчик продолжает считывать строки, используя небольшие временные интервалы, чтобы предотвратить блокировку основного потока, вызывая setTimeout: https://github.com/facebook/react-vr/blob/master /ReactVR/js/Loaders/WavefrontOBJ/OBJParser.js#L281 -L298
    При таком подходе загрузка будет медленнее, поскольку мы просто анализируем несколько строк в каждом временном интервале, но преимущество состоит в том, что после завершения синтаксического анализа у нас будут ТРИ объекта, готовые к использованию без каких-либо дополнительных накладных расходов.

У обоих есть свои плюсы и минусы, и я, честно говоря, не эксперт в области веб-работников, чтобы оценивать эту реализацию, но это интересное обсуждение, которое в идеале привело бы к универсальному модулю, который можно было бы использовать для переноса загрузчиков в неблокирующую версию.

Какие-либо предложения?

/ cc @ mikearmstrong001 @kaisalmen @delapuente @spite

Suggestion

Самый полезный комментарий

Выпуск первого предложения THREE.UpdatableTexture

В идеале он должен быть частью любой THREE.Texture, но я бы сначала изучил этот подход.

Все 53 Комментарий

У вас может быть загрузчик на основе Promise + worker + инкрементный (что-то вроде сочетания обоих пунктов)

Передайте исходный URL-адрес рабочему сценарию, выберите ресурсы, верните структуру переносимых объектов с необходимыми буферами, структурами и даже ImageBitmaps; он должен быть достаточно простым, чтобы не требовалось много накладных расходов на обработку three.js.

Выгрузка данных в графический процессор в любом случае будет блокироваться, но вы можете создать очередь для распределения команд по разным кадрам с помощью display.rAF. Команды могут выполняться по одной для каждого кадра или вычислять среднее время операции и запускать столько, сколько «безопасно» для выполнения в текущем кадре сдвинуть с места (что-то похожее на requestIdleCallback было бы неплохо, но это не поддерживается широко , и это проблематично в сессиях WebVR). Также можно улучшить, используя bufferSubData, texSubImage2D и т. Д.

Поддержка рабочих и переносимых объектов сейчас довольно прочна, особенно в браузерах с поддержкой WebVR.

Привет всем, у меня есть прототип, который может вас заинтересовать в этом контексте. См. Следующую ветку:
https://github.com/kaisalmen/WWOBJLoader/tree/Commons
Здесь часть подготовки сетки полностью отделена от WWOBJLoader2 :
https://github.com/kaisalmen/WWOBJLoader/blob/Commons/src/loaders/WWLoaderCommons.js

WWLoaderCommons упрощает реализацию других поставщиков сетки (загрузчики форматов файлов). По сути, он определяет, как реализация веб-воркера должна предоставлять данные сетки обратно в основной поток и обрабатывать / интегрировать их в сцену. Посмотрите на случайного поставщика мусора в виде треугольника 😉, который служит техническим демонстратором:
https://github.com/kaisalmen/WWOBJLoader/tree/Commons/test/meshspray
https://kaisalmen.de/proto/test/meshspray/main.src.html

Даже в текущей реализации WWOBJLoader2 полагается на передаваемые объекты (ArrayBuffers / ByteBuffers) для предоставления необработанных данных BufferedGeometry для Mesh от рабочего к основному потоку. По времени создание Mesh из предоставленных ByteBuffers незначительно. Однако всякий раз, когда в сцену интегрируется сетка большего размера, рендеринг останавливается (копии данных, настройки графа сцены ...!?). Это всегда происходит независимо от источника (поправьте меня, если я ошибаюсь).
«Потоковый» режим WWOBJLoader2 сглаживает эти задержки, но если одна часть сетки из вашей модели OBJ весит 0,5 миллиона вершин, рендеринг будет приостановлен на более длительный период времени.

Я открыл новый выпуск, чтобы подробно рассказать, что именно я сделал в специальной ветке и почему:
https://github.com/kaisalmen/WWOBJLoader/issues/11
Проблема пока не решена, и подробности скоро появятся.

Чтобы предложить некоторые цифры, вот профиль производительности https://threejs.org/examples/webgl_loader_gltf2.html , загружающий модель 13 МБ с текстурами 2048x2048.

screen shot 2017-07-11 at 10 11 42 pm

В этом случае основная вещь, блокирующая основной поток, - это загрузка текстур в графический процессор, и, насколько я знаю, это невозможно сделать из WW ... либо загрузчик должен добавлять текстуры постепенно, либо three.js должны обрабатывать это внутренне.

Для любопытных, последний блок, блокирующий основной поток, - это добавление кубической карты окружения.

Основная цель react-vr - не обязательно иметь самый оптимальный загрузчик с точки зрения времени настенных часов, но не вызывать внезапных и неожиданных выходов кадров при загрузке нового контента. Все, что мы можем сделать, чтобы минимизировать это, полезно для всех, но особенно для VR.

Текстуры определенно представляют собой проблему, и очевидным первым шагом может быть факультативная загрузка их постепенно - набор строк за раз для большой текстуры. Поскольку загрузка скрыта для клиентских программ, им будет сложно управлять, но я бы хотел, чтобы это было более открыто для визуализатора webgl, чтобы уменьшить давление на three.js

Для синтаксического анализа gltf я обычно вижу блокировку 500 мс в моих тестах, это важно, и я бы предпочел инкрементный подход ко всем загрузчикам (которые также должны быть клонируемыми)

Предпосылка React VR состоит в том, чтобы поощрять простой динамический контент, управляемый веб-стилем, чтобы поощрять больше разработчиков, и это будет уделять больше внимания улучшению динамической обработки. В большинстве случаев мы не знаем, какие ресурсы потребуются в начале наших пользовательских приложений.

@kaisalmen Спасибо за ссылку

В Elation Engine / JanusWeb мы фактически выполняем весь анализ нашей модели с использованием пула рабочих потоков, что работает довольно хорошо. После того, как рабочие завершили загрузку каждой модели, мы сериализуем ее с помощью object.toJSON() , отправляем в основной поток с помощью postMessage() , а затем загружаем с помощью ObjectLoader.parse() . Это удаляет большую часть блокирующих частей кода загрузчика - еще есть некоторое время, потраченное на ObjectLoader.parse() которое, вероятно, можно было бы оптимизировать, но общая интерактивность и скорость загрузки значительно улучшились. Поскольку мы используем пул воркеров, мы также можем анализировать несколько моделей параллельно, что является огромным преимуществом в сложных сценах.

Что касается текстур, то да, я думаю, что необходимо внести некоторые изменения в функцию загрузки текстур в three.js. Разделенный загрузчик с использованием texSubImage2D был бы идеальным, тогда мы могли бы выполнять частичное обновление больших текстур в нескольких кадрах, как упоминалось выше.

Я был бы более чем счастлив поработать над этим изменением, так как оно принесет пользу многим проектам, использующим Three.js в качестве основы.

Я думаю, что использовать texSubImage2D - хорошая идея.
Но также почему WebGL не загружает текстуры асинхронно.
У OpenGL и других библиотек такое же ограничение?

И еще я думаю о компиляции GLSL.
Будет ли выпадать рамка? Или достаточно быстро, и нам не нужно заботиться?

Да, это проблема и в собственном OpenGL - компиляция шейдеров и загрузка данных изображения являются синхронными / блокирующими операциями. Вот почему большинство игровых движков рекомендуют или даже заставляют вас предварительно загружать весь контент перед запуском уровня - обычно считается, что загрузка новых ресурсов даже с жесткого диска слишком сильно снижает производительность, и здесь мы пытаемся сделать это асинхронно. через Интернет ... на самом деле у нас есть более сложная проблема, чем у большинства разработчиков игр, и нам придется прибегнуть к использованию более продвинутых методов, если мы хотим иметь возможность передавать новый контент на лету.

Загрузка текстур будет менее проблематичной, если в будущем мы будем использовать новый ImageBitmap API. См. Https://youtu.be/wkDd-x0EkFU?t=82 .

Кстати: благодаря @spite у нас уже есть экспериментальный ImageBitmapLoader в проекте.

@ Mugen87 на самом деле я уже выполняю все свои загрузки текстур с ImageBitmap в Elation Engine / JanusWeb - это определенно помогает и стоит интегрировать в ядро ​​Three.js, но есть две основные затраты, связанные с использованием текстур в WebGL - время декодирования изображения , и время загрузки изображения - ImageBitmap помогает только с первым.

В моих тестах это сокращает время блокировки процессора примерно на 50%, но загрузка больших текстур в графический процессор, особенно 2048x2048 и выше, может легко занять секунду или больше.

Было бы удобно попробовать то, что предлагает @jbaicoianu . В любом случае, если вы выберете альтернативу для основного потока, это будет идеальным вариантом для

Я согласен со всеми вами, я считаю, что подход к загрузке и синтаксическому анализу всего на рабочем, создании необходимых объектов обратно в основном потоке (если это очень дорого, это можно сделать в несколько шагов), а затем включить инкрементную загрузку в рендерер.
Для MVP мы могли бы определить maxTexturesUploadPerFrame (по умолчанию бесконечный), и рендеринг позаботится о загрузке из пула в соответствии с этим числом.
В следующих итерациях мы могли бы добавить логику, как прокомментировал @spite , для измерения среднего и автоматической загрузки их на основе времени безопасного диапазона перед блокировкой. Первоначально это можно было сделать для каждой текстуры как целого, но затем это можно было бы улучшить, добавив в нее фрагменты для более крупных текстур.

requestIdleCallback было бы неплохо, но он не поддерживается широко и проблематичен в сессиях WebVR.

@spite Мне интересно ваше предложение, что вы имеете в виду под проблемным?

У меня есть THREE.UpdatableTexture для постепенного обновления текстур с помощью texSubImage2D, но мне нужно немного подправить three.js. Идея состоит в том, чтобы подготовить PR для добавления поддержки.

Относительно requestIdleCallback (rIC):

  • Во-первых, он поддерживается в Chrome и Firefox, и, хотя его можно легко полифилить, полифиллированная версия может немного не справиться с этой задачей.

  • во-вторых: так же, как vrDisplay.requestAnimationFrame (rAF) должен вызываться вместо window.rAF при представлении, то же самое относится и к rIC, как обсуждается в этом crbug . Это означает, что загрузчик должен всегда знать о текущем активном дисплее, иначе он перестанет стрелять в зависимости от того, что отображается. Это не очень сложно, это просто добавляет сложности к подключению загрузчиков (которые в идеале должны просто выполнять свою работу, независимо от состояния представления). Другой вариант - иметь часть в threejs, которая запускает инкрементные задания в основном потоке, чтобы совместно использовать текущий дисплей; Я думаю, что теперь это намного проще сделать с последними изменениями в VR в threejs.

Еще одно соображение: чтобы иметь возможность загружать одну большую текстуру в несколько этапов с помощью texSubImage2D (256x256 или 512x512), нам нужен контекст WebGL2, чтобы иметь функции смещения и обрезки. В противном случае изображения должны быть предварительно обрезаны на холсте, в основном на стороне клиента перед загрузкой.

@spite Хороший момент, я не думал о том, что rIC не вызывается при представлении, сначала я подумал, что нам нужен display.rIC, но я считаю, что .rIC должен быть прикреплен к окну и вызываться, когда окно или дисплей оба простаивают.
Я полагаю, что не слышал ничего по этому поводу в обсуждениях спецификаций webvr. @Kearwood, возможно, имеет больше информации, но определенно это проблема, которую мы должны решить.

С нетерпением жду вашего PR UpdatableTexture! :) Даже если это просто WIP, мы могли бы перенести туда часть обсуждения.

Может, загрузчики могли бы стать чем-то вроде этого ...

THREE.MyLoader = ( function () {

    // parse file and output js object
    function parser( text ) {
        return { 'vertices': new Float32Array() }
    }

    // convert js object to THREE objects.
    function builder( data ) {
        var geometry = new THREE.BufferGeometry();
        geometry.addAttribute( new THREE.BufferAttribute( data.vertices, 3 );
        return geometry;
    }

    function MyLoader( manager ) {}
    MyLoader.prototype = {
        constructor: MyLoader,
        load: function ( url, onLoad, onProgress, onError  ) {},
        parse: function ( text ) {
            return builder( parser( text ) );
        },
        parseAsync: function ( text, onParse ) {
            var code = parser.toString() + '\nonmessage = function ( e ) { postMessage( parser( e.data ) ); }';
            var blob = new Blob( [ code ], { type: 'text/plain' } );
            var worker = new Worker( window.URL.createObjectURL( blob ) );
            worker.addEventListener( 'message', function ( e ) {
                onParse( builder( e.data ) );
            } );
            worker.postMessage( text );
        }
    }
} )();

Выпуск первого предложения THREE.UpdatableTexture

В идеале он должен быть частью любой THREE.Texture, но я бы сначала изучил этот подход.

@mrdoob, я вижу заслугу в том, что один и тот же код передается рабочему по конвейеру, это просто так неправильно. Интересно, каково будет влияние сериализации, блоббинга и переоценки сценария; ничего страшного, но я не думаю, что браузер оптимизирован для этих причуд 🙃

Кроме того, в идеале выборка самого ресурса должна происходить в worker. И я думаю, что для метода parser () в браузере потребуется importScripts самого three.js.

Но одна точка для определения загрузчиков синхронизации / асинхронного режима была бы потрясающей!

@mrdoob функция builder может быть полностью общей и общей для всех загрузчиков (WIP: https://github.com/kaisalmen/WWOBJLoader/blob/Commons/src/loaders/support/WWMeshProvider.js#LL215- LL367; Обновление: еще не изолировано в функции). Если входные данные ограничены чистыми объектами js без ссылки на какие-либо объекты THREE (это то, что вы имеете в виду, верно?), Мы могли бы создать сериализуемый рабочий код без необходимости импорта в worker (что WWOBJLoader делает). Это легко для геометрии, но материалы / шейдеры (если они определены в файле) могут быть созданы только в построителе и описываются только как JSON раньше parser .
Я думаю, что рабочий должен сигнализировать о каждой новой сетке и ее завершении. Это можно было бы изменить так:

// parse file and output js object
function parser( text, onMeshLoaded, onComplete ) {
    ....
}
parse: function ( text ) {
    var node = new THREE.Object3d();
    var onMeshLoaded = function ( data ) {
        node.add( builder( data ) );
    };

    // onComplete as second callbackonly provided in async case
    parser( text, onMeshLoaded ) );
    return node;
},

Полезна утилита worker builder + некоторый общий протокол связи, который не противоречит вашей идее использования синтаксического анализатора как есть, но, я думаю, он нуждается в некоторой обертке. Текущее состояние эволюции WWOBJLoader: https://github.com/kaisalmen/WWOBJLoader/blob/Commons/src/loaders/support/WWMeshProvider.js#LL40 -LL133, тогда как внешние вызовы - это report_progress, meshData и complete.

Обновление2:

  • Как вы думаете, парсер должен быть апатридом? Это нормально для builder , но имеет смысл установить некоторые параметры для настройки поведения parser . Это также означает, что параметры конфигурации должны передаваться исполнителю независимо от синтаксического анализа.
  • Было бы здорово иметь что-то вроде функции запуска, которая питается универсальным объектом конфигурации. Общий директор может затем снабдить любой загрузчик инструкциями (теперь это работает в специальной ветке Commons WWOBJLoader , кстати)
  • текущий разработчик: WWOBJLoader2 теперь расширяет OBJLoader и переопределяет синтаксический анализ. Итак, у нас есть обе крышки синтаксического анализа, но в разных классах. Это близко к предложению, но пока не соответствует. Некоторый код парсера должен быть унифицирован, и в конечном итоге оба класса должны быть объединены

На этом пока все. Обратная связь приветствуется 😄

@mrdoob Мне нравится идея

Мне нравится подход с использованием урезанного формата передачи для передачи между рабочими процессами, потому что эти TypedArrays легко пометить как передаваемые при передаче обратно в основной поток. В моем текущем подходе я использую метод .toJSON() в worker, но затем я прохожу и заменяю массивы JS для вершин, UV и т. Д. На соответствующий тип TypedArray и помечаю их как передаваемые при вызове postMessage. Это делает синтаксический анализ / использование памяти немного легче в основном потоке за счет немного большего использования обработки / памяти рабочим - это прекрасный компромисс, но его можно было бы сделать более эффективным, введя либо новый формат передачи, как вы предлагаете, или изменив .toJSON() чтобы при желании предоставить нам TypedArrays вместо массивов JS.

Я вижу два недостатка этого упрощенного подхода:

  • Требуется переписать существующие модели погрузчиков. Многие загрузчики используют встроенные ТРИ класса и функции с пространством имен для выполнения своей задачи, поэтому нам, вероятно, придется использовать некоторый минимальный набор кода three.js - а с рабочими это становится непросто.
  • Формат передачи должен правильно отражать иерархию объектов, а также различные типы объектов (Mesh, SkinnedMesh, Light, Camera, Object3D, Line и т. Д.)

@spite Относительно «Кроме того, в идеале выборка самого ресурса должна происходить в

Как только мы это осознали, мы добавили еще один уровень для управления загрузкой всех наших ресурсов, а затем загрузчик ресурсов запускает события, чтобы сообщить нам, когда ресурсы станут доступны. Затем мы передаем их рабочему пулу, используя передаваемые данные двоичного файла, чтобы эффективно передать их рабочему. С этим изменением все загрузки происходят быстрее, даже если они находятся в основном потоке, и синтаксические анализаторы получают возможность работать на полную мощность при обработке, вместо того, чтобы вертеть руками в ожидании данных. В целом это оказалось одной из лучших оптимизаций, которые мы сделали с точки зрения скорости загрузки ресурсов.

Что касается загрузки текстур, я построил доказательство концепции нового класса FramebufferTexture , который поставляется с компаньоном FramebufferTextureLoader . Этот тип текстуры расширяет WebGLRenderTarget , и его загрузчик может быть настроен для загрузки текстур фрагментированными фрагментами заданного размера и компоновки их во фреймбуфер с помощью requestIdleCallback() .

https://baicoianu.com/~bai/three.js/examples/webgl_texture_framebuffer.html

В этом примере просто выберите размер изображения и размер плитки, и начнется процесс загрузки. Сначала мы инициализируем текстуру чисто красным цветом. Мы начинаем загрузку изображений (их около 10 МБ, поэтому давайте немного), а когда они завершатся, мы меняем фон на синий. На этом этапе мы начинаем синтаксический анализ изображения с помощью createImageBitmap() для синтаксического анализа файла, и когда это будет сделано, мы настроим ряд неактивных обратных вызовов, которые содержат дальнейшие вызовы createImageBitmap() которые эффективно разделяют изображение на плитки. . Эти фрагменты визуализируются во фреймбуфере в течение нескольких кадров и оказывают значительно меньшее влияние на время кадра, чем выполнение всех операций за один раз.

ПРИМЕЧАНИЕ. В настоящее время FireFox, похоже, не реализует все версии createImageBitmap , и в настоящее время выдает мне ошибку, когда пытается разделиться на плитки. В результате эта демонстрация в настоящее время работает только в Chrome. Есть ли у кого-нибудь ссылка на план поддержки createImageBitmap в FireFox?

Мне нужно кое-что очистить, этот прототип немного запутан, но я очень доволен результатами, и как только я смогу найти способ решения кросс-браузерных проблем (резервный холст и т. Д.), Я учитывая использование этого параметра по умолчанию для всех текстур в JanusWeb. Эффект постепенного появления тоже довольно изящный, и мы могли бы даже пофантазировать и сначала скопировать уменьшенную версию, а затем постепенно загружать плитки с более высокой детализацией.

Есть ли какие-то причины, связанные с производительностью или функциями, которые кто-то может придумать, почему было бы плохой идеей иметь фреймбуфер для каждой текстуры в сцене, в отличие от стандартной ссылки на текстуру? Я не мог найти ничего о макс. кадровых буферов на сцену, насколько я могу судить после настройки кадрового буфера, если вы не выполняете рендеринг в него, тогда он такой же, как и любая другая ссылка на текстуру, но у меня такое ощущение, что я упускаю что-то очевидное, что касается почему это было бы действительно плохой идеей :)

@ Несмотря на то, что я тоже подумал, я видел эту ошибку о том, что не поддерживает словарь параметров, но в этом случае я даже не использую это, я просто пытаюсь использовать параметры x, y, w, h. Конкретная ошибка, которую я получаю:

Argument 4 of Window.createImageBitmap '1024' is not a valid value for enumeration ImageBitmapFormat.

Это сбивает с толку, потому что я не вижу ни одной версии createImageBitmap в спецификации, которая принимает ImageBitmapFormat в качестве аргумента.

Есть ли какие-то причины, связанные с производительностью или функциями, которые кто-то может придумать, почему было бы плохой идеей иметь фреймбуфер для каждой текстуры в сцене, в отличие от стандартной ссылки на текстуру? Я не мог найти ничего о макс. кадровых буферов на сцену, насколько я могу судить после настройки кадрового буфера, если вы не выполняете рендеринг в него, тогда он такой же, как и любая другая ссылка на текстуру, но у меня такое ощущение, что я упускаю что-то очевидное, что касается почему это было бы действительно плохой идеей :)

@jbaicoianu THREE.WebGLRenderTarget хранит буфер кадра , текстуру и буфер рендеринга. Когда у вас собрана текстура, вы можете удалить буфер кадра и буфер рендеринга и сохранить только текстуру. Что-то вроде этого должно делать это (не проверено):

texture = target.texture;
target.texture = null; // so the webgl texture is not deleted by dispose()
target.dispose();

@wrr это хорошо знать, спасибо. Я определенно должен сделать переход на эффективность памяти и в этом - он неизбежно выйдет из строя в какой-то момент, если вы достаточно измените параметры, поэтому я знаю, что есть некоторая очистка, которую я еще не делаю. Будем очень признательны за любые другие подобные подсказки.

@mrdoob и @jbaicoianu Я забыл упомянуть, что идея мне тоже нравится. 😄
Я не загромождал код (переработанный init, объект рабочих инструкций, замененная обработка многократного обратного вызова мусора, общее описание ресурса и т. Д.) OBJLoader и WWOBJLoader и все примеры ( код ). Оба погрузчика теперь готовы к объединению. Надеюсь, они будут соответствовать вашему плану на следующей неделе, в зависимости от моего свободного времени:
Направленный тест WWOBJLoader2:
https://kaisalmen.de/proto/test/wwparallels/main.src.html
Направленный пользователь универсального WorkerSupport :
https://kaisalmen.de/proto/test/meshspray/main.src.html
Тест большого файла OBJ:
https://kaisalmen.de/proto/test/wwobjloader2stage/main.src.html

Я обновлю приведенные выше примеры новым кодом, когда они будут доступны, и дам вам знать.
Обновление 30.07.2017: OBJLoader2 и WWOBJLoader2 теперь используют одинаковые парсеры. Они передают данные в общую функцию построителя напрямую или от worker.
Обновление 31.07.2017: WWOBJLoader2 больше нет. OBJLoader2 предоставляет parse и parseAsync , load и run (подача осуществляется LoaderDirector или вручную)

Обновление 2017-08-09:
Обновление перенесено в новый пост.

OBJLoader2 - это сигнатура и поведение, снова совместимые с OBJLoader (я сломал это во время эволюции), OBJLoader2 предоставляет parseAsync и load с useAsync Флаг
https://github.com/kaisalmen/WWOBJLoader/tree/V2.0.0-Beta/src/loaders

Я извлек классы LoaderSupport (не зависящие от OBJ), которые служат утилитами и необходимыми инструментами поддержки. Их можно было бы повторно использовать для других потенциальных загрузчиков на базе рабочих. Весь приведенный ниже код я поместил в пространство имен THREE.LoaderSupport чтобы выделить его зависимость от OBJLoader2 :

  • Builder : Для построения общей сетки.
  • WorkerDirector : Создает загрузчики посредством отражения, обрабатывает PrepData в очереди с настроенным количеством рабочих. Используется для полной автоматизации загрузчиков (демо MeshSpray и Parallels)
  • WorkerSupport : служебный класс для создания рабочих процессов из существующего кода и установления простого протокола связи.
  • PrepData + ResourceDescriptor : Описание, используемое для автоматизации или просто для единого описания среди примеров.
  • Commons : Возможный базовый класс для загрузчиков (объединяет общие параметры)
  • Callbacks : (onProgress, onMeshAlter, onLoad) используется для автоматизации и направления, а LoadedMeshUserOverride используется для получения информации из onMeshAlter (добавление нормалей в тесте objloader2 ниже)
  • Validator : проверка нулевых / неопределенных переменных

@mrdoob @jbaicoianu OBJLoader2 теперь обертывает синтаксический анализатор, как предлагается (он настроен с параметрами, глобально установленными или полученными PrepData для запуска). Builder получает каждую сырую сетку, а синтаксический анализатор возвращает базовый узел, но помимо этого он соответствует чертежу.
В OBJLoader2 все еще есть вспомогательный код для сериализации парсера, который, вероятно, не нужен.
Строитель нуждается в очистке, так как объект контракта / параметра для функции buildMeshes по-прежнему сильно зависит от загрузки OBJ и, следовательно, все еще считается строящимся.

Код нуждается в доработке, но затем он готов для обратной связи, обсуждения, критики и т. Д. 😄

Примеры и тесты

Загрузчик OBJ с использованием запуска и загрузки:
https://kaisalmen.de/proto/test/objloader2/main.src.html
Загрузчик OBJ с использованием run async и parseAsync:
https://kaisalmen.de/proto/test/wwobjloader2/main.src.html
Направленное использование run async OBJLoader2:
https://kaisalmen.de/proto/test/wwparallels/main.src.html
Направленное использование универсальной WorkerSupport:
https://kaisalmen.de/proto/test/meshspray/main.src.html
Тест большого файла OBJ:
https://kaisalmen.de/proto/test/wwobjloader2stage/main.src.html

Хорошо выглядеть! Известны ли вам об этих изменениях в OBJLoader ? # 11871 565c6fd0f3d9b146b9434e5fccfa2345a90a3842

Да, мне нужно это портировать. Я предложил некоторые воспроизводимые измерения производительности. Начну работать над обоими в эти выходные. Когда планируете выпустить r87? Поддержка N-gon может быть в зависимости от даты.

Обновление статуса ( код ):
Созданные воркеры теперь могут настраивать любой синтаксический анализатор внутри воркера с помощью параметров, полученных в сообщении. WorkerSupport предоставляет реализацию ( код ) эталонного рабочего рабочего, который может быть полностью заменен собственным кодом при желании или в случае необходимости.
Рабочий создаст и запустит синтаксический анализатор в методе run метода WorkerRunnerRefImpl ( Parser доступен внутри рабочей области; this.applyProperties вызывает сеттеры или свойства парсер):

WorkerRunnerRefImpl.prototype.run = function ( payload ) {
    if ( payload.cmd === 'run' ) {

        console.log( 'WorkerRunner: Starting Run...' );

        var callbacks = {
            callbackBuilder: function ( payload ) {
                self.postMessage( payload );
            },
            callbackProgress: function ( message ) {
                console.log( 'WorkerRunner: progress: ' + message );
            }
        };

        // Parser is expected to be named as such
        var parser = new Parser();
        this.applyProperties( parser, payload.params );
        this.applyProperties( parser, payload.materials );
        this.applyProperties( parser, callbacks );
        parser.parse( payload.buffers.input );

        console.log( 'WorkerRunner: Run complete!' );

        callbacks.callbackBuilder( {
            cmd: 'complete',
            msg: 'WorkerRunner completed run.'
        } );

    } else {

        console.error( 'WorkerRunner: Received unknown command: ' + payload.cmd );

    }
};

Сообщение от OBJLoader2.parseAsync выглядит так:

this.workerSupport.run(
    {
        cmd: 'run',
        params: {
            debug: this.debug,
            materialPerSmoothingGroup: this.materialPerSmoothingGroup
        },
        materials: {
            materialNames: this.materialNames
        },
        buffers: {
            input: content
        }
    },
    [ content.buffer ]
);

Объект сообщения зависит от загрузчика, но конфигурация синтаксического анализатора в работнике является общей.
Код, используемый в связанных примерах в предыдущем посте, был обновлен до последней версии.

Я думаю, что эволюция OBJLoader2 и извлечение вспомогательных функций достигли точки, когда требуется ваше мнение. Когда все примеры будут перенесены из репозитория в указанную выше ветку, я открою PR с полным обзором, а затем запрошу обратную связь.

К вашему сведению, вот незавершенная работа по использованию ImageBitmapLoader воркера, как описано выше. Возможно, что более интересно, некоторые точные цифры результатов: https://github.com/mrdoob/three.js/pull/12456

firefox createImageBitmap, причина в том, что они не поддерживают параметр словаря, поэтому он не поддерживает ориентацию изображения или преобразование цветового пространства. это делает большинство приложений API бесполезными.

Это прискорбно. ☹️

@mrdoob Планируете ли вы переключить ImageLoader на ImageBitmapLoader в TextureLoader потому что ImageBitmap должен быть менее блокирующим при загрузке в текстуру? createImageBitmap() похоже, работает с FireFox, если мы передаем только первый аргумент. (Возможно, нам не нужно передавать второй и более аргументов через TextureLoader ?)

return createImageBitmap( blob );

На самом деле важно, чтобы createImageBitmap () поддерживал словарь параметров. В противном случае вы не можете изменить такие вещи, как ориентация изображения (flip-Y) или указать предварительно умноженную альфа. Дело в том, что вы не можете использовать WebGLRenderingContext.pixelStorei для ImageBitmap . Из спецификации :

_Если TexImageSource является ImageBitmap, то эти три параметра (UNPACK_FLIP_Y_WEBGL, UNPACK_PREMULTIPLY_ALPHA_WEBGL, UNPACK_COLORSPACE_CONVERSION_WEBGL) будут проигнорированы. Вместо этого следует использовать эквивалент ImageBitmapOptions для создания ImageBitmap с желаемым форматом.

Поэтому я думаю, что мы можем переключиться на ImageBitmapLoader если FF поддерживает словарь параметров. Кроме того, такие свойства, как Texture.premultiplyAlpha и Texture.flipY сейчас не работают с ImageBitmap . Я имею в виду, что если пользователи устанавливают их, они не будут влиять на текстуру на основе ImageBitmap что несколько прискорбно.

Ах хорошо. Я пропустил эту спецификацию.

Здесь также обсуждается важность словаря опций:

https://bugzilla.mozilla.org/show_bug.cgi?id=1335594

Ошибки на bugzilla (https://bugzilla.mozilla.org/show_bug.cgi?id=1367251, https://bugzilla.mozilla.org/show_bug.cgi?id=1335594) оставались нетронутыми в течение ... двух лет сейчас? Я не думал, что им понадобится так много времени, чтобы это исправить.

Проблема в том, что «технически» эта функция поддерживается в FF, но на практике бесполезна. Чтобы использовать его, у нас может быть один путь для Chrome, который его использует, и другой путь для других браузеров, который его не использует. Проблема в том, что, поскольку у Firefox есть такая функция, нам пришлось бы выполнять сниффинг UA, что отстой.

Практическое решение - выполнить обнаружение функций: построить изображение 2x2, используя cIB с флагом переворота, а затем прочитать его и убедиться, что значения верны.

Что касается ошибок FireFox, я также собираюсь связаться с ними внутри компании. Посмотрим, нужно ли нам обходное решение после того, как мы узнаем их план.

Ошибки на bugzilla (https://bugzilla.mozilla.org/show_bug.cgi?id=1367251, https://bugzilla.mozilla.org/show_bug.cgi?id=1335594) оставались нетронутыми в течение ... двух лет сейчас? Я не думал, что им понадобится так много времени, чтобы это исправить.

Да, извините за это, я действительно какое-то время не следил за этим -_-

Проблема в том, что «технически» эта функция поддерживается в FF, но на практике бесполезна. Чтобы использовать его, у нас может быть один путь для Chrome, который его использует, и другой путь для других браузеров, который его не использует. Проблема в том, что, поскольку у Firefox есть такая функция, нам пришлось бы выполнять сниффинг UA, что отстой.

Практическое решение - выполнить обнаружение функций: построить изображение 2x2, используя cIB с флагом переворота, а затем прочитать его и убедиться, что значения верны.

Да, я согласен с тем, что оба решения действительно отстой, и мы должны стараться избегать их, поэтому, прежде чем копаться в любом из них, давайте посмотрим, сможем ли мы разблокировать его на нашей стороне.

Я сделал тест производительности загрузки ImageBitmap . Загрузка текстуры каждые 5 секунд.

Вы можете сравнить Regular Image с ImageBitmap.

https://rawgit.com/takahirox/three.js/ImageBitmapTest/examples/webgl_texture_upload.html (обычное изображение)
https://rawgit.com/takahirox/three.js/ImageBitmapTest/examples/webgl_texture_upload.html?imagebitmap (ImageBitmap)

В моих окнах я вижу

| Браузер | 8192x4096 JPG 4.4MB | 2048x2048 PNG 4.5MB |
| ---- | ---- | ---- |
| Изображение Chrome | 500 мс | 140 мс |
| Chrome ImageBitmap | 165 мс | 35 мс |
| Изображение FireFox | 500 мс | 40 мс |
| FireFox ImageBitmap | 500 мс | 60 мс |

( texture.generateMipmaps равно true )

Мои мысли

  1. Повышение производительности в 3 раза с ImageBitmap в Chrome. Очень хорошее улучшение.
  2. FireFox теперь имеет проблемы с производительностью ImageBitmap? Вы тоже можете попробовать на своем компьютере? Приветствуется примерка и на мобильном телефоне.
  3. Даже с ImageBitmap загрузка текстуры все еще блокирует большие текстуры. Может быть, нам нужна техника постепенной частичной загрузки или что-то для неблокирующего.

Даже с ImageBitmap загрузка текстуры все еще блокирует большие текстуры. Может быть, нам нужна техника частичной загрузки или что-то для неблокирующего.

Я предполагаю, что одним из решений этой проблемы может быть использование формата сжатия текстур и отказ от JPG или PNG (и, следовательно, ImageBitmap ). Было бы интересно увидеть некоторые данные о производительности в этом контексте.

Да, согласен. Но я думаю, что мы, вероятно, все еще видим блокировку для больших текстур, особенно на устройствах с низким энергопотреблением, таких как мобильные. В любом случае, сначала оцените производительность.

Или используйте запланированный / requestIdleCallback texSubImage2D

rIC = requestIdleCallback?

да, я сделал ниндзя править

OK. Да согласен.

Кстати, я еще не знаком с сжатой текстурой. Позвольте мне подтвердить мое понимание. Мы не можем использовать сжатую текстуру с ImageBitmap потому что compressedTexImage2D не принимает ImageBitmap , верно?

https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/compressedTexImage2D

Я вернулся, чтобы пересмотреть свои старые эксперименты с TiledTextureLoader - похоже, теперь они вызывают сбой и перезапуск моего видеодрайвера :(

(править: на самом деле, похоже, что даже загрузка самой большой текстуры (16k x 16k - https://baicoianu.com/~bai/three.js/examples/textures/dotamap1_25.jpg) непосредственно в Chrome вызывает сбой. Раньше это работало нормально, поэтому, похоже, есть некоторый регресс в обработке изображений Chrome)

Я провел несколько экспериментов с использованием генераторов requestIdleCallback, ImageBitmap и ES6, чтобы разбить большую текстуру на несколько частей для загрузки в графический процессор. Я использовал фреймбуфер, а не обычную текстуру, потому что даже если вы используете texSubimage2D для заполнения данных изображения, вам все равно нужно предварительно выделить память, что требует загрузки кучи пустых данных в графический процессор, тогда как фреймбуфер может быть создан и инициализируется одним вызовом GL.

Репозиторий для этих изменений по-прежнему доступен здесь https://github.com/jbaicoianu/THREE.TiledTexture/

Некоторые заметки из того, что я помню об экспериментах:

  • requestIdleCallback определенно помог уменьшить джанк при загрузке текстур за счет значительного увеличения общего времени загрузки.
  • Приложив немного дополнительных усилий, это можно смягчить, сначала загрузив уменьшенную версию текстуры, а затем заполнив данные в полном разрешении в более медленном темпе.
  • Генераторы ES6 помогли упростить понимание кода и облегчить его написание, не тратя впустую память, но, вероятно, в этом нет необходимости.

Мои результаты были похожими: был компромисс между скоростью загрузки и резкостью. (Кстати, я создал этот https://github.com/spite/THREE.UpdatableTexture).

Я думаю, что для работы второго варианта в WebGL 1 вам действительно понадобятся две текстуры или, по крайней мере, модификаторы UV-координат. Я думаю, что в WebGL 2 проще копировать источники, размер которых отличается от целевой текстуры.

Да, с texSubImage2D я думаю, что такое изменение размера было бы невозможно, но при использовании фреймбуфера я использую OrthographicCamera для рендеринга плоскости с фрагментом текстуры, так что это просто вопрос изменения масштаба плоскости для этот вызов на ничью.

По поводу проблемы с производительностью ImageBItmap в FireFox я обнаружил ошибку на bugzilla

https://bugzilla.mozilla.org/show_bug.cgi?id=1486454

Я пытался лучше понять, когда данные, связанные с текстурой, действительно загружаются в графический процессор и наталкиваются на этот поток. В моем конкретном случае использования меня НЕ беспокоит загрузка и декодирование локальных файлов jpeg / gif в текстуры, меня беспокоит только попытка предварительной загрузки данных текстуры в графический процессор. После прочтения этой ветки я должен признаться, что не совсем уверен, решает ли она обе проблемы или только первую? Учитывая, что меня волнует только последнее, нужно ли мне искать другое решение или здесь есть что-то, что поможет принудительно загрузить данные текстуры в графический процессор?

Была ли эта страница полезной?
0 / 5 - 0 рейтинги