I am currently working on a demo that calls readPixels.
This answer on SO is most of the information I can find on the preserveDrawingBuffer
option.
While testing I've observed that in WebGL 2 this answer remains true - you have to set preserveDrawingBuffer
to true
.
Is this actually correct?
Is there an OpenGL equivalent for preserveDrawingBuffer
?
Is there any way to set preserveDrawingBuffer
to false
and still call readPixels?
This answer makes it seem like you could call gl.flush
instead.
How is preserveDrawingBuffer the same thing as flushing the context?
You don't need preserveDrawingBuffer: true
to call readPixels
. What you need is to call readPixels
before exiting the current event.
The spec says if you call any function that affects the canvas (gl.clear, gl.drawXXX) then the browser will clear the canvas after the next composite operation. When that composite operation happens is up to the browser. It could be after it processes several mouse events or keyboard events or click events. The order is undefined. What is defined is that it won't do it until the current event exits so
render
read
const gl = document.querySelector("canvas").getContext("webgl2");
render();
read(); // read in same event
function render() {
gl.clearColor(.25, .5, .75, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
}
function read() {
const pixel = new Uint8Array(4);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
log(pixel);
}
function log(...args) {
const elem = document.createElement("pre");
elem.textContent = [...args].join(' ');
document.body.appendChild(elem);
}
<canvas></canvas>
works where as
render
setTimeout(read, 1000); // some other event
does not work
const gl = document.querySelector("canvas").getContext("webgl2");
render();
setTimeout(read, 1000); // read in other event
function render() {
gl.clearColor(.25, .5, .75, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
}
function read() {
const pixel = new Uint8Array(4);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
log(pixel);
}
function log(...args) {
const elem = document.createElement("pre");
elem.textContent = [...args].join(' ');
document.body.appendChild(elem);
}
<canvas></canvas>
Note that since it's the composite operation (the browser actually drawing the canvas on the page with the rest of the HTML) that triggers the clear, if the canvas is not on the page then it's not composited and won't be cleared.
In other words the case that didn't work above does work here
// create an offscreen canvas. Because it's offscreen it won't be composited
// and therefore will not be cleared.
const gl = document.createElement("canvas").getContext("webgl2");
render();
setTimeout(read, 1000); // read in other event
function render() {
gl.clearColor(.25, .5, .75, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
}
function read() {
const pixel = new Uint8Array(4);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
log(pixel);
}
function log(...args) {
const elem = document.createElement("pre");
elem.textContent = [...args].join(' ');
document.body.appendChild(elem);
}
If you want to call readPixels
in some other event, like when the user clicks an element, then you have at least 2 options
Set preserveDrawingBuffer: true
Render again in your event
screenshotButton.addEventListener('click', () => {
render();
read();
});
From the spec section 2.2
WebGL presents its drawing buffer to the HTML page compositor immediately before a compositing operation, but only if at least one of the following has occurred since the previous compositing operation:
- Context creation
- Canvas resize
- clear, drawArrays, or drawElements has been called while the drawing buffer is the currently bound framebuffer
Before the drawing buffer is presented for compositing the implementation shall ensure that all rendering operations have been flushed to the drawing buffer. By default, after compositing the contents of the drawing buffer shall be cleared to their default values, as shown in the table above.
This default behavior can be changed by setting the preserveDrawingBuffer attribute of the WebGLContextAttributes object. If this flag is true, the contents of the drawing buffer shall be preserved until the author either clears or overwrites them. If this flag is false, attempting to perform operations using this context as a source image after the rendering function has returned can lead to undefined behavior. This includes readPixels or toDataURL calls, or using this context as the source image of another context's texImage2D or drawImage call.