Resolution issue with framebuffers

Started by frucost, October 22, 2014, 01:44:15

Previous topic - Next topic

frucost

Hey guys,

I've recently experimented with framebuffers in openGL to achieve a pixelated look.
I'm basically rendering my scene into a 320*192px wide texture and afterwards drawing the texture to the screen (as far as I know the idea behind framebuffers).
To avoid distortion I'm using the methods 'recalculateViewport' and 'fitViewport' (Graphics.java), which adjust the viewport to preserve the aspect ratio of the 320*192px scene.
This is working as intended but inside the viewport the resolutions seems to be "reversed". :/

Example (with lower resolution):

Display dimensions: 200*64px
Render resolution: 8*4px

As expected, the viewport is set to 128*64px and moved 36px to the right.
But now the problem: instead of the resolution of 8*4px and a pixel-size of 16*16, I'm getting a resolution of 4*8 pixels with each rendered pixel being 32*8 actual pixels wide.

What I expected:


What I got:


Here's a screenshot of the program with those settings:


It would be great if someone had an idea how to fix this, I'm searching for a solution for hours now.

- Mario :D

PS: Please excuse mistakes, english is not my first language :)

Game.java
package net.frucost.game;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.EXTFramebufferObject.*;

import net.frucost.berry.core.Berry;

import org.lwjgl.opengl.Display;

public class Game {
    private long         lasttick;
    
    private final int    tps           = 60;
    private final int    tickdelay     = 1000 / tps;
    
    public int           framebuffer;
    public int           texture;
    
    public static void main(String[] args) {
        new Game().init();
    }
    
    public void init() {
        Berry.init();
        Berry.statemachine.addState("menu.main", new MenuState());
        Berry.statemachine.setNextState("menu.main");
        
        // create framebuffer and colortexture
        framebuffer = glGenFramebuffersEXT();
        texture = glGenTextures();

        // init framebuffer and colortexture
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer);
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, (int) Berry.graphics.resolution.x, (int) Berry.graphics.resolution.y, 0, GL_RGBA, GL_INT, (java.nio.ByteBuffer) null);
        glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, texture, 0);
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
        
        loop();
    }
    
    public void loop() {
        while(!Berry.app.closeRequested()) {
            if(System.currentTimeMillis() > lasttick + tickdelay) {
                update();
                lasttick += tickdelay;
            }
            render();
        }
    }
    
    public void update() {
        Berry.tick();
        Berry.statemachine.getCurrentState().update();
    }
    
    public void render() {
        { // Render scene into framebuffer
            glViewport(0, 0, (int) Berry.graphics.resolution.x, (int) Berry.graphics.resolution.y);
            glBindTexture(GL_TEXTURE_2D, 0); // Unbind texture
            glBindFramebufferEXT((GL_FRAMEBUFFER_EXT), framebuffer); // Bind backbuffer
            glPushMatrix();
            {
                glClearColor(0, 0, 0, 1);
                glClear(GL_COLOR_BUFFER_BIT);

                Berry.statemachine.getCurrentState().render();
            }
            glPopMatrix();
        }
        { // Render framebuffer to screen
            glEnable(GL_TEXTURE_2D);
            glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); // Bind frontbuffer
            
            Berry.graphics.fitViewport();
            
            glPushMatrix();
            {
                glColor3f(1, 1, 1);
                glClearColor(0, 0, 0, 1);
                glClear(GL_COLOR_BUFFER_BIT);
                glBindTexture(GL_TEXTURE_2D, texture);
                glBegin(GL_QUADS);
                {
                    glTexCoord2f(0.0f, 0.0f);
                    glVertex2i(0, 0);
                    glTexCoord2f(0.0f, 1.0f);
                    glVertex2i((int) Berry.graphics.resolution.x, 0);
                    glTexCoord2f(1.0f, 1.0f);
                    glVertex2i((int) Berry.graphics.resolution.x, (int) Berry.graphics.resolution.y);
                    glTexCoord2f(1.0f, 0.0f);
                    glVertex2i(0, (int) Berry.graphics.resolution.y);
                }
                glEnd();
                glDisable(GL_TEXTURE_2D);
            }
        }
        
        Display.update();
        Display.sync(60);
    }
}


