How to speedup my Java2D framework?

Hello guys, I’m just over a week learning Java and for some crazy reason I am learning Java2D(which is weird since I’ve been coding in OpenGL for years now and have developed an OpenGL framework http://rel.phatcode.net/junk.php?id=133 )

Anyways, here’s the source and binary for the Java2D framework:

http://rel.phatcode.net/junk.php?id=131

Somehow disabling sprite transparency by composition speeds things up quite a bit. Any thoughts on why that happens or any way to speed things up?

Does Java2D do stuff in software via GDI?

Thanks!

Java2DFramework.java



import javax.swing.JFrame;

public class Java2DFramework extends JFrame 
{

	private static final long serialVersionUID = 1L;

	
	public Java2DFramework() 
    {

        add( new Screen() );

        setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        setSize( Globals.SCREEN_WIDTH, Globals.SCREEN_HEIGHT );
        setLocationRelativeTo( null );
        setTitle( "Relminator's Java 2D Framework" );
        setResizable( false );
        setVisible( true );
        
    }

    public static void main(String[] args) 
    {
        new Java2DFramework();
    }
}


screen.java


import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.Font;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.Dimension;
import java.awt.BasicStroke;
import java.awt.geom.*;
import java.awt.AlphaComposite;


import javax.swing.JPanel;


public class Screen extends JPanel implements Runnable  
{

	private static final long serialVersionUID = 1L;
	
	private Thread animator;
	    
    private double accumulator = 0;
	
    private int fps = 0;
    private int framesPerSecond = 0;
    private double previousTime = 0;
    private double oldTime = 0;
    private double secondsElapsed = 0;
	
    private Starfield stars = new Starfield();
    private Picture picture = new Picture();
    
    public Screen() 
    {

        addKeyListener(new TAdapter());
        setFocusable(true);
        setBackground(Color.BLACK);
        setDoubleBuffered(true);
        
    }

    public void addNotify() 
    {
        super.addNotify();
        animator = new Thread( this );
        animator.start();
    }
    
    public void paint( Graphics g ) 
    {
        super.paint(g);
        
        Graphics2D g2D = (Graphics2D)g;

        render( g2D );
        
        Toolkit.getDefaultToolkit().sync();
        g.dispose();
        
    }

    private class TAdapter extends KeyAdapter 
    {

        public void keyReleased( KeyEvent e ) 
        {
        	picture.keyReleased( e );
        }

        public void keyPressed( KeyEvent e ) 
        {
        	picture.keyPressed( e );
        }
        
    }
    
    
    public void update() 
    {

    	stars.update();
    	picture.move();
    	repaint();

    }

    public void render( Graphics2D g2D )
    {
    	
        
    	AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f);
    	g2D.setComposite(ac);
    	
    	g2D.setFont(new Font("Courier New", Font.BOLD, 24));
    	g2D.setColor( Color.BLUE );
        g2D.drawString( "FPS :" + fps, 0, 24 );
        
        stars.render( g2D, secondsElapsed );
        
        Dimension size = getSize();
        double w = size.getWidth();
        double h = size.getHeight();

        Rectangle2D e = new Rectangle2D.Double(-10, -10, 20, 20);
        g2D.setStroke(new BasicStroke(1));
        
        
        
        double hw = w/2;
        double hh = h/2;
        
        double scale = 0;
        float color = 0;
        
        
        double posX = hw + ( Math.cos(secondsElapsed * 0.5) + Math.sin(secondsElapsed * 1.25) ) * hw/2;
        double posY = hh + ( Math.sin(secondsElapsed * 2.5) + Math.sin(secondsElapsed * 0.25) ) * hh/2;
        
        for( int deg = 0; deg < (360*4); deg += 25) 
        {
        	
            AffineTransform at = AffineTransform.getTranslateInstance( posX, posY );
            at.rotate( Math.toRadians(deg * 5 + secondsElapsed * 150) );
            at.scale( scale + Math.abs(Math.sin(secondsElapsed * 0.9) * 10), 
            		  scale + Math.abs(Math.sin(secondsElapsed * 1.5) * 10));
            
            scale += 0.2;
            color = (float)Math.abs( Math.sin(secondsElapsed * 2.5 + deg) );
            g2D.setColor( new Color(color, 1.0f - color, color/2.0f) );
            
            g2D.draw( at.createTransformedShape(e) );
              
         }
        
        g2D.drawImage( picture.getImage(), picture.getX(), picture.getY(), this );
        
    }

	public void run()
	{
		
		double dt = getDeltaTime( getSystemTime() );
		
        while( true ) 
        {

        	dt = getDeltaTime( getSystemTime() );
			if( dt > Globals.FIXED_TIME_STEP ) dt = Globals.FIXED_TIME_STEP;
			accumulator += dt;
			secondsElapsed += dt;
			
			while( accumulator >= Globals.FIXED_TIME_STEP )
			{
				update();
				accumulator -= Globals.FIXED_TIME_STEP;
				
			}
			
            try 
            {
                Thread.sleep(15);
            } 
            catch (InterruptedException e) 
            {
                System.out.println("interrupted");
            }

        }
        
	}
	
	
	
	public double getSystemTime() 
	{
		return System.currentTimeMillis() / (double)1000.0;
	}

	public double getDeltaTime( double timerInSeconds )
	{
		
		double currentTime = timerInSeconds;
		double elapsedTime = currentTime - oldTime;
		oldTime = currentTime;
		
		framesPerSecond++;
		
		if( (currentTime - previousTime) > 1.0 )
		{
			previousTime = currentTime;
			fps = framesPerSecond;
			framesPerSecond = 0;
		}
		
		return elapsedTime;
	}
	
    
}

