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...
Related
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.
In my game, I'm using the ShapeRenderer class to draw a maze. Basically, I'm using the rectangle function (in the ShapeRenderer class) to draw small black lines. In the past, I had no problem debugging the game performance-wise (fps = 60). But lately, I've been having some performance issues. To make it short, I took out every sprites and actors I've drawn in the game and decided to draw the maze ONLY. Everytime I debug my game through the Desktop Launcher, the fps performance lowers by half (basically around 33, 34 fps). Yet, when i run it, it goes up to 60 fps.
I believe that it's a clear indication that the ShapeRenderer class wouldn't be the best choice for me to draw the maze performance-wise. I've tried with a spritebatch with texture png image (rectangle) and that didn't change a thing. I was wondering if there was a better choice to draw the maze and still maintain an fps performance to 60 (in debug mode) or is it just normal that debugging the game would lower my fps performance by half?
P.S: This is my code which is inside the render method to draw the maze:
for(int x = 0; x < rows; x++){
for(int y = 0; y < columns; y++){
if(this.grid[x][y].north.hasWall){ //NORTH BORDER LINE
shapeRenderer.rect(22+((GENERIC_WIDTH_HEIGHT_MAZE+10)*x), 450-((GENERIC_WIDTH_HEIGHT_MAZE+10)*y), GENERIC_WIDTH_HEIGHT_MAZE+10, 0, color1, color2, color3, color4);
}
if(this.grid[x][y].west.hasWall){ //WEST BORDER LINE
shapeRenderer.rect(22+((GENERIC_WIDTH_HEIGHT_MAZE+10)*x), 450-((GENERIC_WIDTH_HEIGHT_MAZE+10)*y), 0, -GENERIC_WIDTH_HEIGHT_MAZE-10, color1, color2, color3, color4);
}
if(this.grid[x][y].east.hasWall){ //EAST BORDER LINE
shapeRenderer.rect(22+((GENERIC_WIDTH_HEIGHT_MAZE+10)*(x+1)), 450-((GENERIC_WIDTH_HEIGHT_MAZE+10)*y), 0, -GENERIC_WIDTH_HEIGHT_MAZE-10, color1, color2, color3, color4);
}
if(this.grid[x][y].south.hasWall){ //SOUTH BORDER LINE
shapeRenderer.rect(22+((GENERIC_WIDTH_HEIGHT_MAZE+10)*x), 450-((GENERIC_WIDTH_HEIGHT_MAZE+10)*(y+1)), GENERIC_WIDTH_HEIGHT_MAZE+10, 0, color1, color2, color3, color4);
}
}
}
Any insights would be appreciated. are the following values:
GENERIC_WIDTH_HEIGHT_MAZE = 26 (Integer)
rows = 9
columns = 12
color1 = color2 = color3 = color4 = Color.BLACK
If when you run it the rendering speed is good enough then I would not worry about the performance when debugging.
But in general this looks like something you can optimize greatly:
Since it is a maze you can significantly reduce the number of draw calls by generating "blobs". You can join the walls and even use triangle stripes to draw the whole chunks.
Are you using face culling to reduce a number of fragments? (you should)
You most likely don't need to draw all of the walls anyway. Create a system to find only the walls that are not behind other walls (should be easy since it looks like a normal 2d grid).
Reduce redundant calls: I assume you keep setting things like color for every rect you draw. Try to do that only when it needs changing.
The maze is most likely static or changes rarely. Generate a GPU buffer on load time with all the vertices and then keep reusing that buffer to reduce the traffic to the GPU.
Again these are just a few pointers where you may optimize but I would try to optimize it as late as possible and only if needed. Being too slow on debug is usually not a good reason to start optimizing.
Since there can be very many reasons why the debug is slow you might want to have a system to check what is your actual drawing FPS at the moment. You may test this by drawing your scene to a FBO of the same size as your screen and try to just keep drawing your scene in a for (or) while loop and measure FPS. This gives you the rough estimation as to how close to your limit you are.
Well, in the end, I made a TextureAtlas containing the walls drawn (small png pics) and called two of them (one horizontal and on vertical) to draw the maze and got rid of ShapeRenderer. Like Deniz Yılmaz mentionned, ShapeRendereris only used for debugging which is probably why the performance slows down inside the for loop. I also a couple of other performance optimization on other parts of my code. Now the performance is at 60 fps all the time. Thanks everyone!
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 :):
I'm currently working on an Arduino Uno and Processing interface with the idea of revealing an image much like a "scratch off lottery ticket." I currently have the image uploaded into the sketch and the black background ready, however I'm not sure at this point how to start revealing the image through fill()
I know that I could technically use an ellipse of 1 pixel wide that will SLOWLY reveal the image (and subsequently take forever because the Arduino joystick isn't very cooperative.) but I was hoping there would be an easier way to reveal it. Does anyone have any ideas?
Here is the code:
void draw() {
noStroke();
ellipse(xPos, yPos, 1,1);
if(zButton == 0){
background(0);
}
color c = img.get(xPos, yPos);
fill(c);
serialEvent(myPort);
}
This is the draw function, it reads the joystick's interface through the serialEvent function, as of right now I have a 1x1 ellipse revealing the image pixel by pixel, but that would be extremely tedious
Thanks guys, any help is appreciated
Instead of using .get(x,y) to get individual pixels, you can use loadPixels to expose the pixel array, and simply color those instead. This is generally much faster than using the get and set functions.
To speed things up further, cache the image's pixels and track which consecutive lines you can just copy over (for instance, if you offer a 5 pixel radius ellipse, then you can track the fact that you need to copy 5 pixels starting at position X, which you can then copy over as a single call. The more the user scratches, the more pixels you can copy that way in a single call)
I know this question has been accessed before (like here), but I was wondering how to do the following. In my game, I have a scrolling background. There is for example a blue sky that is light blue at the bottom and gets darker the higher you go. This is not really possible with the suggested solution:
shapeRenderer.filledRect(x, y, width, height,
lightBlue, lightBlue, darkBlue, darkBlue);
since you can only give the colors that really will be shown. I would like to have a gradientPaint with at the top darkblue and the bottom lightblue that stretches out over for example 500 pixels. This, while I only draw only 200 pixels of it. With this, the color would still get darker when the background scrolls. Does anybody know how to do this with libgdx?
What you want is to see a smaller (say 200 pixel) window onto a larger (say 500 pixel) gradient. To do that you just need to compute the colors of four corners colors based on the location of your window in the overall gradient, and then draw just that. (So don't think about drawing the entire background, but about figuring out how to draw just the part that you need.)
Since you're just moving smoothly between the two colors (between 0 and 500), you're doing a "linear interpolation" (that is a straight-line estimation) between the colors based on where the Window is. Libgdx supports this via the lerp() methods on Color.
Assuming the window is travelling along the Y axis, something like this should give what you want:
Color baseColor = lightBlue;
Color topColor = darkBlue;
int skyHeight = 500;
int windowHeight = 200;
int windowLocation = ...; // something betweeen 0 and skyHeight - windowHeight;
Color windowBottomColor = baseColor.copy().lerp(topColor, windowLocation / skyHeight);
Color windowTopColor = baseColor.copy().lerp(topColor, (windowLocation + windowHeight) / skyHeight);
Now windowBottomColor and windowTopColor should be suitable for calling filledRect:
shapeRenderer.filledRect(x, y, width, height,
windowBottomColor, windowBottomColor, windowTopColor, windowTopColor);
Note that the "copy()" calls create a new Color object for each invocation, so you might want to optimize that to avoid the allocation.
Disclaimer: I haven't tried this code, so it probably has some stupid bugs in it, but hopefully it gives you the right idea.