Loading an Image cause game lag

Started by
14 comments, last by Nicholas Kong 10 years, 5 months ago

There seems to be a laggy with my game. Character movement and attack animations and enemy movement animation starts to slow down in the game.

This issue started when I decided to load an image with a width of 3392 pixels and draw that image to the game. I am using this image as a background for my game.

My game screen width is 660.

The laggy issue stops when I decided to use a different image with a width of 768 pixels.

Why is this happening exactly? Memory issue? Image issue?

Do larger image use more memory than smaller image?

It is certainly an interesting issue.

Advertisement

Does your 3392 pixel image have mip maps? If not, then trying to render that image into a screen that is only 660 pixels wide is going to cause a lot of trouble for the texture caches on your GPU since every texture fetch is going to be looking up texels very far away from the texels requried for neighboring pixels. Having mipmaps for your giant background texture will mitigate this problem as the GPU will use a lower resolution mip and texel fetches will be much more local. But this raises the obvious question: why are you using a 3392 pixel background when your screen is only 660 pixels? The memory requirements are much higher, and with mip mapping the higher mips will never be used, so it is just wasted memory.


Do larger image use more memory than smaller image?

Missed this part originally. The answer is yes, larger images use much more memory than smaller images. In an RGBA image (an image with a red, green, blue and alpha channel) with 8 bits per channel, each pixel is 4 bytes. If an image is 512x512, then you have 512 * 512 pixels, each of which is 4 bytes. This is 1048576 bytes (1mb). Now, imaging your image is 1024x1024 with 4 bytes per pixel. This image has 1024*1024 pixels, each of which is 4 bytes. That ends up being 4194304 bytes (4mb)! So, merely doubling the width and height of the image resulted in the memory requirements quadrupling.

Does your 3392 pixel image have mip maps? If not, then trying to render that image into a screen that is only 660 pixels wide is going to cause a lot of trouble for the texture caches on your GPU since every texture fetch is going to be looking up texels very far away from the texels requried for neighboring pixels. Having mipmaps for your giant background texture will mitigate this problem as the GPU will use a lower resolution mip and texel fetches will be much more local. But this raises the obvious question: why are you using a 3392 pixel background when your screen is only 660 pixels? The memory requirements are much higher, and with mip mapping the higher mips will never be used, so it is just wasted memory.

Ah I see. Larger image means larger memory usage. Thanks for the information!

The image is not using mip maps.

That is a pretty cool technique though now that I think about it...Calculating beforehand for optimal performance. I will research more about this and apply it to my game! Thanks!

Also - I understand that performance is generally better if your image/texture is sized on each dimension as 2^n (so here 512/1024/2048/4096).... and pre-processed mip-maps obviously... (all your mip-maps will be powers of 2 also)

Also - I understand that performance is generally better if your image/texture is sized on each dimension as 2^n (so here 512/1024/2048/4096).... and pre-processed mip-maps obviously... (all your mip-maps will be powers of 2 also)


Why does performance works better when the image has those dimensions of 2 to the n?

Why does performance works better when the image has those dimensions of 2 to the n?

It has to do with math and the way graphics texturing hardware works.

Having power of two (POT) lengths allows you to make many optimizations because computers are based on POT hardware. When you have non power of two (NPOT) textures you lose those optimizations.

If you go back a few years all the major graphics engines required textures to have a POT length on each edge. So a texture could be 64x2048, or 256x256, or any other size as long as both edges were a POT length. One of the many reasons for this is generating mip-maps; it is easy to generate an image that is half as big, and another image that is half as big, and another image that is half as big, as small as needed. Another is some of the nice mathematical properties involved, such that you can always double the dimensions and store all the lower resolutions in that space, and that if you use any convolution kernels on the image it is guaranteed to work at every detail level. There are filters built in to the major graphics APIs, and they are usually box filters, using a 4x4 box filter works wonderful on POT images, but produce edge effects if the image is not a proper multiple(some NPOT sizes can still be proper multiples of the filter, but it is no longer guaranteed in all NPOT cases). Texture coordinate calculations and texture wrapping modes (floor/ceiling) mathematics is much simpler and can be done with bit shifts rather than multiplication and division. Another is that you can guarantee memory alignment. Etc., etc., etc.

For almost ten years now, newer graphics libraries require that implementations support NPOT textures.

Having NPOT has many indirect effects. Some of these include mipmap generation handling edges differently, texture compression may require encoding of blank space, wrapping and border functionality gets more complex, LOD filtering gets more complex, and there is a loss of compatibility with various algorithms that are optimized for POT sizes.

But getting back to your point, it is going to do with your specific algorithm and your specific implementation details in your code.

You would need to share a lot more details. If you are allowing the video card to do everything and you have a small number of sprites, the video card can easily handle a few hundred sprites, even a 4000-pixel wide background sprite is easy for the card. If instead you are running everything on the CPU then you could easily be trashing your CPU cache with your graphics data, or consuming all your memory bandwidth by moving images back and forth into memory, or having memory access patterns that are cache unfriendly.

HI there

