JavaFX GrowableDataBuffer, Canvas performance hiccup - java

So I have an application (a game) that draws a number of layered PNGs to screen in a grid.
for (Image anImage : image) {
if ((x + offset + width) >= 0 && x + offset <= canvasWidth) {
gc.drawImage(anImage, x + offset, y, width, height);
drawn++;
} else {
segmentsSkipped++;
}
offset += width;
}
// if (drawn == 1) gc.drawImage(image[0], x + offset, y, width, height);
This is moving fast and is a simple loop. I'm running
-Djavafx.animation.fullspeed=true
I can't provide a SSCE, as a proper example would need scrolling etc and sprites to reproduce properly. As you can see in the code, in order to reduce draw operations, I'm testing whether each grid segment is within the canvas area and not drawing those outside. The gives me about 30FPS improvement in use but is causing an odd problem: normally on each cycle of the loop, on a 4K monitor, the renderer is "skipping" 3 parts of the 5 part grid - (as expected). I.e the renderer is drawing two images to screen. As far as I can isolate in practice, the hiccup occurs when 4 parts are skipped (i.e a single image is drawn that fills the whole screen). There is a then a noticeable hiccup in the scrolling. Sometimes this is significant, and is always noticeable. On higher resolutions (beyond 4k) the little skip is noticeable on as 2 moves to 3 skipped parts.
The images are large 2800 in x. Too large to render in one call on non DX12 cards. My temp. solution has been to force another draw call, as you can see in the commented code section. This helps the problem. My suspicion, and this is a complete guess, is that the GrowableDataBuffer is changing rapidly, as the size of the graphics required area doubles. I've had a poke around in the GraphicsContext, and this 'could' seem a candidate for problems as it appears to grow at n^2.
My temp. solution might be workable, because even though at different resolutions, different quantities of the grid are drawn, always requiring minimum 2 pieces at this size, will allow scope for a large n^2 texture. But I'd much prefer a less hacked solution. I did try getting access to the buffer, but it's package-protected.
I wonder -- if this is the problem -- is there anyway to advise the GrowableDataBuffer not to shrink, and to maintain it's larger size? Or perhaps someone has an idea what's causing this.

Another (but more logical) implementation of your "hack" would be to also draw those background tiles which are slightly off-screen and could get on-screen the next couple of frames:
for (Image anImage : image) {
if ((x + offset + width) >= -RENDER_MARGIN && x + offset <= (canvasWidth + RENDER_MARGIN)) {
gc.drawImage(anImage, x + offset, y, width, height);
drawn++;
} else {
segmentsSkipped++;
}
offset += width;
}
The constant RENDER_MARGIN thereby defines how many pixels a tile may be off-screen and still be drawn. The actual value depends on your scroll speed.
However, I would suggest to improve the rendering logic to keep the GrowableDataBuffer at a constant size by using the method GraphicsContext.drawImage(Image img, double sx, double sy, double sw, double sh, double dx, double dy, double dw, double dh) which allows you to define the source and destination areas, i.e. to only draw the exact region of the image which is on-screen at the current frame.
Off-topic: I would suggest to divide your background images into smaller chunks to decrease memory usage and probably increase the overall rendering performance as well.

Related

Is it possible to position a JLabel using a decimal number such as double?

