How to toggle preserveDrawingBuffer in three.js? - javascript

Basically, I want the setup where I could go to preserveDrawingBuffer=true, render the scene once, grab the screenshot, and go back. However, this poses two problems:
there is no method in renderer to dispose all the buffers,
canvas goes black if I do
renderer = new THREE.WebGLRenderer({canvas:renderer.domElement,preserveDrawingBuffer:true});
How do I do this properly?
EDIT: I did not find the way to toggle this, so I had to clone scene and create second renderer instead to make the screenshot. See https://github.com/mrdoob/three.js/issues/189

You don't need preserveDrawingBuffer: true to take a screenshot. What you need is to take the screenshot immediately after rendering. A screenshot is guaranteed to work as long as you take it after rendering but before exiting the current event.
So for example this will always work
renderer.render( scene, camera );
var screenshot = renderer.domElement.toDataURL();
Whereas this will only work randomly if you're luck
someElement.addEventListener('click', function() {
// this is not immediately after rendering. you have no
// idea when it is relative to rendering.
var screenshot = renderer.domElement.toDataURL();
});
Most of the THREE.js examples have a render function if you need to take a screenshot when the user requests one you could do this
someElement.addEventListener('click', function() {
render();
var screenshot = renderer.domElement.toDataURL();
});
Or you could do this
var takeScreenshot;
function render() {
...
if (takeScreenshot) {
takeScreenshot = false;
var screenshot = renderer.domElement.toDataURL();
}
}
someElement.addEventListener('click', function() {
takeScreenshot = true;
});
Or any number of other ways to just make sure you take the screenshot immediately after rendering.

I have also encountered this problem.my screenshot is blank in threejs webgl canvas, because not set renderer = new THREE.WebGLRenderer({canvas:renderer.domElement,preserveDrawingBuffer:true});,
Here is my way to toggle preserveDrawingBuffer.
let canvas = this.renderer.domElement;
canvas.getContext('webgl' , {preserveDrawingBuffer: true});
this.render();
var data = {
image: canvas.toDataURL(),
};
canvas.getContext('webgl' , {preserveDrawingBuffer: false});

Related

Three.JS instance can't be re-created

