Screen Tearing

Hello all,

I seem to be experiencing a bit of screen tearing – or, at least, that’s what I hope it is.

How does one go about mending this heinous deed?

Level.java:

package com.mizzath.sandbox.level;

import java.util.Random;

import com.mizzath.sandbox.screen.Screen;
import com.mizzath.sandbox.tile.Tile;

public class Level {
	public int width, height;
	public int xOffset, yOffset;
	public int[] tiles;
	public Screen screen;
	
	Random random = new Random();
	
	public Level(int width, int height, Screen screen) {
		this.width = width;
		this.height = height;
		this.screen = screen;
		
		tiles = new int[width * height];
		
		for (int i = 0; i < tiles.length; i++) {
			tiles[i] = random.nextInt(2);
		}
	}
	
	public void render(int xOffset, int yOffset) {	
		int x0 = 0, x1 = 0, y0 = 0, y1 = 0;
		
		int xPixelChange = 0, yPixelChange = 0;
		
		if (yOffset > 0) {
			if (yOffset <= 16) {
				yPixelChange = -yOffset; // ypos + yPixelChange
				y0 = 0; 
			} else if (yOffset > 16) {
				yPixelChange = -(yOffset % 16); // ypos + yPixelChange 
				y0 = yOffset >> 4;
			}
		} else if (yOffset < 0) {
			if (yOffset >= -16) {
				yPixelChange = 16 + yOffset; // ypos - yPixelChange 
				y0 = -1;
			} else if (yOffset < -16) {
				yPixelChange = (16 + (yOffset % 16)); // ypos - yPixelChange 
				y0 = (yOffset - yPixelChange) >> 4;
			}
		}
		
		int deltaY = yOffset + screen.height;
		y1 = deltaY + (16 - (deltaY % 16)) >> 4;
		
		if (xOffset > 0) {
			if (xOffset <= 16) {
				xPixelChange = 16 - xOffset; // xpos - xPixelChange
				x0 = -1;
			} else if (xOffset > 16) {
				xPixelChange = 16 - (xOffset % 16); // xpos - xPixelChange
				x0 = (-xOffset - xPixelChange) >> 4;
			}
		} else if (xOffset < 0) {
			if (xOffset >= -16) {
				xPixelChange = xOffset; // xpos + xPixelChange 
				x0 = 0; 
			} else if (xOffset < -16) {
				xPixelChange = xOffset % 16; // xpos + xPixelChange
				x0 = -xOffset >> 4;
			}
		}
		
		int deltaX = -xOffset + screen.width;
		x1 = deltaX + (16 - (deltaX % 16)) >> 4;
		
		for (int y = y0, yTilePos = 0; y < y1; y++, yTilePos++) {
			int yAbsPos = yTilePos << 4;
			
			if (yOffset > 0) {
				yAbsPos += yPixelChange;
			} else if (yOffset < 0){
				yAbsPos -= yPixelChange;
			}
			
			for (int x = x0, xTilePos = 0; x < x1; x++, xTilePos++) {
				int xAbsPos = xTilePos << 4;
				
				if (xOffset > 0) {
					xAbsPos -= xPixelChange;
				} else if (xOffset < 0) {
					xAbsPos += xPixelChange;
				}
				
				getTile(x, y).render(xAbsPos, yAbsPos, screen);
			}
		}
	}
	
	public Tile getTile(int x, int y) {	
		if (x < 0 || y < 0 || x >= width || y >= height) return Tile.nil;
		
		int tile = tiles[x + y * width];
		
		if (tile == 0) {
			return Tile.grass;
		} else if (tile == 1) {
			return Tile.water;
		} else {
			return Tile.nil;
		}
	}
}

Screen.java:

package com.mizzath.sandbox.screen;

import com.mizzath.sandbox.tile.Tile;

public class Screen {
	public int width, height;
	public int[] pixels;
	public int xOffset, yOffset;
	
	public Screen(int width, int height) {
		this.width = width;
		this.height = height;
		pixels = new int[width * height];
	}
	
	public void wipe() {
		for (int i = 0; i < pixels.length; i++) {
			pixels[i] = 0xffffff;
		}
	}
	
