Setting a low resolution to mimic older games.

BlitzMax Forums/BlitzMax OpenGL Programming/Setting a low resolution to mimic older games.

Hello,

Recently I have wanted to mimic lower resolution games using OpenGL. I need to set the OpenGL display window to run in a very low resolution.

I've been kindly helped but I'm not quite advanced enough in C++ to know what is going on in this code. I was told that making a low resolution screen in OpenGL involved writting the buffer into a texture then making a quad that takes the texture and blows it up to the screen resolution, be it 640x480 or whatever else.

I would really appreciate some help if possible. I'm on a very tight timeline to finish this small project. I can't use the native graphics commands in Bmax because vector drawing remains at the screen resoultion.


Here is some of the code in C

// create buffer for reading back the opengl framebuffer (use a size of 2^n so we can directly dump the buffer into a texture)
   uint32 *vbuffer= new uint32[256*128];

// create a texture to hold the framebuffer
   unsigned int mOffscreen;
   glGenTextures(1, &mOffscreen);
   glBindTexture(GL_TEXTURE_2D, mOffscreen);

// disable filtering for this texture
   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);

while (running)
{

// set desired resolution
   glViewport(0,0,160,120);

  glClear(...);

// do totally amazing opengl magic here

// read backbuffer (reads more than actually required, so we don't need to convert to texture-size)
  glReadPixels(0,0,256,128, GL_BGRA, GL_UNSIGNED_BYTE, vbuffer);

// do wicked colour-reduction-things on the buffer
// remember that you only need to process the first 160 pixels of each scanline

  // write buffer to texture
  glBindTexture(GL_TEXTURE_2D, mOffscreen);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_BGRA, 256, 128, 0, GL_BGRA, GL_UNSIGNED_BYTE, vbuffer);

// viewport to full screen size
  glViewport(0,0, 640, 480);

// set up a useful projection to make a fullscreen-polygon (glOrtho does well, too)
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();   
  gluPerspective(90.0f, 1.0f, 0.1f, 100.0f);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();   

  glDisable(GL_DEPTH_TEST); // don't need to clear again.

// draw quad with framebuffer as texture
  glBegin(GL_QUADS);
  float y= 120.0 / 128.0; // texcoord of 160,120 on a 256x128 texture.
  float x= 160.0 / 256.0;
  glColor4f(1,1,1,1);
  glTexCoord2f(x,0); glVertex3f( 1,-1,-1);
  glTexCoord2f(0,0); glVertex3f(-1,-1,-1);
  glTexCoord2f(0,y); glVertex3f(-1, 1,-1);
  glTexCoord2f(x,y); glVertex3f( 1, 1,-1);
  glEnd();

  SwapBuffers();
}


One way to do it is the way you mentioned, where you draw a smaller-than-screen-size version of your game either onto the backbuffer or onto a texture, and then either you draw that texture full-screen size or you grab that portion of the backbuffer into a texture and draw it full-screen.

However, that really is involving steps that you don't need to take. What you need to do is correctly define your viewing Frustum ie your projection matrix, with which in a single call it's possible to set up a scaled display. If you define the viewing area for a 800x600 screen as 0,0 through 400,300, for example, anything you draw in the top left 400x300 area of your screen will fill the whole screen with 2x2-sized `pixels`. Similarly if you said your projection covers coordinates 0,0 through 200,150 then you'll get 4x4-sized pixels. see the glFrustum, glPerspective or glOrtho2D commands. With this method you don't need to play with textures at all and you don't have the extra overhead of redrawing them.

Yes but won't vector based drawing commands still appear as single pixel drawings? I don't think that the vector based drawing commands will scale will they? The raster based images on quads will probably size correctly but lines and polygons will still be a single pixel thick.

Yes they will. If you scale the projection matrix you are telling it to scale how it SEES the game world, and anything you `draw` you are drawing as geometry within the game world, so it will all be interpreted and scaled by the matrix.

If possible, would you mind showing a quick bit of code?

It would help me immensely to see it done correctly first.

See the documentation for glOrtho or gluOrtho2D:

http://www.talisman.org/opengl-1.1/Reference/glOrtho.html

It's something like..

glMatrixMode GL_PROJECTION
glLoadIdentity()
glOrtho 0,400,0,300
glMatrixMode GL_MODELVIEW

which would give you a 2x2 pixel size on an 800x600 screen.

You might need to call glViewport or SetViewport (BlitzMax) afterwards e.g. SetViewport 0,0,400,300 - not sure.

Give it a try.

I've done as you said, the lines and vector based drawing commands still draw in the original screen resolution.

SuperStrict
Framework brl.GLGraphics
GLGraphics 640, 480



glMatrixMode GL_PROJECTION
glLoadIdentity()
glOrtho 0, 160, 0, 140, 0, 1
glMatrixMode GL_MODELVIEW



glClear GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT


glBegin(GL_LINE_LOOP)
glColor3f(.75, .25, .75)

glVertex3f(0, 0, 0)
glVertex3f(0, 32, 0)
glVertex3f(32, 32, 0)
glVertex3f(32, 0, 0)
glEnd()
glViewport(0, 0, 160, 120)
Flip
WaitKey()


