This answer is just based on some light research I’ve just done. My understanding is that preserveDrawingBuffer is a bit misleading- it just controls whether WebGL uses a single drawing buffer that gets copied / cleared on each draw (which is slow), versus using a double buffer with a swap each draw (more memory intensive but faster and generally preferred). Without preserveDrawingBuffer turned on, the data in buffer still exists and only gets swapped out on the next draw, so as long as you write it out before the next draw, in theory it works.
Got some of that info from this page, if you search for preserveDrawingBuffer
I have extracted some descriptions from the above content:
By default, preserveDrawingBuffer=false, swaps these buffers once rendering is done. And after the swap, the new drawing buffer is cleared.
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.
A very important time point is “rendering completion”. After rendering is completed, the drawing buffer will be cleared. Can it be considered as rendering completion after calling viewer. render()? If so, why can we still retrieve the contents of the drawing buffer?
It could very well be the case that this is simply undefined behavior that this sandcastle is leveraging and getting away with.
Per the WebGL spec: “…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”
That doesn’t mean it won’t work in some implementations, just that it shouldn’t be relied upon. That, or the viewer.render() function isn’t actually invoking a WebGL render when called this way, but that would require deeper investigation to confirm.