I am making a platformer game using Java. I have a character in the form of a JLable with an Image Icon that can be moved around using the arrow keys. I am using the setBounds() method to set the position of the JLabel, but this method only takes int values as x and y arguments, and I would like more precise control. Using a null layout, is it possible to use more precise values such as double to position a JLabel?
Thank you for your time, and have a great day.
Well if you are using JLabels then you are using Swing, and Swing has non-floating point coordinates system, so no you cannot.
Looking at the JavaDocs for JLabel...
setLocation takes ints as parameters. There doesn't seem to be a way to use decimal coordinates.
JavaFX can do. However in Swing the coordinates are integer pixels.
You could draw scaled 2x2 in memory (Image getGraphics) and then rescale to achieve half int steps of 0.5; but that would blur (interpolated pixels) or flicker (best nearby match).
For zooming scalable vector graphics, .vg, using the batik library would be feasible.
For text subpixel hinting can on some LED screens position black text on thirds of a pixel, depending on the layout of the Red, Green and Blue subpixels.
In general it is not worth the effort to attempt the above. Except that JavaFX has nice animation support, CSS styling and animation effects.
As others are saying, UI components are designed to position only with exact pixel coordinates.
You could store a location in double and then round it to int when you actually set the location. Something like this:
double velX = ..., velY = ...;
double x = ..., y = ...;
void example() {
int t = 1000 / 30;
new Timer(t, (ActionEvent e) -> {
x += t * velX;
y += t * velY;
label.setLocation((int) Math.round(x),
(int) Math.round(y));
}).start();
}
That way you can move in smaller increments. It just won't be visible.
There is a really great example of animating an image here in a way very similar to what you're describing which you should take a look at. Trying to position components is really more trouble than it's worth, in my opinion, especially if you're trying to make something like a game. Performing custom painting is more flexible. (The tutorial is here.)

How can I scale a 2D grid to the size of the canvas?

I'm making a simple grid-based variant of Conway's Game of Life in JavaFX, and I'm using an example I found on StackExchange. The problem is that the scaling method in that example doesn't seem to work for me. In this example, you can choose the "size" of the grid, meaning how many rows/columns it is composed of, and then the scale is supposed to determine the size of each cell in that grid, allowing the grid to fit inside the canvas if the user resizes the window.
I tested this by trying to "fill in" the top-left and bottom-right cells, using (0,0) and ((xMax - 1), (yMax - 1)). But the bottom-right one seems to clip way out of the bounds of the canvas, as I have to subtract more than 1 from yMax to get it to appear.
Here is the "scaling factor" method I'm using...
private double getScaleFactor() {
return (canvas.getWidth() + canvas.getHeight()) / (cellsWide + cellsHigh);
}
Is there anything wrong with this method? Could it be the way I'm constructing the GUI? The canvas is bound to the center of the BorderPane, and there is an HBox bound to the top and VBox bound to the left. However, I've been able to draw things in the center of the canvas using (width / 2) and (height / 2) as usual, so something tells me it's something wrong with how I'm getting the scaling factor.
Can someone help me out?
The formula you are currently using looks like it shouldn't work.
Suppose the following two cases:
You have a canvas that is 100 x 10 with 1 row and 10 columns
You have a canvas that is 100 x 20 with 1 row and 10 columns
In the first case, the scale factor would be (100 + 10) / (1 + 10) = 10, and ideally this would cause the cells to be sized such that they completely fill the canvas. However, in the second case, the scale factor would be (100 + 20) / 1 + 10) = 10.909091, which would imply cells in the second case are larger than those in the first case. Since the width didn't change, and the entire canvas is used in the first case, the larger cells in the second case MUST run off the canvas.
Since I don't know too much about how the scale factor is being used I can't really provide too much more help in the way of what is wrong with what you currently have, but:
Assuming the scale factor is the side length of a cell (in pixels) and that the cells remain square when the window is resized to a different aspect ratio, here is a formula that should work:
private double getScaleFactor()
{
double hfactor = canvas.getHeight() / cellsHigh;
double wfactor = canvas.getWidth() / cellsWide;
return (hfactor < wfactor ? hfactor : wfactor);
}
This computes the height of a cell if the cells were to fill the entire height of the screen, and the width of a cell if the cells were to fill the entire width of the screen. It then takes the minimum of the sizes to ensure that all the cells fit on the screen.

Setting a spherical area in a BufferedImage to be a certain opacity efficently

