Renderer faster than JavaFX 2D

Hi everyone!

Is there any renderer that is faster than JavaFX 2D?

Thanks in advance,

MrImskiy

Hi @MrImskiy,

Faster for which type of workload? What do you want to display and how?

1 Like

Thanks for replying @josephh!

I have a GUI with two oscilloscopes. The first oscilloscope displays the original signal, and the second oscilloscope displays the manipulated signal. The manipulations that are made are various: high-pass filter, low-pass filter, integration, etc.

The signals are coming from the MPU6050 sensor, which consists of a gyroscope and an accelerometer. The goal is to filter those two sensors appropriately, and to integrate the gyroscope once, to get an angle, and the accelerometer twice, to get displacement. All this (plotting included) is happening realtime.

For this application, I need an iteration time that is really small, especially for the double integration of the accelerometer, because otherwise, the signal manipulations will give a very erroneous result. When I integrate the acceleration once, I can, kind of, recognize a velocity, but when I integrate a second time, the displacement is nowhere to be recognized.

1 Like

You do not have to process data during each frame.

This example generates data in a separate thread and updates the sketch window at the selected frameRate and is frameRate independent:

// Generate data in a thread
// GLV
// 2023-8-24

IntList data;
int counter;

void setup() 
  {
  size(800, 300);
  data = new IntList();
  tNow = millis();
  thread("requestData"); // Generates new data every 2 ms
  frameRate(60); //default
  }

void draw() 
  {
  background(0);
  
  synchronized (data) 
    {
    for(int i=0; i<data.size(); i++)
      {
      stroke(255, 255, 64);
      strokeWeight(3);
      point(i, data.get(i));
      }
    }
  }

// This is a separate thread

int tLast = 0;
int tNow;

void requestData() 
  {
  while(true)
    {
    tNow = millis();  
    if(tNow >= tLast+2) // +1 is minimum or will crash
      {
      println(frameCount, tNow-tLast); // Development
      
      int x = counter;
      float th = x*(TAU/360);
      int y = (int)(150+80*sin(th)*cos(th*10)); // Amplitude modulation
      
      // buffer
      synchronized (data) 
        {
        data.append(y);  
        if(data.size() > width) 
          {
          data.remove(0);   
          }
        }
      counter++;
   
      tLast = tNow;
      }
    }
  }    
  
void mousePressed()
  {
  data.clear();    // clears buffer  
  }

The above example was something I cooked up a few days ago to simulate data in place of external events (incoming serial data in this case).

:)

5 Likes

Thanks for the reply @glv ! Is requestData() continuously called in setup() ? What is the function of synchronized() ?

I find the FX2D renderer to be the fastest of the Processing renderers for 2D geometry primitives.

1 Like

Another question: Why do the points start drawing at the utmost right of the window when the code explicitly says that it should start at x = 0?:

void draw(){
  background(0);
  synchronized (data){
    for(int i=0; i<data.size(); i++){
      stroke(255, 255, 64);
      strokeWeight(1);
      point(i, data.get(i));
    }
  }
}

Thanks for the reply @micycle ! Some applications need a faster performance unfortunately…

No it is creating a thread to simulate asynchronous data. This thread runs as a separate process and is independent of the rest of the sketch code. The created thread simply adds new data to the end of the list (data) and removes data from the front of the list to make sure the list length does not exceed a maximum level.

The main thread has the draw method and will display the data in the list. Since we have two threads sharing the list data then we must prevent both threads accessing the list at the same time as it would create a concurrent access exception and the sketch would crash. The code

synchronized (data)   {
  // synchronized code block
}

means do not execute the code in the block if another thread is currently accessing the object data

They started on the left for me and the graph moves to the left because the points at the front are being removed.

FYI the code does not implement a circular buffer because elements are being added and removed from the list. A true circular buffer has a fixed number of elements and they are reused / overwritten as needed so would be faster.

4 Likes

I have adapted @glv’s code to make use of a circular buffer / array. Here it is

// Generate data in a thread
// GLV
// and adapted to use a circular buffer / array by Quark
// 2023-8-24

// Code does NOT include synchronization as yet.

