|
From: <ste...@us...> - 2009-05-25 17:52:00
|
Revision: 1739
http://stella.svn.sourceforge.net/stella/?rev=1739&view=rev
Author: stephena
Date: 2009-05-25 17:51:52 +0000 (Mon, 25 May 2009)
Log Message:
-----------
OK, this is a BIG commit.
Added CRT simulation as described by Ian Bogost in the AtariAge thread
'CRT simulation for Stella. This is just the first pass, but things
are working nicely so far (for those systems that can support it). It
requires at least OpenGL 2.0 with GLSL (GL Shading language). Added
the commandline arugments 'tv_tex', 'tv_bleed', 'tv_noise' and 'tv_phos'
used to set these various effects, as well as the ability to set them
within the Video Settings dialog. More documentation is forthcoming
on this.
All bankswitch modes that use SC RAM now act correctly when reading
from the write port (the RAM is erased).
Patching ROMs with bankswitch type 0840, SB, UA and X07 is now working.
Went through all the bankswitch classes and converted multiplication/
division to shift operations whenever possible. It's a
micro-optimization, but what the heck; every little bit of speed counts.
Modified Paths:
--------------
trunk/Changes.txt
trunk/src/common/FrameBufferGL.cxx
trunk/src/common/FrameBufferGL.hxx
trunk/src/common/FrameBufferSoft.hxx
trunk/src/emucore/Cart0840.cxx
trunk/src/emucore/Cart2K.cxx
trunk/src/emucore/Cart3E.cxx
trunk/src/emucore/Cart3F.cxx
trunk/src/emucore/Cart4K.cxx
trunk/src/emucore/CartAR.cxx
trunk/src/emucore/CartDPC.cxx
trunk/src/emucore/CartE0.cxx
trunk/src/emucore/CartE7.cxx
trunk/src/emucore/CartEF.cxx
trunk/src/emucore/CartEFSC.cxx
trunk/src/emucore/CartF4.cxx
trunk/src/emucore/CartF4SC.cxx
trunk/src/emucore/CartF6.cxx
trunk/src/emucore/CartF6SC.cxx
trunk/src/emucore/CartF8.cxx
trunk/src/emucore/CartF8SC.cxx
trunk/src/emucore/CartFASC.cxx
trunk/src/emucore/CartFE.cxx
trunk/src/emucore/CartMB.cxx
trunk/src/emucore/CartMC.cxx
trunk/src/emucore/CartSB.cxx
trunk/src/emucore/CartUA.cxx
trunk/src/emucore/CartX07.cxx
trunk/src/emucore/CartX07.hxx
trunk/src/emucore/FrameBuffer.hxx
trunk/src/emucore/Settings.cxx
trunk/src/emucore/m6502/src/M6502.cxx
trunk/src/gui/VideoDialog.cxx
trunk/src/gui/VideoDialog.hxx
Added Paths:
-----------
trunk/src/common/GLShaderProgs.hxx
trunk/src/tools/create_shaders.pl
Modified: trunk/Changes.txt
===================================================================
--- trunk/Changes.txt 2009-05-21 22:39:32 UTC (rev 1738)
+++ trunk/Changes.txt 2009-05-25 17:51:52 UTC (rev 1739)
@@ -12,6 +12,23 @@
Release History
===============================================================================
+2.7.7 to 2.8: (June xx, 2009)
+
+ * Added CRT simulation effects as described in the AtariAge posting
+ 'CRT emulation for Stella'. For now, this requires OpenGL 2.0 or
+ greater with support for GLSL (GL Shading Language).
+
+ * All bankswitching schemes which include SC extended RAM will now have
+ memory erased if you attempt to read from the write port. Related to
+ this, entering/exiting the debugger will no longer erase the extended
+ RAM.
+
+ * Patching of ROM for bankswitch types '0840', 'SB', 'UA' and 'X07' is
+ now implemented, but hasn't been extensively tested.
+
+-Have fun!
+
+
2.7.6 to 2.7.7: (May 1, 2009)
* Corrected emulation of CPU opcodes involving 'decimal' mode (ADC/RRA
@@ -24,9 +41,7 @@
* Changed internal sound frequency of Pitfall 2 from 15.75KHz to 20KHz,
as this sounds much more authentic when compared to a real cartridge.
--Have fun!
-
2.7.5 to 2.7.6: (April 14, 2009)
* Added support for 'EF' bankswitching (Paul Slocum Homestar Runner),
Modified: trunk/src/common/FrameBufferGL.cxx
===================================================================
--- trunk/src/common/FrameBufferGL.cxx 2009-05-21 22:39:32 UTC (rev 1738)
+++ trunk/src/common/FrameBufferGL.cxx 2009-05-25 17:51:52 UTC (rev 1739)
@@ -1,8 +1,8 @@
//============================================================================
//
-// SSSS tt lll lll
-// SS SS tt ll ll
-// SS tttttt eeee ll ll aaaa
+// SSSS tt lll lll
+// SS SS tt ll ll
+// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
@@ -30,6 +30,7 @@
#include "Settings.hxx"
#include "Surface.hxx"
#include "TIA.hxx"
+#include "GLShaderProgs.hxx"
#include "FrameBufferGL.hxx"
@@ -61,6 +62,25 @@
OGL_DECLARE(void,glTexImage2D,(GLenum, GLint, GLint, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid*));
OGL_DECLARE(void,glTexSubImage2D,(GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, const GLvoid*));
OGL_DECLARE(void,glTexParameteri,(GLenum, GLenum, GLint));
+OGL_DECLARE(GLuint,glCreateShader,(GLenum));
+OGL_DECLARE(void,glDeleteShader,(GLuint));
+OGL_DECLARE(void,glShaderSource,(GLuint, int, const char**, int));
+OGL_DECLARE(void,glCompileShader,(GLuint));
+OGL_DECLARE(GLuint,glCreateProgram,(void));
+OGL_DECLARE(void,glDeleteProgram,(GLuint));
+OGL_DECLARE(void,glAttachShader,(GLuint, GLuint));
+OGL_DECLARE(void,glLinkProgram,(GLuint));
+OGL_DECLARE(void,glUseProgram,(GLuint));
+OGL_DECLARE(GLint,glGetUniformLocation,(GLuint, const char*));
+OGL_DECLARE(void,glUniform1i,(GLint, GLint));
+OGL_DECLARE(void,glUniform1f,(GLint, GLfloat));
+OGL_DECLARE(void,glCopyTexImage2D,(GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLsizei, GLint));
+OGL_DECLARE(void,glCopyTexSubImage2D,(GLenum, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei));
+OGL_DECLARE(void,glActiveTexture,(GLenum));
+OGL_DECLARE(void,glGetIntegerv,(GLenum, GLint*));
+OGL_DECLARE(void,glTexEnvi,(GLenum, GLenum, GLint));
+OGL_DECLARE(void,glMultiTexCoord2f,(GLenum, GLfloat, GLfloat));
+OGL_DECLARE(GLenum,glGetError,(void));
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -143,10 +163,34 @@
OGL_INIT(void,glTexImage2D,(GLenum, GLint, GLint, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid*));
OGL_INIT(void,glTexSubImage2D,(GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, const GLvoid*));
OGL_INIT(void,glTexParameteri,(GLenum, GLenum, GLint));
+
+ OGL_INIT(GLuint,glCreateShader,(GLenum));
+ OGL_INIT(void,glDeleteShader,(GLuint));
+ OGL_INIT(void,glShaderSource,(GLuint, int, const char**, int));
+ OGL_INIT(void,glCompileShader,(GLuint));
+ OGL_INIT(GLuint,glCreateProgram,(void));
+ OGL_INIT(void,glDeleteProgram,(GLuint));
+ OGL_INIT(void,glAttachShader,(GLuint, GLuint));
+ OGL_INIT(void,glLinkProgram,(GLuint));
+ OGL_INIT(void,glUseProgram,(GLuint));
+ OGL_INIT(GLint,glGetUniformLocation,(GLuint, const char*));
+ OGL_INIT(void,glUniform1i,(GLint, GLint));
+ OGL_INIT(void,glUniform1f,(GLint, GLfloat));
+ OGL_INIT(void,glCopyTexImage2D,(GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLsizei, GLint));
+ OGL_INIT(void,glCopyTexSubImage2D,(GLenum, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei));
+ OGL_INIT(void,glActiveTexture,(GLenum));
+ OGL_INIT(void,glGetIntegerv,(GLenum, GLint*));
+ OGL_INIT(void,glTexEnvi,(GLenum, GLenum, GLint));
+ OGL_INIT(void,glMultiTexCoord2f,(GLenum, GLfloat, GLfloat));
+ OGL_INIT(GLenum,glGetError,(void));
}
else
return false;
+ // Grab OpenGL version number
+ string version((const char *)p_glGetString(GL_VERSION));
+ myGLVersion = atof(version.substr(0, 3).c_str());
+
return true;
}
@@ -215,6 +259,31 @@
uInt32 baseWidth = mode.image_w / mode.gfxmode.zoom;
uInt32 baseHeight = mode.image_h / mode.gfxmode.zoom;
+ // Update the graphics filter options
+ myUseTexture = true; myTextureStag = false;
+ const string& tv_tex = myOSystem->settings().getString("tv_tex");
+ if(tv_tex == "stag") myTextureStag = true;
+ else if(tv_tex != "normal") myUseTexture = false;
+
+ myUseBleed = true;
+ const string& tv_bleed = myOSystem->settings().getString("tv_bleed");
+ if(tv_bleed == "low") myBleedQuality = 0;
+ else if(tv_bleed == "medium") myBleedQuality = 1;
+ else if(tv_bleed == "high") myBleedQuality = 2;
+ else myUseBleed = false;
+
+ myUseNoise = true;
+ const string& tv_noise = myOSystem->settings().getString("tv_noise");
+ if(tv_noise == "low") myNoiseQuality = 5;
+ else if(tv_noise == "medium") myNoiseQuality = 15;
+ else if(tv_noise == "high") myNoiseQuality = 25;
+ else myUseNoise = false;
+
+ myUseGLPhosphor = myOSystem->settings().getBool("tv_phos");
+
+ // Set the zoom level
+ myZoomLevel = mode.gfxmode.zoom;
+
// Aspect ratio and fullscreen stretching only applies to the TIA
if(!inUIMode)
{
@@ -338,8 +407,10 @@
// and other UI surfaces are no longer tied together
// Note that this may change in the future, when we add more
// complex filters/scalers, but for now it's fine
+ // Also note that TV filtering is only available with OpenGL 2.0+
myTiaSurface = new FBSurfaceGL(*this, baseWidth>>1, baseHeight,
- mode.image_w, mode.image_h);
+ mode.image_w, mode.image_h,
+ myGLVersion >= 2.0);
myTiaSurface->setPos(mode.image_x, mode.image_y);
myTiaSurface->setFilter(myOSystem->settings().getString("gl_filter"));
}
@@ -473,14 +544,28 @@
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FBSurfaceGL::FBSurfaceGL(FrameBufferGL& buffer,
uInt32 baseWidth, uInt32 baseHeight,
- uInt32 scaleWidth, uInt32 scaleHeight)
+ uInt32 scaleWidth, uInt32 scaleHeight,
+ bool allowFiltering)
: myFB(buffer),
myTexture(NULL),
myTexID(0),
+ myFilterTexID(0),
+ mySubMaskTexID(0),
+ myNoiseMaskTexID(NULL),
+ myPhosphorTexID(0),
+ mySubpixelTexture(NULL),
+ myNoiseTexture(NULL),
myXOrig(0),
myYOrig(0),
myWidth(scaleWidth),
- myHeight(scaleHeight)
+ myHeight(scaleHeight),
+ myBleedProgram(0),
+ myTextureProgram(0),
+ myNoiseProgram(0),
+ myPhosphorProgram(0),
+ myTextureNoiseProgram(0),
+ myNoiseNum(0),
+ myTvFiltersEnabled(allowFiltering)
{
// Fill buffer struct with valid data
// This changes depending on the texturing used
@@ -510,6 +595,198 @@
0x00007c00, 0x000003e0, 0x0000001f, 0x00000000);
myPitch = myTexture->pitch >> 1;
+ // Only do this if TV filters enabled, otherwise it won't be used anyway
+ if(myTvFiltersEnabled)
+ {
+ // For a reason that hasn't been investigated yet, some of the filter and mask
+ // texture coordinates need to be swapped in order for it not to render upside down
+
+ myFilterTexCoord[0] = 0.0f;
+ myFilterTexCoord[3] = 0.0f;
+
+ if(myFB.myHaveTexRectEXT)
+ {
+ myFilterTexWidth = scaleWidth;
+ myFilterTexHeight = scaleHeight;
+ myFilterTexCoord[2] = (GLfloat) myFilterTexWidth;
+ myFilterTexCoord[1] = (GLfloat) myFilterTexHeight;
+ }
+ else
+ {
+ myFilterTexWidth = power_of_two(scaleWidth);
+ myFilterTexHeight = power_of_two(scaleHeight);
+ myFilterTexCoord[2] = (GLfloat) scaleWidth / myFilterTexWidth;
+ myFilterTexCoord[1] = (GLfloat) scaleHeight / myFilterTexHeight;
+ }
+ }
+
+ // Only do this if TV and color bleed filters are enabled
+ // This filer applies a color averaging of surrounding pixels for each pixel
+ if(myTvFiltersEnabled && myFB.myUseBleed)
+ {
+ // Load shader programs. If it fails, don't use this filter.
+ myBleedProgram = genShader(SHADER_BLEED);
+ if(myBleedProgram == 0)
+ {
+ myFB.myUseBleed = false;
+ cout << "ERROR: Failed to make bleed programs" << endl;
+ }
+ }
+
+ // If the texture and noise filters are enabled together, we can use a single shader
+ // Make sure we can use three textures at once first
+ GLint texUnits;
+ p_glGetIntegerv(GL_MAX_TEXTURE_UNITS, &texUnits);
+ if(myTvFiltersEnabled && texUnits >= 3 && myFB.myUseTexture && myFB.myUseNoise)
+ {
+ // Load shader program. If it fails, don't use this shader.
+ myTextureNoiseProgram = genShader(SHADER_TEXNOISE);
+ if(myTextureNoiseProgram == 0)
+ {
+ cout << "ERROR: Failed to make texture/noise program" << endl;
+
+ // Load shader program. If it fails, don't use this filter.
+ myTextureProgram = genShader(SHADER_TEX);
+ if(myTextureProgram == 0)
+ {
+ myFB.myUseTexture = false;
+ cout << "ERROR: Failed to make texture program" << endl;
+ }
+
+ // Load shader program. If it fails, don't use this filter.
+ myNoiseProgram = genShader(SHADER_NOISE);
+ if(myNoiseProgram == 0)
+ {
+ myFB.myUseNoise = false;
+ cout << "ERROR: Failed to make noise program" << endl;
+ }
+ }
+ }
+ // Else, detect individual settings
+ else if(myTvFiltersEnabled)
+ {
+ if(myFB.myUseTexture)
+ {
+ // Load shader program. If it fails, don't use this filter.
+ myTextureProgram = genShader(SHADER_TEX);
+ if(myTextureProgram == 0)
+ {
+ myFB.myUseTexture = false;
+ cout << "ERROR: Failed to make texture program" << endl;
+ }
+ }
+
+ if(myFB.myUseNoise)
+ {
+ // Load shader program. If it fails, don't use this filter.
+ myNoiseProgram = genShader(SHADER_NOISE);
+ if(myNoiseProgram == 0)
+ {
+ myFB.myUseNoise = false;
+ cout << "ERROR: Failed to make noise program" << endl;
+ }
+ }
+ }
+
+ // Only do this if TV and color texture filters are enabled
+ // This filer applies an RGB color pixel mask as well as a blackspace mask
+ if(myTvFiltersEnabled && myFB.myUseTexture)
+ {
+ // Prepare subpixel texture
+ mySubpixelTexture = SDL_CreateRGBSurface(SDL_SWSURFACE,
+ myFilterTexWidth, myFilterTexHeight, 16,
+ 0x00007c00, 0x000003e0, 0x0000001f, 0x00000000);
+
+ uInt32 pCounter = 0;
+ for (uInt32 y = 0; y < (uInt32)myFilterTexHeight; y++)
+ {
+ for (uInt32 x = 0; x < (uInt32)myFilterTexWidth; x++)
+ {
+ // Cause vertical offset for every other black row if enabled
+ uInt32 offsetY;
+ if (!myFB.myTextureStag || x % 6 < 3)
+ offsetY = y;
+ else
+ offsetY = y + 2;
+
+ // Make a row of black for the mask every so often
+ if (offsetY % 4 == 0)
+ {
+ ((uInt16*)mySubpixelTexture->pixels)[pCounter] = 0x0000;
+ }
+ // Apply the coorect color mask
+ else
+ {
+ ((uInt16*)mySubpixelTexture->pixels)[pCounter] = 0x7c00 >> ((x % 3) * 5);
+ }
+ pCounter++;
+ }
+ }
+ }
+
+ // Only do this if TV and noise filters are enabled
+ // This filter applies a texture filled with gray pixel of random intensities
+ if(myTvFiltersEnabled && myFB.myUseNoise)
+ {
+ // Get the current number of nose textures to use
+ myNoiseNum = myFB.myNoiseQuality;
+
+ // Allocate space for noise textures
+ myNoiseTexture = new SDL_Surface*[myNoiseNum];
+ myNoiseMaskTexID = new GLuint[myNoiseNum];
+
+ // Prepare noise textures
+ for(int i = 0; i < myNoiseNum; i++)
+ {
+ myNoiseTexture[i] = SDL_CreateRGBSurface(SDL_SWSURFACE,
+ myFilterTexWidth, myFilterTexHeight, 16,
+ 0x00007c00, 0x000003e0, 0x0000001f, 0x00000000);
+ }
+
+ uInt32 pCounter = 0;
+ for(int i = 0; i < myNoiseNum; i++)
+ {
+ pCounter = 0;
+
+ // Attempt to make the numbers as random as possible
+ int temp = (unsigned)time(0) + rand()/4;
+ srand(temp);
+
+ for (uInt32 y = 0; y < (uInt32)myFilterTexHeight; y++)
+ {
+ for (uInt32 x = 0; x < (uInt32)myFilterTexWidth; x++)
+ {
+ // choose random 0 - 2
+ // 0 = 0x0000
+ // 1 = 0x0421
+ // 2 = 0x0842
+ int num = rand() % 3;
+ if (num == 0)
+ ((uInt16*)myNoiseTexture[i]->pixels)[pCounter] = 0x0000;
+ else if (num == 1)
+ ((uInt16*)myNoiseTexture[i]->pixels)[pCounter] = 0x0421;
+ else if (num == 2)
+ ((uInt16*)myNoiseTexture[i]->pixels)[pCounter] = 0x0842;
+
+ pCounter++;
+ }
+ }
+ }
+ }
+
+ // Only do this if TV and phosphor filters are enabled
+ // This filter merges the past screen with the current one, to give a phosphor burn-off effect
+ if(myTvFiltersEnabled && myFB.myUseGLPhosphor)
+ {
+ // Load shader program. If it fails, don't use this filter.
+ myPhosphorProgram = genShader(SHADER_PHOS);
+ if(myPhosphorProgram == 0)
+ {
+ myFB.myUseGLPhosphor = false;
+ cout << "ERROR: Failed to make phosphor program" << endl;
+ }
+ }
+
// Associate the SDL surface with a GL texture object
reload();
}
@@ -520,6 +797,13 @@
if(myTexture)
SDL_FreeSurface(myTexture);
+ if(mySubpixelTexture)
+ SDL_FreeSurface(mySubpixelTexture);
+
+ if(myNoiseTexture)
+ for(int i = 0; i < myNoiseNum; i++)
+ SDL_FreeSurface(myNoiseTexture[i]);
+
free();
}
@@ -567,7 +851,7 @@
chr = desc.defaultchar;
}
chr -= desc.firstchar;
-
+
// Get the bounding box of the character
int bbw, bbh, bbx, bby;
if(!desc.bbx)
@@ -586,7 +870,7 @@
}
const uInt16* tmp = desc.bits + (desc.offset ? desc.offset[chr] : (chr * desc.fbbh));
- uInt16* buffer = (uInt16*) myTexture->pixels +
+ uInt16* buffer = (uInt16*) myTexture->pixels +
(ty + desc.ascent - bby - bbh) * myPitch +
tx + bbx;
@@ -594,7 +878,7 @@
{
const uInt16 ptr = *tmp++;
uInt16 mask = 0x8000;
-
+
for(int x = 0; x < bbw; x++, mask >>= 1)
if(ptr & mask)
buffer[x] = (uInt16) myFB.myDefPalette[color];
@@ -708,24 +992,196 @@
{
if(mySurfaceIsDirty)
{
- // Texturemap complete texture to surface so we have free scaling
- // and antialiasing
- p_glBindTexture(myTexTarget, myTexID);
- p_glTexSubImage2D(myTexTarget, 0, 0, 0, myTexWidth, myTexHeight,
- GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV, myTexture->pixels);
- p_glBegin(GL_QUADS);
- p_glTexCoord2f(myTexCoord[0], myTexCoord[1]);
- p_glVertex2i(myXOrig, myYOrig);
+ GLint loc;
- p_glTexCoord2f(myTexCoord[2], myTexCoord[1]);
- p_glVertex2i(myXOrig + myWidth, myYOrig);
+ // Set a boolean to tell which filter is a first render (if any are applied).
+ // Being a first render means using the Atari frame buffer instead of the
+ // previous rendered data.
+ bool firstRender = true;
- p_glTexCoord2f(myTexCoord[2], myTexCoord[3]);
- p_glVertex2i(myXOrig + myWidth, myYOrig + myHeight);
+ // Render as usual if no filters are used
+ if(!myTvFiltersEnabled ||
+ (!myFB.myUseTexture && !myFB.myUseNoise && !myFB.myUseBleed && !myFB.myUseGLPhosphor))
+ {
+ p_glUseProgram(0);
- p_glTexCoord2f(myTexCoord[0], myTexCoord[3]);
- p_glVertex2i(myXOrig, myYOrig + myHeight);
- p_glEnd();
+ // Texturemap complete texture to surface so we have free scaling
+ // and antialiasing
+ p_glActiveTexture(GL_TEXTURE0);
+ p_glBindTexture(myTexTarget, myTexID);
+ p_glTexSubImage2D(myTexTarget, 0, 0, 0, myTexWidth, myTexHeight,
+ GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV, myTexture->pixels);
+
+ // Pass in texture as a variable
+ p_glBegin(GL_QUADS);
+ p_glTexCoord2f(myTexCoord[0], myTexCoord[1]);
+ p_glVertex2i(myXOrig, myYOrig);
+
+ p_glTexCoord2f(myTexCoord[2], myTexCoord[1]);
+ p_glVertex2i(myXOrig + myWidth, myYOrig);
+
+ p_glTexCoord2f(myTexCoord[2], myTexCoord[3]);
+ p_glVertex2i(myXOrig + myWidth, myYOrig + myHeight);
+
+ p_glTexCoord2f(myTexCoord[0], myTexCoord[3]);
+ p_glVertex2i(myXOrig, myYOrig + myHeight);
+ p_glEnd();
+ }
+
+ // If TV filters are enabled
+ if(myTvFiltersEnabled)
+ {
+ // If combined texture/noise program exists,
+ // use the combined one; else do them separately
+ if(myTextureNoiseProgram != 0)
+ {
+ p_glUseProgram(myTextureNoiseProgram);
+
+ // Pass in subpixel mask texture
+ p_glActiveTexture(GL_TEXTURE1);
+ p_glBindTexture(myTexTarget, mySubMaskTexID);
+ loc = p_glGetUniformLocation(myTextureNoiseProgram, "texMask");
+ p_glUniform1i(loc, 1);
+
+ // Choose random mask texture
+ int num = rand() % myNoiseNum;
+ // Pass in noise mask texture
+ p_glActiveTexture(GL_TEXTURE2);
+ p_glBindTexture(myTexTarget, myNoiseMaskTexID[num]);
+ loc = p_glGetUniformLocation(myTextureNoiseProgram, "noiseMask");
+ p_glUniform1i(loc, 2);
+
+ renderThreeTexture(myTextureNoiseProgram, firstRender);
+
+ // We have rendered, set firstRender to false
+ firstRender = false;
+ }
+ else
+ {
+ // Check if texture filter is enabled
+ if(myFB.myUseTexture)
+ {
+ p_glUseProgram(myTextureProgram);
+
+ // Pass in subpixel mask texture
+ p_glActiveTexture(GL_TEXTURE1);
+ p_glBindTexture(myTexTarget, mySubMaskTexID);
+ loc = p_glGetUniformLocation(myTextureProgram, "mask");
+ p_glUniform1i(loc, 1);
+
+ renderTwoTexture(myTextureProgram, firstRender);
+
+ // We have rendered, set firstRender to false
+ firstRender = false;
+ }
+
+ if(myFB.myUseNoise)
+ {
+ p_glUseProgram(myNoiseProgram);
+
+ // Choose random mask texture
+ int num = rand() % myNoiseNum;
+
+ // Pass in noise mask texture
+ p_glActiveTexture(GL_TEXTURE1);
+ p_glBindTexture(myTexTarget, myNoiseMaskTexID[num]);
+ loc = p_glGetUniformLocation(myNoiseProgram, "mask");
+ p_glUniform1i(loc, 1);
+
+ renderTwoTexture(myNoiseProgram, firstRender);
+
+ // We have rendered, set firstRender to false
+ firstRender = false;
+ }
+ }
+
+ // Check if bleed filter is enabled
+ if(myFB.myUseBleed)
+ {
+ p_glUseProgram(myBleedProgram);
+
+ // Set some values based on high, medium, or low quality bleed. The high quality
+ // scales by applying additional passes, the low and medium quality scales by using
+ // a width and height based on the zoom level
+ int passes;
+ // High quality
+ if(myFB.myBleedQuality == 2)
+ {
+ // Precalculate pixel shifts
+ GLfloat pH = 1.0 / myHeight;
+ GLfloat pW = 1.0 / myWidth;
+ GLfloat pWx2 = pW * 2.0;
+
+ loc = p_glGetUniformLocation(myBleedProgram, "pH");
+ p_glUniform1f(loc, pH);
+ loc = p_glGetUniformLocation(myBleedProgram, "pW");
+ p_glUniform1f(loc, pW);
+ loc = p_glGetUniformLocation(myBleedProgram, "pWx2");
+ p_glUniform1f(loc, pWx2);
+
+ // Set the number of passes based on zoom level
+ passes = myFB.getZoomLevel();
+ }
+ // Medium and low quality
+ else
+ {
+ // The scaling formula was produced through trial and error
+ // Precalculate pixel shifts
+ GLfloat pH = 1.0 / (myHeight / (0.35 * myFB.getZoomLevel()));
+ GLfloat pW = 1.0 / (myWidth / (0.35 * myFB.getZoomLevel()));
+ GLfloat pWx2 = pW * 2.0;
+
+ loc = p_glGetUniformLocation(myBleedProgram, "pH");
+ p_glUniform1f(loc, pH);
+ loc = p_glGetUniformLocation(myBleedProgram, "pW");
+ p_glUniform1f(loc, pW);
+ loc = p_glGetUniformLocation(myBleedProgram, "pWx2");
+ p_glUniform1f(loc, pWx2);
+
+ // Medium quality
+ if(myFB.myBleedQuality == 1)
+ passes = 2;
+ // Low quality
+ else
+ passes = 1;
+ }
+
+ // If we are using a texture effect, we need more bleed
+ if (myFB.myUseTexture)
+ passes <<= 1;
+
+ for (int i = 0; i < passes; i++)
+ {
+ renderTexture(myBleedProgram, firstRender);
+
+ // We have rendered, set firstRender to false
+ firstRender = false;
+ }
+ }
+
+ // Check if phosphor burn-off filter is enabled
+ if(myFB.myUseGLPhosphor)
+ {
+ p_glUseProgram(myPhosphorProgram);
+
+ // Pass in subpixel mask texture
+ p_glActiveTexture(GL_TEXTURE1);
+ p_glBindTexture(myTexTarget, myPhosphorTexID);
+ loc = p_glGetUniformLocation(myPhosphorProgram, "mask");
+ p_glUniform1i(loc, 1);
+
+ renderTwoTexture(myPhosphorProgram, firstRender);
+
+ p_glActiveTexture(GL_TEXTURE1);
+ p_glBindTexture(myTexTarget, myPhosphorTexID);
+ // We only need to copy the scaled size, which may be smaller than the texture width
+ p_glCopyTexSubImage2D(myTexTarget, 0, 0, 0, myXOrig, myYOrig, myWidth, myHeight);
+
+ // We have rendered, set firstRender to false
+ firstRender = false;
+ }
+ }
+
mySurfaceIsDirty = false;
// Let postFrameUpdate() know that a change has been made
@@ -734,9 +1190,222 @@
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void FBSurfaceGL::renderTexture(GLuint program, bool firstRender)
+{
+ GLint loc;
+ GLfloat texCoord[4];
+
+ p_glActiveTexture(GL_TEXTURE0);
+
+ // If this is a first render, use the Atari frame buffer
+ if(firstRender)
+ {
+ // Pass in Atari frame
+ p_glBindTexture(myTexTarget, myTexID);
+ p_glTexSubImage2D(myTexTarget, 0, 0, 0, myTexWidth, myTexHeight,
+ GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV, myTexture->pixels);
+
+ // Set the texture coord appropriately
+ texCoord[0] = myTexCoord[0];
+ texCoord[1] = myTexCoord[1];
+ texCoord[2] = myTexCoord[2];
+ texCoord[3] = myTexCoord[3];
+ }
+ else
+ {
+ // Copy frame buffer to texture, this isn't the fastest way to do it, but it's simple
+ // (rendering directly to texture instead of copying may be faster)
+ p_glBindTexture(myTexTarget, myFilterTexID);
+ // We only need to copy the scaled size, which may be smaller than the texture width
+ p_glCopyTexSubImage2D(myTexTarget, 0, 0, 0, myXOrig, myYOrig, myWidth, myHeight);
+
+ // Set the texture coord appropriately
+ texCoord[0] = myFilterTexCoord[0];
+ texCoord[1] = myFilterTexCoord[1];
+ texCoord[2] = myFilterTexCoord[2];
+ texCoord[3] = myFilterTexCoord[3];
+ }
+
+ // Pass the texture to the program
+ loc = p_glGetUniformLocation(program, "tex");
+ p_glUniform1i(loc, 0);
+
+ // Pass in texture as a variable
+ p_glBegin(GL_QUADS);
+ p_glTexCoord2f(texCoord[0], texCoord[1]);
+ p_glVertex2i(myXOrig, myYOrig);
+
+ p_glTexCoord2f(texCoord[2], texCoord[1]);
+ p_glVertex2i(myXOrig + myWidth, myYOrig);
+
+ p_glTexCoord2f(texCoord[2], texCoord[3]);
+ p_glVertex2i(myXOrig + myWidth, myYOrig + myHeight);
+
+ p_glTexCoord2f(texCoord[0], texCoord[3]);
+ p_glVertex2i(myXOrig, myYOrig + myHeight);
+ p_glEnd();
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void FBSurfaceGL::renderTwoTexture(GLuint program, bool firstRender)
+{
+ GLint loc;
+ GLfloat texCoord[4];
+
+ p_glActiveTexture(GL_TEXTURE0);
+
+ // If this is a first render, use the Atari frame buffer
+ if(firstRender)
+ {
+ // Pass in Atari frame
+ p_glBindTexture(myTexTarget, myTexID);
+ p_glTexSubImage2D(myTexTarget, 0, 0, 0, myTexWidth, myTexHeight,
+ GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV, myTexture->pixels);
+
+ // Set the texture coord appropriately
+ texCoord[0] = myTexCoord[0];
+ texCoord[1] = myTexCoord[1];
+ texCoord[2] = myTexCoord[2];
+ texCoord[3] = myTexCoord[3];
+ }
+ else
+ {
+ // Copy frame buffer to texture, this isn't the fastest way to do it, but it's simple
+ // (rendering directly to texture instead of copying may be faster)
+ p_glBindTexture(myTexTarget, myFilterTexID);
+ // We only need to copy the scaled size, which may be smaller than the texture width
+ p_glCopyTexSubImage2D(myTexTarget, 0, 0, 0, myXOrig, myYOrig, myWidth, myHeight);
+
+ // Set the filter texture coord appropriately
+ texCoord[0] = myFilterTexCoord[0];
+ texCoord[1] = myFilterTexCoord[1];
+ texCoord[2] = myFilterTexCoord[2];
+ texCoord[3] = myFilterTexCoord[3];
+ }
+
+ // Pass the texture to the program
+ loc = p_glGetUniformLocation(program, "tex");
+ p_glUniform1i(loc, 0);
+
+ // Pass in textures as variables
+ p_glBegin(GL_QUADS);
+ p_glMultiTexCoord2f(GL_TEXTURE0, texCoord[0], texCoord[1]);
+ p_glMultiTexCoord2f(GL_TEXTURE1, myFilterTexCoord[0], myFilterTexCoord[1]);
+ p_glVertex2i(myXOrig, myYOrig);
+
+ p_glMultiTexCoord2f(GL_TEXTURE0, texCoord[2], texCoord[1]);
+ p_glMultiTexCoord2f(GL_TEXTURE1, myFilterTexCoord[2], myFilterTexCoord[1]);
+ p_glVertex2i(myXOrig + myWidth, myYOrig);
+
+ p_glMultiTexCoord2f(GL_TEXTURE0, texCoord[2], texCoord[3]);
+ p_glMultiTexCoord2f(GL_TEXTURE1, myFilterTexCoord[2], myFilterTexCoord[3]);
+ p_glVertex2i(myXOrig + myWidth, myYOrig + myHeight);
+
+ p_glMultiTexCoord2f(GL_TEXTURE0, texCoord[0], texCoord[3]);
+ p_glMultiTexCoord2f(GL_TEXTURE1, myFilterTexCoord[0], myFilterTexCoord[3]);
+ p_glVertex2i(myXOrig, myYOrig + myHeight);
+ p_glEnd();
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void FBSurfaceGL::renderThreeTexture(GLuint program, bool firstRender)
+{
+ GLint loc;
+ GLfloat texCoord[4];
+
+ p_glActiveTexture(GL_TEXTURE0);
+
+ // If this is a first render, use the Atari frame buffer
+ if(firstRender)
+ {
+ // Pass in Atari frame
+ p_glBindTexture(myTexTarget, myTexID);
+ p_glTexSubImage2D(myTexTarget, 0, 0, 0, myTexWidth, myTexHeight,
+ GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV, myTexture->pixels);
+
+ // Set the texture coord appropriately
+ texCoord[0] = myTexCoord[0];
+ texCoord[1] = myTexCoord[1];
+ texCoord[2] = myTexCoord[2];
+ texCoord[3] = myTexCoord[3];
+ }
+ else
+ {
+ // Copy frame buffer to texture, this isn't the fastest way to do it, but it's simple
+ // (rendering directly to texture instead of copying may be faster)
+ p_glBindTexture(myTexTarget, myFilterTexID);
+ // We only need to copy the scaled size, which may be smaller than the texture width
+ p_glCopyTexSubImage2D(myTexTarget, 0, 0, 0, myXOrig, myYOrig, myWidth, myHeight);
+
+ // Set the filter texture coord appropriately
+ texCoord[0] = myFilterTexCoord[0];
+ texCoord[1] = myFilterTexCoord[1];
+ texCoord[2] = myFilterTexCoord[2];
+ texCoord[3] = myFilterTexCoord[3];
+ }
+
+ // Pass the texture to the program
+ loc = p_glGetUniformLocation(program, "tex");
+ p_glUniform1i(loc, 0);
+
+ // Pass in textures as variables
+ p_glBegin(GL_QUADS);
+ p_glMultiTexCoord2f(GL_TEXTURE0, texCoord[0], texCoord[1]);
+ p_glMultiTexCoord2f(GL_TEXTURE1, myFilterTexCoord[0], myFilterTexCoord[1]);
+ p_glMultiTexCoord2f(GL_TEXTURE2, myFilterTexCoord[0], myFilterTexCoord[1]);
+ p_glVertex2i(myXOrig, myYOrig);
+
+ p_glMultiTexCoord2f(GL_TEXTURE0, texCoord[2], texCoord[1]);
+ p_glMultiTexCoord2f(GL_TEXTURE1, myFilterTexCoord[2], myFilterTexCoord[1]);
+ p_glMultiTexCoord2f(GL_TEXTURE2, myFilterTexCoord[2], myFilterTexCoord[1]);
+ p_glVertex2i(myXOrig + myWidth, myYOrig);
+
+ p_glMultiTexCoord2f(GL_TEXTURE0, texCoord[2], texCoord[3]);
+ p_glMultiTexCoord2f(GL_TEXTURE1, myFilterTexCoord[2], myFilterTexCoord[3]);
+ p_glMultiTexCoord2f(GL_TEXTURE2, myFilterTexCoord[2], myFilterTexCoord[3]);
+ p_glVertex2i(myXOrig + myWidth, myYOrig + myHeight);
+
+ p_glMultiTexCoord2f(GL_TEXTURE0, texCoord[0], texCoord[3]);
+ p_glMultiTexCoord2f(GL_TEXTURE1, myFilterTexCoord[0], myFilterTexCoord[3]);
+ p_glMultiTexCoord2f(GL_TEXTURE2, myFilterTexCoord[0], myFilterTexCoord[3]);
+ p_glVertex2i(myXOrig, myYOrig + myHeight);
+ p_glEnd();
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FBSurfaceGL::free()
{
p_glDeleteTextures(1, &myTexID);
+
+ // The below is borken up a bit because of the possible combined texture/noise shader
+
+ if(myFilterTexID)
+ p_glDeleteTextures(1, &myFilterTexID);
+
+ if(mySubMaskTexID)
+ p_glDeleteTextures(1, &mySubMaskTexID);
+
+ if(myTextureProgram)
+ p_glDeleteProgram(myTextureProgram);
+
+ if(myNoiseMaskTexID)
+ {
+ delete[] myNoiseTexture;
+ p_glDeleteTextures(myNoiseNum, myNoiseMaskTexID);
+ delete[] myNoiseMaskTexID;
+ }
+
+ if(myNoiseProgram)
+ p_glDeleteProgram(myNoiseProgram);
+
+ if(myPhosphorTexID)
+ {
+ p_glDeleteTextures(1, &myPhosphorTexID);
+ p_glDeleteProgram(myPhosphorProgram);
+ }
+
+ if(myTextureNoiseProgram)
+ p_glDeleteProgram(myTextureNoiseProgram);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -751,6 +1420,9 @@
// Basically, all that needs to be done is to re-call glTexImage2D with a
// new texture ID, so that's what we do here
+ p_glActiveTexture(GL_TEXTURE0);
+ p_glEnable(myTexTarget);
+
p_glGenTextures(1, &myTexID);
p_glBindTexture(myTexTarget, myTexID);
p_glTexParameteri(myTexTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
@@ -763,7 +1435,69 @@
myTexWidth, myTexHeight, 0,
GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV, myTexture->pixels);
- p_glEnable(myTexTarget);
+ // Do the same for the TV filter textures
+ // Only do this if TV filters are enabled
+ if(myTvFiltersEnabled)
+ {
+ // Generate the generic filter texture
+ p_glGenTextures(1, &myFilterTexID);
+ p_glBindTexture(myTexTarget, myFilterTexID);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ // Make the initial texture, this will get overwritten later
+ p_glCopyTexImage2D(myTexTarget, 0, GL_RGB5, 0, 0, myFilterTexWidth, myFilterTexHeight, 0);
+
+ // Only do this if TV and color texture filters are enabled
+ if(myFB.myUseTexture)
+ {
+ // Generate the subpixel mask texture
+ p_glGenTextures(1, &mySubMaskTexID);
+ p_glBindTexture(myTexTarget, mySubMaskTexID);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ // Write the data
+ p_glTexImage2D(myTexTarget, 0, GL_RGB5,
+ myFilterTexWidth, myFilterTexHeight, 0,
+ GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV, mySubpixelTexture->pixels);
+ }
+
+ // Only do this if TV and noise filters are enabled
+ if(myFB.myUseNoise)
+ {
+ // Generate the noise mask textures
+ p_glGenTextures(myNoiseNum, myNoiseMaskTexID);
+ for(int i = 0; i < myNoiseNum; i++)
+ {
+ p_glBindTexture(myTexTarget, myNoiseMaskTexID[i]);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ // Write the data
+ p_glTexImage2D(myTexTarget, 0, GL_RGB5,
+ myFilterTexWidth, myFilterTexHeight, 0,
+ GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV, myNoiseTexture[i]->pixels);
+ }
+ }
+
+ // Only do this if TV and phosphor filters are enabled
+ if(myFB.myUseGLPhosphor)
+ {
+ // Generate the noise mask textures
+ p_glGenTextures(1, &myPhosphorTexID);
+ p_glBindTexture(myTexTarget, myPhosphorTexID);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ // Make the initial texture, this will get overwritten later
+ p_glCopyTexImage2D(myTexTarget, 0, GL_RGB5, 0, 0, myFilterTexWidth, myFilterTexHeight, 0);
+ }
+ }
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -780,11 +1514,131 @@
p_glTexParameteri(myTexTarget, GL_TEXTURE_MIN_FILTER, filter);
p_glTexParameteri(myTexTarget, GL_TEXTURE_MAG_FILTER, filter);
+ // Do the same for the filter textures
+ // Only do this if TV filters are enabled
+ if(myTvFiltersEnabled)
+ {
+ p_glBindTexture(myTexTarget, myFilterTexID);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_MIN_FILTER, filter);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_MAG_FILTER, filter);
+
+ // Only do this if TV and color texture filters are enabled
+ if(myFB.myUseTexture)
+ {
+ p_glBindTexture(myTexTarget, mySubMaskTexID);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_MIN_FILTER, filter);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_MAG_FILTER, filter);
+ }
+
+ // Only do this if TV and noise filters are enabled
+ if(myFB.myUseNoise)
+ {
+ for(int i = 0; i < myNoiseNum; i++)
+ {
+ p_glBindTexture(myTexTarget, myNoiseMaskTexID[i]);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_MIN_FILTER, filter);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_MAG_FILTER, filter);
+ }
+ }
+
+ // Only do this if TV and phosphor filters are enabled
+ if(myFB.myUseGLPhosphor)
+ {
+ p_glBindTexture(myTexTarget, myPhosphorTexID);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_MIN_FILTER, filter);
+ p_glTexParameteri(myTexTarget, GL_TEXTURE_MAG_FILTER, filter);
+ }
+ }
+
// The filtering has changed, so redraw the entire screen
mySurfaceIsDirty = true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+GLuint FBSurfaceGL::genShader(ShaderType type)
+{
+ string fFile = "";
+ char* fCode = NULL;
+ switch(type)
+ {
+ case SHADER_BLEED:
+ fFile = "bleed.frag";
+ fCode = (char*)GLShader::bleed_frag[0];
+ break;
+ case SHADER_TEX:
+ fFile = "texture.frag";
+ fCode = (char*)GLShader::texture_frag[0];
+ break;
+ case SHADER_NOISE:
+ fFile = "noise.frag";
+ fCode = (char*)GLShader::noise_frag[0];
+ break;
+ case SHADER_PHOS:
+ fFile = "phosphor.frag";
+ fCode = (char*)GLShader::phosphor_frag[0];
+ break;
+ case SHADER_TEXNOISE:
+ fFile = "texture_noise.frag";
+ fCode = (char*)GLShader::texture_noise_frag[0];
+ break;
+ }
+
+ // First try opening an external fragment file
+ // These shader files are stored in 'BASEDIR/shaders/'
+ char* buffer = NULL;
+ const string& filename =
+ myFB.myOSystem->baseDir() + BSPF_PATH_SEPARATOR + "shaders" +
+ BSPF_PATH_SEPARATOR + fFile;
+ ifstream in(filename.c_str());
+ if(in && in.is_open())
+ {
+ // Get file size
+ in.seekg(0, std::ios::end);
+ streampos size = in.tellg();
+
+ // Reset position
+ in.seekg(0);
+
+ // Make buffer of proper size;
+ buffer = new char[size+(streampos)1]; // +1 for '\0'
+
+ // Read in file
+ in.read(buffer, size);
+ buffer[in.gcount()] = '\0';
+ in.close();
+
+ fCode = buffer;
+ }
+
+ // Make the shader program
+ GLuint fShader = p_glCreateShader(GL_FRAGMENT_SHADER);
+ GLuint program = p_glCreateProgram();
+ p_glShaderSource(fShader, 1, (const char**)&fCode, NULL);
+ p_glCompileShader(fShader);
+ p_glAttachShader(program, fShader);
+ p_glLinkProgram(program);
+
+ // Go ahead and flag the shader for deletion so it is deleted once the program is
+ p_glDeleteShader(fShader);
+
+ // Clean up
+ delete[] buffer;
+
+ return program;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+float FrameBufferGL::myGLVersion = 0.0;
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool FrameBufferGL::myLibraryLoaded = false;
#endif // DISPLAY_OPENGL
Modified: trunk/src/common/FrameBufferGL.hxx
===================================================================
--- trunk/src/common/FrameBufferGL.hxx 2009-05-21 22:39:32 UTC (rev 1738)
+++ trunk/src/common/FrameBufferGL.hxx 2009-05-25 17:51:52 UTC (rev 1739)
@@ -1,8 +1,8 @@
//============================================================================
//
-// SSSS tt lll lll
-// SS SS tt ll ll
-// SS tttttt eeee ll ll aaaa
+// SSSS tt lll lll
+// SS SS tt ll ll
+// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
@@ -156,8 +156,13 @@
private:
bool loadFuncs();
+ /**
+ Enable/disable texture effect.
+ */
+ void enableTexture(bool enable);
+
private:
- // The lower-most base surface (will always be a TIA surface,
+ // The lower-most base surface (will always be a TIA surface,
// since Dialog surfaces are allocated by the Dialog class directly).
FBSurfaceGL* myTiaSurface;
@@ -179,6 +184,30 @@
// Indicates that the texture has been modified, and should be redrawn
bool myDirtyFlag;
+ // Indicates whether or not color bleed filter is enabled
+ bool myUseBleed;
+
+ // Indicates the quality of the color bleed filter to use
+ int myBleedQuality;
+
+ // Indicates whether or not color texture filter is enabled
+ bool myUseTexture;
+
+ // Indicates whetehr or not color texture filter is staggered
+ bool myTextureStag;
+
+ // Indicates whether or not the noise filter is enabled
+ bool myUseNoise;
+
+ // Indicates the quality of the noise filter to use
+ int myNoiseQuality;
+
+ // Indicates whether or not the phosphor filter is enabled
+ bool myUseGLPhosphor;
+
+ // Indicates the OpenGL version found (0 indicates none)
+ static float myGLVersion;
+
// Indicates if the OpenGL library has been properly loaded
static bool myLibraryLoaded;
};
@@ -196,7 +225,8 @@
public:
FBSurfaceGL(FrameBufferGL& buffer,
uInt32 baseWidth, uInt32 baseHeight,
- uInt32 scaleWidth, uInt32 scaleHeight);
+ uInt32 scaleWidth, uInt32 scaleHeight,
+ bool allowFiltering = false);
virtual ~FBSurfaceGL();
void hLine(uInt32 x, uInt32 y, uInt32 x2, uInt32 color);
@@ -221,6 +251,54 @@
private:
void setFilter(const string& name);
+ /**
+ This method generates an OpenGL shader program from a fragment shader.
+
+ @param fragment The filename of the fragment shader (not including location)
+
+ @return The generated shader program
+ */
+ enum ShaderType {
+ SHADER_BLEED, SHADER_TEX, SHADER_NOISE, SHADER_PHOS, SHADER_TEXNOISE
+ };
+ GLuint genShader(ShaderType type);
+
+ /**
+ This method performs the final steps of rendering a single texture filter:
+ passing the previously rendered screen to the given program and drawing
+ to the screen. It does not include setting the program through
+ p_glUseProgram() because this needs to be done before the custom program
+ variables are set.
+
+ @param program The program to use to render the filter
+ @param firstRender True if this is the first render for this frame, false if not
+ */
+ void renderTexture(GLuint program, bool firstRender);
+
+ /**
+ This method performs the final steps of rendering a two-texture filter:
+ passing the previously rendered screen to the given program and drawing
+ the previous texture and mask texture to the screen. It does not include
+ setting the program through p_glUseProgram() because this needs to be
+ done before the mask texture and custom program variables are set.
+
+ @param program The program to use to render the filter
+ @param firstRender True if this is the first render for this frame, false if not
+ */
+ void renderTwoTexture(GLuint program, bool firstRender);
+
+ /**
+ This method performs the final steps of rendering a three-texture filter:
+ passing the previously rendered screen to the given program and drawing
+ the previous texture and two mask textures to the screen. It does not include
+ setting the program through p_glUseProgram() because this needs to be
+ done before the mask texture and custom program variables are set.
+
+ @param program The program to use to render the filter
+ @param firstRender True if this is the first render for this frame, false if not
+ */
+ void renderThreeTexture(GLuint program, bool firstRender);
+
inline void* pixels() const { return myTexture->pixels; }
inline uInt32 pitch() const { return myPitch; }
@@ -242,9 +320,43 @@
GLsizei myTexHeight;
GLfloat myTexCoord[4];
+ // The filter texture is what is used to hold data from screen after one
+ // filter has been used. Needed since more than one filter is being used.
+ // The size and texture coordinates are also used for the other filter
+ // textures: mySubMaskTexID and myNoiseTexID
+ GLuint myFilterTexID;
+ GLsizei myFilterTexWidth;
+ GLsizei myFilterTexHeight;
+ GLfloat myFilterTexCoord[4];
+
+ // The subpixel texture used for the texture filter
+ GLuint mySubMaskTexID;
+ // The noise textures used for the noise filter
+ GLuint* myNoiseMaskTexID;
+ // The past texture used for the phosphor filter
+ GLuint myPhosphorTexID;
+
+ // Surface for the subpixel texture filter mask
+ SDL_Surface* mySubpixelTexture;
+ // Surfaces for noise filter mask (array of pointers)
+ SDL_Surface** myNoiseTexture;
+
uInt32 myXOrig, myYOrig, myWidth, myHeight;
bool mySurfaceIsDirty;
uInt32 myPitch;
+
+ // OpenGL shader programs
+ GLuint myBleedProgram; // Shader for color bleed filter
+ GLuint myTextureProgram; // Shader for color texture filter
+ GLuint myNoiseProgram; // Shader for noise filter
+ GLuint myPhosphorProgram; // Shader for the phosphor filter
+ GLuint myTextureNoiseProgram; // Shader for both color texture and noise filters
+
+ // Used to save the number of noise textures to use at game launch
+ int myNoiseNum;
+
+ // Specifies whether the TV filters can be applied to this surface
+ bool myTvFiltersEnabled;
};
#endif // DISPLAY_OPENGL
Modified: trunk/src/common/FrameBufferSoft.hxx
===================================================================
--- trunk/src/common/FrameBufferSoft.hxx 2009-05-21 22:39:32 UTC (rev 1738)
+++ trunk/src/common/FrameBufferSoft.hxx 2009-05-25 17:51:52 UTC (rev 1739)
@@ -142,7 +142,6 @@
string about() const;
private:
- int myZoomLevel;
int myBytesPerPixel;
int myBaseOffset;
int myPitch;
Added: trunk/src/common/GLShaderProgs.hxx
===================================================================
--- trunk/src/common/GLShaderProgs.hxx (rev 0)
+++ trunk/src/common/GLShaderProgs.hxx 2009-05-25 17:51:52 UTC (rev 1739)
@@ -0,0 +1,133 @@
+#ifndef GL_SHADER_PROGS_HXX
+#define GL_SHADER_PROGS_HXX
+
+/**
+ This code is generated using the 'create_shaders.pl' script,
+ located in the src/tools directory.
+*/
+
+namespace GLShader {
+
+static const char* bleed_frag[] = {
+"uniform sampler2D tex;\n"
+"uniform float pH;\n"
+"uniform float pW;\n"
+"uniform float pWx2;\n"
+"\n"
+"void main()\n"
+"{\n"
+" // Save current color\n"
+" vec4 current = texture2D(tex, vec2(gl_TexCoord[0].s, gl_TexCoord[0].t));\n"
+"\n"
+" // Box filter\n"
+" // Comments for position are given in (x,y) coordinates with the current pixel as the origin\n"
+" vec4 color = ( \n"
+" // (-1,1)\n"
+" texture2D(tex, vec2(gl_TexCoord[0].s-pW, gl_TexCoord[0].t+pH))\n"
+" // (0,1)\n"
+" + texture2D(tex, vec2(gl_TexCoord[0].s, gl_TexCoord[0].t+pH))\n"
+" // (1,1)\n"
+" + texture2D(tex, vec2(gl_TexCoord[0].s+pW, gl_TexCoord[0].t+pH))\n"
+" // (-1,0)\n"
+" + texture2D(tex, vec2(gl_TexCoord[0].s-pW, gl_TexCoord[0].t))\n"
+" // (0,0)\n"
+" + current\n"
+" // (1,0)\n"
+" + texture2D(tex, vec2(gl_TexCoord[0].s+pW, gl_TexCoord[0].t))\n"
+" // (-1,-1)\n"
+" + texture2D(tex, vec2(gl_TexCoord[0].s-pW, gl_TexCoord[0].t-pH))\n"
+" // (0,-1)\n"
+" + texture2D(tex, vec2(gl_TexCoord[0].s, gl_TexCoord[0].t-pH))\n"
+" // (1,-1)\n"
+" + texture2D(tex, vec2(gl_TexCoord[0].s+pW, gl_TexCoord[0].t-pH))\n"
+"\n"
+" // Make it wider\n"
+" // (-2,1)\n"
+" + texture2D(tex, vec2(gl_TexCoord[0].s-pWx2, gl_TexCoord[0].t+pH))\n"
+" // (-2,0)\n"
+" + texture2D(tex, vec2(gl_TexCoord[0].s-pWx2, gl_TexCoord[0].t))\n"
+" // (-2,-1)\n"
+" + texture2D(tex, vec2(gl_TexCoord[0].s-pWx2, gl_TexCoord[0].t-pH))\n"
+" // (2,1)\n"
+" + texture2D(tex, vec2(gl_TexCoord[0].s+pWx2, gl_TexCoord[0].t+pH))\n"
+" // (2,0)\n"
+" + texture2D(tex, vec2(gl_TexCoord[0].s+pWx2, gl_TexCoord[0].t))\n"
+" // (2,-1)\n"
+" + texture2D(tex, vec2(gl_TexCoord[0].s+pWx2, gl_TexCoord[0].t-pH))\n"
+" ) / 15.0;\n"
+"\n"
+" // Make darker colors not bleed over lighter colors (act like light)\n"
+" color = vec4(max(current.x, color.x), max(current.y, color.y), max(current.z, color.z), 1.0);\n"
+"\n"
+" gl_FragColor = color;\n"
+"}\n"
+"\0"
+};
+
+static const char* noise_frag[] = {
+"uniform sampler2D tex;\n"
+"uniform sampler2D mask;\n"
+"\n"
+"void main()\n"
+"{\n"
+" gl_FragColor =\n"
+" texture2D(tex, vec2(gl_TexCoord[0].s, gl_TexCoord[0].t))\n"
+" + texture2D(mask, vec2(gl_TexCoord[1].s, gl_TexCoord[1].t))\n"
+" ;\n"
+"}\n"
+"\0"
+};
+
+static const char* phosphor_frag[] = {
+"uniform sampler2D tex;\n"
+"uniform sampler2D mask;\n"
+"\n"
+"void main()\n"
+"{\n"
+" gl_FragColor =\n"
+" 0.65 * texture2D(tex, vec2(gl_TexCoord[0].s, gl_TexCoord[0].t))\n"
+" + 0.35 * texture2D(mask, vec2(gl_TexCoord[1].s, gl_TexCoord[1].t))\n"
+" ;\n"
+"}\n"
+"\0"
+};
+
+static const char* texture_frag[] = {
+"uniform sampler2D tex;\n"
+"uniform sampler2D mask;\n"
+"\n"
+"void main()\n"
+"{\n"
+" gl_FragColor =\n"
+" texture2D(tex, vec2(gl_TexCoord[0].s, gl_TexCoord[0].t))\n"
+" * texture2D(mask, vec2(gl_TexCoord[1].s, gl_TexCoord[1].t))\n"
+" * 1.05\n"
+" + 0.07\n"
+" ;\n"
+"}\n"
+"\0"
+};
+
+static const char* texture_noise_frag[] = {
+"uniform sampler2D tex;\n"
+"uniform sampler2D texMask;\n"
+"uniform sampler2D noiseMask;\n"
+"\n"
+"void main()\n"
+"{\n"
+" gl_FragColor =\n"
+" // Texture part\n"
+" texture2D(tex, vec2(gl_TexCoord[0].s, gl_TexCoord[0].t))\n"
+" * texture2D(texMask, vec2(gl_TexCoord[1].s, gl_TexCoord[1].t))\n"
+" * 1.05\n"
+" + 0.07\n"
+" // Noise part\n"
+" + texture2D(noiseMask, vec2(gl_TexCoord[1].s, gl_TexCoord[1].t))\n"
+" ;\n"
+"}\n"
+"\0"
+};
+
+} // namespace GLShader
+
+#endif
Property changes on: trunk/src/common/GLShaderProgs.hxx
___________________________________________________________________
Added: svn:keywords
+ Id
Added: svn:eol-style
+ native
Modified: trunk/src/emucore/Cart0840.cxx
===================================================================
--- trunk/src/emucore/Cart0840.cxx 2009-05-21 22:39:32 UTC (rev 1738)
+++ trunk/src/emucore/Cart0840.cxx 2009-05-25 17:51:52 UTC (rev 1739)
@@ -17,6 +17,7 @@
//============================================================================
#include <cassert>
+#include <cstring>
#include "System.hxx"
#include "Cart0840.hxx"
@@ -25,10 +26,7 @@
Cartridge0840::Cartridge0840(const uInt8* image)
{
// Copy the ROM image into my buffer
- for(uInt32 addr = 0; addr < 8192; ++addr)
- {
- myImage[addr] = image[addr];
- }
+ memcpy(myImage, image, 8192);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -149,7 +147,7 @@
// Remember what bank we're in
myCurrentBank = bank;
- uInt16 offset = myCurrentBank * 4096;
+ uInt16 offset = myCurrentBank << 12;
uInt16 shift = mySystem->pageShift();
// Setup the page access methods for the current bank
@@ -181,10 +179,7 @@
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Cartridge0840::patch(uInt16 address, uInt8 value)
{
- address &= 0x0fff;
- myImage[myCurrentBank * 4096] = value;
- bank(myCurrentBank); // TODO: see if this is really necessary
-
+ myImage[(myCurrentBank << 12) + (address & 0x0fff)] = value;
return true;
}
Modified: trunk/src/emucore/Cart2K.cxx
===================================================================
--- trunk/src/emucore/Cart2K.cxx 2009-05-21 22:39:32 UTC (rev 1738)
+++ trunk/src/emucore/Cart2K.cxx 2009-05-25 17:51:52 UTC (rev 1739)
@@ -17,6 +17,7 @@
//============================================================================
#include <cassert>
+#include <cstring>
#include "System.hxx"
#include "Cart2K.hxx"
@@ -25,10 +26,7 @@
Cartridge2K::Cartridge2K(const uInt8* image)
{
// Copy the ROM image into my buffer
- for(uInt32 addr = 0; addr < 2048; ++addr)
- {
- myImage[addr] = image[addr];
- }
+ memcpy(myImage, image, 2048);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Modified: trunk/src/emucore/Cart3E.cxx
===================================================================
--- trunk/src/emucore/Cart3E.cxx 2009-05-21 22:39:32 UTC (rev 1738)
+++ trunk/src/emucore/Cart3E.cxx 2009-05-25 17:51:52 UTC (rev 1739)
@@ -97,9 +97,9 @@
if(address < 0x0800)
{
if(myCurrentBank < 256)
- return myImage[(address & 0x07FF) + myCurrentBank * 2048];
+ return myImage[(address & 0x07FF) + (myCurrentBank << 11)];
else
- return myRam[(address & 0x03FF) + (myCurrentBank - 256) * 1024];
+ return myRam[(address & 0x03FF) + ((myCurrentBank - 256) << 10)];
}
else
{
@@ -138,7 +138,7 @@
if(bank < 256)
{
// Make sure the bank they're asking for is reasonable
- if((uInt32)bank * 2048 < mySize)
+ if(((uInt32)bank << 11) < uInt32(mySize))
{
myCurrentBank = bank;
}
@@ -146,10 +146,10 @@
{
// Oops, the bank they're asking for isn't valid so let's wrap it
// around to a valid bank number
- myCurrentBank = bank % (mySize / 2048);
+ myCurrentBank = bank % (mySize >> 11);
}
- uInt32 offset = myCurrentBank * 2048;
+ uInt32 offset = myCurrentBank << 11;
uInt16 shift = mySystem->pageShift();
// Setup the page access methods for the current bank
@@ -170,7 +170,7 @@
bank %= 32;
myCurrentBank = bank + 256;
- uInt32 offset = bank * 1024;
+ uInt32 offset = bank << 10;
uInt16 shift = mySystem->pageShift();
uInt32 address;
@@ -217,9 +217,9 @@
if(address < 0x0800)
{
if(myCurrentBank < 256)
- myImage[(address & 0x07FF) + myCurrentBank * 2048] = value;
+ myImage[(address & 0x07FF) + (myCurrentBank << 11)] = value;
else
- myRam[(address & 0x03FF) + (myCurrentBank - 256) * 1024] = value;
+ myRam[(address & 0x03FF) + ((myCurrentBank - 256) << 10)] = value;
}
else
myImage[(address & 0x07FF) + mySize - 2048] = value;
Modified: trunk/src/emucore/Cart3F.cxx
===================================================================
--- trunk/src/emucore/Cart3F.cxx 2009-05-21 22:39:32 UTC (rev 1738)
+++ trunk/src/emucore/Cart3F.cxx 2009-05-25 17:51:52 UTC (rev 1739)
@@ -90,7 +90,7 @@
if(address < 0x0800)
{
- return myImage[(address & 0x07FF) + myCurrentBank * 2048];
+ return myImage[(address & 0x07FF) + (myCurrentBank << 11)];
}
else
{
@@ -122,7 +122,7 @@
if(myBankLocked) return;
// Make sure the bank they're asking for is reasonable
- if((uInt32)bank * 2048 < mySize)
+ if(((uInt32)bank << 11) < mySize)
{
myCurrentBank = bank;
}
@@ -130,10 +130,10 @@
{
// Oops, the bank they're asking for isn't valid so let's wrap it
// around to a valid bank number
- myCurrentBank = bank % (mySize / 2048);
+ myCurrentBank = bank % (mySize >> 11);
}
- uInt32 offset = myCurrentBank * 2048;
+ uInt32 offset = myCurrentBank << 11;
uInt16 shift = mySystem->pageShift();
// Setup the page access methods for the current bank
@@ -158,7 +158,7 @@
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int Cartridge3F::bankCount()
{
- return mySize / 2048;
+ return mySize >> 11;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -167,7 +167,7 @@
address &= 0x0FFF;
if(address < 0x0800)
- myImage[(address & 0x07FF) + myCurrentBank * 2048] = value;
+ myImage[(address & 0x07FF) + (myCurrentBank << 11)] = value;
else
myImage[(address & 0x07FF) + mySize - 2048] = value;
Modified: trunk/src/emucore/Cart4K.cxx
===================================================================
--- trunk/src/emucore/Cart4K.cxx 2009-05-21 22:39:32 UTC (rev 1738)
+++ trunk/src/emucore/Cart4K.cxx 2009-05-25 17:51:52 UTC (rev 1739)
@@ -17,6 +17,7 @@
//============================================================================
#include <cassert>
+#include <cstring>
#include "System.hxx"
#include "Cart4K.hxx"
@@ -25,10 +26,7 @@
Cartridge4K::Cartridge4K(const uInt8* image)
{
// Copy the ROM image into my buffer
- for(uInt32 addr = 0; addr < 4096; ++addr)
- {
- myImage[addr] = image[addr];
- }
+ memcpy(myImage, image, 4096);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Modified: trunk/src/emucore/CartAR.cxx
===================================================================
--- trunk/src/emucore/CartAR.cxx 2009-05-21 22:39:32 UTC (rev 1738)
+++ trunk/src/emucore/CartAR.cxx 2009-05-25 17:51:52 UTC (rev 1739)
@@ -440,7 +440,7 @@
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartridgeAR::patch(uInt16 address, uInt8 value)
{
- // myImage[address & 0x0FFF] = value;
+ // TODO - add support for debugger
return false;
}
Modified: trunk/src/emucore/CartDPC.cxx
===================================================================
--- trunk/src/emucore/CartDPC.cxx 2009-05-21 22:39:32 UTC (rev 1738)
+++ trunk/src/emucore/CartDPC.cxx 2009-05-25 17:51:52 UTC (rev 1739)
@@ -17,6 +17,7 @@
//============================================================================
#include <cassert>
+#include <cstring>
#include <iostream>
#include "System.hxx"
@@ -25,30 +26,19 @@
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CartridgeDPC::CartridgeDPC(const uInt8* image, uInt32 size)
{
- uInt32 addr;
-
// Make a copy of the entire image as-is, for use by getImage()
// (this wastes 12K of RAM, should be controlled by a #ifdef)
- for(addr = 0; addr < size; ++addr)
- myImageCopy[addr] = image[addr];
+ memcpy(myImageCopy, image, size);
// Copy the program ROM image into my buffer
- for(addr = 0; addr < 8192; ++addr)
- {
- myProgramImage[addr] = image[addr];
- }
+ memcpy(myProgramImage, image, 8192);
// Copy the display ROM image into my buffer
- for(addr = 0; addr < 2048; ++addr)
- {
- myDisplayImage[addr] = image[8192 + addr];
- }
+ memcpy(myDisplayImage, image + 8192, 2048);
// Initialize the DPC data fetcher registers
...
[truncated message content] |