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:
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. ???
 
      
    