1 Reply Latest reply on Feb 11, 2013 7:16 PM by jsmith

    PixelWriter::setPixels() - is it fast enough?

    870611
      I've made some calculations to measure speed of PixelWriter, and results are not good, as I can see.

      Shortly: it takes 5-10 milliseconds to renew 2 megapixel WritableImage ( with Sandy Bridge 2500 and Radeon 6850 ), and I think it is a quite slow. But maybe I've made wrong test. Please, can anybody take a look and relaunch the code?

      Other examples would be cool to see!
      import java.nio.IntBuffer;
      import java.util.Random;
      import java.util.logging.Level;
      import java.util.logging.Logger;
      import javafx.animation.KeyFrame;
      import javafx.animation.Timeline;
      import javafx.application.Application;
      import javafx.beans.InvalidationListener;
      import javafx.beans.Observable;
      import javafx.event.EventHandler;
      import javafx.scene.Scene;
      import javafx.scene.image.ImageView;
      import javafx.scene.image.PixelWriter;
      import javafx.scene.image.WritableImage;
      import javafx.scene.image.WritablePixelFormat;
      import javafx.scene.layout.StackPane;
      import javafx.stage.Stage;
      import javafx.stage.WindowEvent;
      import javafx.util.Duration;
      
      class BackWorker implements Runnable {
      
          int width;
          int height;
          int[] data;
          boolean runOk = true;
      
          public BackWorker(int w, int h) {
      
              width = w;
              height = h;
      
              data = new int[w * h];
      
              (new Thread(this)).start();
          }
      
          @Override
          public void run() {
      
              Random rand = new Random();
      
              while (runOk) {
                  try {
                      Thread.sleep(10000);
                  } catch (InterruptedException ex) {
                      Logger.getLogger(BackWorker.class.getName()).log(Level.SEVERE, null, ex);
                  }
      
                  for (int i = 0; i < 100000; i++) {
      
                      int a = rand.nextInt(width);
                      int b = rand.nextInt(height);
      
                      int randomColor = rand.nextInt();
      
                      data[b * width + a] = randomColor;
                  }
              }
          }
      }
      
      public class JavaFXApplication12 extends Application {
      
          static final int W = 1600;
          static final int H = 1000;
          Timeline t;
          BackWorker backWorker;
          int iterationNumber = 0;
      
          @Override
          public void start(Stage primaryStage) {
      
      
              final WritableImage wim = new WritableImage(W, H);
      
              backWorker = new BackWorker(W, H);
      
              final PixelWriter pw = wim.getPixelWriter();
      
              final WritablePixelFormat<IntBuffer> format = WritablePixelFormat.getIntArgbInstance();
      
      
              t = new Timeline();
      
              t.getKeyFrames().addAll(
                      new KeyFrame(Duration.ZERO),
                      new KeyFrame(Duration.INDEFINITE));
      
              t.currentTimeProperty().addListener(new InvalidationListener() {
                  @Override
                  public void invalidated(Observable o) {
      
                      long timeStart = System.nanoTime();
      
                      pw.setPixels(0, 0, W, H, format, backWorker.data, 0, W);
      
                      System.out.println(System.nanoTime() - timeStart);
      
                  }
              });
      
              t.playFromStart();
      
              ImageView iview = new ImageView(wim);
      
              StackPane root = new StackPane();
      
              root.getChildren().add(iview);
      
              Scene scene = new Scene(root, W, H);
      
              primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
                  @Override
                  public void handle(WindowEvent t) {
      
                      backWorker.runOk = false;
                  }
              });
      
              primaryStage.setTitle("Hello World!");
              primaryStage.setScene(scene);
              primaryStage.setResizable(false);
              primaryStage.show();
      
          }
      
          public static void main(String[] args) {
              launch(args);
          }
      }
        • 1. Re: PixelWriter::setPixels() - is it fast enough?
          jsmith
          Use a ByteBgraPreInstance instead of an IntArgbInstance:
          final WritablePixelFormat<ByteBuffer> format = WritablePixelFormat.getByteBgraPreInstance();
          In tests on my machine, win7 + Radeon HD4600 + Core 2 Quad Q9505 @ 2.83ghz + jdk7u11, refreshing pixels using a ByteBgraPre format was about 3 times faster than the IntArgb format (~2.5-3.5ms per operation (~300fps) as opposed to ~8-9ms per operation (~100fps)). The reason I chose ByteBgraPre was that this appeared to be the default format used when working with WritableImages in JavaFX on my machine.
          Other examples would be cool to see!
          Take a look at: Canvas performance "Thread: Canvas performance"

          To run a similar test on your machine, try substituting the following class for yours.
          class BackWorker implements Runnable {
           
              int width;
              int height;
              byte[] data;
              boolean runOk = true;
           
              public BackWorker(int w, int h) {
           
                  width = w;
                  height = h;
           
                  data = new byte[w * h * 4];
           
                  (new Thread(this)).start();
              }
           
              @Override
              public void run() {
           
                  Random rand = new Random();
           
                  while (runOk) {
                      try {
                          Thread.sleep(10000);
                      } catch (InterruptedException ex) {
                          Logger.getLogger(BackWorkerTest.class.getName()).log(Level.SEVERE, null, ex);
                      }
           
                      for (int i = 0; i < 100000; i++) {
           
                          int a = rand.nextInt(width);
                          int b = rand.nextInt(height);
           
                          int randomColor = rand.nextInt();
                          
                          int idx = (b * width + a) * 4;
           
                          data[idx]     = (byte) ((randomColor & 0xFF000000) >> 24);
                          data[idx + 1] = (byte) ((randomColor & 0x00FF0000) >> 16);
                          data[idx + 2] = (byte) ((randomColor & 0x0000FF00) >> 8);
                          data[idx + 3] = (byte) (0x000000FF);
                      }
                  }
              }
          }
          One thing to note about the above code is that you probably don't want to be updating the data buffer while the rendering process is occurring, so you may not want to run this update on a seperate thread. For the purposes of this test (with just a single update of the buffer every 10 seconds and outputting pixel data which represent digital static), it doesn't really matter though.