Using createGraphics().get() in the draw loop causing memory usage?

Hello, and thank you so much for your time. I've been working on a project, and found it to be using a ton of memory -- enough that it crashed the program it was being built for. I spent a couple days poking around to no avail. I did, however, figure out some of the details of the problem, and was wondering if you kind folks would be able to help me pinpoint a solution. Anyway, cutting to the chase:

The minimum amount of code I could recreate the problem with was this:

var graphics;

function setup() {
    graphics = createGraphics(1000,1000);
}

function draw() {
    graphics.get();
}

This produces the expected result, but uses a ton of memory. Here's the relevant info I could get out of Chrome dev tools: Task Manager The memory is currently at 1.2 gigs, but I've seen it reach 3 gigs, given enought time. Either way, that's way too much memory.

Timeline The saw tooth pattern continues indefinitely, with the teeth progressively growing slightly larger, but always dropping back down to the same height. I also checked out the javascript heap snapshot, but it rarely exceeded 10mb, even during peaks.

Based on this, my current theory is that the Graphics.get() method creates excess detached DOM nodes, which build up until garbage collected. However, I don't know why the spikes get progressively larger, nor do I have any idea as to how to fix it. If this is a known bug, or common mistake, any further information, solutions, or workarounds would be greatly appreciated. Thank you again so much for your help!

Answers

  • edited April 2017 Answer ✓
    • Hello! Method get() creates a clone as a p5.Image object:
    1. https://p5js.org/reference/#/p5.Image
    2. https://p5js.org/reference/#/p5.Image/get
    • Each p5.Image object got an HTMLCanvasElement as 1 of its properties: https://developer.Mozilla.org/en-US/docs/Web/API/HTMLCanvasElement
    • Considering draw() is called back at about 60 FPS, and the p5.Image's 1000 x 1000 pixel resolution, it's no wonder JS' garbage collector is having a hard time to get rid of all those previous p5.Image objects! :-SS
    • What I don't get is why you'd need to create clones at such fast rate after all. :-??
  • Thanks for the response! That makes sense, and explains most of the problem. I ran into this trying to create a mask of a live graphics object. The only solution I could find was converting the graphics object to an image, masking it, and then drawing it to the canvas each frame. I'm sure this isn't very efficient, but it was all I could find. I still have a couple of questions though, if you don't mind. First, why do the spikes progressively get bigger? And second, do you know any other ways to accomplish a real-time mask off the top of your head without murdering whatever computer the program is being run on? Thank you!

  • edited April 2017

    First, why do the spikes progressively get bigger?

    Dunno JS that deep. Also diff. browsers running on diff. systems can get diff. performances.

    My best guess is that the garbage collection is losing the create & destroy object's race little by little. :-O

  • edited March 2019 Answer ✓

    Here's an idea for a workaround:

    • Rather than keep creating clones w/ get() within draw() all the time, just create 1 clone once!
    • Instead, within draw(), merely transfer the original's pixels[] to its clone's! *-:)
    • For that pixels[] transfer job, I've implemented a function called transferPixels().
    • It relies on p5.Image's pixels[] + its TypedArray's set() method: :ar!
    1. https://p5js.org/reference/#/p5.Image/pixels
    2. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/set

    You can see it in action going to the link below btW: :bz
    https://OpenProcessing.org/sketch/421073

    /**
     * Transfer Pixels (v1.0)
     * GoToLoop (2017-Apr-17)
     *
     * forum.Processing.org/two/discussion/22039/
     * using-creategraphics-get-in-the-draw-loop-causing-memory-usage#Item_4
     *
     * OpenProcessing.org/sketch/421073
     */
    
    "use strict";
    
    let pg, imgClone;
    
    function setup() {
      createCanvas(1000, 600);
      frameRate(60);
      pg = createGraphics(width, height); // p5.Graphics w/ same canvas' dimensions
      imgClone = pg.get(); // pg cloned as a p5.Image
      imgClone.loadPixels(); // Init imgClone's Uint8ClampedArray
    }
    
    function draw() {
      pg.background('#' + hex(~~random(0x1000), 3)); // Change pg's bg color
      transferPixels(pg, imgClone); // transfer all pg's pixels[] to imgClone's
      background(imgClone); // Display imgClone, proving it's same content as pg's
    }
    
    function transferPixels(src, dst) {
      src.loadPixels(); // Fill up source's pixels[] w/ its current content
      dst.pixels.set(src.pixels); // Transfer it all to destination's
      dst.updatePixels(); // Refresh destination w/ transferred content
    }
    
  • This worked perfectly! Thank you so much! You just saved a weeks worth of work from going down the drain. I can't thank you enough!

Sign In or Register to comment.