I'm working on a simple 2D game, rendering via the Java2D API. I've noticed that when I try to draw on integrated graphics card the performance crashes.
I've tested this game on both my main rig with a newer ATI Radeon and my 5 year old laptop which also has an (incredibly antiquated) Radeon. On both I get good FPS, but when I try to use my Intel i5's onboard HD 4000 graphics, it crawls at around 20 FPS.
I'm using Full Screen Exclusive mode.
At any given moment, I am rendering approximately 1000 images at once.
Annoyingly, when I try to getAvailableAcceleratedMemory() it just returns -1 for this card, and it seems to refuse to accelerate any images.
Does anyone have any ideas how to fix this issue?
Rendering code:
Graphics g = bufferStrategy.getDrawGraphics();
g.drawImage(img, x, y, img.getWidth(), img.getHeight(), null)
g.dispose();
bufferStrategy.show();
Image Loading code:
BufferedImage I = null;
I = ImageIO.read(new File(currentFolder+imgPath));
imgMap.put(imgIdentifier, I);
The images are stored in a hashmap of BufferedImages identified by strings, so when an entity needs to draw and image it just gets it out of the hashmap and draws it. In the current case, the entities are mostly floor and wall tiles, so they never change (and thus don't have to get the image from the hashmap other than the very first time).
EDIT - I've incorporated MadProgrammer's method, but it didn't change my FPS.
This is an example of converting an image to a compatiable image...not an answer in of itself
This is some of the library code that I use...
public static BufferedImage createCompatibleImage(BufferedImage image) {
BufferedImage target = createCompatibleImage(image, image.getWidth(), image.getHeight());
Graphics2D g2d = target.createGraphics();
g2d.drawImage(image, 0, 0, null);
g2d.dispose();
return target;
}
public static BufferedImage createCompatibleImage(BufferedImage image,
int width, int height) {
return getGraphicsConfiguration().createCompatibleImage(width, height, image.getTransparency());
}
public static GraphicsConfiguration getGraphicsConfiguration() {
return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
}
I would do something like...
I = createCompatibleImage(ImageIO.read(new File(currentFolder+imgPath)));
imgMap.put(imgIdentifier, I);
Related
a have a problem with scaling images up. I have a png file that looks like this:
raw png
The image is 8px * 8px and has some red straight lines on it.
But when i draw this image with java and scale it up this happens: java image
And as you can barly see, the line is not exactly straight. It is always one pixel off and makes this kind of wavy shape. If the image gets drawn somewhere else on the frame the "waves" are somewhere else. The image is rotated 90° but I tested it without rotation and it was still there. Apart from this I do need rotated images.
Is there any way to fix this? I enabled text-antialiasing in the same Graphics2D object. Is there also some sort of anitaliasing for this?
Code
private void loadImage(String path, int field, int imageNumber) {
BufferedImage image;
image = new Resource().readImg(path);
images[field][imageNumber][0] = image;
images[field][imageNumber][1] = rotateClockwise90(image);
images[field][imageNumber][2] = rotateClockwise90(rotateClockwise90(image));
images[field][imageNumber][3] = rotateClockwise90(rotateClockwise90(rotateClockwise90(image)));
}
private BufferedImage rotateClockwise90(BufferedImage src) {
int width = src.getWidth();
int height = src.getHeight();
BufferedImage dest = new BufferedImage(height, width, src.getType());
Graphics2D graphics2D = dest.createGraphics();
graphics2D.translate((height - width) / 2, (height - width) / 2);
graphics2D.rotate(Math.PI / 2, height / 2, width / 2);
graphics2D.drawRenderedImage(src, null);
return dest;
}
When the program starts I load the image I rotate it in all 4 directions, so I don't have to do this over and over again while the program is running.
public BufferedImage getTile(int type, int part, int rotation) {
return images[type][part][rotation];
}
And then all I have to do is calling this get method and draw the image:
g2d.drawImage(tiles.getShipTile(type, part, rotation), x, y, null);
I actually found a way to avoid these weird pixels but this method makes the image a little bit blurry.
Instead of using
g2d.drawImage(img, x, y, width, height, null);
you can simply use
g2d.drawImage(img.getScaledInstance(width, height, Image.SCALE_AREA_AVERAGING), x, y, null);
which does basically the same thing but wehn I scale it up it uses this smooth making key.
I tried this and noticed that its not verry comfortable because it lags a lot.
So I just scale it up in the beginning when I also rotate the images.
As I said this method is a bit blurry but if there are no other ways avoiding this problem I have to get use of this. You almost can't see the blur, so this would be an option for me.
I am stuck (beyond the limits of fun) at trying to fix text quality with offscreen image double buffering.
Screen capture worth a thousand words.
The ugly String is drawn to an offscreen image, and then copied to the paintComponent's Graphics argument.
The good looking String is written directly to the paintComponent's Graphics argument, bypassing the offscreen image.
Both Graphics instances (onscreen and offscreen) are identically setup in terms of rendering quality, antialiasing, and so on...
Thank you very much in advance for your wisdom.
The very simple code follows:
public class AcceleratedPanel extends JPanel {
private Dimension osd; //offscreen dimension
private BufferedImage osi; //offscreen image
private Graphics osg; //offscreen graphic
public AcceleratedPanel() {
super();
}
#Override
public final void paintComponent(Graphics g) {
super.paintComponent(g);
// --------------------------------------
//OffScreen painting
Graphics2D osg2D = getOffscreenGraphics();
setupGraphics(osg2D);
osg2D.drawString("Offscreen painting", 10, 20);
//Dump offscreen buffer to screen
g.drawImage(osi, 0, 0, this);
// --------------------------------------
// OnScreen painting
Graphics2D gg = (Graphics2D)g;
setupGraphics(gg);
gg.drawString("Direct painting", 10, 35);
}
/*
To make sure same settings are used in different Graphics instances,
a unique setup procedure is used.
*/
private void setupGraphics(Graphics2D g) {
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
}
private Graphics2D getOffscreenGraphics() {
//Graphics Acceleration
Dimension currentDimension = getSize();
if (osi == null || !currentDimension.equals(osd)) {
osi = (BufferedImage)createImage(currentDimension.width, currentDimension.height);
osg = osi.createGraphics();
osd = currentDimension;
}
return (Graphics2D) osg;
}
} //End of mistery
You are not drawing your two strings with the same color. The default color for the offscreen Graphics is rgb(0, 0, 0) (that is, pure black), while Swing will set the color of a Graphics object to the look-and-feel’s default color—which, for me on Windows 7, using the default theme, is rgb(51, 51, 51), or dark gray.
Try placing g.setColor(Color.BLACK); in your setupGraphics method, to ensure both strings are drawn with the same color.
Thanks for the replies.
With mentioning DPI, MadProgrammer has lead me to a working fix which I offer here more as workaround than as a 'clean' solution to be proud of. It solves the issue, anyway.
I noticed that while my screen resolution is 2880x1800 (Retina Display), MouseEvent's getPoint() method reads x=1440, y=900 at the lower right corner of the screen. Then, the JPanel size is half the screen resolution, although it covers the full screen.
This seen, the solution is as follows:
first, create an offscreen image matching the screen resolution, not the JPanel.getSize() as suggested in dozens of double buffering articles.
then, draw in the offscreen image applying a magnifying transform, bigger than needed, in particular scaling by r = screen dimension / panel dimension ratio.
finally, copy a down scaled version of the offscreen image back into the screen, applying a shrinking factor of r (or scaling factor 1/r).
The solution implementation is split into two methods:
An ammended version of the initial paintComponent posted earlier,
a helper method getDPIFactor() explained afterwards.
The ammended paintComponent method follows:
public final void paintComponent(Graphics g) {
super.paintComponent(g);
double dpiFactor = getDPIFactor();
// --------------------------------------
//OffScreen painting
Graphics2D osg2D = getOffscreenGraphics();
setupGraphics(osg2D);
//Paint stuff bigger than needed
osg2D.setTransform(AffineTransform.getScaleInstance(dpiFactor, dpiFactor));
//Custom painting
performPainting(osg2D);
//Shrink offscreen buffer to screen.
((Graphics2D)g).drawImage(
osi,
AffineTransform.getScaleInstance(1.0/dpiFactor, 1.0/dpiFactor),
this);
// --------------------------------------
// OnScreen painting
Graphics2D gg = (Graphics2D)g;
setupGraphics(gg);
gg.drawString("Direct painting", 10, 35);
}
To complete the task, the screen resolution must be obtained.
A call to Toolkit.getDefaultToolkit().getScreenResolution() doesn't solve the problem, as it returns the size of a JPanel covering the whole screen. As seen above, this figure doesn't match the actual screen size in physical dots.
The way to get this datum is cleared by Sarge Bosch in this stackoverflow post.
I have adapted his code to implement the last part of the puzzle, getDPIFactor().
/*
* Adapted from Sarge Bosch post in StackOverflow.
* https://stackoverflow.com/questions/40399643/how-to-find-real-display-density-dpi-from-java-code
*/
private Double getDPIFactor() {
GraphicsDevice defaultScreenDevice =
GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice();
// on OS X, it would be CGraphicsDevice
if (defaultScreenDevice instanceof CGraphicsDevice) {
CGraphicsDevice device = (CGraphicsDevice) defaultScreenDevice;
// this is the missing correction factor.
// It's equal to 2 on HiDPI a.k.a. Retina displays
int scaleFactor = device.getScaleFactor();
// now we can compute the real DPI of the screen
return scaleFactor * (device.getXResolution() + device.getYResolution()) / 2
/ Toolkit.getDefaultToolkit().getScreenResolution();
} else
return 1.0;
}
This code solves the issue for Mac Retina displays, but I am affraid nowhere else, since CGraphicsDevice is an explicit mention to a proprietary implementation of GraphicsDevice.
I do not have other HDPI hardware with which to play around to have a chance to offer a wider solution.
I am currently trying to get some kind of 2D dungeoncrawler (think: roguelike) running. Now I want to work with square tiles (32x32) but I wonder if there's a way to make my textures in a higher resolution, say 64x64, and scale them down onto a 32x32 square?
I imagine there has to be since almost all games do this in one way or another but all I can seem to find online is about 3D stuff.
Yeah. When you draw an image, you can add the new width and height to it to resize it.
public static BufferedImage resizeImage(BufferedImage image, int newwidth, int newheight) {
BufferedImage image2 = new BufferedImage(newwidth, newheight, BufferedImage.TYPE_INT_ARGB);
Graphics g = image2.getGraphics();
g.drawImage(image, 0, 0, newwidth, newheight, null);
g.dispose();
return image2;
}
Refer to here for more info.
I am thinking of some different ways of rendering the game into the screen.
I've currently working on a indie game and usually I just paint the textures on the screen right away:
public void paintComponent(Graphics g){
for(int i=0;i<textures.size;i++){
g.drawImage(texture.get(i).getTexture(),texture.get(i).getX(), texture.get(i).getY(), null)
}
}
But this is just an example. The problem with this is that if you have too many textures, you might start noticing some flickering or the process of drawing because when the g.drawImage is called, it paints the texture into the screen right after.
So I've though of a solution however I am not sure if it's a good way of doing it.
What I've done is instead of painting the textures into the screen, I simply draw them into the BufferedImage. After that, I simply draw the BufferedImage into the screen.
Here is an example:
public void paintComponent(Graphics g){
g.drawImage(bufferRender(), 0, 0, null);
}
public BufferedImage bufferRender(){
BufferedImage render = new BufferedImage(Main.window.getWidth(), Main.window.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics g = render.getGraphics();
for(int i=0;i<textures.size;i++){
g.drawImage(texture.get(i).getTexture(),texture.get(i).getX(), texture.get(i).getY(), null)
}
return render;
}
Now the question is... Is it a fine way of doing this?
I am currently doing a project that requires me to draw a large number (100+) images on the screen. The original resolution of each image is 20*20 and I am scaling them to 80*80 with nearest neighbor before drawing them.
I currently use AffineTransform to scale them up when the program is initialized, and redraw the enlarged images every frame in different positions.
Seeing as the target framerate is about 60 fps, I need to find a way to draw them faster, preferably so that it doesn't render them as 80*80 bitmaps. I tried Graphics2D's drawImage method with the width and height parameters, but it actually slowed my program down.
In pseudocode:
Image image; //loaded from 20*20 png file
public void init(){
image = resize(image, 80, 80);
}
public void repaint(Graphics g){
g.drawImage(image, 0, 0, null);
}
I also tried:
Image image;
public void repaint(Graphics g){
((Graphics2D)g).setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
g.drawImage(image, 0, 0, 80, 80, null);
}
Is there a solution to my problem?
There was a similar question about that: Load and Resize Image
Here is my answer: Load and Resize Image
I hope I could help.