Starfield.java


import java.awt.Color;
import java.awt.Graphics2D;
import java.util.ArrayList;


public class Starfield 
{
	private static final int NUMSTARS = 512;
	
	private ArrayList<Star> stars = new ArrayList<Star>();
	
	public Starfield()
	{
	 
		for( int i = 0; i < NUMSTARS; i++ )
		{
			Star star = new Star( -Globals.SCREEN_WIDTH/2.0f + (float)Math.random() * Globals.SCREEN_WIDTH/1.0f,
					 	          -Globals.SCREEN_HEIGHT/2.0f + (float)Math.random() * Globals.SCREEN_HEIGHT/1.0f,
					 	          -(float)Math.random() * 255,
					 	          01.f + (float)Math.random() * 2);
			stars.add( star );
		}
		
	}
	
	public void update()
	{
		
		for( int i = 0; i < stars.size(); i++ )
		{
			Star star = stars.get(i);
			star.move();
		}
		
	}
	
	public void render( Graphics2D g2D, double secondsElapsed )
	{
		
		float color = (float)Math.abs( Math.sin(secondsElapsed * 0.5) );
        g2D.setColor( new Color(1.0f - color, color, color) );
        
		
		for( int i = 0; i < stars.size(); i++ )
		{
			Star star = stars.get( i );
			int sx = star.getScreenX();
			int sy = star.getScreenY();
			g2D.drawLine( sx, sy, sx, sy );
		}
		
		
	}
	
	
}  // end class


Star.java



public class Star 
{
	private float x;
	private float y;
	private float z;
	private float speed;
	private float screenX;
	private float screenY;
	
	public Star()
	{
		x = 0;
		y = 0;
		z = 0;
		speed = 0;
	}

	public Star( float x, float y, float z, float speed )
	{
		this.x = x;
		this.y = y;
		this.z = z;
		this.speed = speed;
	}

	public int getScreenX()
	{
		return (int)screenX;
	}
	
	public int getScreenY()
	{
		return (int)screenY;
	}
	
	public float getDistance()
	{
		return (Globals.LENS - z);
	}
	
	public void init( float x, float y, float z, float speed )
	{
		this.x = x;
		this.y = y;
		this.z = z;
		this.speed = speed;
	}
	
	public void move()
	{
		
		z += speed;
		if( z > Globals.LENS )
		{
			z = 0;
		}
		
		float sx = x;
		float sy = y;
		float sz = z;
		float distance = Globals.LENS - sz;
		if( distance > 0 )
		{
			screenX = Globals.SCREEN_WIDTH/2 + (Globals.LENS * sx / distance);
            screenY = Globals.SCREEN_HEIGHT/2 - (Globals.LENS * sy / distance);
		}
		
		
	}
	
}