I'm trying to destroy (or destruct or dispose) an 'instance' of Three.JS using this:
Full example: https://jsfiddle.net/v7oLzs4m/58/
function kill() {
const rendererDomWas = renderer.domElement;
renderer.dispose();
controls.dispose();
renderer = scene = camera = controls = null;
document.body.removeChild( rendererDomWas );
shouldRender = false;
}
function animate() {
if(!shouldRender) {return} // no more requesting animation frames if 'shouldRender' is false
frameID = window.requestAnimationFrame( animate );
renderer.render( scene, camera );
}
(i.e. disposing, setting references to null, and stopping the draw loop from touching renderer while shouldRender is false)
It appears to work at first (The renderer content stops showing) but when I re-create the instance, it never comes back.
It's as if something is still... holding onto the GLContext which prevents it from being invoked again.
Why can't I re-create a new instance of Three.JS?
I'm not sure why this works (since I was already doing this in my Fiddle...)
But it turns out the secret ingredient (for me) to letting Three.JS get GC'd/disposed is this:
window.cancelAnimationFrame(frameID);
I suppose that this stops the render function from being stored in the hardware-level draw loop, which holds references to GLContext (and the chain)
The GC can't ever happen unless the 'loop is broken'
(Correct me if I'm wrong please)

How to force a redraw of the JointJS canvas?

How does one force a set of linkages to render in JointJS?
We are finding that drawing the linkages between nodes is taking too long. We would like to show our users progress of the linkages even if it slows the overall length down. We are using the Javascript library JointJS at jointjs.com and have tried multiple Javascript methods for forcing redraw of the DOM but nothing seems to be triggering a redraw.
Here are examples of code we have tried to trigger a redraw of even a portion of the DOM let alone the JointJS canvas:
// Fails
// var logger = document.getElementById('log');
// logger.innerHTML = "Processing..."; // + node_name;
// logger.offsetHeight;
// Fails
//$('#logger').redraw();
// Fails
// $("#logger").hide().html(node_name).fadeIn('fast');
// Fails
// document.getElementById('log').style.display = 'none';
// document.getElementById('log').style.display = 'block';
Any ideas what might work?
You can just use backbone's view.render().
For example, to do an arbitrary link:
let link = graph.getLinks()[0];
let view = paper.findViewByModel(link.id).render();

Canvas toDataURL() returns blank image

I'm using glfx.js to edit my image but when I'm trying to get that image's data using the toDataURL() function I get a blank image (width the same size as the original image).
The strange thing is that in Chrome the script works perfect.
What I want to mention is that the image is loaded in canvas using the onload event:
img.onload = function(){
try {
canvas = fx.canvas();
} catch (e) {
alert(e);
return;
}
// convert the image to a texture
texture = canvas.texture(img);
// draw and update canvas
canvas.draw(texture).update();
// replace the image with the canvas
img.parentNode.insertBefore(canvas, img);
img.parentNode.removeChild(img);
}
Also my image's path is on the same domain;
The problem (in Firefox) is when i hit the save button. Chrome returns the expected result but Firefox return this:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA7YAAAIWCAYAAABjkRHCAAAHxklEQVR4nO3BMQEAAADCoPVPbQZ/oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
... [ lots of A s ] ...
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAzwD6aAABkwvPRgAAAABJRU5ErkJggg==
What could cause this result and how can I fix it?
Most likely there's some async event between the time you draw to the canvas and the time you call toDataURL. By default the canvas is cleared after every composite. Either prevent the canvas from being cleared by creating the WebGL context with preserveDrawingBuffer: true as in
var gl = canvas.getContext("webgl", {preserveDrawingBuffer: true});
or make sure toDataURL is called before exiting whatever event you're using to render. For example if you do this
function render() {
drawScene();
requestAnimationFrame(render);
}
render();
And somewhere else do this
someElement.addEventListener('click', function() {
var data = someCanvas.toDataURL();
}, false);
Those 2 events, the animation frame, and the click are not in sync and the canvas may be cleared between calling them. Note: The canvas won't appear cleared as it's double buffered but the buffer toDataURL and other commands that effect that buffer are looking at is cleared.
The solution is either use preserveDrawingBuffer or make your call to toDataURL inside the same event as rendering. For example
var captureFrame = false;
function render() {
drawScene();
if (captureFrame) {
captureFrame = false;
var data = someCanvas.toDataURL();
...
}
requestAnimationFrame(render);
}
render();
someElement.addEventListener('click', function() {
captureFrame = true;
}, false);
What's the point of preserveDrawingBuffer: false which is the default? It can be significantly faster, especially on mobile to not have to preserve the drawing buffer. Another way to look at it is the browser needs 2 copies of your canvas. The one you're drawing to and the one it's displaying. It has 2 ways to deal with these 2 buffers. (A) double buffer. Let you draw to one, display the other, swap the buffers when you're done rendering which is inferred from exiting any event that issued draw commands (B) Copy the contents of the buffer you're drawing to do the buffer that's being displayed. Swapping is much faster than copying. So, swapping is the default. It's up to the browser what actually happens. The only requirement is that if preserveDrawingBuffer is false that the drawing buffer get cleared after a composite (which is yet another async event and therefore unpredictable) if preserveDrawingBuffer is true then it must copy so that the drawingbuffer's contents is preserved.
Note that once a canvas has a context it will always have the same context. So in other words let's say you change the code that initializes the WebGL context but you still want to set preserveDrawingBuffer: true
There are at least 2 ways.
find the canvas first, get a context on it
since the code later will end up with the same context.
<script>
document.querySelector('#somecanvasid').getContext(
'webgl', {preserveDrawingBuffer: true});
</script>
<script src="script/that/will/use/somecanvasid.js"></script>
Because you've already created a context for that canvas whatever script comes after will get the same context.
augment getContext
<script>
HTMLCanvasElement.prototype.getContext = function(origFn) {
return function(type, attributes) {
if (type === 'webgl') {
attributes = Object.assign({}, attributes, {
preserveDrawingBuffer: true,
});
}
return origFn.call(this, type, attributes);
};
}(HTMLCanvasElement.prototype.getContext);
</script>
<script src="script/that/will/use/webgl.js"></script>
In this case any webgl context created after augmenting the getContext will have preserveDrawingBuffer set to true.

How do you save an image from a Three.js canvas?

How do you save an image from a Three.js canvas?
I'm attempting to use Canvas2Image but it doesn't like to play with Threejs. Since the canvas isn't defined until it has a div to attach the canvas object to.
http://ajaxian.com/archives/canvas2image-save-out-your-canvas-data-to-images
Since the toDataURL is a method of canvas html element, that will work for 3d context too. But you have to take care of couple of things.
Make sure when the 3D context is initialized you set preserveDrawingBuffer flag to true, like so:
var context = canvas.getContext("experimental-webgl", {preserveDrawingBuffer: true});
Then user canvas.toDataURL() to get the image
In threejs you would have to do the following when the renderer is instantiated:
new THREE.WebGLRenderer({
preserveDrawingBuffer: true
});
Also, keep in mind this can have performance implications. (Read: https://github.com/mrdoob/three.js/pull/421#issuecomment-1792008)
This is only for webgl renderer, in case of threejs canvasRenderer though, you can simply do renderer.domElement.toDataURL(); directly, no initialization parameter needed.
My webgl experiment: http://jsfiddle.net/TxcTr/3/ press 'p' to screenshot.
Props to gaitat, I just followed the link in his comment to get to this answer.
I read the conversation posted by Dinesh (https://github.com/mrdoob/three.js/pull/421#issuecomment-1792008) and came up with a solution that won't slow down your application.
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
if(getImageData == true){
imgData = renderer.domElement.toDataURL();
getImageData = false;
}
}
With this you can leave the preserveDrawingBuffer-Flag at false and still get the image from THREE.js. Simply set getImageData to true and call render() and you are good to go.
getImageData = true;
render();
console.debug(imgData);
Hope this helps people like me who need the high fps :)
use canvas to create the url, and then the same can be downloaded
function createImage(saveAsFileName) {
var canvas = document.getElementById("canvas");
var url = canvas.toDataURL();
var link = document.createElement('a');
link.setAttribute('href', url);
link.setAttribute('target', '_blank');
link.setAttribute('download', saveAsFileName);
link.click();
}

Three js memory management

I have a large scene with a lot of Mesh and MorphAnimMesh. I want to free memory when the meshes are removed. If i know right this is the best way to do:
for ( var i = scene.children.length - 1; i >= 0 ; i -- ) {
var obj = scene.children[i];
scene.remove(obj);
obj.deallocate();
obj.geometry.deallocate();
obj.material.deallocate();
obj.material.map.deallocate();
}
if i check the memory usage at task manager after this, nothing changes. ( tried to wait a few min for GC but nothing. ) Google Chrome memory snapshot shows the objects still there. morphTargets in THREE.Geometry #1862203 etc.
Tried to set the obj to null, but still no memory decrease.
Any idea what am i doing wrong?
Its a game with levels and the player can change from one to another. After a few change memory usage increases to really high. Thats why i want to remove all object from memory before the level change.
Most likely, you need to add some, or all, of the following:
geometry.dispose();
material.dispose();
texture.dispose();
Check out these examples:
http://mrdoob.github.com/three.js/examples/webgl_test_memory.html
http://mrdoob.github.com/three.js/examples/webgl_test_memory2.html
three.js r.60
I did try all the dispose and deallocate methods but nothing worked.
Then I did the following for my ionic application which is using webgl renderer to render a 360 image.
this.renderer = new THREE.WebGLRenderer({ antialias: true });
RicohView.prototype.stopRendering = function () {
this.canRender = false;
this.renderer.forceContextLoss();
this.renderer.dispose();
console.log('renderer disposed');
cancelAnimationFrame(this.requestId);
}
requestId is something which can be captured from
this.requestId = requestAnimationFrame(render);

Categories