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