Graphics.java (a instance of this class is referenced by 'Berry.graphics')
package net.frucost.berry.engine;

import java.util.ArrayList;
import java.util.logging.Logger;

import net.frucost.berry.utils.LogUtils;
import net.frucost.berry.utils.Vec2;

import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;

import static org.lwjgl.opengl.GL11.*;

public class Graphics {
	/**
	 * An array of valid DisplayModes (fullscreen-capable, with bitdepth of 32 and frequency of 60hz)
	 */
	private DisplayMode[]	displaymodes;
	
	/**
	 * Currently active Displaymode
	 */
	private DisplayMode		displaymode;
	
	/**
	 * Resolution of the rendering-area, will be scaled up to fit into window
	 */
	public final Vec2		resolution	= new Vec2(320, 192);
																		
	/**
	 * Size of the actual window
	 */
	public Vec2				dimension	= new Vec2(1280, 720);
	
	/**
	 * Cached position of the fit viewport
	 */
	public Vec2				viewportPos;
	
	/**
	 * Cached dimension of the fit viewport
	 */
	public Vec2				viewportDim;
	
	/**
	 * Class logger object
	 */
	private Logger			logger		= LogUtils.get(Graphics.class);
	
	/**
	 * Collect valid DisplayModes, initialize Display and fit Viewport
	 */
	public void init() {
		// Collect and filter DisplayModes
		logger.fine("Collecting displaymodes");
		try {
			ArrayList<DisplayMode> displaylist = new ArrayList<DisplayMode>();
			for(DisplayMode dm : Display.getAvailableDisplayModes()) {
				if(dm.getBitsPerPixel() == 32 && dm.getFrequency() == 60 && dm.isFullscreenCapable()) {
					logger.finest(dm.toString() + " (1:" + ((float) dm.getWidth() / (float) dm.getHeight()) + ")");
					displaylist.add(dm);
				}
			}
			this.displaymodes = new DisplayMode[displaylist.size()];
			for(int i = 0; i < displaylist.size(); i++) {
				this.displaymodes[i] = displaylist.get(i);
				if(displaylist.get(i).getWidth() == dimension.x && displaylist.get(i).getHeight() == dimension.y) {
					setDisplaymode(displaylist.get(i), false);
				}
			}
		}
		catch(LWJGLException e) {
			logger.severe("Failed to collect displaymodes: " + e.getLocalizedMessage());
		}
		// Create LWJGL display
		try {
			Display.setResizable(true);
			Display.create();
			initGL();
		}
		catch(LWJGLException e) {
			logger.severe("Failed to create LWJGL display: " + e.getLocalizedMessage());
		}
	}
	
	/**
	 * Initialize openGL
	 */
	public void initGL() {
		fitViewport();
		
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	}
	
	/**
	 * Recalculate position and dimension of the viewport inside the window
	 */
	public void recalculateViewport() {
		// Fit and center view
		double w2 = resolution.x, h2 = resolution.y, w1 = Display.getWidth(), h1 = Display.getHeight();
		
		double outerAspectRatio = w1 / h1;
		double innerAspectRatio = w2 / h2;
		
		double factor = (innerAspectRatio >= outerAspectRatio) ? (w1 / w2) : (h1 / h2);
		
		double w = w2 * factor;
		double h = h2 * factor;
		double l = (w1 - w) / 2;
		double t = (h1 - h) / 2;
		
		viewportPos = new Vec2(l, t);
		viewportDim = new Vec2(w, h);
	}
	
	/**
	 * Apply position and dimension of the viewport
	 */
	public void fitViewport() {
		if(viewportPos == null || viewportDim == null) {
			recalculateViewport();
		}
		glMatrixMode(GL_PROJECTION);
		{
			glLoadIdentity();
			System.out.println(viewportPos + ", " + viewportDim);
			glViewport((int) viewportPos.x, (int) viewportPos.y, (int) viewportDim.x, (int) viewportDim.y);
                        // (0|0) in the upper-left corner
                        glOrtho(0, resolution.x, resolution.y, 0, 1, -1);
		}
		glMatrixMode(GL_MODELVIEW);
	}
	
	/**
	 * @return a list of valid DisplayModes
	 */
	public DisplayMode[] getDisplaymodes() {
		return displaymodes;
	}
	
