diff --git a/Changes.txt b/Changes.txt index 95db44feb..9d02a05ba 100644 --- a/Changes.txt +++ b/Changes.txt @@ -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,8 +41,6 @@ * 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) diff --git a/src/common/FrameBufferGL.cxx b/src/common/FrameBufferGL.cxx index b2dd5d901..0292bbe62 100644 --- a/src/common/FrameBufferGL.cxx +++ b/src/common/FrameBufferGL.cxx @@ -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,glBindTexture,(GLenum, GLuint)); 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 @@ bool FrameBufferGL::loadFuncs() 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 @@ bool FrameBufferGL::setVidMode(VideoMode& mode) 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 @@ cerr << "dimensions: " << (fullScreen() ? "(full)" : "") << endl // 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 @@ void FrameBufferGL::scanline(uInt32 row, uInt8* data) const // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 @@ FBSurfaceGL::FBSurfaceGL(FrameBufferGL& buffer, 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 @@ FBSurfaceGL::~FBSurfaceGL() 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 @@ void FBSurfaceGL::drawChar(const GUI::Font* font, uInt8 chr, 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 @@ void FBSurfaceGL::drawChar(const GUI::Font* font, uInt8 chr, } 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 @@ void FBSurfaceGL::drawChar(const GUI::Font* font, uInt8 chr, { 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 @@ void FBSurfaceGL::update() { 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); + + // 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; + } + } - p_glTexCoord2f(myTexCoord[0], myTexCoord[3]); - p_glVertex2i(myXOrig, myYOrig + myHeight); - p_glEnd(); mySurfaceIsDirty = false; // Let postFrameUpdate() know that a change has been made @@ -733,10 +1189,223 @@ void FBSurfaceGL::update() } } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +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 @@ void FBSurfaceGL::reload() // 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 @@ void FBSurfaceGL::reload() 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,10 +1514,130 @@ void FBSurfaceGL::setFilter(const string& name) 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; diff --git a/src/common/FrameBufferGL.hxx b/src/common/FrameBufferGL.hxx index d5b4093a3..2fdfc10ec 100644 --- a/src/common/FrameBufferGL.hxx +++ b/src/common/FrameBufferGL.hxx @@ -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 @@ class FrameBufferGL : public FrameBuffer 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 @@ class FrameBufferGL : public FrameBuffer // 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 @@ class FBSurfaceGL : public FBSurface 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 @@ class FBSurfaceGL : public FBSurface 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 @@ class FBSurfaceGL : public FBSurface 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 diff --git a/src/common/FrameBufferSoft.hxx b/src/common/FrameBufferSoft.hxx index 32354649d..2d1bbf02a 100644 --- a/src/common/FrameBufferSoft.hxx +++ b/src/common/FrameBufferSoft.hxx @@ -142,7 +142,6 @@ class FrameBufferSoft : public FrameBuffer string about() const; private: - int myZoomLevel; int myBytesPerPixel; int myBaseOffset; int myPitch; diff --git a/src/common/GLShaderProgs.hxx b/src/common/GLShaderProgs.hxx new file mode 100644 index 000000000..560a9fa53 --- /dev/null +++ b/src/common/GLShaderProgs.hxx @@ -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 diff --git a/src/emucore/Cart0840.cxx b/src/emucore/Cart0840.cxx index 97e979bfa..46c1c5c26 100644 --- a/src/emucore/Cart0840.cxx +++ b/src/emucore/Cart0840.cxx @@ -17,6 +17,7 @@ //============================================================================ #include +#include #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 @@ void Cartridge0840::bank(uInt16 bank) // 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 @@ int Cartridge0840::bankCount() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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; } diff --git a/src/emucore/Cart2K.cxx b/src/emucore/Cart2K.cxx index 46ac8ae94..725d9bcd5 100644 --- a/src/emucore/Cart2K.cxx +++ b/src/emucore/Cart2K.cxx @@ -17,6 +17,7 @@ //============================================================================ #include +#include #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); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/emucore/Cart3E.cxx b/src/emucore/Cart3E.cxx index 15a0e11d6..0a3ea2204 100644 --- a/src/emucore/Cart3E.cxx +++ b/src/emucore/Cart3E.cxx @@ -97,9 +97,9 @@ uInt8 Cartridge3E::peek(uInt16 address) 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 @@ void Cartridge3E::bank(uInt16 bank) 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 @@ void Cartridge3E::bank(uInt16 bank) { // 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 @@ void Cartridge3E::bank(uInt16 bank) bank %= 32; myCurrentBank = bank + 256; - uInt32 offset = bank * 1024; + uInt32 offset = bank << 10; uInt16 shift = mySystem->pageShift(); uInt32 address; @@ -217,9 +217,9 @@ bool Cartridge3E::patch(uInt16 address, uInt8 value) 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; diff --git a/src/emucore/Cart3F.cxx b/src/emucore/Cart3F.cxx index a32ef1d78..44c681596 100644 --- a/src/emucore/Cart3F.cxx +++ b/src/emucore/Cart3F.cxx @@ -90,7 +90,7 @@ uInt8 Cartridge3F::peek(uInt16 address) if(address < 0x0800) { - return myImage[(address & 0x07FF) + myCurrentBank * 2048]; + return myImage[(address & 0x07FF) + (myCurrentBank << 11)]; } else { @@ -122,7 +122,7 @@ void Cartridge3F::bank(uInt16 bank) 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 @@ void Cartridge3F::bank(uInt16 bank) { // 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::bank() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - int Cartridge3F::bankCount() { - return mySize / 2048; + return mySize >> 11; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -167,7 +167,7 @@ bool Cartridge3F::patch(uInt16 address, uInt8 value) address &= 0x0FFF; if(address < 0x0800) - myImage[(address & 0x07FF) + myCurrentBank * 2048] = value; + myImage[(address & 0x07FF) + (myCurrentBank << 11)] = value; else myImage[(address & 0x07FF) + mySize - 2048] = value; diff --git a/src/emucore/Cart4K.cxx b/src/emucore/Cart4K.cxx index 7fb516f4b..6b82e1d11 100644 --- a/src/emucore/Cart4K.cxx +++ b/src/emucore/Cart4K.cxx @@ -17,6 +17,7 @@ //============================================================================ #include +#include #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); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/emucore/CartAR.cxx b/src/emucore/CartAR.cxx index 20017d44e..9698c87a2 100644 --- a/src/emucore/CartAR.cxx +++ b/src/emucore/CartAR.cxx @@ -440,7 +440,7 @@ int CartridgeAR::bankCount() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeAR::patch(uInt16 address, uInt8 value) { - // myImage[address & 0x0FFF] = value; + // TODO - add support for debugger return false; } diff --git a/src/emucore/CartDPC.cxx b/src/emucore/CartDPC.cxx index a99390f48..9be3d273f 100644 --- a/src/emucore/CartDPC.cxx +++ b/src/emucore/CartDPC.cxx @@ -17,6 +17,7 @@ //============================================================================ #include +#include #include #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 for(uInt16 i = 0; i < 8; ++i) - { myTops[i] = myBottoms[i] = myCounters[i] = myFlags[i] = 0; - } // None of the data fetchers are in music mode myMusicMode[0] = myMusicMode[1] = myMusicMode[2] = false; @@ -460,8 +450,7 @@ int CartridgeDPC::bankCount() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeDPC::patch(uInt16 address, uInt8 value) { - address = address & 0x0FFF; - myProgramImage[myCurrentBank * 4096 + address] = value; + myProgramImage[(myCurrentBank << 12) + (address & 0x0FFF)] = value; return true; } diff --git a/src/emucore/CartE0.cxx b/src/emucore/CartE0.cxx index 18800791c..e67ea735e 100644 --- a/src/emucore/CartE0.cxx +++ b/src/emucore/CartE0.cxx @@ -211,7 +211,7 @@ int CartridgeE0::bankCount() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeE0::patch(uInt16 address, uInt8 value) { - address = address & 0x0FFF; + address &= 0x0FFF; myImage[(myCurrentSlice[address >> 10] << 10) + (address & 0x03FF)] = value; return true; } diff --git a/src/emucore/CartE7.cxx b/src/emucore/CartE7.cxx index 225727881..d5a35f4ca 100644 --- a/src/emucore/CartE7.cxx +++ b/src/emucore/CartE7.cxx @@ -222,7 +222,6 @@ bool CartridgeE7::patch(uInt16 address, uInt8 value) { address = address & 0x0FFF; myImage[(myCurrentSlice[address >> 11] << 11) + (address & 0x07FF)] = value; - bank(myCurrentSlice[0]); return true; } diff --git a/src/emucore/CartEF.cxx b/src/emucore/CartEF.cxx index c4431dfb0..007448c53 100644 --- a/src/emucore/CartEF.cxx +++ b/src/emucore/CartEF.cxx @@ -74,7 +74,7 @@ uInt8 CartridgeEF::peek(uInt16 address) if((address >= 0x0FE0) && (address <= 0x0FEF)) bank(address - 0x0FE0); - return myImage[myCurrentBank * 4096 + address]; + return myImage[(myCurrentBank << 12) + address]; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -94,7 +94,7 @@ void CartridgeEF::bank(uInt16 bank) // Remember what bank we're in myCurrentBank = bank; - uInt16 offset = myCurrentBank * 4096; + uInt16 offset = myCurrentBank << 12; uInt16 shift = mySystem->pageShift(); uInt16 mask = mySystem->pageMask(); @@ -127,8 +127,7 @@ int CartridgeEF::bankCount() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeEF::patch(uInt16 address, uInt8 value) { - address = address & 0x0FFF; - myImage[myCurrentBank * 4096 + address] = value; + myImage[(myCurrentBank << 12) + (address & 0x0FFF)] = value; return true; } diff --git a/src/emucore/CartEFSC.cxx b/src/emucore/CartEFSC.cxx index afd33ac68..1752941d4 100644 --- a/src/emucore/CartEFSC.cxx +++ b/src/emucore/CartEFSC.cxx @@ -98,10 +98,16 @@ uInt8 CartridgeEFSC::peek(uInt16 address) if((address >= 0x0FE0) && (address <= 0x0FEF)) bank(address - 0x0FE0); - // NOTE: This does not handle accessing RAM, however, this function - // should never be called for RAM because of the way page accessing - // has been setup - return myImage[myCurrentBank * 4096 + address]; + // Reading from the write port triggers an unwanted write + // The value written to RAM is somewhat undefined, so we use 0 + // Thanks to Kroko of AtariAge for this advice and code idea + if(address < 0x0080) // Write port is at 0xF000 - 0xF080 (128 bytes) + { + if(myBankLocked) return 0; + else return myRAM[address] = 0; + } + else + return myImage[(myCurrentBank << 12) + address]; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -125,7 +131,7 @@ void CartridgeEFSC::bank(uInt16 bank) // Remember what bank we're in myCurrentBank = bank; - uInt16 offset = myCurrentBank * 4096; + uInt16 offset = myCurrentBank << 12; uInt16 shift = mySystem->pageShift(); uInt16 mask = mySystem->pageMask(); @@ -158,8 +164,7 @@ int CartridgeEFSC::bankCount() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeEFSC::patch(uInt16 address, uInt8 value) { - address = address & 0x0FFF; - myImage[myCurrentBank * 4096 + address] = value; + myImage[(myCurrentBank << 12) + (address & 0x0FFF)] = value; return true; } diff --git a/src/emucore/CartF4.cxx b/src/emucore/CartF4.cxx index a39751666..b214b4abd 100644 --- a/src/emucore/CartF4.cxx +++ b/src/emucore/CartF4.cxx @@ -17,6 +17,7 @@ //============================================================================ #include +#include #include "Random.hxx" #include "System.hxx" @@ -26,10 +27,7 @@ CartridgeF4::CartridgeF4(const uInt8* image) { // Copy the ROM image into my buffer - for(uInt32 addr = 0; addr < 32768; ++addr) - { - myImage[addr] = image[addr]; - } + memcpy(myImage, image, 32768); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -79,7 +77,7 @@ uInt8 CartridgeF4::peek(uInt16 address) bank(address - 0x0FF4); } - return myImage[myCurrentBank * 4096 + address]; + return myImage[(myCurrentBank << 12) + address]; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -101,7 +99,7 @@ void CartridgeF4::bank(uInt16 bank) // Remember what bank we're in myCurrentBank = bank; - uInt16 offset = myCurrentBank * 4096; + uInt16 offset = myCurrentBank << 12; uInt16 shift = mySystem->pageShift(); uInt16 mask = mySystem->pageMask(); @@ -134,8 +132,7 @@ int CartridgeF4::bankCount() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeF4::patch(uInt16 address, uInt8 value) { - address = address & 0x0FFF; - myImage[myCurrentBank * 4096 + address] = value; + myImage[(myCurrentBank << 12) + (address & 0x0FFF)] = value; return true; } diff --git a/src/emucore/CartF4SC.cxx b/src/emucore/CartF4SC.cxx index 51d14559c..b4508ac93 100644 --- a/src/emucore/CartF4SC.cxx +++ b/src/emucore/CartF4SC.cxx @@ -96,14 +96,24 @@ uInt8 CartridgeF4SC::peek(uInt16 address) // Switch banks if necessary if((address >= 0x0FF4) && (address <= 0x0FFB)) - { bank(address - 0x0FF4); - } + + // Reading from the write port triggers an unwanted write + // The value written to RAM is somewhat undefined, so we use 0 + // Thanks to Kroko of AtariAge for this advice and code idea + if(address < 0x0080) // Write port is at 0xF000 - 0xF080 (128 bytes) + { + if(myBankLocked) return 0; + else return myRAM[address] = 0; + } + else + return myImage[(myCurrentBank << 12) + address]; + // NOTE: This does not handle accessing RAM, however, this function // should never be called for RAM because of the way page accessing // has been setup - return myImage[myCurrentBank * 4096 + address]; + return myImage[(myCurrentBank << 12) + address]; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -113,9 +123,7 @@ void CartridgeF4SC::poke(uInt16 address, uInt8) // Switch banks if necessary if((address >= 0x0FF4) && (address <= 0x0FFB)) - { bank(address - 0x0FF4); - } // NOTE: This does not handle accessing RAM, however, this function // should never be called for RAM because of the way page accessing @@ -129,7 +137,7 @@ void CartridgeF4SC::bank(uInt16 bank) // Remember what bank we're in myCurrentBank = bank; - uInt16 offset = myCurrentBank * 4096; + uInt16 offset = myCurrentBank << 12; uInt16 shift = mySystem->pageShift(); uInt16 mask = mySystem->pageMask(); @@ -162,8 +170,7 @@ int CartridgeF4SC::bankCount() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeF4SC::patch(uInt16 address, uInt8 value) { - address = address & 0x0FFF; - myImage[myCurrentBank * 4096 + address] = value; + myImage[(myCurrentBank << 12) + (address & 0x0FFF)] = value; return true; } diff --git a/src/emucore/CartF6.cxx b/src/emucore/CartF6.cxx index 5c9e633ea..4509188b7 100644 --- a/src/emucore/CartF6.cxx +++ b/src/emucore/CartF6.cxx @@ -17,6 +17,7 @@ //============================================================================ #include +#include #include "System.hxx" #include "Serializer.hxx" @@ -27,10 +28,7 @@ CartridgeF6::CartridgeF6(const uInt8* image) { // Copy the ROM image into my buffer - for(uInt32 addr = 0; addr < 16384; ++addr) - { - myImage[addr] = image[addr]; - } + memcpy(myImage, image, 16384); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -101,7 +99,7 @@ uInt8 CartridgeF6::peek(uInt16 address) break; } - return myImage[myCurrentBank * 4096 + address]; + return myImage[(myCurrentBank << 12) + address]; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -144,7 +142,7 @@ void CartridgeF6::bank(uInt16 bank) // Remember what bank we're in myCurrentBank = bank; - uInt16 offset = myCurrentBank * 4096; + uInt16 offset = myCurrentBank << 12; uInt16 shift = mySystem->pageShift(); uInt16 mask = mySystem->pageMask(); @@ -177,8 +175,7 @@ int CartridgeF6::bankCount() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeF6::patch(uInt16 address, uInt8 value) { - address = address & 0x0FFF; - myImage[myCurrentBank * 4096 + address] = value; + myImage[(myCurrentBank << 12) + (address & 0x0FFF)] = value; return true; } diff --git a/src/emucore/CartF6SC.cxx b/src/emucore/CartF6SC.cxx index 123b96e79..dbc579191 100644 --- a/src/emucore/CartF6SC.cxx +++ b/src/emucore/CartF6SC.cxx @@ -121,10 +121,16 @@ uInt8 CartridgeF6SC::peek(uInt16 address) break; } - // NOTE: This does not handle accessing RAM, however, this function - // should never be called for RAM because of the way page accessing - // has been setup - return myImage[myCurrentBank * 4096 + address]; + // Reading from the write port triggers an unwanted write + // The value written to RAM is somewhat undefined, so we use 0 + // Thanks to Kroko of AtariAge for this advice and code idea + if(address < 0x0080) // Write port is at 0xF000 - 0xF080 (128 bytes) + { + if(myBankLocked) return 0; + else return myRAM[address] = 0; + } + else + return myImage[(myCurrentBank << 12) + address]; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -171,7 +177,7 @@ void CartridgeF6SC::bank(uInt16 bank) // Remember what bank we're in myCurrentBank = bank; - uInt16 offset = myCurrentBank * 4096; + uInt16 offset = myCurrentBank << 12; uInt16 shift = mySystem->pageShift(); uInt16 mask = mySystem->pageMask(); @@ -204,8 +210,7 @@ int CartridgeF6SC::bankCount() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeF6SC::patch(uInt16 address, uInt8 value) { - address = address & 0x0FFF; - myImage[myCurrentBank * 4096 + address] = value; + myImage[(myCurrentBank << 12) + (address & 0x0FFF)] = value; return true; } diff --git a/src/emucore/CartF8.cxx b/src/emucore/CartF8.cxx index 148f2cb48..20c824ebb 100644 --- a/src/emucore/CartF8.cxx +++ b/src/emucore/CartF8.cxx @@ -91,7 +91,7 @@ uInt8 CartridgeF8::peek(uInt16 address) break; } - return myImage[myCurrentBank * 4096 + address]; + return myImage[(myCurrentBank << 12) + address]; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -124,7 +124,7 @@ void CartridgeF8::bank(uInt16 bank) // Remember what bank we're in myCurrentBank = bank; - uInt16 offset = myCurrentBank * 4096; + uInt16 offset = myCurrentBank << 12; uInt16 shift = mySystem->pageShift(); uInt16 mask = mySystem->pageMask(); @@ -157,9 +157,7 @@ int CartridgeF8::bankCount() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeF8::patch(uInt16 address, uInt8 value) { - address &= 0xfff; - myImage[myCurrentBank * 4096 + address] = value; - bank(myCurrentBank); + myImage[(myCurrentBank << 12) + (address & 0x0FFF)] = value; return true; } diff --git a/src/emucore/CartF8SC.cxx b/src/emucore/CartF8SC.cxx index e9465005c..742fb1caf 100644 --- a/src/emucore/CartF8SC.cxx +++ b/src/emucore/CartF8SC.cxx @@ -111,10 +111,16 @@ uInt8 CartridgeF8SC::peek(uInt16 address) break; } - // NOTE: This does not handle accessing RAM, however, this function - // should never be called for RAM because of the way page accessing - // has been setup - return myImage[myCurrentBank * 4096 + address]; + // Reading from the write port triggers an unwanted write + // The value written to RAM is somewhat undefined, so we use 0 + // Thanks to Kroko of AtariAge for this advice and code idea + if(address < 0x0080) // Write port is at 0xF000 - 0xF080 (128 bytes) + { + if(myBankLocked) return 0; + else return myRAM[address] = 0; + } + else + return myImage[(myCurrentBank << 12) + address]; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -184,8 +190,7 @@ int CartridgeF8SC::bankCount() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeF8SC::patch(uInt16 address, uInt8 value) { - address = address & 0x0FFF; - myImage[myCurrentBank * 4096 + address] = value; + myImage[(myCurrentBank << 12) + (address & 0x0FFF)] = value; return true; } diff --git a/src/emucore/CartFASC.cxx b/src/emucore/CartFASC.cxx index 8fbc70698..9844a7d92 100644 --- a/src/emucore/CartFASC.cxx +++ b/src/emucore/CartFASC.cxx @@ -117,14 +117,16 @@ uInt8 CartridgeFASC::peek(uInt16 address) } // Reading from the write port triggers an unwanted write + // The value written to RAM is somewhat undefined, so we use 0 // Thanks to Kroko of AtariAge for this advice and code idea if(address < 0x0100) // Write port is at 0xF000 - 0xF100 (256 bytes) { - return myRAM[address & 0x00FF] = 0; + if(myBankLocked) return 0; + else return myRAM[address] = 0; } else { - return myImage[myCurrentBank * 4096 + address]; + return myImage[(myCurrentBank << 12) + address]; } } @@ -167,7 +169,7 @@ void CartridgeFASC::bank(uInt16 bank) // Remember what bank we're in myCurrentBank = bank; - uInt16 offset = myCurrentBank * 4096; + uInt16 offset = myCurrentBank << 12; uInt16 shift = mySystem->pageShift(); uInt16 mask = mySystem->pageMask(); @@ -200,8 +202,7 @@ int CartridgeFASC::bankCount() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeFASC::patch(uInt16 address, uInt8 value) { - address = address & 0x0FFF; - myImage[myCurrentBank * 4096 + address] = value; + myImage[(myCurrentBank << 12) + (address & 0x0FFF)] = value; return true; } diff --git a/src/emucore/CartFE.cxx b/src/emucore/CartFE.cxx index 19f6d70b4..f94b77fc4 100644 --- a/src/emucore/CartFE.cxx +++ b/src/emucore/CartFE.cxx @@ -17,6 +17,7 @@ //============================================================================ #include +#include #include "System.hxx" #include "CartFE.hxx" @@ -25,10 +26,7 @@ CartridgeFE::CartridgeFE(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); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/emucore/CartMB.cxx b/src/emucore/CartMB.cxx index d624a6bae..60cac656e 100644 --- a/src/emucore/CartMB.cxx +++ b/src/emucore/CartMB.cxx @@ -17,6 +17,7 @@ //============================================================================ #include +#include #include "System.hxx" #include "CartMB.hxx" @@ -25,10 +26,7 @@ CartridgeMB::CartridgeMB(const uInt8* image) { // Copy the ROM image into my buffer - for(uInt32 addr = 0; addr < 65536; ++addr) - { - myImage[addr] = image[addr]; - } + memcpy(myImage, image, 65536); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -75,9 +73,10 @@ uInt8 CartridgeMB::peek(uInt16 address) address &= 0x0FFF; // Switch to next bank - if(address == 0x0FF0) incbank(); + if(address == 0x0FF0) + incbank(); - return myImage[myCurrentBank * 4096 + address]; + return myImage[(myCurrentBank << 12) + address]; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -86,7 +85,8 @@ void CartridgeMB::poke(uInt16 address, uInt8) address &= 0x0FFF; // Switch to next bank - if(address == 0x0FF0) incbank(); + if(address == 0x0FF0) + incbank(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -97,7 +97,7 @@ void CartridgeMB::incbank() // Remember what bank we're in myCurrentBank ++; myCurrentBank &= 0x0F; - uInt16 offset = myCurrentBank * 4096; + uInt16 offset = myCurrentBank << 12; uInt16 shift = mySystem->pageShift(); uInt16 mask = mySystem->pageMask(); @@ -120,7 +120,7 @@ void CartridgeMB::bank(uInt16 bank) { if(myBankLocked) return; - myCurrentBank = (bank - 1); + myCurrentBank = bank - 1; incbank(); } @@ -139,8 +139,7 @@ int CartridgeMB::bankCount() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeMB::patch(uInt16 address, uInt8 value) { - address = address & 0x0FFF; - myImage[myCurrentBank * 4096 + address] = value; + myImage[(myCurrentBank << 12) + (address & 0x0FFF)] = value; return true; } diff --git a/src/emucore/CartMC.cxx b/src/emucore/CartMC.cxx index 1e76678f3..4c132c6f2 100644 --- a/src/emucore/CartMC.cxx +++ b/src/emucore/CartMC.cxx @@ -128,7 +128,7 @@ uInt8 CartridgeMC::peek(uInt16 address) if(block & 0x80) { // ROM access - return myImage[(uInt32)(block & 0x7F) * 1024 + (address & 0x03FF)]; + return myImage[(uInt32)((block & 0x7F) << 10) + (address & 0x03FF)]; } else { @@ -136,12 +136,12 @@ uInt8 CartridgeMC::peek(uInt16 address) if(address & 0x0200) { // Reading from the read port of the RAM block - return myRAM[(uInt32)(block & 0x3F) * 512 + (address & 0x01FF)]; + return myRAM[(uInt32)((block & 0x3F) << 9) + (address & 0x01FF)]; } else { // Oops, reading from the write port of the RAM block! - myRAM[(uInt32)(block & 0x3F) * 512 + (address & 0x01FF)] = 0; + myRAM[(uInt32)((block & 0x3F) << 9) + (address & 0x01FF)] = 0; return 0; } } @@ -188,7 +188,7 @@ void CartridgeMC::poke(uInt16 address, uInt8 value) if(!(block & 0x80) && !(address & 0x0200)) { // Handle the write to RAM - myRAM[(uInt32)(block & 0x3F) * 512 + (address & 0x01FF)] = value; + myRAM[(uInt32)((block & 0x3F) << 9) + (address & 0x01FF)] = value; } } } diff --git a/src/emucore/CartSB.cxx b/src/emucore/CartSB.cxx index c4d907199..5a44d820d 100644 --- a/src/emucore/CartSB.cxx +++ b/src/emucore/CartSB.cxx @@ -17,6 +17,7 @@ //============================================================================ #include +#include #include "System.hxx" #include "CartSB.hxx" @@ -24,14 +25,13 @@ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CartridgeSB::CartridgeSB(const uInt8* image, uInt32 size) : mySize(size), - myLastBank((mySize>>12)-1) + myLastBank((mySize >> 12) - 1) { // Allocate array for the ROM image myImage = new uInt8[mySize]; // Copy the ROM image into my buffer - for(uInt32 addr = 0; addr < mySize; ++addr) - myImage[addr] = image[addr]; + memcpy(myImage, image, mySize); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -85,7 +85,7 @@ void CartridgeSB::install(System& system) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt8 CartridgeSB::peek(uInt16 address) { - address = address & (0x17FF+(mySize>>12)); + address = address & (0x17FF + (mySize >> 12)); // Switch banks if necessary if ((address & 0x1800) == 0x0800) @@ -105,7 +105,7 @@ uInt8 CartridgeSB::peek(uInt16 address) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void CartridgeSB::poke(uInt16 address, uInt8 value) { - address = address & (0x17FF+(mySize>>12)); + address = address & (0x17FF + (mySize >> 12)); // Switch banks if necessary if((address & 0x1800) == 0x0800) @@ -127,7 +127,7 @@ void CartridgeSB::bank(uInt16 bank) // Remember what bank we're in myCurrentBank = bank; - uInt32 offset = myCurrentBank * 4096; + uInt32 offset = myCurrentBank << 12; uInt16 shift = mySystem->pageShift(); // Setup the page access methods for the current bank @@ -152,15 +152,13 @@ int CartridgeSB::bank() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - int CartridgeSB::bankCount() { - return mySize>>12; + return mySize >> 12; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeSB::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; } diff --git a/src/emucore/CartUA.cxx b/src/emucore/CartUA.cxx index a008e85a0..30c35cddc 100644 --- a/src/emucore/CartUA.cxx +++ b/src/emucore/CartUA.cxx @@ -70,6 +70,8 @@ void CartridgeUA::install(System& system) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt8 CartridgeUA::peek(uInt16 address) { +// address &= 0x1FFF; TODO - is this needed here? + // Switch banks if necessary switch(address) { @@ -132,9 +134,8 @@ void CartridgeUA::bank(uInt16 bank) // Remember what bank we're in myCurrentBank = bank; - uInt16 offset = myCurrentBank * 4096; + uInt16 offset = myCurrentBank << 12; uInt16 shift = mySystem->pageShift(); -// uInt16 mask = mySystem->pageMask(); // Setup the page access methods for the current bank System::PageAccess access; @@ -164,8 +165,7 @@ int CartridgeUA::bankCount() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeUA::patch(uInt16 address, uInt8 value) { - address &= 0x0fff; - myImage[myCurrentBank * 4096] = value; + myImage[(myCurrentBank << 12) + (address & 0x0FFF)] = value; return true; } diff --git a/src/emucore/CartX07.cxx b/src/emucore/CartX07.cxx index f0c202f74..cc4cfc472 100644 --- a/src/emucore/CartX07.cxx +++ b/src/emucore/CartX07.cxx @@ -13,10 +13,11 @@ // See the file "license" for information on usage and redistribution of // this file, and for a DISCLAIMER OF ALL WARRANTIES. // -// $Id: +// $Id$ //============================================================================ #include +#include #include "System.hxx" #include "M6532.hxx" @@ -27,10 +28,7 @@ CartridgeX07::CartridgeX07(const uInt8* image) { // Copy the ROM image into my buffer - for(uInt32 addr = 0; addr < 65536; ++addr) - { - myImage[addr] = image[addr]; - } + memcpy(myImage, image, 65536); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -84,7 +82,8 @@ uInt8 CartridgeX07::peek(uInt16 address) value = mySystem->tia().peek(address); // Switch banks if necessary - if((address & 0x180f) == 0x080d) bank((address & 0xf0) >> 4); + if((address & 0x180f) == 0x080d) + bank((address & 0xf0) >> 4); else if((address & 0x1880) == 0) { if((myCurrentBank & 0xe) == 0xe) @@ -105,7 +104,8 @@ void CartridgeX07::poke(uInt16 address, uInt8 value) mySystem->tia().poke(address, value); // Switch banks if necessary - if((address & 0x180f) == 0x080d) bank((address & 0xf0) >> 4); + if((address & 0x180f) == 0x080d) + bank((address & 0xf0) >> 4); else if((address & 0x1880) == 0) { if((myCurrentBank & 0xe) == 0xe) @@ -120,7 +120,7 @@ void CartridgeX07::bank(uInt16 bank) // Remember what bank we're in myCurrentBank = (bank & 0x0f); - uInt32 offset = myCurrentBank * 4096; + uInt32 offset = myCurrentBank << 12; uInt16 shift = mySystem->pageShift(); // Setup the page access methods for the current bank @@ -151,9 +151,7 @@ int CartridgeX07::bankCount() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeX07::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; } diff --git a/src/emucore/CartX07.hxx b/src/emucore/CartX07.hxx index e632152db..ce41bb526 100644 --- a/src/emucore/CartX07.hxx +++ b/src/emucore/CartX07.hxx @@ -13,7 +13,7 @@ // See the file "license" for information on usage and redistribution of // this file, and for a DISCLAIMER OF ALL WARRANTIES. // -// $Id: +// $Id$ //============================================================================ #ifndef CARTRIDGEX07_HXX @@ -39,7 +39,7 @@ class System; Note that the latter will hit on almost any TIA access. @author Eckhard Stolberg - @version $Id: + @version $Id$ */ class CartridgeX07 : public Cartridge { diff --git a/src/emucore/FrameBuffer.hxx b/src/emucore/FrameBuffer.hxx index 0fb4ba9a2..4bbdf6231 100644 --- a/src/emucore/FrameBuffer.hxx +++ b/src/emucore/FrameBuffer.hxx @@ -270,6 +270,11 @@ class FrameBuffer */ void stateChanged(EventHandler::State state); + /** + Get the zoom level. + */ + uInt32 getZoomLevel() { return myZoomLevel; } + ////////////////////////////////////////////////////////////////////// // The following methods are system-specific and must be implemented // in derived classes. @@ -436,6 +441,9 @@ class FrameBuffer // Names of the TIA filters that can be used for this framebuffer StringMap myTIAFilters; + // Holds the zoom level being used + uInt32 myZoomLevel; + private: /** Set the icon for the main SDL window. diff --git a/src/emucore/Settings.cxx b/src/emucore/Settings.cxx index 7e2ffa977..85b9bde44 100644 --- a/src/emucore/Settings.cxx +++ b/src/emucore/Settings.cxx @@ -58,6 +58,13 @@ Settings::Settings(OSystem* osystem) setInternal("colorloss", "false"); setInternal("timing", "sleep"); + // TV filter options + setInternal("tv_tex", "off"); + setInternal("tv_bleed", "off"); + setInternal("tv_noise", "off"); +// setInternal("tv_curve", "false"); // not yet implemented + setInternal("tv_phos", "false"); + // Sound options setInternal("sound", "true"); setInternal("fragsize", "512"); @@ -218,50 +225,37 @@ void Settings::validate() int i; s = getString("video"); - if(s != "soft" && s != "gl") - setInternal("video", "soft"); + if(s != "soft" && s != "gl") setInternal("video", "soft"); s = getString("timing"); - if(s != "sleep" && s != "busy") - setInternal("timing", "sleep"); + if(s != "sleep" && s != "busy") setInternal("timing", "sleep"); #ifdef DISPLAY_OPENGL s = getString("gl_filter"); - if(s != "linear" && s != "nearest") - setInternal("gl_filter", "nearest"); + if(s != "linear" && s != "nearest") setInternal("gl_filter", "nearest"); i = getInt("gl_aspectn"); - if(i < 80 || i > 120) - setInternal("gl_aspectn", "100"); - + if(i < 80 || i > 120) setInternal("gl_aspectn", "100"); i = getInt("gl_aspectp"); - if(i < 80 || i > 120) - setInternal("gl_aspectp", "100"); + if(i < 80 || i > 120) setInternal("gl_aspectp", "100"); #endif #ifdef SOUND_SUPPORT i = getInt("volume"); - if(i < 0 || i > 100) - setInternal("volume", "100"); + if(i < 0 || i > 100) setInternal("volume", "100"); i = getInt("freq"); - if(i < 0 || i > 48000) - setInternal("freq", "31400"); + if(i < 0 || i > 48000) setInternal("freq", "31400"); i = getInt("tiafreq"); - if(i < 0 || i > 48000) - setInternal("tiafreq", "31400"); + if(i < 0 || i > 48000) setInternal("tiafreq", "31400"); #endif i = getInt("joydeadzone"); - if(i < 0) - setInternal("joydeadzone", "0"); - else if(i > 29) - setInternal("joydeadzone", "29"); + if(i < 0) setInternal("joydeadzone", "0"); + else if(i > 29) setInternal("joydeadzone", "29"); i = getInt("pspeed"); - if(i < 1) - setInternal("pspeed", "1"); - else if(i > 15) - setInternal("pspeed", "15"); + if(i < 1) setInternal("pspeed", "1"); + else if(i > 15) setInternal("pspeed", "15"); s = getString("palette"); if(s != "standard" && s != "z26" && s != "user") @@ -272,10 +266,20 @@ void Settings::validate() setInternal("launcherfont", "medium"); i = getInt("romviewer"); - if(i < 0) - setInternal("romviewer", "0"); - else if(i > 2) - setInternal("romviewer", "2"); + if(i < 0) setInternal("romviewer", "0"); + else if(i > 2) setInternal("romviewer", "2"); + + s = getString("tv_tex"); + if(s != "normal" && s != "stag") + setInternal("tv_tex", "off"); + + s = getString("tv_bleed"); + if(s != "low" && s != "medium" && s != "high") + setInternal("tv_bleed", "off"); + + s = getString("tv_noise"); + if(s != "low" && s != "medium" && s != "high") + setInternal("tv_noise", "off"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -306,6 +310,18 @@ void Settings::usage() << " -gl_vsync <1|0> Enable synchronize to vertical blank interrupt\n" << " -gl_texrect <1|0> Enable GL_TEXTURE_RECTANGLE extension\n" // << " -gl_accel <1|0> Enable SDL_GL_ACCELERATED_VISUAL\n" + << " -tv_tex TV texturing, type is one of the following:\n" + << " normal TODO - document\n" + << " stag TODO - document\n" + << " -tv_bleed TV color bleed, type is one of the following:\n" + << " low TODO - document\n" + << " medium TODO - document\n" + << " high TODO - document\n" + << " -tv_noise TV noise, type is one of the following:\n" + << " low TODO - document\n" + << " medium TODO - document\n" + << " high TODO - document\n" + << " -tv_phos <1|0> TV phosphor burn-off\n" << endl #endif << " -tia_filter Use the specified filter in emulation mode\n" @@ -339,7 +355,7 @@ void Settings::usage() << " -audiofirst <1|0> Initial audio before video (required for some ATI video cards)\n" << " -ssdir The directory to save snapshot files to\n" << " -sssingle <1|0> Generate single snapshot instead of many\n" - << " -ss1x <1|0> Generate TIA snapshot in 1x mode (ignore scaling)\n" + << " -ss1x <1|0> Generate TIA snapshot in 1x mode (ignore scaling/effects)\n" << endl << " -rominfo Display detailed information for the given ROM\n" << " -listrominfo Display contents of stella.pro, one line per ROM entry\n" @@ -347,7 +363,7 @@ void Settings::usage() << " -launcherfont \n" << " -launcherexts Show ROM info viewer at given zoom level in ROM launcher (0 for off)\n" << " -uipalette <1|2> Used the specified palette for UI elements\n" diff --git a/src/emucore/m6502/src/M6502.cxx b/src/emucore/m6502/src/M6502.cxx index de0ec088b..784f19036 100644 --- a/src/emucore/m6502/src/M6502.cxx +++ b/src/emucore/m6502/src/M6502.cxx @@ -55,10 +55,11 @@ M6502::M6502(uInt32 systemCyclesPerProcessorCycle) mySystemCyclesPerProcessorCycle; } - +#ifdef DEBUG_OUTPUT debugStream << "( Fm Ln Cyc Clk) ( P0 P1 M0 M1 BL) " << "flags A X Y SP Code Disasm" << endl << endl; +#endif } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/gui/VideoDialog.cxx b/src/gui/VideoDialog.cxx index f7ba1288a..cb88617d1 100644 --- a/src/gui/VideoDialog.cxx +++ b/src/gui/VideoDialog.cxx @@ -33,6 +33,7 @@ #include "Settings.hxx" #include "StringList.hxx" #include "Widget.hxx" +#include "TabWidget.hxx" #include "VideoDialog.hxx" @@ -46,7 +47,7 @@ VideoDialog::VideoDialog(OSystem* osystem, DialogContainer* parent, fontHeight = font.getFontHeight(), buttonWidth = font.getStringWidth("Defaults") + 20, buttonHeight = font.getLineHeight() + 4; - int xpos, ypos; + int xpos, ypos, tabID; int lwidth = font.getStringWidth("GL Aspect (P): "), pwidth = font.getStringWidth("1920x1200"), fwidth = font.getStringWidth("Renderer: "); @@ -54,15 +55,24 @@ VideoDialog::VideoDialog(OSystem* osystem, DialogContainer* parent, StringMap items; // Set real dimensions - _w = 48 * fontWidth + 10; - _h = 13 * (lineHeight + 4) + 10; + _w = 49 * fontWidth + 10; + _h = 15 * (lineHeight + 4) + 10; - xpos = 5; ypos = 10; + // The tab widget + xpos = ypos = 5; + myTab = new TabWidget(this, font, xpos, ypos, _w - 2*xpos, _h - buttonHeight - 20); + addTabWidget(myTab); + addFocusWidget(myTab); + + ////////////////////////////////////////////////////////// + // 1) General options + wid.clear(); + tabID = myTab->addTab(" General "); // Video renderer - new StaticTextWidget(this, font, xpos + (lwidth-fwidth), ypos, fwidth, + new StaticTextWidget(myTab, font, xpos + (lwidth-fwidth), ypos, fwidth, fontHeight, "Renderer:", kTextAlignLeft); - myRenderer = new EditTextWidget(this, font, xpos+lwidth, ypos, + myRenderer = new EditTextWidget(myTab, font, xpos+lwidth, ypos, pwidth, fontHeight, ""); ypos += lineHeight + 4; @@ -71,14 +81,14 @@ VideoDialog::VideoDialog(OSystem* osystem, DialogContainer* parent, #ifdef DISPLAY_OPENGL items.push_back("OpenGL", "gl"); #endif - myRendererPopup = new PopUpWidget(this, font, xpos, ypos, pwidth, lineHeight, + myRendererPopup = new PopUpWidget(myTab, font, xpos, ypos, pwidth, lineHeight, items, "(*) ", lwidth); wid.push_back(myRendererPopup); ypos += lineHeight + 4; // TIA filters (will be dynamically filled later) items.clear(); - myTIAFilterPopup = new PopUpWidget(this, font, xpos, ypos, pwidth, + myTIAFilterPopup = new PopUpWidget(myTab, font, xpos, ypos, pwidth, lineHeight, items, "TIA Filter: ", lwidth); wid.push_back(myTIAFilterPopup); ypos += lineHeight + 4; @@ -88,7 +98,7 @@ VideoDialog::VideoDialog(OSystem* osystem, DialogContainer* parent, items.push_back("Standard", "standard"); items.push_back("Z26", "z26"); items.push_back("User", "user"); - myTIAPalettePopup = new PopUpWidget(this, font, xpos, ypos, pwidth, + myTIAPalettePopup = new PopUpWidget(myTab, font, xpos, ypos, pwidth, lineHeight, items, "TIA Palette: ", lwidth); wid.push_back(myTIAPalettePopup); ypos += lineHeight + 4; @@ -99,7 +109,7 @@ VideoDialog::VideoDialog(OSystem* osystem, DialogContainer* parent, for(uInt32 i = 0; i < instance().supportedResolutions().size(); ++i) items.push_back(instance().supportedResolutions()[i].name, instance().supportedResolutions()[i].name); - myFSResPopup = new PopUpWidget(this, font, xpos, ypos, pwidth, + myFSResPopup = new PopUpWidget(myTab, font, xpos, ypos, pwidth, lineHeight, items, "FS Res: ", lwidth); wid.push_back(myFSResPopup); ypos += lineHeight + 4; @@ -108,7 +118,7 @@ VideoDialog::VideoDialog(OSystem* osystem, DialogContainer* parent, items.clear(); items.push_back("Sleep", "sleep"); items.push_back("Busy-wait", "busy"); - myFrameTimingPopup = new PopUpWidget(this, font, xpos, ypos, pwidth, lineHeight, + myFrameTimingPopup = new PopUpWidget(myTab, font, xpos, ypos, pwidth, lineHeight, items, "Timing (*): ", lwidth); wid.push_back(myFrameTimingPopup); ypos += lineHeight + 4; @@ -117,100 +127,160 @@ VideoDialog::VideoDialog(OSystem* osystem, DialogContainer* parent, items.clear(); items.push_back("Linear", "linear"); items.push_back("Nearest", "nearest"); - myGLFilterPopup = new PopUpWidget(this, font, xpos, ypos, pwidth, lineHeight, + myGLFilterPopup = new PopUpWidget(myTab, font, xpos, ypos, pwidth, lineHeight, items, "GL Filter: ", lwidth); wid.push_back(myGLFilterPopup); ypos += lineHeight + 4; // GL aspect ratio (NTSC mode) myNAspectRatioSlider = - new SliderWidget(this, font, xpos, ypos, pwidth, lineHeight, + new SliderWidget(myTab, font, xpos, ypos, pwidth, lineHeight, "GL Aspect (N): ", lwidth, kNAspectRatioChanged); myNAspectRatioSlider->setMinValue(80); myNAspectRatioSlider->setMaxValue(120); wid.push_back(myNAspectRatioSlider); myNAspectRatioLabel = - new StaticTextWidget(this, font, xpos + myNAspectRatioSlider->getWidth() + 4, + new StaticTextWidget(myTab, font, xpos + myNAspectRatioSlider->getWidth() + 4, ypos + 1, fontWidth * 3, fontHeight, "", kTextAlignLeft); myNAspectRatioLabel->setFlags(WIDGET_CLEARBG); ypos += lineHeight + 4; // GL aspect ratio (PAL mode) myPAspectRatioSlider = - new SliderWidget(this, font, xpos, ypos, pwidth, lineHeight, + new SliderWidget(myTab, font, xpos, ypos, pwidth, lineHeight, "GL Aspect (P): ", lwidth, kPAspectRatioChanged); myPAspectRatioSlider->setMinValue(80); myPAspectRatioSlider->setMaxValue(120); wid.push_back(myPAspectRatioSlider); myPAspectRatioLabel = - new StaticTextWidget(this, font, xpos + myPAspectRatioSlider->getWidth() + 4, + new StaticTextWidget(myTab, font, xpos + myPAspectRatioSlider->getWidth() + 4, ypos + 1, fontWidth * 3, fontHeight, "", kTextAlignLeft); myPAspectRatioLabel->setFlags(WIDGET_CLEARBG); ypos += lineHeight + 4; // Framerate myFrameRateSlider = - new SliderWidget(this, font, xpos, ypos, pwidth, lineHeight, + new SliderWidget(myTab, font, xpos, ypos, pwidth, lineHeight, "Framerate: ", lwidth, kFrameRateChanged); myFrameRateSlider->setMinValue(0); myFrameRateSlider->setMaxValue(300); wid.push_back(myFrameRateSlider); myFrameRateLabel = - new StaticTextWidget(this, font, xpos + myFrameRateSlider->getWidth() + 4, + new StaticTextWidget(myTab, font, xpos + myFrameRateSlider->getWidth() + 4, ypos + 1, fontWidth * 3, fontHeight, "", kTextAlignLeft); myFrameRateLabel->setFlags(WIDGET_CLEARBG); + // Add message concerning usage + ypos += (lineHeight + 4) * 2; + lwidth = font.getStringWidth("(*) Requires application restart"); + new StaticTextWidget(myTab, font, 10, ypos, lwidth, fontHeight, + "(*) Requires application restart", + kTextAlignLeft); + // Move over to the next column - xpos += myNAspectRatioSlider->getWidth() + myNAspectRatioLabel->getWidth(); + xpos += myNAspectRatioSlider->getWidth() + myNAspectRatioLabel->getWidth() + 10; ypos = 10; // Fullscreen - myFullscreenCheckbox = new CheckboxWidget(this, font, xpos, ypos, + myFullscreenCheckbox = new CheckboxWidget(myTab, font, xpos, ypos, "Fullscreen mode", kFullScrChanged); wid.push_back(myFullscreenCheckbox); ypos += lineHeight + 4; // PAL color-loss effect - myColorLossCheckbox = new CheckboxWidget(this, font, xpos, ypos, + myColorLossCheckbox = new CheckboxWidget(myTab, font, xpos, ypos, "PAL color-loss"); wid.push_back(myColorLossCheckbox); ypos += lineHeight + 4; // GL FS stretch - myGLStretchCheckbox = new CheckboxWidget(this, font, xpos, ypos, + myGLStretchCheckbox = new CheckboxWidget(myTab, font, xpos, ypos, "GL FS Stretch"); wid.push_back(myGLStretchCheckbox); ypos += lineHeight + 4; // Use sync to vblank in OpenGL - myUseVSyncCheckbox = new CheckboxWidget(this, font, xpos, ypos, + myUseVSyncCheckbox = new CheckboxWidget(myTab, font, xpos, ypos, "GL VSync"); wid.push_back(myUseVSyncCheckbox); ypos += lineHeight + 4; // Grab mouse (in windowed mode) - myGrabmouseCheckbox = new CheckboxWidget(this, font, xpos, ypos, + myGrabmouseCheckbox = new CheckboxWidget(myTab, font, xpos, ypos, "Grab mouse"); wid.push_back(myGrabmouseCheckbox); ypos += lineHeight + 4; // Center window (in windowed mode) - myCenterCheckbox = new CheckboxWidget(this, font, xpos, ypos, + myCenterCheckbox = new CheckboxWidget(myTab, font, xpos, ypos, "Center window (*)"); wid.push_back(myCenterCheckbox); ypos += lineHeight + 4; - // Add message concerning usage - lwidth = font.getStringWidth("(*) Requires application restart"); - new StaticTextWidget(this, font, 10, _h - 2*buttonHeight - 10, lwidth, fontHeight, - "(*) Requires application restart", - kTextAlignLeft); + // Add items for tab 0 + addToFocusList(wid, tabID); + + ////////////////////////////////////////////////////////// + // 2) TV effects options + wid.clear(); + tabID = myTab->addTab(" TV Effects "); + xpos = ypos = 8; + lwidth = font.getStringWidth("TV Color Texture: "); + pwidth = font.getStringWidth("Staggered"); + + // Use TV color texture effect + items.clear(); + items.push_back("Off", "off"); + items.push_back("Normal", "normal"); + items.push_back("Staggered", "stag"); + myTexturePopup = + new PopUpWidget(myTab, font, xpos, ypos, pwidth, lineHeight, items, + "TV Color Texture: ", lwidth); + wid.push_back(myTexturePopup); + ypos += lineHeight + 4; + + // Use color bleed effect + items.clear(); + items.push_back("Off", "off"); + items.push_back("Low", "low"); + items.push_back("Medium", "medium"); + items.push_back("High", "high"); + myBleedPopup = + new PopUpWidget(myTab, font, xpos, ypos, pwidth, lineHeight, items, + "TV Color Bleed: ", lwidth); + wid.push_back(myBleedPopup); + ypos += lineHeight + 4; + + // Use image noise effect + items.clear(); + items.push_back("Off", "off"); + items.push_back("Low", "low"); + items.push_back("Medium", "medium"); + items.push_back("High", "high"); + myNoisePopup = + new PopUpWidget(myTab, font, xpos, ypos, pwidth, lineHeight, items, + "TV Image Noise: ", lwidth); + wid.push_back(myNoisePopup); + ypos += lineHeight + 4; + + // Use phosphor burn-off effect + ypos += 4; + myPhosphorCheckbox = + new CheckboxWidget(myTab, font, xpos, ypos, "TV Phosphor Burn-off"); + wid.push_back(myPhosphorCheckbox); + ypos += lineHeight + 4; + + // Add items for tab 2 + addToFocusList(wid, tabID); + + // Activate the first tab + myTab->setActiveTab(0); // Add Defaults, OK and Cancel buttons + wid.clear(); ButtonWidget* b; b = new ButtonWidget(this, font, 10, _h - buttonHeight - 10, buttonWidth, buttonHeight, "Defaults", kDefaultsCmd); wid.push_back(b); addOKCancelBGroup(wid, font); - - addToFocusList(wid); + addBGroupToFocusList(wid); // Disable certain functions when we know they aren't present #ifndef DISPLAY_GL @@ -221,6 +291,11 @@ VideoDialog::VideoDialog(OSystem* osystem, DialogContainer* parent, myPAspectRatioLabel->clearFlags(WIDGET_ENABLED); myGLStretchCheckbox->clearFlags(WIDGET_ENABLED); myUseVSyncCheckbox->clearFlags(WIDGET_ENABLED); + + myTexturePopup->clearFlags(WIDGET_ENABLED); + myBleedPopup->clearFlags(WIDGET_ENABLED); + myNoisePopup->clearFlags(WIDGET_ENABLED); + myPhosphorCheckbox->clearFlags(WIDGET_ENABLED); #endif #ifndef WINDOWED_SUPPORT myFullscreenCheckbox->clearFlags(WIDGET_ENABLED); @@ -306,6 +381,24 @@ void VideoDialog::loadConfig() // Center window myCenterCheckbox->setState(instance().settings().getBool("center")); + + // TV color texture effect + myTexturePopup->setSelected(instance().settings().getString("tv_tex"), "off"); + myTexturePopup->setEnabled(gl); + + // TV color bleed effect + myBleedPopup->setSelected(instance().settings().getString("tv_bleed"), "off"); + myBleedPopup->setEnabled(gl); + + // TV random noise effect + myNoisePopup->setSelected(instance().settings().getString("tv_noise"), "off"); + myNoisePopup->setEnabled(gl); + + // TV phosphor burn-off effect + myPhosphorCheckbox->setState(instance().settings().getBool("tv_phos")); + myPhosphorCheckbox->setEnabled(gl); + + myTab->loadConfig(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -362,6 +455,18 @@ void VideoDialog::saveConfig() // Center window instance().settings().setBool("center", myCenterCheckbox->getState()); + // TV color texture effect + instance().settings().setString("tv_tex", myTexturePopup->getSelectedTag()); + + // TV color bleed effect + instance().settings().setString("tv_bleed", myBleedPopup->getSelectedTag()); + + // TV image noise effect + instance().settings().setString("tv_noise", myNoisePopup->getSelectedTag()); + + // TV phosphor burn-off effect + instance().settings().setBool("tv_phos", myPhosphorCheckbox->getState()); + // Finally, issue a complete framebuffer re-initialization instance().createFrameBuffer(); } @@ -390,6 +495,11 @@ void VideoDialog::setDefaults() myGrabmouseCheckbox->setState(false); myCenterCheckbox->setState(true); + myTexturePopup->setSelected("off", ""); + myBleedPopup->setSelected("off", ""); + myNoisePopup->setSelected("off", ""); + myPhosphorCheckbox->setState(false); + // Make sure that mutually-exclusive items are not enabled at the same time handleFullscreenChange(false); } diff --git a/src/gui/VideoDialog.hxx b/src/gui/VideoDialog.hxx index b92a88ae8..23389fef8 100644 --- a/src/gui/VideoDialog.hxx +++ b/src/gui/VideoDialog.hxx @@ -23,12 +23,13 @@ #define VIDEO_DIALOG_HXX class CommandSender; +class CheckboxWidget; class DialogContainer; class EditTextWidget; class PopUpWidget; class SliderWidget; class StaticTextWidget; -class CheckboxWidget; +class TabWidget; #include "OSystem.hxx" #include "Dialog.hxx" @@ -49,6 +50,9 @@ class VideoDialog : public Dialog virtual void handleCommand(CommandSender* sender, int cmd, int data, int id); private: + TabWidget* myTab; + + // General options EditTextWidget* myRenderer; PopUpWidget* myRendererPopup; PopUpWidget* myTIAFilterPopup; @@ -70,6 +74,12 @@ class VideoDialog : public Dialog CheckboxWidget* myCenterCheckbox; CheckboxWidget* myGrabmouseCheckbox; + // TV effects options + PopUpWidget* myTexturePopup; + PopUpWidget* myBleedPopup; + PopUpWidget* myNoisePopup; + CheckboxWidget* myPhosphorCheckbox; + enum { kNAspectRatioChanged = 'VDan', kPAspectRatioChanged = 'VDap', diff --git a/src/tools/create_shaders.pl b/src/tools/create_shaders.pl new file mode 100755 index 000000000..7bc23c3be --- /dev/null +++ b/src/tools/create_shaders.pl @@ -0,0 +1,52 @@ +#!/usr/bin/perl + +use File::Basename; + +usage() if @ARGV < 2; + +$numfiles = @ARGV; +$outfile = $ARGV[$numfiles-1]; + +# Construct the output file in C++ format +# Walk the ARGV list and convert each item +open(OUTFILE, ">$outfile"); + +print OUTFILE "#ifndef GL_SHADER_PROGS_HXX\n"; +print OUTFILE "#define GL_SHADER_PROGS_HXX\n"; +print OUTFILE "\n"; +print OUTFILE "/**\n"; +print OUTFILE " This code is generated using the 'create_shaders.pl' script,\n"; +print OUTFILE " located in the src/tools directory.\n"; +print OUTFILE "*/\n"; +print OUTFILE "\n"; +print OUTFILE "namespace GLShader {\n\n"; + +for ($i = 0; $i < $numfiles - 1; $i++) +{ + open(INFILE, "$ARGV[$i]"); + + ($base,$path,$type) = fileparse($ARGV[$i]); + $base =~ s/\./_/g; + + print OUTFILE "static const char* " . $base . "[] = {\n"; + foreach $line () + { + chomp($line); + print OUTFILE "\"" . $line . "\\n\"\n"; + } + print OUTFILE "\"\\0\"\n"; + print OUTFILE "};\n\n"; + + close(INFILE); +} + +print OUTFILE "} // namespace GLShader\n\n"; +print OUTFILE "#endif\n"; + +close(OUTFILE); + + +sub usage { + print "create_shaders.pl \n"; + exit(0); +}