-
Notifications
You must be signed in to change notification settings - Fork 667
Description
What is wrong?
While other textures seem to be deleting properly, if I apply the same immutable pipelined kernel over and over again, it causes a memory leak.
Where does it happen?
I've been using GPU.js version 2.16.0 (though reverting to other versions has not helped) in an app that I've been testing in Chromium and Firefox with Browserify on Arch Linux. The app is written in TypeScript and uses Express/React.
How do we replicate the issue?
WARNING: this code causes a memory leak, execute at your own risk; reducing the size or numIterations variables will change the size of the leak.
Also, in my use case, this code is being executed once a frame. In order to diagnose the issue without causing Chromium to crash, I've capped the framerate in my app to one frame a second. So, the loop that causes the issue is being executed once every second. (Creating the GPU and the kernels happens outside of the animation frames, so they are not the source of the leak)
const size = 256
const numIterations = 30
const gpu = new GPU()
const generateInput = gpu.createKernel(function() {
return [0, 0]
})
.setOutput([size, size])
.setPipeline(true)
const kernel = gpu.createKernel(function(input: number[][][]) {
const result = input[this.thread.y][this.thread.x]
return [result[0] + 1, result[1] + 1]
})
.setOutput([size, size])
.setPipeline(true)
.setImmutable(true)
.setArgumentTypes({ input: 'Array2D(2)' })
const input = generateInput()
kernel(input)
let frameCount = 0
let prevTimeStamp = Date.now()
const animate = () => {
frameCount++
const timeStamp = Date.now()
if (timeStamp - prevTimeStamp >= 1000) {
prevTimeStamp = timeStamp
frameCount = 0
}
if (frameCount === 1) {
// Loop where memory leak happens
for (let x = 0; x < numIterations; x++) {
let prevPass = kernel.texture
kernel(kernel.texture)
prevPass.delete()
}
}
requestAnimationFrame(animate)
}
I will note that changing the line from
const prevPass = kernel.texture
to
const prevPass = kernel.texture.clone()
makes no difference.
I have gone in circles attempting everything under the sun to fix this. I can believe that there's something I haven't tried yet, but I would be genuinely surprised. There are only two things that I've found that have seemed to help, but both of those solutions lead me to believe there maybe an issue with GPU.js itself.
How important is this (1-5)?
To me this is a 5. The algorithm that I'm trying to use in the actual app just can't work without repeated passes in some way. I haven't been able to find any workaround that will produce the same result while avoiding the memory leak. Also, since the loop needs to be called every frame, I need the kernel to be pipelined.
Expected behavior (i.e. solution)
The expected behavior is for there to be no memory leak from a loop like this, so long as the textures are deleted appropriately.
As mentioned before, there are two "solutions" I've found, but neither are very satisfactory.
The first "solution" is to go into into the module itself and modify it.
In gpu.js/src/backend/gl/texture/index.js there is a block of code:
delete() {
if (this._deleted) return;
this._deleted = true;
if (this.texture._refs) {
this.texture._refs--;
if (this.texture._refs) return;
}
this.context.deleteTexture(this.texture);
// TODO: Remove me
// if (this.texture._refs === 0 && this._framebuffer) {
// this.context.deleteFramebuffer(this._framebuffer);
// this._framebuffer = null;
// }
}
After deleting (in practice I've only ever commented it out) the if (this.texture._refs)... block so it looks like this:
delete() {
if (this._deleted) return;
this._deleted = true;
this.context.deleteTexture(this.texture);
// TODO: Remove me
// if (this.texture._refs === 0 && this._framebuffer) {
// this.context.deleteFramebuffer(this._framebuffer);
// this._framebuffer = null;
// }
}
Suddenly the memory leak is gone.
The second "solution" I've found is to attempt to delete the textures from previous passes more forcefully by changing the loop to
for (let x = 0; x < numIterations; x++) {
const prevPass = kernel.texture
kernel(kernel.texture)
kernel.context.deleteTexture(prevPass.texture)
}
Of course, that internal texture field is not public in Texture (At least in the version of GPU.js I'm using), so the TypeScript compiler doesn't like this. But it does seem to work. I saw that doing Texture.clear() also decrements _refs, so I've tried doing prevPass.clear() before prevPass.delete() but it doesn't seem to work.
Other Comments
If the real issue is that I'm using something wrong, then I would greatly appreciate any advice on how to get this to work without a memory leak.