Try this AI suggestion:
PlayCanvas WebGL2 Canvas Image Download Fix
Problem
When using PlayCanvas with WebGL2, calling canvas.toDataURL() returns an empty/transparent image, while the same code works correctly with WebGPU.
Root Cause
The preserveDrawingBuffer: true option is being passed to the Application constructor in graphicsDeviceOptions, but by that time the WebGL context has already been created by createGraphicsDevice() without that option.
In WebGL, when preserveDrawingBuffer is false (the default), the drawing buffer is cleared after each frame is presented to the screen. This means when you call canvas.toDataURL(), the buffer is already empty.
Solution
Move preserveDrawingBuffer: true into the createGraphicsDevice() options:
pc.createGraphicsDevice(canvas, {
deviceTypes: [pc.DEVICETYPE_WEBGL2],
preserveDrawingBuffer: true // Must be here, not in Application constructor
}).then((device) => {
const app = new pc.Application(canvas, {
mouse: new pc.Mouse(canvas),
touch: new pc.TouchDevice(canvas),
elementInput: new pc.ElementInput(canvas),
graphicsDevice: device
// graphicsDeviceOptions is not needed here
});
// ... rest of your code
});
Why WebGPU Works Without This
WebGPU handles the backbuffer differently. Its canvas configuration includes GPUTextureUsage.COPY_SRC which allows copying data out of the canvas, and WebGPU doesn’t have the same “drawing buffer is cleared after present” behavior that WebGL has by default.
Complete Working Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>PlayCanvas Green Cube Example</title>
<script src="https://code.playcanvas.com/playcanvas-2.14.4.js"></script>
<style>
body { margin: 0; overflow: hidden; }
canvas { display: block; }
</style>
</head>
<body>
<canvas id="application-canvas"></canvas>
<button id="download-btn" style="position:absolute;top:10px;left:10px;z-index:10;">Download Image</button>
<script>
const canvas = document.getElementById('application-canvas');
pc.createGraphicsDevice(canvas, {
deviceTypes: [pc.DEVICETYPE_WEBGL2],
preserveDrawingBuffer: true
}).then((device) => {
const app = new pc.Application(canvas, {
mouse: new pc.Mouse(canvas),
touch: new pc.TouchDevice(canvas),
elementInput: new pc.ElementInput(canvas),
graphicsDevice: device
});
app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
app.setCanvasResolution(pc.RESOLUTION_AUTO);
app.start();
// Create a camera
const camera = new pc.Entity();
camera.addComponent('camera', {
clearColor: new pc.Color(0.2, 0.2, 0.2)
});
camera.setPosition(0, 0, 3);
app.root.addChild(camera);
// Create a directional light
const light = new pc.Entity();
light.addComponent('light');
light.setEulerAngles(45, 30, 0);
app.root.addChild(light);
// Create a green material
const material = new pc.StandardMaterial();
material.diffuse = new pc.Color(0, 1, 0);
material.update();
// Create a cube
const cube = new pc.Entity();
cube.addComponent('model', {
type: 'box'
});
cube.model.material = material;
app.root.addChild(cube);
cube.rotate(45, 45, 0);
});
// Download button handler
document.getElementById('download-btn').addEventListener('click', function() {
const dataURL = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.href = dataURL;
link.download = 'canvas-image.png';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
</script>
</body>
</html>