I am new here ,and i am not so fimiliar with gamedev.You said that the issue started when you decided to load an image with a width of 3392 pixels.I have encountered the same question.I just want to know that if there is any powerful program which supports to do that.Thanks for any suggestion


You would need to share a lot more details. If you are allowing the video card to do everything and you have a small number of sprites, the video card can easily handle a few hundred sprites, even a 4000-pixel wide background sprite is easy for the card. If instead you are running everything on the CPU then you could easily be trashing your CPU cache with your graphics data, or consuming all your memory bandwidth by moving images back and forth into memory, or having memory access patterns that are cache unfriendly.

I am happy to share code. Here is the code of my 2D image in my 2D Java game.

I have comments that explains the code.

I am doing my painting and I am drawing this background image to Java double buffered graphics canvas

Note: The game is not full screen and the code does not use the GraphicsConfiguration Object in Java.

I am not sure if the game is using the video card or the CPU because I never use any code that

tells the game to take over either of the two.


package com.nicholaskong.SpaceShoot.characterSprites;

import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

import javax.imageio.ImageIO;

import com.nicholaskong.SpaceShoot.Game;
import com.nicholaskong.SpaceShoot.Main;
import com.nicholaskong.SpaceShoot.Sprite;
import com.nicholaskong.SpaceShoot.Vector2D;

public class MountainStage extends Sprite implements KeyListener{

    private ArrayList<Image> stage;
    private BufferedImage image;
    private double speed = 5;
    
    public MountainStage(Vector2D position,Spiderman spiderman)
    {
        this(position.getX(),position.getY());
    }
    public MountainStage(double x, double y) {
        
        position = new Vector2D(x,y);
        velocity = new Vector2D();
        
        stage = new ArrayList<>();
        try {
            image = ImageIO.read(new File("src/stage/stage.png"));
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
            stage.add(image);
            // mountain stage can now listen for key events
            Game.getInstance().addKeyListener(this);
            
        
    }
    public void draw(Graphics g)
    {
        // draw the mountain stage to Java graphics canvas
        g.drawImage(stage.get(0), getX(), getY(),getWidth(),getHeight(),null);
    }
    
    public void update(long milliseconds)
    {
        // mountain stage gets updated with a time-based motion
        position = position.add(velocity.multiply(milliseconds / 16.667));

    }
    
    public int getWidth()
    {
        return image.getWidth();
    }
    
    public int getHeight()
    {
        return image.getHeight();
    }
    public int getX()
    {
        return (int) position.getX();
    }
    
    public int getY()
    {
        return (int) position.getY();
    }
    @Override
    public void keyPressed(KeyEvent e) {
        // TODO Auto-generated method stub
        
        // while the character Spiderman is alive
        if(Spiderman.alive)
        {
            
            if(e.getKeyCode() == KeyEvent.VK_LEFT)
            {
                /*
                * use -5 instead of say 0 because
                * if I use 0, otherwise the image won;t
                * cover the default black screen of the canvas
                * when moving the mountain stage image
                */
                int lowXThreshold = -5;
                if(position.getX() > lowXThreshold)
                {
                    // lock the image at a particular point
                    position.setX(lowXThreshold);
                
                
                }
                
                // moves the mountain stage background
                position.setX(position.getX() + speed );
            
            }
            else if(e.getKeyCode() == KeyEvent.VK_RIGHT)
            {
               /* As the player moves farther right,
                * this makes the mountain stage background
                * image move farther left to achieve the
                * illusion that the player is actually
                * moving in this background that makes
                * up the world.
                *  
                * You will notice the top left corner that represents the
                * image coordinate x position will be at a negative x value
                * when the player moves farther right.
                *
                * subtract off the image width from the game screen's width
                * the difference of this calculation is the distance of the image
                * the player does not see in the game but is merely outside
                * the game window in view.
                *
                * NOTE: when the player sees the game, the player
                * only see a part of an actual image
                *
                * The user will see the rest as the player
                * moves father right in the game
                */
                
                /* the calculation logic to enable the player to scroll
                *  through the entire image.
                *  The logic scales well so it works with any image
                *  of any width size.
                */
                if(-position.getX() > image.getWidth() - Main.getWidth())
                {
                    
                    
                    // locks the image at a particular point
                    position.setX(-(image.getWidth() - Main.getWidth()));
                    
                    
                }
                
                // moves the mountain stage background
                position.setX(position.getX() - speed);
            }
        }
    }
    @Override
    public void keyReleased(KeyEvent arg0) {
        // TODO Auto-generated method stub
        
    }
    @Override
    public void keyTyped(KeyEvent arg0) {
        // TODO Auto-generated method stub
        
    }
}
 
Why does performance works better when the image has those dimensions of 2 to the n?

I would also like to add:

Multiplying and dividng with texture-width values of power of 2 (which happens a lot) is a matter of of shifting bits to left / right, and shift-operations are very fast!

E.g. to multiply x with 256 you just need to shift bits in x 8 times. ( x = x<<8 )

This topic is closed to new replies.

Advertisement