Best Graphics Card for Processing Development

I do a fair amount of Processing development, mainly to create images. I'm building a new machine. I'll be using Windows 7, and I program in the Eclipse IDE for the most part.

I'd like to get a GPU that will improve the performance of my Processing code. Any advice?

Today I have a Radeon 4890. Would a workstation GPU be better? I've been looking at the Firepro 2270. Is this better than a desktop card for Processing computations? I have to tell you, I've read a lot on the comparison between desktop and workstation cards and I just can't seem to decide which might better for me. Any help is appreciated.

Answers

  • I downloaded GPU-Z and monitored my graphics card (Radeon 4890) while I was running sketches. I use double buffering to draw on a second PGraphics instance, and then when I'm done I swap it to the screen. It looks like I'm not exercising the GPU at all while I'm drawing on the second buffer. I only use the GPU when I draw to the screen. This seems like a problem.

    Is there a way to tell Processing to use the GPU to help with the OpenGL calculations? If I had a workstation graphics card (like the Firepro 2270) would Processing automatically use that card's OpenGL support to help in the drawing?

    Again, any help is appreciated.

  • The OpenGL renderers (P2D and P3D) use GPU acceleration for all rendering operations, including offscreen drawing with a PGraphics canvas.

    Any recent GPU will help, but which one is the most adequate really depends on how demanding your Processing sketches are (in terms of output resolution, polygon complexity, etc.)

    Here there are some comparison benchmarks for the two GPUs you mentioned: http://www.videocardbenchmark.net/gpu.php?gpu=Radeon+HD+4890 http://www.videocardbenchmark.net/gpu.php?gpu=FirePro+2270

  • edited January 2014

    Thanks codeanticode.

    I'm just not seeing this behavior, at least with my card.

    For instance, consider the code below. It draws a bunch of spheres to the screen. If I add more spheres, or increase their detail, the GPU load actually goes down because there are fewer draws to the screen. If I put in 1000 spheres, the program moves very slow, it's putting out maybe 1 draw per second, and there is almost no load on the card. If I lower the detail and the number of spheres, there are closer to 30 draws per second, and then the load on the card is about 35%.

    So it looks like, at least for my setup, that my card is only getting used to draw to the screen, and probably the CPU is doing most of the OPENGL calculations.

    Is there a setting somewhere to control this?

    Code example below

    package jjc.processing.test;
    
    import java.awt.Color;
    import java.util.ArrayList;
    
    import processing.core.PApplet;
    import processing.core.PGraphics;
    
    @SuppressWarnings("serial")
    public class GpuTest extends PApplet {
    
        public static void main(String args[]) {
            PApplet.main(new String[] { jjc.processing.bezier.Main.class.getName() });
        }
    
        /**
         * Store all configurations
         */
        static class Config {
            public static final int width = 1024;
            public static final int height = 1024;
            public static final int multi = 2048;
            public static final int balls = 10;
            public static final int detail = 3;
            public static final boolean lines = false;
        }
    
        PGraphics pg;
        public boolean go = true;
        public boolean buffer = true;
        public long frame = 0;
        public ArrayList<Ball> balls = null;
    
        public void setup() {
            // Use numbers, not vars, due to limitation with export plugin
            size(1024, 1024, P2D);
    
            pg = createGraphics(1024, 1024, P3D);
            pg.colorMode(RGB, 0xff);
            pg.background(0);
    
            balls = new ArrayList<Ball>();
            for (int i = 0; i < Config.balls; i++) {
                Ball ball = new Ball();
                ball.setPosition(0, Config.width);
                ball.setPositionChange(-20, 20);
                ball.setRotation(0, .1f);
                ball.setRotationChange(-.1f, .1f);
                ball.lowerBound = new Tuple(0, 0, -1000);
                ball.upperBound = new Tuple(Config.width, Config.height, 0);
                ball.color = new Color(random(1), random(1), random(1));
                ball.size = 100;
                balls.add(ball);
            }
        }
    
        public void keyPressed() {
            if (key == CODED) {
                if (keyCode == UP) ;
                if (keyCode == DOWN) ;
                if (keyCode == RIGHT) ;
                if (keyCode == LEFT) ;
            }
            go = !go;
        }
    
        public void mouseReleased() {
            buffer = !buffer;
        }
    
        public void draw() {
            if (!go) return;
            frame++;
    
            drawBalls();
    
            if (buffer) image(pg, 0, 0);
            System.out.println(frame);
        }
    
        public void drawBalls() {
            pg.beginDraw();
            if (Config.lines) pg.stroke(0xff, 0x80);
            else pg.noStroke();
            pg.background(0);
            for (Ball ball : balls) {
                ball.move();
                ball.bounce();
                pg.fill(ball.color.getRGB());
                pg.sphereDetail(Config.detail);
                pg.pushMatrix();
                pg.translate(ball.p.x, ball.p.y, ball.p.z);
                pg.rotateX(ball.r.x);
                pg.rotateY(ball.r.y);
                pg.rotateZ(ball.r.z);
                pg.sphere(ball.size);
                pg.popMatrix();
            }
            pg.lights();
            pg.endDraw();
        }
    
        class Ball {
            public Tuple p, dp, r, dr, lowerBound, upperBound;
            public Color color;
            public float size;
    
            public Ball() {}
    
            public void setPosition(float min, float max) {
                this.p = new Tuple(min, max);
            }
    
            public void setPositionChange(float min, float max) {
                this.dp = new Tuple(min, max);
            }
    
            public void setRotation(float min, float max) {
                this.r = new Tuple(min, max);
            }
    
            public void setRotationChange(float min, float max) {
                this.dr = new Tuple(min, max);
            }
    
            public void move() {
                p.x += dp.x;
                p.y += dp.y;
                p.z += dp.z;
                r.x += dr.x;
                r.y += dr.y;
                r.z += dr.z;
            }
    
            public void bounce() {
                if (p.x < lowerBound.x) {
                    p.x = lowerBound.x;
                    dp.x *= -1;
                }
                if (p.x > upperBound.x) {
                    p.x = upperBound.x;
                    dp.x *= -1;
                }
                if (p.y < lowerBound.y) {
                    p.y = lowerBound.y;
                    dp.y *= -1;
                }
                if (p.y > upperBound.y) {
                    p.y = upperBound.y;
                    dp.y *= -1;
                }
                if (p.z < lowerBound.z) {
                    p.z = lowerBound.z;
                    dp.z *= -1;
                }
                if (p.z > upperBound.z) {
                    p.z = upperBound.z;
                    dp.z *= -1;
                }
            }
        }
    
        class Tuple {
            public float x, y, z;
    
            public Tuple(float x, float y, float z) {
                this.x = x;
                this.y = y;
                this.z = z;
            }
    
            public Tuple(float min, float max) {
                this(random(min, max), random(min, max), random(min, max));
            }
        }
    
    }
    
  • Made some adjustments in order to compile it under Processing's own IDE: O:-)

    /** 
     * Bouncing Spheres (v2.0)
     * by  JColosi (2014/Jan)
     * mod GoToLoop
     *
     * forum.processing.org/two/discussion/2675/
     * best-graphics-card-for-processing-development
     * 
     */
    
    import java.awt.Color;
    
    interface Config {
      int BALLS = 100;
      int DETAIL = 8;
      boolean WIREFRAME = true;
    }
    
    PGraphics pg;
    boolean go = true;
    boolean buffer = true;
    
    //final ArrayList<Ball> balls = new ArrayList();
    final Ball[] balls = new Ball[Config.BALLS];
    
    void setup() {
      size(1024, 1024, P2D);
    
      pg = createGraphics(width, height, P3D);
    
      pg.beginDraw();
      pg.colorMode(RGB, 0xff);
      pg.smooth(8);
      pg.endDraw();
    
      for (int i = 0; i != Config.BALLS; i++) {
        final Ball ball = new Ball();
    
        ball.setPosition(0, width);
        ball.setPositionChange(-20, 20);
        ball.setRotation(0, .1f);
        ball.setRotationChange(-.1f, .1f);
        ball.lowerBound = new Tuple(0, 0, -1000);
        ball.upperBound = new Tuple(width, height, 0);
        ball.colour = new Color(random(1), random(1), random(1));
        ball.size = 100;
    
        balls[i] = ball;
      }
    }
    
    void keyPressed() {
      go = !go;
    }
    
    void mouseReleased() {
      buffer = !buffer;
    }
    
    void draw() {
      if (!go)  return;
    
      drawBalls();
    
      if (buffer)  image(pg, 0, 0);
    
      frame.setTitle("FPS " + nf(frameRate, 2, 2));
    }
    
    void drawBalls() {
      pg.beginDraw();
    
      if (Config.WIREFRAME)  pg.stroke(0xff, 0x80);
      else pg.noStroke();
    
      pg.background(0);
    
      for (Ball ball: balls) {
        ball.move();
        ball.bounce();
    
        pg.fill(ball.colour.getRGB());
        pg.sphereDetail(Config.DETAIL);
    
        pg.pushMatrix();
        pg.translate(ball.p.x, ball.p.y, ball.p.z);
        pg.rotateX(ball.r.x);
        pg.rotateY(ball.r.y);
        pg.rotateZ(ball.r.z);
        pg.sphere(ball.size);
        pg.popMatrix();
      }
    
      pg.lights();
      pg.endDraw();
    }
    
    class Tuple {
      float x, y, z;
    
      Tuple(float px, float py, float pz) {
        x = px;
        y = py;
        z = pz;
      }
    
      Tuple(float min, float max) {
        this(random(min, max), random(min, max), random(min, max));
      }
    }
    
    class Ball {
      Tuple p, dp, r, dr, lowerBound, upperBound;
      Color colour;
      short size;
    
      void setPosition(float min, float max) {
        this.p = new Tuple(min, max);
      }
    
      void setPositionChange(float min, float max) {
        this.dp = new Tuple(min, max);
      }
    
      void setRotation(float min, float max) {
        this.r = new Tuple(min, max);
      }
    
      void setRotationChange(float min, float max) {
        this.dr = new Tuple(min, max);
      }
    
      void move() {
        p.x += dp.x;
        p.y += dp.y;
        p.z += dp.z;
        r.x += dr.x;
        r.y += dr.y;
        r.z += dr.z;
      }
    
      void bounce() {
        if (p.x < lowerBound.x) {
          p.x = lowerBound.x;
          dp.x *= -1;
        }
        if (p.x > upperBound.x) {
          p.x = upperBound.x;
          dp.x *= -1;
        }
        if (p.y < lowerBound.y) {
          p.y = lowerBound.y;
          dp.y *= -1;
        }
        if (p.y > upperBound.y) {
          p.y = upperBound.y;
          dp.y *= -1;
        }
        if (p.z < lowerBound.z) {
          p.z = lowerBound.z;
          dp.z *= -1;
        }
        if (p.z > upperBound.z) {
          p.z = upperBound.z;
          dp.z *= -1;
        }
      }
    }
    
  • @jcolosi: the problem with your code is that you are using the so called "immediate mode" to render your spheres: the vertices are computed on the CPU and then sent to the GPU on every frame. Because the CPU stage is fairly slow, it bottlenecks the entire drawing process and the GPU ends up idling most of the time.

    The immediate mode has always been the default drawing mode in Processing, but Processing 2 includes a new mode that could help improve performance under this kind of situations. In this new mode ("retained") you store your geometry inside PShape objects, that only need to be computed and sent to the GPU just once. Therefore, the CPU calculations to initialize the shapes only take place during setup and GPU utilization in each frame should be maximized. A modified version of your code using PShapes is the following:

    int BALLS = 100;
    int DETAIL = 8;
    boolean WIREFRAME = true;
    
    PGraphics pg;
    boolean go = true;
    boolean buffer = true;
    
    Ball[] balls = new Ball[BALLS];
    
    void setup() {
      size(1024, 1024, P2D);
    
      pg = createGraphics(width, height, P3D);
    
      pg.beginDraw();
      pg.smooth(8);
      pg.endDraw();
    
      for (int i = 0; i != BALLS; i++) {
        final Ball ball = new Ball(pg, 100, color(random(255), random(255), random(255)));
    
        ball.setPosition(0, width);
        ball.setPositionChange(-20, 20);
        ball.setRotation(0, .1f);
        ball.setRotationChange(-.1f, .1f);
        ball.lowerBound = new Tuple(0, 0, -1000);
        ball.upperBound = new Tuple(width, height, 0);
    
        balls[i] = ball;
      }
    }
    
    void keyPressed() {
      go = !go;
    }
    
    void mouseReleased() {
      buffer = !buffer;
    }
    
    void draw() {
      if (!go)  return;
    
      drawBalls();
    
      if (buffer)  image(pg, 0, 0);
    
      frame.setTitle("FPS " + nf(frameRate, 2, 2));
    }
    
    void drawBalls() {
      pg.beginDraw();
    
      pg.background(0);
      pg.lights();
    
      for (Ball ball: balls) {
        ball.move();
        ball.bounce();
        ball.display(pg);
      }
    
      pg.endDraw();
    }
    
    class Tuple {
      float x, y, z;
    
      Tuple(float px, float py, float pz) {
        x = px;
        y = py;
        z = pz;
      }
    
      Tuple(float min, float max) {
        this(random(min, max), random(min, max), random(min, max));
      }
    }
    
    class Ball {
      PShape sh;
      Tuple p, dp, r, dr, lowerBound, upperBound;
      color colour;
      float size;
    
      Ball(PGraphics pg, float s, color c) {
        size = s;
        colour = c;
        sh = pg.createShape(SPHERE, s, DETAIL);
        if (WIREFRAME) sh.setStroke(color(0xff, 0x80));
        else sh.setStroke(false);        
        sh.setFill(c);    
      }
    
      void setPosition(float min, float max) {
        this.p = new Tuple(min, max);
      }
    
      void setPositionChange(float min, float max) {
        this.dp = new Tuple(min, max);
      }
    
      void setRotation(float min, float max) {
        this.r = new Tuple(min, max);
      }
    
      void setRotationChange(float min, float max) {
        this.dr = new Tuple(min, max);
      }
    
      void move() {
        p.x += dp.x;
        p.y += dp.y;
        p.z += dp.z;
        r.x += dr.x;
        r.y += dr.y;
        r.z += dr.z;
      }
    
      void bounce() {
        if (p.x < lowerBound.x) {
          p.x = lowerBound.x;
          dp.x *= -1;
        }
        if (p.x > upperBound.x) {
          p.x = upperBound.x;
          dp.x *= -1;
        }
        if (p.y < lowerBound.y) {
          p.y = lowerBound.y;
          dp.y *= -1;
        }
        if (p.y > upperBound.y) {
          p.y = upperBound.y;
          dp.y *= -1;
        }
        if (p.z < lowerBound.z) {
          p.z = lowerBound.z;
          dp.z *= -1;
        }
        if (p.z > upperBound.z) {
          p.z = upperBound.z;
          dp.z *= -1;
        }
      }
    
      void display(PGraphics pg) {
        pg.pushMatrix();
        pg.translate(p.x, p.y, p.z);
        pg.rotateX(r.x);
        pg.rotateY(r.y);
        pg.rotateZ(r.z);
        pg.shape(sh);
        pg.popMatrix();   
      }    
    }
    
  • Guys, thanks for the code above. I took input from both and created the code below, which switches (on mouseReleased) back and forth between "Retained Mode" (uses PShape) and "Immediate Mode" (uses PGraphics.sphere)

    The results for me, with the Radeon 4890 is that the "Retained Mode" (PShape) has a higher FPS, but lower GPU load. When I switch (by clicking the mouse anywhere) to the "Immediate Mode" the frameRate drops, but the GPU load goes up.

    I'm not sure that's exactly what I expected. Again, the goal is to make the most of the GPU. So I want to have a higher frameRate, but I also expected that the GPU load would be higher. Maybe the whole PShape method is simply more efficient?

    Anyway, the code:

    /** 
     * Bouncing Spheres (v2.0)
     * by  JColosi (2014/Jan)
     * mod GoToLoop
     * mod CodeAntiCode
     *
     * forum.processing.org/two/discussion/2675/
     * best-graphics-card-for-processing-development
     * 
     */
    
    import java.awt.Color;
    
    interface Config {
      int BALLS = 100;
      int DETAIL = 20;
      boolean WIREFRAME = true;
    }
    
    PGraphics pg;
    boolean go = true;
    boolean useDraw = true;
    boolean useShape = true;
    
    final Ball[] balls = new Ball[Config.BALLS];
    
    void setup() {
      size(1024, 1024, P2D);
    
      pg = createGraphics(width, height, P3D);
    
      pg.beginDraw();
      pg.smooth(8);
      pg.endDraw();
    
      for (int i = 0; i != Config.BALLS; i++) {
        final Ball ball = new Ball(pg, 100, color(random(255), random(255), random(255)));
    
        ball.setPosition(0, width);
        ball.setPositionChange(-20, 20);
        ball.setRotation(0, .1f);
        ball.setRotationChange(-.1f, .1f);
        ball.lowerBound = new Tuple(0, 0, -1000);
        ball.upperBound = new Tuple(width, height, 0);
    
        balls[i] = ball;
      }
    }
    
    void keyPressed() {
    }
    
    void mouseReleased() {
      useShape = !useShape;
    }
    
    void draw() {
      String msg = String.format("FPS: %2.2f     Shape: %s", frameRate, useShape);
      frame.setTitle(msg);
      if (!go)  return;
    
      drawBalls();
    
      if (useDraw)  image(pg, 0, 0);
    }
    
    void drawBalls() {
      pg.beginDraw();
    
      pg.background(0);
      pg.lights();
    
      for (Ball ball: balls) {
        ball.move();
        ball.bounce();
        if (useShape) ball.displayShape(pg);
        else ball.display(pg);
      }
    
      pg.endDraw();
    }
    
    class Tuple {
      float x, y, z;
    
      Tuple(float px, float py, float pz) {
        x = px;
        y = py;
        z = pz;
      }
    
      Tuple(float min, float max) {
        this(random(min, max), random(min, max), random(min, max));
      }
    }
    
    class Ball {
      PShape shape;
      Tuple p, dp, r, dr, lowerBound, upperBound;
      color colour;
      float size;
    
      Ball(PGraphics pg, float size, color colour) {
        this.size = size;
        this.colour = colour;
        this.shape = pg.createShape(SPHERE, size, Config.DETAIL);
        if (Config.WIREFRAME) shape.setStroke(color(0xff, 0x40));
        else shape.setStroke(false);        
        this.shape.setFill(colour);
      }
    
      void setPosition(float min, float max) {
        this.p = new Tuple(min, max);
      }
    
      void setPositionChange(float min, float max) {
        this.dp = new Tuple(min, max);
      }
    
      void setRotation(float min, float max) {
        this.r = new Tuple(min, max);
      }
    
      void setRotationChange(float min, float max) {
        this.dr = new Tuple(min, max);
      }
    
      void move() {
        p.x += dp.x;
        p.y += dp.y;
        p.z += dp.z;
        r.x += dr.x;
        r.y += dr.y;
        r.z += dr.z;
      }
    
      void bounce() {
        if (p.x < lowerBound.x) {
          p.x = lowerBound.x;
          dp.x *= -1;
        }
        if (p.x > upperBound.x) {
          p.x = upperBound.x;
          dp.x *= -1;
        }
        if (p.y < lowerBound.y) {
          p.y = lowerBound.y;
          dp.y *= -1;
        }
        if (p.y > upperBound.y) {
          p.y = upperBound.y;
          dp.y *= -1;
        }
        if (p.z < lowerBound.z) {
          p.z = lowerBound.z;
          dp.z *= -1;
        }
        if (p.z > upperBound.z) {
          p.z = upperBound.z;
          dp.z *= -1;
        }
      }
    
      void display(PGraphics pg) {
        if (Config.WIREFRAME)  pg.stroke(0xff, 0x40);
        else pg.noStroke();
    
        pg.fill(colour);
        pg.sphereDetail(Config.DETAIL);
    
        pg.pushMatrix();
        pg.translate(p.x, p.y, p.z);
        pg.rotateX(r.x);
        pg.rotateY(r.y);
        pg.rotateZ(r.z);
        pg.sphere(size);
        pg.popMatrix();
      }
    
      void displayShape(PGraphics pg) {
        pg.pushMatrix();
        pg.translate(p.x, p.y, p.z);
        pg.rotateX(r.x);
        pg.rotateY(r.y);
        pg.rotateZ(r.z);
        pg.shape(shape);
        pg.popMatrix();
      }
    }
    
  • I'd guess the increase in GPU usage in the immediate mode is due to the continuous transfers between CPU and GPU memory (you are pushing the vertices to the GPU in every frame), but I might be wrong. The GPU seems to have enough processing throughput to render the geometry without being fully utilized, this means you have room to render a more complex scene.

Sign In or Register to comment.