Picture.java


import javax.swing.ImageIcon;
import java.awt.Image;
import java.awt.event.KeyEvent;

public class Picture 
{
	private String fileName = "gfx/fairy.png";
	private Image image;

	private float x;
	private float y;
	
	private float dx;
	private float dy;
	
	private float speed = 2.5f;
	
	Picture()
	{
		
		ImageIcon ii = new ImageIcon(this.getClass().getResource(fileName));
        
		image = ii.getImage();
        
        x = Globals.SCREEN_WIDTH/2.0f - image.getWidth(null)/2.0f;
        y = Globals.SCREEN_HEIGHT/2.0f - image.getHeight(null)/2.0f;
        
	}
	
	public int getX()
	{
		return (int)x;
	}
	
	public int getY()
	{
		return (int)y;
	}
	
	public Image getImage() 
    {
        return image;
    }

	public void move() 
    {
        x += dx;
        y += dy;
    }

	public void keyPressed( KeyEvent e ) 
    {

        int key = e.getKeyCode();

        if (key == KeyEvent.VK_LEFT) 
        {
            dx = -speed;
        }

        if (key == KeyEvent.VK_RIGHT) 
        {
            dx = speed;
        }

        if (key == KeyEvent.VK_UP) 
        {
            dy = -speed;
        }

        if (key == KeyEvent.VK_DOWN) 
        {
            dy = speed;
        }
    }

    public void keyReleased(KeyEvent e) 
    {
        int key = e.getKeyCode();

        if (key == KeyEvent.VK_LEFT) 
        {
            dx = 0;
        }

        if (key == KeyEvent.VK_RIGHT) 
        {
            dx = 0;
        }

        if (key == KeyEvent.VK_UP) 
        {
            dy = 0;
        }

        if (key == KeyEvent.VK_DOWN) 
        {
            dy = 0;
        }
    }
}


Globals.java



public class Globals 
{
	
	  public static final double FIXED_TIME_STEP = 1/60.0;
	  
	  public final static int SCREEN_WIDTH = 640;
	  public final static int SCREEN_HEIGHT = 480;
	
	  public final static int LENS = 256;
	  
}

java2D is a magic box which will use your graphic card for stuff if it can. For example it will cache some images as textures if you arn’t changing them.

There are already some topics which discuss this, also others with more knowledge with this might answer as well :slight_smile:

Thanks! But if you could peruse the source for the Java2D project and point out some glaring mistakes (I know I made lots), I would be very thankful.

[quote=“relminator,post:1,topic:41125”]
It might be easier for people to give you feedback if you put your code in a public online repository, e.g. GitHub or Google Code. Or just in a pastebin.

About the sprite transparency by composition: AFAIK drawing a sprite without transparency is pretty simple and fast: pixels just get written directly to the backbuffer. Drawing with transparency is more complicated as the value of the pixel that is currently in the buffer (i.e. the one that “shines through” the transparent pixel) need to be combined using a calculation with the pixel value of the sprite. That takes more processing time, and hence is slower.

You may try explicitly enabling OpenGL as the rendering pipeline, described here as a hint, but I’ve never tried:

System.setProperty("sun.java2d.opengl","True");

Added this code but still the same issue:


System.setProperty("sun.java2d.opengl","True");

I fixed it now. Thanks!

I put the blending code after drawing the primitives and before drawing the image. Seems like compositing is also applied to primitives.

I get ~900 FPS (with Thread.sleep(1)) on my dinosaur laptop with intel gfx, LOL

Updated the binaries.

My god, Rel, is that you? QB45, FreeBasic, etc. Not sure if you remember me, marzec here. Man, it’s been 10 years. Glad you are still active :slight_smile:

Yes my friend! I posted a message on your forums (which you never answered BTW. LOL)

BTW, I’ve been telling everybody I know to buy your book.

Good job on libGDX!!! Will use it once I get onto droid (using the NDK for now since I’m a Java noob).