Сохранить холст Empscripten webGL как изображение в JS

У меня есть холст webGL, управляемый Emscripten, который мне нужно сохранить как изображение из обработчика Javascript. Допустим, есть простая кнопка JS «Сохранить».

<script type="text/javascript">
var Exporter = {
    preRun: [],
    postRun: [],
    save: function() {
    var c=Module.canvas;
    var d=c.toDataURL("image/png");
    var w=window.open('about:blank','image from canvas');
    w.document.write("<img src='"+d+"' alt='from canvas'/>");
    }
};
</script>
<input type="button" value="Save" onclick="Exporter.save()" />

По умолчанию в контексте webGL для параметра preserveDrawingBuffer установлено значение false, поэтому результирующее изображение будет пустым.

Чтобы на изображении отображалась визуализированная сцена webGL, мне нужно добавить preserveDrawingBuffer: true к атрибутам, переданным в вызове getContext внутри моего скомпилированного кода Empscripten. Я могу сделать это, вручную отредактировав скомпилированный код empscripten js; результирующее изображение будет правильным, но я бы хотел избежать этого хака - мне пришлось бы делать это после каждой перекомпиляции.

Есть ли более простой и чистый способ добавить preserveDrawingBuffer к webGLContextAttributes извне? то есть как вариант компиляции для emcc, какой-то параметр SDL внутри кода C или из Javascript на странице хостинга?

ОБНОВЛЕНИЕ См. ниже решение; не связанная с этим проблема, с которой я столкнулся, заключалась в том, что сохраненное изображение имело меньшую битовую глубину, а сглаженные линии выглядели довольно плохо. Использование c.toDataURL( "image/jpeg" ) решило это.


person Tomas Andrle    schedule 15.05.2017    source источник
comment
Я совсем не знаю Эмскриптена; не говоря уже о webgl, но лучший способ экспортировать холст webgl — дождаться следующего цикла рендеринга и вызвать toDataURL перед выпуском текущего потока js. Буфер рисования останется, и вы улучшите производительность по сравнению с вариантом preserveDrawingBuffer.   -  person Kaiido    schedule 15.05.2017
comment
@Kaiido Проблема с предложенным вами подходом заключается в том, что в моем случае я не рендерю все время с фиксированной частотой кадров, а только после получения новых событий ввода. Кнопка Javascript для сохранения изображения может быть нажата в любое время. Поэтому мне пришлось бы сначала заставить холст перерисовать, а затем из кода рендеринга холста (C) вызвать другую функцию Javascript для захвата буфера до конца кадра. Не невозможно, но пока я предпочитаю перфоманс.   -  person Tomas Andrle    schedule 15.05.2017


Ответы (1)


Ну, во-первых, весь emscripten и все его библиотеки имеют открытый исходный код, поэтому вы можете просто изменить их.

В частности, скопируйте library_gl.js в папку вашего проекта, а затем удалите -lGL и добавьте --js-library library_gl.js в свой скрипт сборки, после чего вы сможете взломать локальный library_gl.js, чтобы делать все, что захотите.

В противном случае я вообще не знаю SDL, но вы можете просто получить контекст самостоятельно, прежде чем вызывать код emscripten. Холст может иметь только один контекст, если вы снова вызовете getContext для того же типа контекста, вы получите тот же контекст. Другими словами, если ваш JavaScript сначала создает контекст, код emscripten получит тот же контекст.

так что это должно работать

 theCanvasElement.getContext("webgl", {preserveDrawingBuffer: true});

 ... now execute emscripten and have it use `theCanvasElement`

Если вы даже этого не можете сделать, вы можете переопределить getContext

HTMLCanvasElement.prototype.getContext = (function(oldGetContextFn) {
  return function(type, attrs) {
    attrs = attrs || {};
    if (type === "webgl") {
      attrs.preserveDrawingBuffer = true;
    }
    return oldGetContextFn.call(this, type, attrs);
  };
}(HTMLCanvasElement.prototype.getContext));
person gman    schedule 15.05.2017
comment
Да, получение контекста до того, как это сделает emscripten, и предоставление ему нужных параметров кажется разумной идеей, я попробую. Все еще хак, но, по крайней мере, мне придется сделать это только один раз. - person Tomas Andrle; 15.05.2017
comment
Я не уверен, почему вы считаете это взломом. Если вы хотите решить эту проблему на уровне SDL, вам, вероятно, потребуется отправить патч в библиотеку SDL, чтобы добавить некоторые флаги построения или отправьте патч в emscripten, чтобы добавить некоторые другие emscripten только открытые API C++, чтобы принудительно установить определенные флаги. - person gman; 20.05.2017
comment
не имел в виду это оскорбительно, просто так кажется, вероятно, потому, что он полагается на то, что он остается неизменным во второй раз, когда вызывается getContext. Может быть, это просто семантика API, должен быть вызов createContext? - person Tomas Andrle; 24.05.2017
comment
Вам нужно будет изменить apply на call здесь, чтобы переопределение getContext заработало. Это или использовать oldGetContextFn.apply(this, [type, attrs]). - person Benjamin; 08.01.2020