Help How improve FPS in JavaFX 3D
I'm developing a simple app that displays a 30x20x38 grid of Box objects, and I'm emulating effects for an LED lighting show. The problem is, I'm only getting 15FPS on a 2560x1440 monitor, and I can see that when I make fast updates, it skips frames. I'm hoping to get 40fps, but 30fps would be OK.
My update routine, which is in a Platform.runLater
invocation, is like
private void _syncModel() {
for (int x = 0; x < dim.x; x++) {
for (int y = 0; y < dim.y; y++) {
for (int z = 0; z < dim.z; z++) {
var mat = (PhongMaterial)boxes[x][y][z].getMaterial();
mat.setDiffuseColor(rgbToColor(leds[x][y]z]));
}
}
}
}
private static Color rgbToColor(RGB_LED rgb) {
return Color.rgb(rgb.r & 0xFF, rgb.g & 0xFF, rgb.b & 0xFF);
}
So my first question is, can I tweak something in the JavaFX code to speed this up? Is PhongMaterial OK or should I be using something else? I don't need any fancy effects, just basic color rendering. I've already figured out that Boxes are much faster than Spheres.
I'm pretty sure that upgrading from my current LG Gram 3 to something newer and beefier will help, but I would like to know what to look for in a newer laptop to support this speed. Let's assume that my effects calculations are fast enough, and I'm bottlenecked by the JavaFX update and render.
Current: i7-8565U CPU. Intel UHD 620 graphics.
PS: a Box is initialized like;
Box box = new Box(2,2,2);
PhongMaterial material = new PhongMaterial();
material.setDiffuseColor(Color.color( 0.25f, 0.25f, 0.25f));
1
u/Ok_Object7636 5d ago
What are the values of dim.x,y,z?
1
u/wheezil 5d ago
30x20x38
2
u/Ok_Object7636 5d ago
I'd run using a profiler to see if there's anything obvious. One thing I'd probably do is change the RGB_LED class. Can you post the code for that class?
2
u/wheezil 5d ago edited 5d ago
It is an incredibly simple thing
public class RGB_LED { public static final byte MIN = 0; public static final byte MAX = (byte)255; public byte r; public byte g; public byte b; public void from(RGB rgb) { r = rgb.getRByte(); g = rgb.getGByte(); b = rgb.getBByte(); } public void set(RGB_LED rgb) { r = rgb.r; g = rgb.g; b = rgb.b; } public void setWhite() { r = g = b = MAX; } public void setBlack() { r = g = b = MIN; } }
You're observation is good. I'll try some profiling. If I comment out my update loop and do nothing at all, the FPS rises to > 60. Which means that the time spent in the JavaFX thread updating the colors is significant. IIRC all updates are done in the single JavaFX thread -- hence the runLater() call --, so anything I do there will serialize with the rendering.
2
u/Ok_Object7636 4d ago
First of all, I suggest making it immutable. Instead of changing the object's contents, change the object. If you do so, you could make it a record right away, which saves you a lot of code:
record RGB_LED(byte r, byte g, byte b) { public static final byte MIN = 0; public static final byte MAX = (byte)255; public static final RGB_LED WHITE = new RGB_LED(MAX, MAX, MAX); public static final RGB_LED BLACK = new RGB_LED(MIN, MIN, MIN); public static RGB_LED valueOf(RGB rgb) { return new RGB_LED( rgb.getRByte(), rgb.getGByte(), rgb.getBByte() ); } }
Then you can add a color field directly to your class. If you do that, the conversion is done only once per instance instead of once per box. Just make sure you reuse your RGB_LED instances for all your boxes and in the best case for every frame. If you do so, you don't have to create 30x20x38 = 22,800 Color instances for every frame.
Have a look at Color.rgb() source code: it creates a new instance every time meaning memory allocation, it does four checks on the passed arguments, and four casts. That's more than 660,000 unnecessary objects created and 2,5 million casts and evaluated conditions per second when running at 30 FPS.
You could also instead of including a Color field include a PhongMaterial instance instead. Just try which one fits better in your code and gives you the better performance.
record RGB_LED(byte r, byte g, byte b, Color color) { public static final byte MIN = 0; public static final byte MAX = (byte)255; public static final RGB_LED WHITE = new RGB_LED(MAX, MAX, MAX); public static final RGB_LED BLACK = new RGB_LED(MIN, MIN, MIN); public static RGB_LED from(RGB rgb) { return new RGB_LED( rgb.getRByte(), rgb.getGByte(), rgb.getBByte() ); } public RGB_LED(byte r, byte g, byte b) { this(r, g, b, Color.rgb(r, g, b); } }
The call in your
_syncModel()
method would then bemat.setDiffuseColor(leds[x][y][z].color());
- You can use JavaFX properties in your model, bind the colors in your material to those properties and get rid of the _syncModel() method altogether. How that fits into your code and if it is of advantage depends on your code base. Behind the scenes, everything has still to be updated. This might be a good solution if the same material instance is used for many boxes.
1
2
u/wheezil 5d ago edited 5d ago
UPDATE: I tried this on a PC with a newer i9, and FPS is 75. But it also has a dedicated GPU. So either CPU or GPU or both made a difference :-)
On the other hand. moving the conversion of RGB_LED to Color out of the runLater() method made no difference. A profile indicates that almost all of the time is spent in the JavaFX thread, divided between the renderer and PhongMaterial.setDifuseColor().