	/* Renders a tile.
	 * 
	 * xp = the starting pixel's x position
	 * yp = the starting pixel's y position
	 * xa = the x position of the pixel being rendered
	 * ya = the y position of the pixel being rendered
	 * tile = the tile to be rendered
	 */
	
	public void renderTile(int xp, int yp, Tile tile) {		
		for (int y = 0; y < tile.size; y++) {
			int ya = y + yp;
			if (ya < 0 || ya >= height) continue;

			for (int x = 0; x < tile.size; x++) {
				int xa = x + xp;
				if (xa < 0 || xa >= width) continue;
				
				pixels[xa + ya * width] = tile.colour;
			}
		}
	}
}

Sorry for dumping all of that upon you, and in no way are you required to meander through all of it. However, if you have some spare time, why not rummage though my predicament?

Thanks.

-Miles

What graphics lib are you using? I am assuming it is Java2D, yes? If you are, then take a look at BufferCapabilities (off the top of my head) not sure if there is an option there or not, I haven’t used Java2D in a long time. :wink:

Other than than, with my quick scan of your code, I didn’t find any logic errors.

A few things:

  1. Java2D is the current library.
  2. I am using a triple buffer.

Oh, and a bit more code to brighten your day. ;D

package com.mizzath.sandbox;

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;

import javax.swing.JFrame;

import com.mizzath.sandbox.input.Keyboard;
import com.mizzath.sandbox.level.Level;
import com.mizzath.sandbox.screen.Screen;

public class Sandbox implements Runnable {
	final int WIDTH = 300;
	final int HEIGHT = WIDTH * 9 / 16;
	final int SCALE = 3;
	
	private String title = "Sandbox";
	
	private JFrame frame;
	private Canvas canvas;
	private BufferStrategy bufferStrategy;
	private BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
	private int[] pixels = ((DataBufferInt)image.getRaster().getDataBuffer()).getData();
	
	private Screen screen;
	private Level level;
	private Keyboard keyboard;
	
	boolean running = true;
	
	public Sandbox() {
		Dimension size = new Dimension(WIDTH * SCALE, HEIGHT * SCALE);
		
		frame = new JFrame(title);
		frame.setPreferredSize(size);
		
		canvas = new Canvas();
		
		frame.add(canvas);

		canvas.setPreferredSize(size);
		
		keyboard = new Keyboard();
		canvas.addKeyListener(keyboard);

		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setResizable(false);
		frame.setIgnoreRepaint(true);
		frame.pack();
		frame.setVisible(true);
		
		canvas.requestFocus();
		
		canvas.createBufferStrategy(3);
		bufferStrategy = canvas.getBufferStrategy();
		
		screen = new Screen(WIDTH, HEIGHT);
		level = new Level(64, 64, screen);
	}
	
	public void run() {
		long lastTime = System.nanoTime();
		double timer = System.currentTimeMillis();
		double ns = 1000000000.0 / 60;
		double delta = 0;
		int frames = 0;
		int updates = 0;
		
		while (running) {
			long now = System.nanoTime();
			delta += (now - lastTime) / ns;
			
			lastTime = now;
			
			while (delta >= 1) {
				delta -= 1;
				update();
				updates++;
			}
			
			frames++;
			render();
			
			if (System.currentTimeMillis() - timer >= 1000) {
				timer = System.currentTimeMillis();
				frame.setTitle(title + " || Updates: " + updates + ", FPS: " + frames);
				updates = 0;
				frames = 0;
			}
		}
	}
	
	private void render() {
		Graphics g = null;
		
		try {
			g = bufferStrategy.getDrawGraphics();
			render(g);
		} finally {
			g.dispose();
		}

		bufferStrategy.show();	
	}
	
	public void render(Graphics g) {
		screen.wipe();
		level.render(keyboard.x, keyboard.y);
		
		for (int i = 0; i < pixels.length; i++) {
			pixels[i] = screen.pixels[i];
		}
		
		g.drawImage(image, 0, 0, canvas.getWidth(), canvas.getHeight(), null);
		g.setColor(Color.CYAN);
		g.drawString("X: " + -keyboard.x + ", Y: " + -keyboard.y, 10, 10); 
	}
	
	public void update() {
		
	}
	