	/**
	 * @return the current DisplayMode
	 */
	public DisplayMode getDisplaymode() {
		return displaymode;
	}
	
	/**
	 * Try to enter a DisplayMode
	 * 
	 * @param displaymode
	 * @param fullscreen
	 */
	public void setDisplaymode(DisplayMode displaymode, boolean fullscreen) {
		for(DisplayMode mode : displaymodes) {
			if(mode.getWidth() == displaymode.getWidth() && mode.getHeight() == displaymode.getHeight()) {
				this.displaymode = mode;
				setFullscreen(fullscreen);
				updateDisplayMode();
				return;
			}
		}
		logger.warning("Tried to set unregistered displaymode: " + displaymode + (fullscreen ? " fullscreen" : ""));
	}
	
	/**
	 * Try to enter a DisplayMode
	 * 
	 * @param width
	 * @param height
	 * @param fullscreen
	 */
	public void setDisplaymode(int width, int height, boolean fullscreen) {
		setDisplaymode(new DisplayMode(width, height), fullscreen);
	}
	
	/**
	 * Try to enter/leave fullscreen mode
	 * 
	 * @param fullscreen
	 */
	public void setFullscreen(boolean fullscreen) {
		try {
			Display.setFullscreen(fullscreen);
		}
		catch(LWJGLException e) {
			logger.severe("Failed to set fullscreen mode");
		}
	}
	
	/**
	 * @return Whether fullscreen mode is active
	 */
	public boolean isFullscreen() {
		return Display.isFullscreen();
	}
	
	/**
	 * Try to apply displaymode
	 */
	public void updateDisplayMode() {
		try {
			Display.setDisplayMode(displaymode);
			logger.config("Updated displaymode: " + displaymode);
		}
		catch(LWJGLException e) {
	    	logger.severe("Failed to update displaymode: " + e.getLocalizedMessage());
		}
	}
}

Cornix

I have not looked over your code but I can tell you that you could simply change the way you render to get a look like that much simpler and easier.
There is absolutely no reason to use FrameBuffers for what you want to do. Just scale everything up.

frucost

Hey, thanks for your answer but how would you do that?
As far as I know the GL_NEAREST scaling-policy can only be applied to textures and not to the glOrtho-view itself.
Am I wrong with that? And if so, how would I?

- Mario

Cornix

I dont see what GL_NEAREST has to do with it.
There is no build in anti-aliasing in openGL.

frucost

Well, if I'm just setting the resolution of the ortho-view, everything becomes blurry.
There has to be a way to change the interpolation mode, right?

Cornix

Could you show an image of how exactly it looks like?
Do you use GL_NEAREST or GL_LINEAR as the mag filter of your textures?

frucost

I'm using GL_NEAREST :)

Game.java, Line 36:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);


I'm at my laptop at the moment, i'll post an image of what I mean when I'm at home :D

broumbroum

You're mixing vertices indeed ...
You also may want to check if the Rectangle Texture Extension gets better results, because of non-power-of-two texture sizes. That may be old school doing with newer G. Cards, but it's worth to check.
Quote from: frucost on October 22, 2014, 01:44:15
(..)This is working as intended but inside the viewport the resolutions seems to be "reversed". :/
               glBindTexture(GL_TEXTURE_2D, texture);
                glBegin(GL_QUADS);
                {
                    glTexCoord2f(0.0f, 0.0f);
                    glVertex2i(0, 0);
         glTexCoord2f(0.0f, 1.0f); // this is (1f, 0f)
                    glVertex2i((int) Berry.graphics.resolution.x, 0);
                    glTexCoord2f(1.0f, 1.0f);
                    glVertex2i((int) Berry.graphics.resolution.x, (int) Berry.graphics.resolution.y);
         glTexCoord2f(1.0f, 0.0f);// this is (0f, 1f)
                    glVertex2i(0, (int) Berry.graphics.resolution.y);
                }
                glEnd();
                glDisable(GL_TEXTURE_2D);

:D cu

frucost

THANKS!!

I feel so stupid right now :/
But it's working :D

Btw: Thanks for the link, I'll definitively have a look at those rectange textures ;)



- Mario