Why is my js code so slow (map zoom)

edited July 2015 in JavaScript Mode

Hi all, I made a sketch, kind of a mire poix that ill use for other sketches, I put it on open processing: http://www.openprocessing.org/sketch/206002 in java mode the transition between views is quite smooth but in js its horribly slow, i tried to make it efficient but somehow the framerate is horrendous. I tried to add comments to make the code understandable but making code understandable is not my strong point, sorry...

Answers

  • edited July 2015

    i suspect it is in one of these lines but cant figure out why js has so much difficulty with these:

    for (int i=0; i < wPixels; i++) { for (int j=0; j < hPixels; j++) { if (animatIng) {

        //either this one: //transforming the draw array into the new map
       ** drawPixVal[i][j] = ((1-animCounter)*drawPixVal[i][j])    +(animCounter* pixVal[i][j]);**
    
      }
      //for every cell in the draw array make a rectangle size based on the value in the array 
      if (drawPixVal[i][j] > 0.05) {
        float r = (drawPixVal[i][j]);
    

    //or this one:
    rect((i+1)(width/wPixels), (j+1)(height/hPixels), r(width/wPixels)-1, r(height/hPixels)-1) ;

  • edited July 2015 Answer ✓

    This ran at an ok speed for me in Java, never tried JavaScript mode. I did some code motion modifications that do not affect the sketch, curious to see if it runs faster in JavaScript mode:

    PImage imgWorld;
    float[][] pixVal;
    float[][] drawPixVal;
    float animCounter;
    int startAnim, minX, maxX, minY, maxY, zoomLevel;
    boolean animatIng;
    
    // Never changes so added final keyword
    // Helps others know it will never change
    final int wPixels = 128;
    final int hPixels = 72;
    
    void setup() {
      size(1280, 720, P2D);
      rectMode(CENTER);
    
      // Moved here from draw(), they never change
      fill(255);
      stroke(0);
    
      imgWorld = loadImage("world16-9b.jpg");
      imgWorld.loadPixels();
    
      pixVal = new float[wPixels][hPixels];
      drawPixVal = new float[wPixels][hPixels];
    
      for (int i = 0; i < wPixels; i++)
        for (int j = 0; j < hPixels; j++)
          drawPixVal[i][j] = 0;
    
      resetMap();
    }
    
    void draw() {
      background(0);
    
      animateCount();
    
      // The conditional can be moved outside of the loops
      if (animatIng) {
        // These values are known outside of the loop
        // No need to calculate them inside of the loop
        float oneMinusAc = 1.0 - animCounter;
        int wByPw = width / wPixels;
        int hByPh = height / hPixels;
    
        for (int i = 0; i < wPixels; i++)
          for (int j = 0; j < hPixels; j++) {
            drawPixVal[i][j] = oneMinusAc * drawPixVal[i][j] + animCounter * pixVal[i][j];
    
            if (drawPixVal[i][j] > 0.05)
              rect((i + 1) * wByPw, (j + 1) * hByPh, drawPixVal[i][j] * wByPw - 1, drawPixVal[i][j] * hByPh - 1);
          }
      }
    }
    
    void remakePixArray() {
      // Very difficult to follow and does the same math again and again in the loops
      //for (int i = 0; i < wPixels; i++) {
      //  for (int j = 0; j < hPixels; j++) {
      //    float d = red(imgWorld.pixels[(int)((minY+j*(int)((maxY-minY)/hPixels))*imgWorld.width)+(int)((minX+i*(int)((maxX-minX)/wPixels)))]);
      //    for (int ix = i * (int)((maxX-minX)/wPixels); ix < (i+1)*(int)((maxX-minX)/wPixels); ix++) {
      //      for (int jx = j * (int)((maxY-minY)/hPixels); jx < (j+1)*(int)((maxY-minY)/hPixels); jx++) {
      //        d = (d+red(imgWorld.pixels[(int)((minY+jx)*imgWorld.width)+(int)((minX+ix))]))/2;
      //      }
      //    }
      //    pixVal[i][j] = (255-(d))/255;
      //  }
      //}
    
      // These values are known outside of the loop
      // No need to calculate them inside of the loop
      int xScale = (maxX - minX) / wPixels;
      int yScale = (maxY - minY) / hPixels;
    
      // No need for (int) casting in these loops, almost everything is already an int
      for (int i = 0; i < wPixels; i++)
        for (int j = 0; j < hPixels; j++) {
          // Make use of values known outside of the loops for better performance
          int index = (minY + j * yScale) * imgWorld.width + minX + i * xScale;
    
          // A faster way to access the pixel value
          // Note, actually gets blue instead of red because it is faster here
          float d = imgWorld.pixels[index] & 0xff;
    
          // Make use of values known outside of the loops for better performance
          int ixLimit = (i + 1) * yScale;
          int jxLimit = (j + 1) * yScale;
    
          for (int ix = i * xScale; ix < ixLimit; ix++) {
            for (int jx = j * yScale; jx < jxLimit; jx++) {
              // Breaking things down for the sake of understanding
              // Note, val takes the blue value instead of red value just like above
              int index2 = (minY + jx) * imgWorld.width + minX + ix;
              int val = imgWorld.pixels[index2] & 0xff;
    
              // Multiplying by 0.5 is faster than dividing by 2
              // Usually does not make a big difference but inside a quadruple nested loop it can matter
              d = (d + val) * 0.5;
            }
          }
    
          pixVal[i][j] = (255.0 - d) / 255.0;
        }
    }
    
    void animateCount() {
      if (animatIng) {
        float transitionTime = 6000;
        if (millis() - startAnim < transitionTime)
          animCounter = float(millis() + 1 - startAnim) / transitionTime;
        else {
          animCounter = 1;
          animatIng = false;
        }
      }
    }
    
    void mouseReleased() {
      float zoomFac = 2;
      if (zoomLevel == 16)
        zoomFac = 1;
    
      zoomLevel = zoomLevel * int(zoomFac);
    
      // These can be local variables, no need to make them global
      int centerX = (int)(map(mouseX, 0, width, float(minX), float(maxX)));
      int centerY = (int)(map(mouseY, 0, height, minY, maxY));
    
      if (centerY - (int)((maxY - minY) / 2 / zoomFac) < 0)
        centerY = minY + (int)((maxY - minY) / 2 / zoomFac);
      else if (centerY + (int)((maxY - minY) / 2 / zoomFac) > imgWorld.width)
        centerY = maxY - (int)((maxY - minY) / 2 / zoomFac);
    
      minX = centerX - (int)((maxX - minX) / 2 / zoomFac);
      maxX = centerX + (int)((maxX - minX) / 2 / zoomFac);
      minY = centerY - (int)((maxY - minY) / 2 / zoomFac);
      maxY = centerY + (int)((maxY - minY) / 2 / zoomFac);
    
      remakePixArray();
      animatIng = true;
      startAnim = millis();
    }
    
    void keyReleased() {
      if (keyCode == BACKSPACE)
        resetMap();
    }
    
    void resetMap() {
      animatIng = true;
      startAnim = millis();
      zoomLevel = 1;
    
      minX = minY = 0;
      maxX = imgWorld.width;
      maxY = imgWorld.height;
      remakePixArray();
    }
    
  • @goToLoop, sorry about the code formatting. Ill look into the fixes you applied in the other topic, thanks for the reply. @asimes, thanks for the code cleanup! it works smoother now in java then it did before so thats nice, It doesnt work properly yet in js so Ill check that also later. Have to work now..

  • @goToLoop, im sorry but I could not figure out how some of your changes improved the framerate, I did find that drawwing rectangles is tanking the framerate, if I zoom in on the ocean framerate shoots up so somehow the rectangles are killing the framerate. Thanks for the link though! @asimes, thanks again for the refactor, I fixed it so it runs in javascript, (needed two int casting and a modification in the animating loop to make sure the background is not refreshed after animating). script seems smoother then before thanks!

    //made by jan jacobs, please use this to make cool stuf but do mention the source of your sketch... it's good form you know ;)
    
    /* @pjs preload="world16-9b.jpg"; */
    PImage imgWorld;
    float[][] pixVal;
    float[][] drawPixVal;
    float animCounter;
    int startAnim, minX, maxX, minY, maxY, zoomLevel, debugVal3;
    boolean animatIng;
    
    // Never changes so added final keyword
    // Helps others know it will never change
    final int wPixels = 128;
    final int hPixels = 72;
    
    void setup() {
    
       debugVal3 = millis();
      size(1280, 720, P2D);
     // rectMode(CENTER);
      noSmooth();noStroke();rectMode(CORNER);frameRate(25);
      // Moved here from draw(), they never change
      fill(255);
      stroke(0);
    
      imgWorld = loadImage("world16-9b.jpg");
      imgWorld.loadPixels();
    
      pixVal = new float[wPixels][hPixels];
      drawPixVal = new float[wPixels][hPixels];
    
      for (int i = 0; i < wPixels; i++)
        for (int j = 0; j < hPixels; j++)
          drawPixVal[i][j] = 0;
    
      resetMap();
    }
    
    void draw() {
    
      //roundabout way of finding the duration of code parts
    /*text(str(millis()-debugVal3), 50,70);
    text(str(millis()), 50,50);
    int a1 = millis();*/
      animateCount();
     //float oneMinusAc = 1.0 ;
      // The conditional can be moved outside of the loops (not entirely, the solution was not drawing anything when !animating so moved the background statement into the loop)
      if (animatIng) {
          background(0);
        float oneMinusAc = 1.0 - animCounter;
    
        int wByPw = width / wPixels;
        int hByPh = height / hPixels;
    //int a2 = millis();
        for (int i = 0; i < wPixels; i++){
          for (int j = 0; j < hPixels; j++) {
             if (animatIng) {
            drawPixVal[i][j] = oneMinusAc * drawPixVal[i][j] + animCounter * pixVal[i][j];
             }
    
            if (drawPixVal[i][j] > 0.05)
    
              rect((i + 1) * wByPw, (j + 1) * hByPh, drawPixVal[i][j] * wByPw - 1, drawPixVal[i][j] * hByPh - 1);
          }
      }}
     /* text(str(millis()-a1), 150,50);
        text(str(millis()-a2), 250,50);
     debugVal3 = millis();*/
    }
    
    void remakePixArray() {
    
     //int casting was necesarry for javascript
      int xScale = (int)((maxX - minX) / wPixels);
      int yScale = (int)((maxY - minY) / hPixels);
    
    
      // No need for (int) casting in these loops, almost everything is already an int
      for (int i = 0; i < wPixels; i++)
        for (int j = 0; j < hPixels; j++) {
          // Make use of values known outside of the loops for better performance
          int index = (minY + j * yScale) * imgWorld.width + minX + i * xScale;
    
          // A faster way to access the pixel value
          // Note, actually gets blue instead of red because it is faster here
          float d = imgWorld.pixels[index] & 0xff;
    
          // Make use of values known outside of the loops for better performance
          int ixLimit = (i + 1) * yScale;
          int jxLimit = (j + 1) * yScale;
    
          for (int ix = i * xScale; ix < ixLimit; ix++) {
            for (int jx = j * yScale; jx < jxLimit; jx++) {
              // Breaking things down for the sake of understanding
              // Note, val takes the blue value instead of red value just like above
              int index2 = (minY + jx) * imgWorld.width + minX + ix;
              int val = imgWorld.pixels[index2] & 0xff;
    
              // Multiplying by 0.5 is faster than dividing by 2
              // Usually does not make a big difference but inside a quadruple nested loop it can matter
              d = (d + val) * 0.5;
            }
          }
    
          pixVal[i][j] = (255.0 - d) / 255.0;
        }
    }
    
    void animateCount() {
      if (animatIng) {
        float transitionTime = 6000;
        if (millis() - startAnim < transitionTime)
          animCounter = float(millis() + 1 - startAnim) / transitionTime;
        else {
          animCounter = 1;
          animatIng = false;
        }
      }
    }
    
    void mouseReleased() {
      float zoomFac = 2;
      if (zoomLevel == 16)
        zoomFac = 1;
    
      zoomLevel = zoomLevel * int(zoomFac);
      // These can be local variables, no need to make them global
      int centerX = (int)(map(mouseX, 0, width, float(minX), float(maxX)));
      int centerY = (int)(map(mouseY, 0, height, minY, maxY));
    
      if (centerY - (int)((maxY - minY) / 2 / zoomFac) < 0)
        centerY = minY + (int)((maxY - minY) / 2 / zoomFac);
      else if (centerY + (int)((maxY - minY) / 2 / zoomFac) > imgWorld.width)
        centerY = maxY - (int)((maxY - minY) / 2 / zoomFac);
    
      minX = centerX - (int)((maxX - minX) / 2 / zoomFac);
      maxX = centerX + (int)((maxX - minX) / 2 / zoomFac);
      minY = centerY - (int)((maxY - minY) / 2 / zoomFac);
      maxY = centerY + (int)((maxY - minY) / 2 / zoomFac);
    
      remakePixArray();
      animatIng = true;
      startAnim = millis();
    }
    
    void keyReleased() {
      if (keyCode == BACKSPACE)
        resetMap();
    }
    
    void resetMap() {
      animatIng = true;
      startAnim = millis();
      zoomLevel = 1;
    
      minX = minY = 0;
      maxX = imgWorld.width;
      maxY = imgWorld.height;
      remakePixArray();
    }
    
  • Ah, didn't realize that the int casting could be needed in JavaScript mode but not Java mode (never used Processing's JavaScript mode). Glad the speed up translated into JavaScript mode

Sign In or Register to comment.