	public static void main(String[] args) throws Exception {
		Sandbox sandbox = new Sandbox();
		new Thread(sandbox).start();
	}
}

I just checked out the java docs for BufferedCapabilities, and there is no vsync option there, the only way to get vsync in Java2D that I know of is to use a GraphicsDevice and a GraphicsEnvironment, then set fullscreen and it is enabled by default (I think)

Aside from that the first thing you should do is cast your Graphics g into a Graphics2D object

Graphics2D g = (Graphics2D) bufferStategy.getDrawGraphics();

What about Toolkit.getDefaultToolkit().sync()?

Hi mizzath,
Java 2d has no functionality to mitigate screen tearing. But java fx has, and the user shannon smith tried it out and reported his results in a thread on these forums. I would search it for you but I’m on my phone right now.
Alternatively, instead of switching to java fx and it’s scene graph, you could use open gl or libgdx which can be used to get rid of screen tear.
Cheers, keith

You know, I may as well attempt a dive into libgdx. Thanks for the helpful pointers.

Maybe the attempt to get a Hardware Double Buffer failed. It doesn’t always succeed, particularly when running windowed (or if there isn’t any spare graphics memory). Check BufferCapabilities.isFlipping(). You can use BufferStrategy.GetCapabilities() to obtain the capabilities structure. Also check whether your memory card has enough memory to double buffer at your chosen screen resolution.

If it’s double buffering by blitting (spell checker keeps changing that to blotting - bad spell check, down boy, down boy), you will get tearing when the window gets above a certain size. It’s most noticeable if you have sideways scrolling that scrolls in big increments, or objects that move very fast.

On the whole, I’ve been most successful with BufferStrategy when going fullscreen. Otherwise, particularly for applets, I often just draw onto a bufferedImage and then draw that to the screen with one call. It still tears if the image is big enough. I rely on not clearing the background every frame and a high frame rate, so that the image doesn’t change much between frames. Admittedly I got into that habit for writing 4k games. The BufferStrategy method ought to be better as in some circumstances one might get a hardware double buffer.

Sounds good; however, how does one debug efficiently whilst working in full screen mode? I generally enjoy the convenience of being able to see the console and write various messages to it.

Uh. This isn’t screen tearing. You can’t print-screen screen tearing, because it occurs when the GPU finishes a frame while the screen is refreshing. Also, screen tearing is horizontal. >_>

This. :slight_smile:
Tearing looks like: Someone takes the upper half of the picture and the lower half and rips them out of sync so to speak.

To clarify, you see a clear “tear” somewhere caused by the top half having the previous frame and the bottom half having the current frame. It’s not visible unless the screen is changing.

Alright, fair enough. However: do you have words of advice on how I go about fixing this?

Might be tricky. Your rendering code looks quite complex to me, it’s easily possible that there is something non-obvious going wrong there.

There is a second glitch in your screenshot. It’s a bit smaller than the highlighted one.

Ideas:

  • dump the tile positions to stdout and check if they match your expectations
  • check your tile.render() method if it draws outside the expected are
  • change the tile.render() method to show outlined squares so you can see overlaps better
  • check if there is a race condition in your painting code (only if you use several threads)

Lol, I didn’t realise that the screen shot square showed the ‘tear’. Looks like there’s another one a little lower and to the left.
I’d say you’re not drawing straight lines and that’s why the edge is jagged.
I agree with varkas that you should just print the points you’re drawing and look for the one with a strange y-coord.
Good luck!

Looks like you’re using a JFrame o.O.
Anytime I use a BufferedImage/Pixels or MemoryImageSource/Pixels on a JFrame I always Override the JFrame’s following methods:


public void paint(Graphics g) { }
public void update(Graphics g) { }

Usually prevents screen tearing with pixel[] arrays.

When your using a buffer strategy you really should put it in a do-while to make sure your not losing buffer contents. For example your render method would look something like this:


private void render() {
   do
   {
      do
      {
         Graphics2D g = (Graphics2D) bufferStrategy.getDrawGraphics();
         render(g);
         g.dispose();
      }while(bufferStrategy.contentsRestored());
   
      strategy.show();
   }while(bufferStrategy.contentsLost());
}