Java Sound API, Java Full Screen Exclusive Mode API & Linux

Dear all,

I suppose that Linux needs a ‘specific’ configuration in order for Sound & Full Screen Exclusive Mode APIs to work.
Specifically, when using Sound API I get a MidiUnavailableException because Java is trying to play sound through OSS and not through ALSA (I think).
Also, Full Screen Exclusive Mode API, reports that it can enter full screen but cannot change the display mode, which (again I think) there must be something ‘wrong’ with the configuration of X.

So, does anybody know how to make Sound API & Full Screen Exclusive Mode API work in Linux?

Regards,
Charalampos

The short answer is poorly. I use lwjgl to get around both problems and to be honest the fullscreen mode may “cheat” by just creating a fullscreen undecorated window. Openal is the way i handle sound now for reasons of “just works” on many platforms i have tried…while java sound, even when it worked, had a pretty massive delay.

However depending on your video drivers, if you don’t have the mode lines in the xorg.conf then these resolutions will not be available to you. But my experience is that full screen(pure java way) just doesn’t work under linux.

Thanks for your reply.

Fullscreen ‘cheat’ is ‘hard’ to implement. If you design a game to work in, say, 640x480 mode and the current mode is 1024x768 you have to increase the size of the sprites or provide sprites for that mode. I think I will pass for now. I am too new to games development to go that route.

I have started to look at OpenGL. I was wondering though, can OpenGL handle fullscreen even if xorg.conf doesn’t contain the necessary mode lines?

Regards,
Charalampos

a way to cheat the resolution is to render to an image (via Image.getGraphics()). Instead of the default graphics object in the paint method.

then finally scale it on the fly at the end of the paint(Graphics g) method using, g.drawImage(offScreenImage, 0,0,this.getWidth(),this.getHeight(), 0,0, offScreenImage.getWidth(null), offscreenImage.getHeight(null));

That didn’t sound as hard as I was expecting :stuck_out_tongue:
Thanks for the tip :slight_smile:

Im not sure how experienced you are.

it might be a good idea to look up in google “animation in java applets”.
There should be a section of using an offscreen buffer. not only will it give smooth graphics, on the majority oj java versions, but also help you with the issue of scaling on the fly, as you already have an offscreen buffer to scale.

Well…
I tried the scaling on the fly but I didn’t like the results.

Here is the code I tried


import java.awt.Canvas;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferStrategy;
import java.awt.image.MemoryImageSource;
import java.awt.image.VolatileImage;

public class FakeFullscreen {
	static boolean isRunning;

	public static void main(String[] args) {
		final Toolkit toolkit = Toolkit.getDefaultToolkit();

		final int[] pixels = new int[16 * 16];
		final Image image = toolkit.createImage(new MemoryImageSource(16, 16, pixels, 0, 16));
		final Cursor invisibleCursor = toolkit.createCustomCursor(image, new Point(0, 0), "invisibleCursor");

		final Dimension screenSize = toolkit.getScreenSize();
		final Dimension gameSize = new Dimension(640, 480);
		final Rectangle gameArea = new Rectangle(0, 80, 640, 400);

		final Canvas canvas = new Canvas();
		canvas.setIgnoreRepaint(true);
		canvas.setSize(screenSize);

		final Frame frame = new Frame();
		frame.setAlwaysOnTop(true);
		frame.setCursor(invisibleCursor);
		frame.setIgnoreRepaint(true);
		frame.setLocation(0, 0);
		frame.setResizable(false);
		frame.setSize(screenSize);
		frame.setUndecorated(true);

		frame.addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent event) {
				if (event.getKeyCode() == KeyEvent.VK_ESCAPE) {
					isRunning = false;
				}
			}
		});

		frame.add(canvas);
		frame.pack();
		frame.setVisible(true);

		canvas.createBufferStrategy(2);
		final BufferStrategy buffer = canvas.getBufferStrategy();

		final VolatileImage offScreen = canvas.createVolatileImage(gameSize.width, gameSize.height);
		Graphics2D offScreenGraphics = null;

		Graphics2D graphics = null;
		isRunning = true;

		int fps = 0;
		int frames = 0;
		long totalTime = 0;
		long curTime = System.nanoTime() / 1000000L;
		long lastTime = curTime;

		final int TICKS_PER_SECOND = 25;
		final int SKIP_TICKS = 1000 / TICKS_PER_SECOND;
		final int MAX_FRAMESKIP = 5;

		long nextGameTick = System.nanoTime() / 1000000L;
		int loops;
		float interpolation;

		int ballWidth = 8;
		int ballHeight = 8;
		int ballX = gameArea.width / 2 - ballWidth / 2;
		int ballY = gameArea.y + (gameArea.height / 2 - ballHeight / 2);
		int ballDx = 10;
		int ballDy = 10;

		while (isRunning) {
			loops = 0;
			while ((System.nanoTime() / 1000000L) > nextGameTick && loops < MAX_FRAMESKIP) {
				ballX += ballDx;
				ballY += ballDy;

				if (ballX <= gameArea.x) {
					ballX = gameArea.x;
					ballDx = -ballDx;
				} else if (ballX >= gameArea.width - ballWidth) {
					ballX = gameArea.width - ballWidth;
					ballDx = -ballDx;
				}

				if (ballY <= gameArea.y) {
					ballY = gameArea.y;
					ballDy = -ballDy;
				} else if (ballY >= gameArea.y + gameArea.height - ballHeight) {
					ballY = gameArea.y + gameArea.height - ballHeight;
					ballDy = -ballDy;
				}

				nextGameTick += SKIP_TICKS;
				loops++;
			}

			lastTime = curTime;
			curTime = System.nanoTime() / 1000000L;
			totalTime += curTime - lastTime;
			if (totalTime > 1000) {
				totalTime -= 1000;
				fps = frames;
				frames = 0;
			}
			++frames;

			interpolation = (float) ((System.nanoTime() / 1000000L) + SKIP_TICKS - nextGameTick) / (float) (SKIP_TICKS);

			try {
				offScreenGraphics = offScreen.createGraphics();
				offScreenGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
				offScreenGraphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
				offScreenGraphics.setColor(Color.BLACK);
				offScreenGraphics.fillRect(0, 0, screenSize.width, screenSize.height);
				offScreenGraphics.setColor(Color.WHITE);
				offScreenGraphics.drawString(String.format("FPS: %s", fps), 20, 20);

				int renderX = (int) (ballX + (ballDx * interpolation));
				int renderY = (int) (ballY + (ballDy * interpolation));

				offScreenGraphics.drawLine(gameArea.x, gameArea.y - ballHeight, gameArea.width, gameArea.y - ballHeight);
				offScreenGraphics.fillRect(renderX, renderY, ballWidth, ballHeight);

				graphics = (Graphics2D) buffer.getDrawGraphics();
				if (!offScreen.contentsLost()) {
					graphics.drawImage(offScreen, 0, 0, screenSize.width, screenSize.height, 0, 0, gameSize.width, gameSize.height, null);
				}

				if (!buffer.contentsLost()) {
					buffer.show();
				}

				Thread.yield();
			} finally {
				if (offScreenGraphics != null) {
					offScreenGraphics.dispose();
				}

				if (graphics != null) {
					graphics.dispose();
				}
			}
		}

		frame.setVisible(false);
		System.exit(0);
	}
}

I get about 200FPS on my laptop which runs on Windows XP but I get jerky move.
I haven’t tested it yet on my desktop which has Linux, but I don’t expect too much of a difference.

If I replace


final Dimension screenSize = toolkit.getScreenSize();

with


final Dimension screenSize = new Dimension(640, 480);

i.e. so as to have a normal frame and not a ‘fake’ fullscreen, I get ~700FPS and much better movement.
Either there is something wrong in my code or I didn’t fully understand how to use scaling on the fly.

Perhaps giving out instructions on how to change xorg.conf so Java can use the Full Screen Exclusive Mode API might be a better idea than faking fullscreen mode?

Regards,
Charalampos

In Linux (Linux Mint 8 - XFCE), the above test case gives even worse results.
~50FPS in fake fullscreen, jerky movement and the task bar is still on top of the frame.
:’(

try this:


import java.awt.Canvas;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import javax.swing.JFrame;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferStrategy;
import java.awt.image.MemoryImageSource;
import java.awt.image.VolatileImage;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

public class FakeFullscreen {
	static boolean isRunning;

	public static void main(String[] args) {
		final Toolkit toolkit = Toolkit.getDefaultToolkit();

		final int[] pixels = new int[16 * 16];
		final Image image = toolkit.createImage(new MemoryImageSource(16, 16, pixels, 0, 16));
		final Cursor invisibleCursor = toolkit.createCustomCursor(image, new Point(0, 0), "invisibleCursor");

		final Dimension screenSize = toolkit.getScreenSize();
		final Dimension gameSize = new Dimension(640, 480);
		final Rectangle gameArea = new Rectangle(0, 80, 640, 400);

		final Canvas canvas = new Canvas();
		canvas.setIgnoreRepaint(true);
		canvas.setSize(screenSize);

		final JFrame frame = new JFrame();
		frame.setAlwaysOnTop(true);
		frame.setCursor(invisibleCursor);
		frame.setIgnoreRepaint(true);
		frame.setLocation(0, 0);
		frame.setResizable(false);
		frame.setSize(screenSize);
		frame.setUndecorated(true);

		frame.addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent event) {
				if (event.getKeyCode() == KeyEvent.VK_ESCAPE) {
					isRunning = false;
				}
			}
		});
		
		
		frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		frame.addWindowListener(new WindowAdapter() {
			@Override
            public void windowClosing(WindowEvent e) {
                isRunning = false;
            }
        });
		
		

		frame.add(canvas);
		frame.pack();
		frame.setVisible(true);
		frame.toFront(); 

		canvas.createBufferStrategy(2);
		final BufferStrategy buffer = canvas.getBufferStrategy();

		final VolatileImage offScreen = canvas.createVolatileImage(gameSize.width, gameSize.height);
		Graphics2D offScreenGraphics = null;

		Graphics2D graphics = null;
		isRunning = true;

		int fps = 0;
		int frames = 0;
		long totalTime = 0;
		long curTime = System.nanoTime() / 1000000L;
		long lastTime = curTime;

		final int TICKS_PER_SECOND = 25;
		final int SKIP_TICKS = 1000 / TICKS_PER_SECOND;
		final int MAX_FRAMESKIP = 5;

		long nextGameTick = System.nanoTime() / 1000000L;
		int loops;
		float interpolation;

		int ballWidth = 8;
		int ballHeight = 8;
		int ballX = gameArea.width / 2 - ballWidth / 2;
		int ballY = gameArea.y + (gameArea.height / 2 - ballHeight / 2);
		int ballDx = 10;
		int ballDy = 10;

		while (isRunning) {
			loops = 0;
			while ((System.nanoTime() / 1000000L) > nextGameTick && loops < MAX_FRAMESKIP) {
				ballX += ballDx;
				ballY += ballDy;

				if (ballX <= gameArea.x) {
					ballX = gameArea.x;
					ballDx = -ballDx;
				} else if (ballX >= gameArea.width - ballWidth) {
					ballX = gameArea.width - ballWidth;
					ballDx = -ballDx;
				}

				if (ballY <= gameArea.y) {
					ballY = gameArea.y;
					ballDy = -ballDy;
				} else if (ballY >= gameArea.y + gameArea.height - ballHeight) {
					ballY = gameArea.y + gameArea.height - ballHeight;
					ballDy = -ballDy;
				}

				nextGameTick += SKIP_TICKS;
				loops++;
			}

			lastTime = curTime;
			curTime = System.nanoTime() / 1000000L;
			totalTime += curTime - lastTime;
			if (totalTime > 1000) {
				totalTime -= 1000;
				fps = frames;
				frames = 0;
			}
			++frames;

			interpolation = (float) ((System.nanoTime() / 1000000L) + SKIP_TICKS - nextGameTick) / (float) (SKIP_TICKS);

			try {
				offScreenGraphics = offScreen.createGraphics();
				//offScreenGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
				//offScreenGraphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
				offScreenGraphics.setColor(Color.BLACK);
				offScreenGraphics.fillRect(0, 0, screenSize.width, screenSize.height);
				offScreenGraphics.setColor(Color.WHITE);
				offScreenGraphics.drawString(String.format("FPS: %s", fps), 20, 20);

				int renderX = (int) (ballX + (ballDx * interpolation));
				int renderY = (int) (ballY + (ballDy * interpolation));

				offScreenGraphics.drawLine(gameArea.x, gameArea.y - ballHeight, gameArea.width, gameArea.y - ballHeight);
				offScreenGraphics.fillRect(renderX, renderY, ballWidth, ballHeight);

				graphics = (Graphics2D) buffer.getDrawGraphics();
				if (!offScreen.contentsLost()) {
					graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
					graphics.drawImage(offScreen, 0, 0, screenSize.width, screenSize.height, 0, 0, gameSize.width, gameSize.height, null);
					graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
				}

				if (!buffer.contentsLost()) {
					buffer.show();
				}

				Thread.yield();
			} finally {
				if (offScreenGraphics != null) {
					offScreenGraphics.dispose();
				}

				if (graphics != null) {
					graphics.dispose();
				}
			}
		}

		frame.setVisible(false);
		System.exit(0);
	}
}

and if that is still to slow, remove all the rendering hints

in regards to your taksbar problem check out this link:

Thanks for the above code bobjob.
In order to get decent FPS I have to turn off rendering hints (~10 FPS with rendering hints and ~80 FPS without) but I still get jerky move.
:-\

ok try this version:
class CustomTimer and FPSCounter should probably be in there own files


import java.awt.Canvas;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import javax.swing.JFrame;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferStrategy;
import java.awt.image.MemoryImageSource;
import java.awt.image.VolatileImage;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

public class FakeFullscreen {
	
	//SHOULD BE SET TO TRUE FOR NORMAL GAME LOGIC 
	final static boolean FRAME_SYNC = false;
	final static boolean HIGH_QUALITY = true;
	
	static boolean isRunning;
	static CustomTimer timer;
	static FPSCounter fpsCounter;
	
	public static void main(String[] args) {
		timer = new CustomTimer();
		fpsCounter = new FPSCounter(timer);
		final Toolkit toolkit = Toolkit.getDefaultToolkit();

		final int[] pixels = new int[16 * 16];
		final Image image = toolkit.createImage(new MemoryImageSource(16, 16, pixels, 0, 16));
		final Cursor invisibleCursor = toolkit.createCustomCursor(image, new Point(0, 0), "invisibleCursor");

		final Dimension screenSize = toolkit.getScreenSize();
		final Dimension gameSize = new Dimension(640, 480);
		final Rectangle gameArea = new Rectangle(0, 80, 640, 400);

		final Canvas canvas = new Canvas();
		canvas.setIgnoreRepaint(true);
		canvas.setSize(screenSize);

		final JFrame frame = new JFrame();
		frame.setAlwaysOnTop(true);
		frame.setCursor(invisibleCursor);
		frame.setIgnoreRepaint(true);
		frame.setLocation(0, 0);
		frame.setResizable(false);
		frame.setSize(screenSize);
		frame.setUndecorated(true);

		frame.addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent event) {
				if (event.getKeyCode() == KeyEvent.VK_ESCAPE) {
					isRunning = false;
				}
			}
		});
		
		
		frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		frame.addWindowListener(new WindowAdapter() {
			@Override
            public void windowClosing(WindowEvent e) {
                isRunning = false;
            }
        });

		frame.add(canvas);
		frame.pack();
		frame.setVisible(true);
		frame.toFront(); 

		canvas.createBufferStrategy(2);
		final BufferStrategy buffer = canvas.getBufferStrategy();

		final VolatileImage offScreen = canvas.createVolatileImage(gameSize.width, gameSize.height);
		Graphics2D offScreenGraphics = null;

		Graphics2D graphics = null;
		isRunning = true;

		int ballWidth = 8;
		int ballHeight = 8;
		int ballX = gameArea.width / 2 - ballWidth / 2;
		int ballY = gameArea.y + (gameArea.height / 2 - ballHeight / 2);
		int ballDx = 1;
		int ballDy = 1;

		while (isRunning) {
			if (!FRAME_SYNC || timer.update(60)) {
				fpsCounter.update();
				ballX += ballDx;
				ballY += ballDy;

				if (ballX <= gameArea.x) {
					ballX = gameArea.x;
					ballDx = -ballDx;
				} else if (ballX >= gameArea.width - ballWidth) {
					ballX = gameArea.width - ballWidth;
					ballDx = -ballDx;
				}

				if (ballY <= gameArea.y) {
					ballY = gameArea.y;
					ballDy = -ballDy;
				} else if (ballY >= gameArea.y + gameArea.height - ballHeight) {
					ballY = gameArea.y + gameArea.height - ballHeight;
					ballDy = -ballDy;
				}
				try {
					offScreenGraphics = offScreen.createGraphics();
					offScreenGraphics.setColor(Color.BLACK);
					offScreenGraphics.fillRect(0, 0, screenSize.width, screenSize.height);
					offScreenGraphics.setColor(Color.WHITE);
					offScreenGraphics.drawString(String.format("FPS: %s", fpsCounter.getFPS()), 20, 20);

					int renderX = (int) (ballX + ballDx);
					int renderY = (int) (ballY + ballDy);

					offScreenGraphics.drawLine(gameArea.x, gameArea.y - ballHeight, gameArea.width, gameArea.y - ballHeight);
					offScreenGraphics.fillRect(renderX, renderY, ballWidth, ballHeight);

					graphics = (Graphics2D) buffer.getDrawGraphics();
					if (!offScreen.contentsLost()) {
						if (HIGH_QUALITY) {
							graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
						}
						graphics.drawImage(offScreen, 0, 0, screenSize.width, screenSize.height, 0, 0, gameSize.width, gameSize.height, null);
					}

					if (!buffer.contentsLost()) {
						buffer.show();
					}
				} finally {
					if (offScreenGraphics != null) {
						offScreenGraphics.dispose();
					}

					if (graphics != null) {
						graphics.dispose();
					}
				} 
			}
			Thread.yield();
		}

		frame.setVisible(false);
		System.exit(0);
	}
}

class CustomTimer {
	private long timeThen;
	boolean newVersion = true;
	public CustomTimer() {
		if (System.getProperty("java.version").startsWith("1.4")) newVersion = false;

		if (newVersion) {
			timeThen = System.nanoTime();
		}
		
		else {
			System.out.println("Old Version Detected: Running Old Java Timer Version");
			timeThen = System.currentTimeMillis();
		}
	}
	public boolean update(int fps) {
		if (newVersion) {
			long gapTo = 1000000000L / fps + timeThen;
			long timeNow = System.nanoTime();
			if (gapTo > timeNow) return false;
			timeThen = timeNow;
		} else {
			long gapTo = 1000 / fps + timeThen;
			long timeNow = System.currentTimeMillis();	
			if (gapTo > timeNow) return false;
			timeThen = timeNow;
		}
		return true;
	}

	public long getTime() {
		if (newVersion) {
			return System.nanoTime();
		} else {
			return System.currentTimeMillis();
		}
	}
	public long getTimerResolution() {
		if (newVersion) {
			return 1000000000L;
		} else {
			return 1000L;
		}
	}
}
class FPSCounter {
	private CustomTimer timer;
	private float FPS = 0, fc = 0;

    private long
                 currentTime,
                 elapsedTime,
                 lastTime;
    
    public FPSCounter(CustomTimer timer) {
    	this.timer = timer;
        currentTime     = 0;
        elapsedTime = lastTime = timer.getTime();
    }
    public boolean update() {
		currentTime = timer.getTime();

	      fc++;
	      long updateFrequency = timer.getTimerResolution()>>1;
	      if((elapsedTime = currentTime - lastTime) >= updateFrequency){
	        FPS         = (fc/elapsedTime)*updateFrequency*2;
	        fc         = 0;
	        lastTime    = currentTime;
	        elapsedTime = 0;
	        return true;
	      }
	      return false;
    }
    public float getFPS() {
    	return FPS;
    }
}


try turning boolean SYNC on/off
and try turning the boolean HIGH_QUALITY on/off

I think you’ll find the taskbar problem on Linux Mint is a known issue with Compiz. Try turning off desktop effects and it should work fine.

@nsigma
I am pretty sure there is an option somewhere but it’s definitely not Compiz’s option, as Compiz is not installed on my system.
It does seem to be an issue with XFCE. Fluxbox does not have that issue. I don’t know about Gnome or KDE as they are not installed on my system.

@bobjob
Your last code runs much much better. Thank you.
Though I still believe full exclusive mode is the way to go. If not through pure Java, then through native code.

I gave Slick2D a try and the full screen works without any other hassle.
I think, for now, I will give up on Java2D and focus on creating games using a ready made game engine and later on after I gain some experience I will come back to do my own engine.

Regards,
Charalampos

That’ll teach me to reply on my phone where I can’t read the code boxes! :slight_smile: I also missed the fact you’d written XFCE after Mint, sorry.

Take 2 : Try using the FSEM methods to set the fullscreen window rather than just trying to fake it. ie.

GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
gd.setFullScreenWindow(frame);

Even when simulated, this should add the required hints to make the window appear above panels / toolbars. See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6364134 and related. The hack of just making a window the size of the screen stopped working for me about 2 years ago.

Unfortunately, as I hinted at in my previous post, the window hints still don’t fix the issue when running with Compiz.

Hope that helps this time,

Neil

Thanks nsigma.
I will give it a shot and see what I can come up with :slight_smile:

Regards,
Charalampos

After upgrading my system from Linux Mint 8 to Linux Mint 9, Full Screen Exclusive Mode API seems to be working fine (apart from the taskbar issue in XFCE at least).

???

Anyway, seeing how Java game related APIs are dependent on system configuration, I think I will stick with Slick 2D for now and come back in the future.

Thanks everyone for your time and help provided.

Regards,
Charalampos