first of all I have scoured Google and SO for this answer, finding only how to change the actual pixels to be of a certain alpha value, which would be incredibly slow or actually making a part of the BufferedImage completely transparent via the use of lwg2.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR)). This is the exact functionality I need, however, I need to have the value to be less than 1f, which you cannot do with this specific instance of AlphaComposite.CLEAR.
What I want this implementation for is to make a wall inside my 2.5d game become transparent when the player goes behind it, like so:
The logic behind my game is that the terrain is one BufferedImage which is only updated when called, and then having the rest of the walls, etc, being drawn onto another BufferedImage where entities are also drawn, so the opacity transformation would only affect the trees (or walls).
This is the code I am using atm, but as I said I don't want the circle that I am drawing to make a part of the image completely transparent, but only slightly (about 50%):
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.5f));
g2.fillOval(x - (int) (TILE_WIDTH * 1), y - (int) (TILE_HEIGHT * 1.5), TILE_WIDTH * 2, TILE_HEIGHT * 3);
(The 0.5f in the AlphaComposite constructor does nothing).
The reason I need this to be efficient is because I am updating this image 30 times a second, so efficiency > quality.
So, I ended up solving the issue by not manipulating the image directly via making a part of the image translucent, but manipulating the opacity of the images I am drawing with. As #user343760 and #NESPowerGlove mentioned, I could just make the assets I am using translucent when the player is behind it. Since I am using a underlying grid array to back my game, I could do this by working out if the tile.x - 1 == (int) player.x and tile.y - 1== (int) player.y. In isometry, this meant that the player was on the tile directly above it in our perspective. Then I had to solve the issue if the wall.z is bigger than 0 or 1, hence having the problem where a tile 5 blocks "bellow" the player could obstruct him if the walls extended z = 5 above the tile. For this problem, I implemented the following solution:
for(int i = 0; i < wall.getAsset(1f).getHeight()/TILE_HEIGHT; i++) {
if((tile.x - i - wall.z == (int) world.player.getX() && tile.y - i -wall.z == (int) world.player.getY())) {
lwg2.drawImage(wall.getAsset(0.5f), x, y, this);
}
}
This also ensures that the image is transparent even if the player is "above" the tile "above" the tile where the wall is situated, in terms of the image extending above that limit. I have fixed this via using the for loop which looks above for i number of times, depending on the image.height/tile_height, which is an universal constant.
If you require to make a part of the image transparent, I have not found solutions which would work fault free, except for manipulating the pixels in the low-levels of BufferedImage. If you also want to erase a part of an image directly, use the code g2.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR)); and draw as you would normally. Remember to switch back to a normal composite via g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));.
You could also draw with a certain opacity in the first place using the Composite g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity));, where opacity is a float with values from 0f to 1f, 0f being completely transparent and 1f being completely opaque.
I hope this helped anyone out there. If you find a better way of doing this, please leave a comment for future readers.
This is what my solution looks like :):

Apply Bitmask to BufferedImage