int[] data;
int front = -1, back = 0;
int counter;

void setup() {
  size(800, 300);
  data = new int[width];
  tNow = millis();
  thread("requestData"); // Generates new data every 2 ms
  frameRate(60); //default
}

void draw() {
  background(0);

  synchronized (data) {
    stroke(255, 255, 64);
    strokeWeight(1.5);
    int idx = front, last = (back-1 + data.length) % data.length;
    int x = 0;
    while (idx >= 0 && idx != last) {
      point(x++, data[idx++]);
      idx %= data.length;
    }
  }
}

// This is a separate thread
int tLast = 0;
int tNow;

void requestData() {
  while (true) {
    tNow = millis();
    if (tNow >= tLast+2) { // +1 is minimum or will crash
      println(frameCount, tNow-tLast); // Development
      int x = counter;
      float th = x*(TAU/360);
      int y = (int)(150+80*sin(th)*cos(th*10)); // Amplitude modulation
      // add data to circular buffer
      synchronized (data) {
        if (front == back) { // handle fully filled list
          data[front] = y;
          back = (back + 1) % data.length;
          front = back;
        } else  if (front != -1) { // handle part filled list
          data[back] = y;
          back = (back + 1) % data.length;
        } else {               // handle empty list
          front = 0;
          back = 1;
          data[front] = y;
        }
      }
      counter++;
      tLast = tNow;
    }
  }
}
4 Likes

Thank you very much @quark for the explanation and the code! Much appreciated! :smiley:

2 Likes

I’ll be working with these soon (soldering up today!) and took an interest.

How is your project collecting the data from the sensor and sharing with Processing?
I do not need or want to see code… just a simple description of the system.

Very busy these days for me… pleased to see your questions were answered.

:)

1 Like

Nice to know that you took an interest! :smiley:

And yes, your code seems to solve my problem :slight_smile:

Concerning the sensor:

The MPU6050 is wired to the ESP32 microcontroller board, and the data is sent via bluetooth to my pc. In processing, I just have to select the right COM port to receive the data. ESP32 has built-in bluetooth and WiFi.

I wonder how much buffering there is in the bluetooth hardware and drivers either on the ESP32 or on your PC or in Processing (Java). There might well not be any or at least be negligible, but if there is, it could create stair-stepping in your timing as several readings come in at once.

It might be the case… How can I verify if there is any buffering?

No idea. I’ve never played with it.

Get your app working with a separate data collection thread first and see how well you can timestamp the data. If it’s smooth enough, then ignore what I said. If not, then see if you can find any driver settings in your OS that talk about buffering or window size to see if you can make them smaller.

You might also try testing with wifi or usb just to compare timings even if bluetooth is your ultimate choice.

1 Like

Why would you not put these calulations:

int x = counter;
float th = x*(TAU/360);
int y = (int)(150+80*sin(th)*cos(th*10)); // Amplitude modulation

inside synchronized (data) ?

1 Like

The code I provided was a quick effort for me and should be treated as such.
I head several versions on the go and trickled it down to something simple to share.

Always scrutinize code that is shared on the Internet.

I am not confident that I put the synchronized() in the correct or best place; it seemed right at the time and may revisit this another time.

You can often find references that help explain concepts on the Processing page (references, tutorials, examples, etc.) :

There are Processing references and there are Java references. :slight_smile:

Processing is built on top of Java and often has a wrapper method to call the underlying Java code. Sometimes the source code will help to clarify things.

The Processing dist() function can be found in the source code here:

https://github.com/processing/processing4/blob/main/core/src/processing/core/PApplet.java#L4530 < Give it a moment to open and go to the line!

I liked this Introduction to Processing so sharing:
https://www.geeksforgeeks.org/introduction-to-processing-java/

I don’t have all the answers but do a lot of research, searching (some resources are better than others) and coding and eventually find my way.

And oh! I was typing this post and just after you came in with yours!
Where we synchronized()?

:)

1 Like

Ok, I’ll see where I will get! thanks! :slight_smile:

:laughing: Nice one! :stuck_out_tongue_closed_eyes: Thanks for the info! :slight_smile: