How on earth do I stop my Applet from flickering

Right, I’m about to give up on this, but maybe one of you lot can figure this out - I cannot create a non-trivial applet that doesn’t flicker something awful at seemingly random intervals. Here’s a few links:

  1. Albion
  2. Test 2
  3. Test 3

All of these will flicker for me, in both Safari and Chrome on OSX. Some more than others - Albion appears to be the most stable.

Albion is using a BufferStrategy created on a Canvas inside a JApplet.
Test 2 internally draws onto a BufferedImage, then uses a class derived from Canvas to display that backbuffer. Again, inside a JApplet.
Test 3 is cribbed from Oracle’s applet example, and draws to an image backbuffer, then to a custom JPanel inside a JApplet.

Points of interest:

  • Chrome flickers much worse, but Safari also flickers.
  • All of these are rock solid in AppletViewer.
  • The flicker colour is very odd. It’s sometimes the background colour of the Canvas/JPanel. It’s not the background colour of the applet tag. But it is sometimes the background colour of the webpage, or just white (not a colour used for any of the above).
  • Even the animated Java logo displayed while the applet is loading flickers.
  • Pulpcore example applets flicker too in Chrome. They don’t start in Safari (thinks Java is not installed!).
  • The Oracle example applet doesn’t flicker, until you add more drawing code, at which point it does.
  • The LWJGL applet? Works just fine. >_<

For reference, this is the code for Test 3:



package prototype.move;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.InputStream;
import java.util.ArrayList;

import javax.swing.ImageIcon;
import javax.swing.JApplet;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import com.triangularpixels.rebirth.resources.ResourcePool;
import com.triangularpixels.rebirth.resources.Resources;

import prototype.move.renderer.Util;

public class MainTest extends JApplet implements Runnable
{
	private final static String dir = "Creatures"; // dir to load the images from
	private final static int nimgs = 2; // number of images to animate
	
	int loopslot = -1; // the current frame number
	ImageIcon imgs[]; // the images

	private boolean debug;
	
	private CustomPanel panel;
	
	private BufferedImage framebuffer, backbuffer;
	
	private Thread gameThread;
	private boolean running = true;
	
	private long lastFrameTime;
	
	private ArrayList<Long> frameTimes;
	
	private ResourcePool resources;
	private Flow flow;
	
	@Override
	public void init()
	{
		super.init();
		
		try
		{
			SwingUtilities.invokeAndWait(new Runnable()
			{
				public void run()
				{
					initialiseInternal();
				}
			});
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		
	
		
		running = true;
		
		gameThread = new Thread(this);
		gameThread.start();
	}
	
	@Override
	public void stop()
	{		
		super.stop();
		
		if (gameThread != null)
		{
			running = false;
			
			try
			{
				gameThread.wait();
			}
			catch (Exception e) {}
		}
	}
	
	private void initialiseInternal()
	{
		applySleepWorkaround();
		
		panel = new CustomPanel();
	//	panel.setOpaque(true);
		panel.setBackground(Color.blue);
		setContentPane(panel);
		
		framebuffer = Util.createImage(320, 180, false);
		backbuffer = Util.createImage(getWidth(), getHeight(), false);
		
		try
		{
			debug = false;
			assert (debug = true);
			
			resources = Resources.createPool("Data", Resources.DecoderGroup.CORE, debug, true);
			
			resources.parse("resources.xml");
			
			// Temp hack until we get a proper loading screen
			resources.requestCreateAll();
			while (!resources.areAllCreated())
			{
				resources.update();
				Thread.yield();
			}
			
			flow = new Flow(resources, framebuffer.getWidth(), framebuffer.getHeight(), panel);
		}
		catch(Exception e)
		{
			System.err.println("Couldn't create resource pool");
			e.printStackTrace();
		}
		
		frameTimes = new ArrayList<Long>();
		
		imgs = new ImageIcon[nimgs];
		for (int i = 0; i < nimgs; i++)
		{
			imgs[i] = loadImage(i + 1);
		}
	}
	
	private synchronized void paintInternal(Graphics g)
	{
		if (backbuffer != null)
		{
			g.drawImage(backbuffer, 0, 0, null);
		}
	}
	
	@Override
	public void run()
	{
		while (running)
		{
			updateInternal();
			drawInternal();
			
			panel.repaint();
			
			sync(60);
		}
	}
	
	private void updateInternal()
	{
		resources.update();
		
		flow.update(isPaused());
	}
	
	private boolean isPaused()
	{
		final boolean hasFocus = this.isFocusOwner() || this.hasFocus();
		return !hasFocus;
	}
	
	private synchronized void drawInternal()
	{
		if (!isVisible())
			return;
		
		Graphics2D g = (Graphics2D)backbuffer.getGraphics();
		g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		
		Graphics2D frameGraphics = framebuffer.createGraphics();
		frameGraphics.setColor(Color.black);
		frameGraphics.fillRect(0, 0, framebuffer.getWidth(), framebuffer.getHeight());
		 
		flow.draw(frameGraphics, isPaused());
		frameGraphics.dispose();
		
		g.drawImage(framebuffer, 0, 0, panel.getWidth(), panel.getHeight(), null);
		
		while (frameTimes.size() > 20)
			frameTimes.remove(0);
		
		if (frameTimes.size() > 1)
		{
			long totalDelta = 0;
			for (int i=0; i<frameTimes.size()-1; i++)
			{
				long delta = frameTimes.get(i+1) - frameTimes.get(i);
				totalDelta += delta;
			}
			totalDelta /= (frameTimes.size()-1);
			
			int fps = totalDelta > 0 ? (int)(1000 / totalDelta) : 0;
			
			int x = 800;
			int y = 20;
			String fpsString = fps +" fps";
			
			g.setColor(Color.white);
			g.drawString(fpsString, x-1, y-1);
			g.drawString(fpsString, x+1, y-1);
			g.drawString(fpsString, x-1, y+1);
			g.drawString(fpsString, x+1, y+1);
			
			g.setColor(Color.black);
			g.drawString(fpsString, x, y);
		}

		g.dispose();
	}
	
	@Override
	public synchronized void paint(Graphics g)
	{
		super.paint(g);
	}
	
	@Override
	public synchronized void paintComponents(Graphics g)
	{
		super.paintComponents(g);
	}
	
	/** Starts a long running thread that sleeps for a non-divisible-by-ten amount of time to force the hi-res sleep timer */
	private static void applySleepWorkaround()
	{
		new Thread("SleepTimerHack")
		{
            { this.setDaemon(true); this.start(); }
            public void run()
            {
				while(true)
				{
					try { Thread.sleep(Integer.MAX_VALUE); } catch(InterruptedException ex) {}
				}
            }
        };
	}
	
	public void sync(final int framerate)
	{
		final int framerateMillis = 1000 / framerate;
		
		long current = System.nanoTime() / 1000000;
		long elapsed = current - lastFrameTime;
		
		do
		{
			try
			{
				Thread.sleep(0);
			}
			catch (InterruptedException e) {}
			
			current = System.nanoTime() / 1000000;
			elapsed = current - lastFrameTime;
		}
		while (elapsed < framerateMillis);
		
		lastFrameTime = current;
		
		frameTimes.add(current);
	}
	
	public class CustomPanel extends JPanel
	{
		public CustomPanel()
		{
			super(new BorderLayout());
		}
		
		@Override
		public synchronized void paint(Graphics g)
		{
			super.paint(g);
		}
		
		protected synchronized void paintComponent(Graphics g)
		{
			super.paintComponent(g);
			
			paintInternal(g);
		}
	}
	

	

	
	protected ImageIcon loadImage(final int imageNum)
	{
		String path = dir + "/T" + imageNum + ".png";
		final int MAX_IMAGE_SIZE = 1024 * 16;
		
		InputStream in = this.getClass().getClassLoader().getResourceAsStream(path);
		BufferedInputStream imgStream = new BufferedInputStream(in);
		
		if (imgStream != null)
		{
			byte buf[] = new byte[MAX_IMAGE_SIZE];
			
			int count;
			try
			{
				count = imgStream.read(buf);
				imgStream.close();
			}
			catch (java.io.IOException ioe)
			{
				System.err.println("Couldn't read stream from file: " + path);
				return null;
			}
			
			if (count <= 0)
			{
				System.err.println("Empty file: " + path);
				return null;
			}
			return new ImageIcon(Toolkit.getDefaultToolkit().createImage(buf));
		}
		return null;
	}
}

Very confused. ???

Chrome, not a flicker in sight.

On all of them? What Java vm and OS?

I’m on 1.6.0_26 and OSX 10.6.8.

Yeah, but the 3rd test only shows a white screen.

Albion and Test 2 work beautifully

Test 3 stays white

Win 7, Opera

Edit: Java Version: 1.7.0_01 21.1-b02 (32-bit)

Same as Cero - FFox, java 6

OS X 10.5, Firefox, Java 1.6.0_26.

Albion runs smoothly, looks good.
The second applet flickers white so much it’s unbearable.
The third applet is just a white rectangle.

Test 3

network: Connecting http://triangularpixels.com/MovementPrototype/Creatures/Baron_Casual.png with proxy=DIRECT
Couldn't load 'Creatures/Baron_Casual.png'
java.lang.NullPointerException
	at prototype.move.renderer.ImageResource.loadImage(ImageResource.java:42)
	at prototype.move.renderer.ImageResource.create(ImageResource.java:27)
	at com.triangularpixels.rebirth.resources.ResourceHandle.create(ResourceHandle.java:88)
	at com.triangularpixels.rebirth.resources.ResourcePool.processCreateQueue(ResourcePool.java:211)
	at com.triangularpixels.rebirth.resources.ResourcePool$BgCreator.run(ResourcePool.java:509)
	at java.lang.Thread.run(Unknown Source)

I get the same exception as Riven for Test 3 but Albion and Test 2 work beautifully.

I blame OS X and Apple’s new applet changes as the cause of your flickering.

Albion and Test 2 work beautifully

Test 3 stays white

Win 7, Opera and FFox

Java Version: 1.6.0_27

This is a known issue with applets on OSX. Here on the Apple Java Dev mailing list you can see an approximate cause of the issue, that the applet now lives out of the browser process. That mail is talking about it in relation to drag and drop, but it summarizes the problem.

Here is another discussion about it on the Chromium bug list.

Albion doesn’t flicker, but it’s unbelievably slow (~4 fps). Test 2 doesn’t flicker either, but it runs a lot better than the first one (62 fps). Test 3 stays white.

Ubuntu 10.04
Firefox 7.0.1
Java 1.6.0_26

  1. Blank black screen
  2. Flickers
  3. Blank white screen

MacBook, Safari running in Snow Leopard.

:-\

So applets on osx suck then. Yey! :-\

So? OpenGL on OSX sucks too. There are lots of things that suck on Macs.

Just make the switch then and get on with the game :slight_smile:

Quite a bit of effort went into finally making LWJGL applets work again on OS X after Apple crippled/broke the Java plugin with the switch to CoreAnimation. The performance on both Java2D and PulpCore is now massively crippled making them unusable for certain types of games. On the other hand OS X has solid support for OpenGL (since the OS uses it) and therefore LWJGL applets are a pretty good option there.

Lets not turn this into a platform fanboy thread please.

Just trying to be helpful here. If you’re creating a game would you like to know if it can run on Macs? Or would you rather that I say nothing?

his reply to you was sarcastic and disappointed, that it doesnt work on mac - not critical

Nod, I kind of figured that after I saw the second post. My question is aimed more at the general audience. I’ve noticed a slight trend of “don’t bother me about the problems running your platform (which is different from mine)” attitude.