I'm developing a Worms-like game (with destructible terrain and everything) in Java.
All did fine until i tried to update the terrain image using a bitmask.
Let me explain the process in detail :
Whenever a projectile collision occurs i draw a black circle into my
terrain mask (which has black for transparent and white for opaque pixels).
public void drawExplosion(Vector2 position,BufferedImage explosionImage){
Graphics2D gMask = (Graphics2D) terrainMask.getGraphics();
gMask.drawImage(explosionImage,(int) position.x, (int) position.y, null);
gMask.dispose();
}
After the black circle was drawn into my terrainMask BufferedImage whose type is
BufferedImage.TYPE_BYTE_INDEXED, i update my visible terrain BufferedImage by setting
every pixel to 0 if the terrainMask's pixel is black at the same position.
public void mapUpdate(){
for(int x = 0 ; x < terrainMask.getWidth(); x++){
for(int y = 0 ; y < terrainMask.getHeight(); y++){
if(terrainMask.getRGB(x, y) == -16777216){
terrain.setRGB(x, y, 0);
}
}
}
}
After these steps the terrain BufferedImage is updated and every looks fine, showing the
explosion hole in the terrain.
Here comes my problem :
Whenever I call mapUpdate() the Game stops for 300-500 ms checking 2400*600 pixels and setting transparent pixels in the terrain if a check returns true.
Without setRGB() the lag does not occur. So my Question is how can I apply a bitmask to
a BufferedImage more efficiently.
Important : All BufferedImages are converted to compatible ones using
GraphicsConfiguration.createCompatibleImage() method.
When I call getData() on the BufferedImage to get the pixel array, the fps drops to
~23 fps making the game unplayable, so this is not an option here.
I also setSystem.setProperty("sun.java2d.opengl","True");
to enabled OpenGL Pipeline. Another weird thing is whenever i don't set the openGL property my Game reaches more than 700 fps (with openGL enabled 140 - 250 fps) and my laptop freezes completely. My game loop is the same as described here : http://www.koonsolo.com/news/dewitters-gameloop/ (Constant Game Speed independent of Variable FPS , the last one).
The fastest way you can do this in Java (i.e. no OpenGL) that I know of, would be to:
a) Change your mask (terrainMask) image's colors to white and transparent (instead of white and black). Just changing the color table (IndexColorModel) will do, I guess.
b) Replace the double getRGB/setRGB loop with painting the mask over the terrain, using the proper alpha composite rule. Both setRGB and getRGB are potentially slow operations, due to lookups, color conversion and possible data type conversion (all depending on your images), so they should generally be avoided in performance critical code. The updated code could look something like the following:
public void mapUpdate() {
Graphics2D g = terrain.createGraphics();
try {
g.setComposite(AlphaComposite.DstIn); // Porter-Duff "destination-in" rule
g.drawImage(terrainMask); // Clear out transparent parts from terrainMask
}
finally {
g.dispose();
}
}
Doing it this way should also keep your images managed (i.e. no fps drop).
For more information on AlphaComposite, see Compositing Graphics from the Java2D Advanced Topics tutorial.
PS: Another optimization you could do, is to only update the part of terrain that are covered by the explosion (i.e. the rectangle covered by position.x, position.y, explosionImage.getWidth(), explosionImage.getHeight()). No need to update the pixels you know isn't covered...

3D to 2D projection

I'm trying to write a game in Java (Processing, actually) with some cool 3D effects.
I have a choice of two 3D renderers, but neither have the quality or flexibility of the default renderer. I was thinking that if I could get a function to proje
So say I have a set of coordinates (x, y, z) floating in 3D space. How would I get where on the 2D screen that point should be drawn (perspective)?
Just to clarify, I need only the bare minimum (not taking the position of the camera into account, I can get that effect just by offsetting the points) - I'm not re-writing OpenGL.
And yes, I see that there are many other questions on this - but none of them seem to really have a definitive answer.
Here is your bare minimum.
column = X*focal/Z + width/2
row = -Y*focal/Z + height/2
The notation is:
X, Y, Z are 3D coordinates in distance units such as mm or meters;
focal is a focal length of the camera in pixels (focal= 500 for VGA resolution is a reasonable choice since it will generate a field of view about 60 deg; if you have a larger image size scale your focal length proportionally); note that physically focal~1cm << Z, which simplifies formulas presented in the previous answer.
height and width are the dimensions of the image or sensor in pixels;
row, column - are image pixels coordinates (note: row starts on top and goes down, while Y goes up). This is a standard set of coordinate systems centered on camera center (for X, Y, Z) and on the upper-left image corner (for row, column, see green lines).
You don't need to use OpenGL indeed since these formulas are easy to implement. But there will be some side-effects such as whenever your object has several surfaces they won't display correctly since you have no way to simulate occlusions. Thus you can add a depth buffer which is a simple 2D array with float Z values that keeps track which pixels are closer and which are further; if there is an attempt to write more than once at the same projected location, the closer pixel always wins. Good luck.
Look into Pin hole camera model
http://en.wikipedia.org/wiki/Pinhole_camera_model
ProjectedX = WorldX * D / ( D + worldZ )
ProjectedY = WorldY * D / ( D + worldZ )
where D is the distance between the projection plane and eye

Categories