After drawing a line, after a while, the browser produces a warning: a valid external instance reference no longer exists.
I wonder if it was because of my setVertexBuffer
in requestAnimationFrame
, or maybe for other reasons, I want to know where my error is, and how to solve it.
All Code:
struct MyVSInput {
@location(0) position : vec4f,
};
@vertex
fn myVSMain(v : MyVSInput) -> @builtin(position) vec4f {
return v.position;
}
@fragment
fn myFSMain() -> @location(0) vec4f {
return vec4f(1, 1, 0, 1);
}
import ShaderCode from "./basic.wgsl?raw";
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const canvas = document.querySelector( 'canvas' );
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const context = canvas.getContext( 'webgpu' );
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure( {
device,
format: presentationFormat,
alphaMode: 'premultiplied',
} );
const sampleCount = 4;
const module = device.createShaderModule( { code: ShaderCode } );
const pipeline = device.createRenderPipeline( {
layout: 'auto',
vertex: {
module,
buffers: [
{
arrayStride: 2 * 4,
attributes: [
{
shaderLocation: 0,
offset: 0,
format: 'float32x2'
},
],
},
],
},
fragment: {
module,
targets: [
{ format: presentationFormat },
],
},
primitive: {
topology: "line-strip",
},
multisample: {
count: sampleCount
}
} );
const pointsList: number[][] = [];
const buffers: GPUBuffer[] = [];
const pointsPerBuffer = 1024; // should make this much larger (16k) but keeping it small for testing.
let numPoints = 0;
const addPoint = ( x: number, y: number ) => {
let buffer = buffers[ buffers.length - 1 ];
const points = pointsList[ pointsList.length - 1 ];
const offset = points.length / 2;
if ( points.length > 0 && points.length % pointsPerBuffer * 2 === 0 ) {
// GPUBuffer需要扩容
const pop = buffers.pop();
pop.destroy();
buffer = device.createBuffer( {
size: buffer.size + pointsPerBuffer * 2 * 4,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
mappedAtCreation: true
} );
new Float32Array( buffer.getMappedRange() ).set( points );
buffer.unmap();
buffers.push( buffer );
}
device.queue.writeBuffer( buffer, offset * 2 * 4, new Float32Array( [ x, y ] ) );
points.push( x, y );
++numPoints;
};
const renderPassDescriptor: GPURenderPassDescriptor = {
colorAttachments: [
{
view: undefined, // Assigned later
resolveTarget: undefined,
clearValue: [ 0.2, 0.2, 0.2, 1.0 ],
loadOp: 'clear',
storeOp: 'store',
},
],
};
function render () {
const texture = device.createTexture( {
size: [ canvas.width, canvas.height ],
format: presentationFormat,
sampleCount: sampleCount,
usage: GPUTextureUsage.RENDER_ATTACHMENT
} );
renderPassDescriptor.colorAttachments[ 0 ].view = texture.createView();
const canvasTexture = context.getCurrentTexture();
renderPassDescriptor.colorAttachments[ 0 ].resolveTarget = canvasTexture.createView();
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass( renderPassDescriptor );
pass.setPipeline( pipeline );
buffers.forEach( ( buffer, i ) => {
pass.setVertexBuffer( 0, buffer );
pass.draw( pointsList[ i ].length / 2 );
} );
pass.end();
device.queue.submit( [ encoder.finish() ] );
requestAnimationFrame( render );
}
requestAnimationFrame( render );
function createBuffer ( buffers: GPUBuffer[] ) {
const buffer = device.createBuffer( {
size: pointsPerBuffer * 2 * 4,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
} );
buffers.push( buffer );
}
function createPoints ( pointsList: number[][] ) {
const points = [];
pointsList.push( points );
}
let needCreateBuffer = false;
let beginDraw = false;
canvas.addEventListener( "mousedown", ( event ) => {
if ( event.button === 0 ) {
needCreateBuffer = true;
beginDraw = true;
}
} );
canvas.addEventListener( "mouseup", ( event ) => {
if ( event.button === 0 ) {
beginDraw = false;
}
} );
canvas.addEventListener( "mousemove", ( event ) => {
if ( beginDraw === true ) {
if ( needCreateBuffer === true ) {
createBuffer( buffers );
createPoints( pointsList );
needCreateBuffer = false;
}
const rect = canvas.getBoundingClientRect();
const x = ( event.clientX - rect.left ) / rect.width * 2 - 1;
const y = ( event.clientY - rect.top ) / rect.height * -2 + 1;
addPoint( x, y );
}
} );
The code is creating a new multisample texture every frame and probably running out of memory.
A better pattern would be create one and keep it around unless it's the wrong size. If it is the wrong size, destroy the old one and create a new one.
body { margin: 0; }
canvas { display: block; }
<canvas></canvas>
<script type="module">
const ShaderCode = `
struct MyVSInput {
@location(0) position : vec4f,
};
@vertex
fn myVSMain(v : MyVSInput) -> @builtin(position) vec4f {
return v.position;
}
@fragment
fn myFSMain() -> @location(0) vec4f {
return vec4f(1, 1, 0, 1);
}
`;
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const canvas = document.querySelector( 'canvas' );
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const context = canvas.getContext( 'webgpu' );
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure( {
device,
format: presentationFormat,
alphaMode: 'premultiplied',
} );
const sampleCount = 4;
const module = device.createShaderModule( { code: ShaderCode } );
const pipeline = device.createRenderPipeline( {
layout: 'auto',
vertex: {
module,
buffers: [
{
arrayStride: 2 * 4,
attributes: [
{
shaderLocation: 0,
offset: 0,
format: 'float32x2'
},
],
},
],
},
fragment: {
module,
targets: [
{ format: presentationFormat },
],
},
primitive: {
topology: "line-strip",
},
multisample: {
count: sampleCount
}
} );
const pointsList/*: number[][]*/ = [];
const buffers/*: GPUBuffer[]*/ = [];
const pointsPerBuffer = 1024; // should make this much larger (16k) but keeping it small for testing.
let numPoints = 0;
const addPoint = ( x/*: number*/, y/*: number*/ ) => {
let buffer = buffers[ buffers.length - 1 ];
const points = pointsList[ pointsList.length - 1 ];
const offset = points.length / 2;
if ( points.length > 0 && points.length % pointsPerBuffer * 2 === 0 ) {
// GPUBuffer需要扩容
const pop = buffers.pop();
pop.destroy();
buffer = device.createBuffer( {
size: buffer.size + pointsPerBuffer * 2 * 4,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
mappedAtCreation: true
} );
new Float32Array( buffer.getMappedRange() ).set( points );
buffer.unmap();
buffers.push( buffer );
}
device.queue.writeBuffer( buffer, offset * 2 * 4, new Float32Array( [ x, y ] ) );
points.push( x, y );
++numPoints;
};
const renderPassDescriptor/*: GPURenderPassDescriptor*/ = {
colorAttachments: [
{
view: undefined, // Assigned later
resolveTarget: undefined,
clearValue: [ 0.2, 0.2, 0.2, 1.0 ],
loadOp: 'clear',
storeOp: 'store',
},
],
};
let texture;
function render () {
const canvasTexture = context.getCurrentTexture();
if (!texture || texture.width !== canvasTexture.width || texture.height !== canvasTexture.height) {
texture?.destroy();
texture = device.createTexture( {
size: [ canvas.width, canvas.height ],
format: presentationFormat,
sampleCount: sampleCount,
usage: GPUTextureUsage.RENDER_ATTACHMENT
} );
}
renderPassDescriptor.colorAttachments[ 0 ].view = texture.createView();
renderPassDescriptor.colorAttachments[ 0 ].resolveTarget = canvasTexture.createView();
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass( renderPassDescriptor );
pass.setPipeline( pipeline );
buffers.forEach( ( buffer, i ) => {
pass.setVertexBuffer( 0, buffer );
pass.draw( pointsList[ i ].length / 2 );
} );
pass.end();
device.queue.submit( [ encoder.finish() ] );
requestAnimationFrame( render );
}
requestAnimationFrame( render );
function createBuffer ( buffers/*: GPUBuffer[]*/ ) {
const buffer = device.createBuffer( {
size: pointsPerBuffer * 2 * 4,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
} );
buffers.push( buffer );
}
function createPoints ( pointsList/*: number[][]*/ ) {
const points = [];
pointsList.push( points );
}
let needCreateBuffer = false;
let beginDraw = false;
canvas.addEventListener( "mousedown", ( event ) => {
if ( event.button === 0 ) {
needCreateBuffer = true;
beginDraw = true;
}
} );
canvas.addEventListener( "mouseup", ( event ) => {
if ( event.button === 0 ) {
beginDraw = false;
}
} );
canvas.addEventListener( "mousemove", ( event ) => {
if ( beginDraw === true ) {
if ( needCreateBuffer === true ) {
createBuffer( buffers );
createPoints( pointsList );
needCreateBuffer = false;
}
const rect = canvas.getBoundingClientRect();
const x = ( event.clientX - rect.left ) / rect.width * 2 - 1;
const y = ( event.clientY - rect.top ) / rect.height * -2 + 1;
addPoint( x, y );
}
} );
</script>