Ahh. Oh well. I thought it would work but apparently not. It does scale the coordinate system correctly but the actual drawing operations are not scaled. You'd need to maybe try changing the line thickness. It would work fine with textures/images. Maybe write your own line routine which instead of drawing using GL_LINE_LOOP draws using GL_QUADS with image data - then the image of the line piece will scale.

Yes, as you notice that is why everything was first rendered to a texture. Vector based drawing commands that have no mapping will maintain the window resolution. The initial code renders all vector objects and other drawing commands to a raster texture then expands the texture into a quad.

Now, is anyone willing to try to get this working from C code?

retrorender.bmx

Strict

Import BRL.GLGraphics

Private
Global _vbuffer[],_vwidth,_vheight,_vtexture
Global _rwidth,_rheight

Function Pow2Size( n )
	Local t=1
	While t<n
		t:*2
	Wend
	Return t
End Function

Function _retrorenderhook:Object(id,data:Object,context:Object)
	Local modelview#[16]
	Local projection#[16]
	glGetFloatv(GL_MODELVIEW_MATRIX,modelview)
	glGetFloatv(GL_PROJECTION_MATRIX,projection)
	
	glReadPixels(0,0,_vwidth,_vheight,GL_RGBA,GL_UNSIGNED_BYTE,_vbuffer)
	glBindTexture(GL_TEXTURE_2D,_vtexture)
	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,_vwidth,_vheight,0,GL_RGBA,GL_UNSIGNED_BYTE,_vbuffer)
	glViewport(0,0,GraphicsWidth(),GraphicsHeight())
	glMatrixMode(GL_PROJECTION)
	glLoadIdentity()   
	glOrtho 0,GraphicsWidth(),GraphicsHeight(),0,-1,1
	glMatrixMode(GL_MODELVIEW)
	glLoadIdentity()
	
	Local gltex2d=glIsEnabled(GL_TEXTURE_2D)
	Local gldepth=glIsEnabled(GL_DEPTH_TEST)
	Local gllights=glIsEnabled(GL_LIGHTING)
	glEnable(GL_TEXTURE_2D)
	glDisable(GL_DEPTH_TEST)
	glDisable(GL_LIGHTING)	
	Local v#=Float(_rheight)/Float(_vheight),u#=Float(_rwidth)/Float(_vwidth)
	glColor4f(1,1,1,1)
	glBegin(GL_QUADS)
		glTexCoord2f(0,v)
		glVertex2f(0,0)		
		glTexCoord2f(u,v)
		glVertex2f(GraphicsWidth(),0)		
		glTexCoord2f(u,0)
		glVertex2f(GraphicsWidth(),GraphicsHeight())		
		glTexCoord2f(0,0)
		glVertex2f(0,GraphicsHeight())
	glEnd()
	glMatrixMode(GL_PROJECTION)
	glLoadMatrixf projection
	glMatrixMode(GL_MODELVIEW)
	glLoadMatrixf modelview
	glViewport(0,0,_rwidth,_rheight)
	
	If Not gltex2d glDisable GL_TEXTURE_2D
	If gldepth glEnable(GL_DEPTH_TEST)
	If gllights glEnable(GL_LIGHTING)
	Return data
End Function

Public

Function RenderRetro(width,height)
	_vwidth=Pow2Size(width);_vheight=Pow2Size(height)
	_rwidth=width;_rheight=height
	_vbuffer=New Int[_vwidth*_vheight]
	glGenTextures 1,Varptr _vtexture
	AddHook FlipHook,_retrorenderhook,Null
	glViewport(0,0,width,height)
End Function

Basically, import this into your program and call 'RenderRetro(width,height)', passing the resolution you wish. The rest is taken care of via hooks.

OK, I see a flash of the screen in the correct resolution. When should I call the function? I think I have it in the wrong place perhaps. Thanks for all your help here. It is sort of an unique task that I ask for assistance with.


Strict
Framework brl.GLGraphics
GLGraphics 640, 480
Include "retro_render.bmx"
glMatrixMode (GL_PROJECTION)
glOrtho(0, 640, 480, 0, - 1000, 1000)
glMatrixMode(GL_MODELVIEW)


While Not KeyDown(KEY_ESCAPE)
	glClear GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT
	RenderRetro(160, 120)
	glBegin(GL_LINE_LOOP)
	For Local i:Float = 0 To 10
		glColor3f(i / 10, 0.0, 1.0)
		glVertex3f(80 + Cos(i * 36) * 40, 60 + Cos(i * 36) * 30, 0)
	Next
	glEnd()
	
	Flip
Wend


Yep, just call it once after GLGraphics. Sorry about that.
By the way, you may as well just Import it instead of including it. It'll speed up compile time.

I seriously suggest getting rid of the ReadPixels and reuploading of the texture, and instead use glCopyTexImage2D or glCopyTexSubImage2D directly from the backbuffer to an existing texture. It is MUCH faster than using pixmaps.

ImaginaryHUman, could you provide an example of how this is done? I'm not trying to be a pain but I'm still struggeling a bit.

Look at the documentation for glCopyTexSubImage2D() in the standard OpenGL reference online. You create a dummy texture with glTexImage2D like you already do in your code, but you don't pass any data to it (set the pixels pointer to Null). Then you just call glCopyTexSubImage2D() with the appropriate rectangle to copy it into the currently bound texture.