From f8ea61875c5ea379b798baf3322be1d9b2c2b13f Mon Sep 17 00:00:00 2001 From: Stephen Anthony Date: Sun, 28 May 2017 16:15:39 -0230 Subject: [PATCH] Implemented new phosphor mode by Thomas Jentzsch. - Phosphor mode with Blargg effects is currently broken. --- Changes.txt | 4 ++ src/emucore/Props.cxx | 2 +- src/emucore/TIASurface.cxx | 85 +++++++++++++++++++++----------------- src/emucore/TIASurface.hxx | 22 +++++++--- src/emucore/tia/TIA.cxx | 2 + src/emucore/tia/TIA.hxx | 8 +++- src/gui/GameInfoDialog.cxx | 2 +- 7 files changed, 78 insertions(+), 47 deletions(-) diff --git a/Changes.txt b/Changes.txt index 40b0374a7..581b3d275 100644 --- a/Changes.txt +++ b/Changes.txt @@ -21,6 +21,10 @@ - YStart autodetection - ... + * Implemented new phosphor emulation mode, which is much closer to real + TV output. Special thanks to Thomas Jentzsch for the idea and + implementation. + * Proper emulation of RDY during write cycles. * Much improved RIOT timer emulation never before seen in any emulator. diff --git a/src/emucore/Props.cxx b/src/emucore/Props.cxx index d97357f47..f9eb11987 100644 --- a/src/emucore/Props.cxx +++ b/src/emucore/Props.cxx @@ -66,7 +66,7 @@ void Properties::set(PropertyType key, const string& value) case Display_PPBlend: { int blend = atoi(myProperties[key].c_str()); - if(blend < 0 || blend > 100) blend = 77; + if(blend < 50 || blend > 100) blend = 77; ostringstream buf; buf << blend; myProperties[key] = buf.str(); diff --git a/src/emucore/TIASurface.cxx b/src/emucore/TIASurface.cxx index 2ad1effca..bf3bd7336 100644 --- a/src/emucore/TIASurface.cxx +++ b/src/emucore/TIASurface.cxx @@ -32,7 +32,7 @@ TIASurface::TIASurface(OSystem& system) myTIA(nullptr), myFilterType(kNormal), myUsePhosphor(false), - myPhosphorBlend(77), + myPhosphorPercent(0.77), myScanlinesEnabled(false), myPalette(nullptr) { @@ -95,26 +95,6 @@ void TIASurface::setPalette(const uInt32* tia_palette, const uInt32* rgb_palette { myPalette = tia_palette; - // Set palette for phosphor effect - for(int i = 0; i < 256; ++i) - { - for(int j = 0; j < 256; ++j) - { - uInt8 ri = (rgb_palette[i] >> 16) & 0xff; - uInt8 gi = (rgb_palette[i] >> 8) & 0xff; - uInt8 bi = rgb_palette[i] & 0xff; - uInt8 rj = (rgb_palette[j] >> 16) & 0xff; - uInt8 gj = (rgb_palette[j] >> 8) & 0xff; - uInt8 bj = rgb_palette[j] & 0xff; - - uInt8 r = getPhosphor(ri, rj); - uInt8 g = getPhosphor(gi, gj); - uInt8 b = getPhosphor(bi, bj); - - myPhosphorPalette[i][j] = myFB.mapRGB(r, g, b); - } - } - // The NTSC filtering needs access to the raw RGB data, since it calculates // its own internal palette myNTSCFilter.setTIAPalette(*this, rgb_palette); @@ -146,8 +126,22 @@ const FBSurface& TIASurface::baseSurface(GUI::Rect& rect) const // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt32 TIASurface::pixel(uInt32 idx, uInt8 shift) const { -// FIXME - use TJ phosphor code - return myPalette[*(myTIA->frameBuffer() + idx) | shift]; + uInt8 c = *(myTIA->frameBuffer() + idx) | shift; + + if(!myUsePhosphor) + return myPalette[c]; + else + { + const uInt32 p = *(myTIA->rgbFramebuffer() + idx); + + // Mix current calculated frame with previous displayed frame + const uInt32 retVal = getRGBPhosphor(myPalette[c], p, shift); + + // Store back into displayed frame buffer (for next frame) + *(myTIA->rgbFramebuffer() + idx) = retVal; + + return retVal; + } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -230,19 +224,32 @@ void TIASurface::enableScanlineInterpolation(bool enable) void TIASurface::enablePhosphor(bool enable, int blend) { myUsePhosphor = enable; - myPhosphorBlend = blend; + myPhosphorPercent = blend / 100.0; myFilterType = FilterType(enable ? myFilterType | 0x01 : myFilterType & 0x10); myTiaSurface->setDirty(); mySLineSurface->setDirty(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt8 TIASurface::getPhosphor(uInt8 c1, uInt8 c2) const +uInt32 TIASurface::getRGBPhosphor(uInt32 c, uInt32 p, uInt8 shift) const { - if(c2 > c1) - std::swap(c1, c2); + uInt8 rc, gc, bc, rp, gp, bp; - return ((c1 - c2) * myPhosphorBlend)/100 + c2; + myFB.getRGB(c, &rc, &gc, &bc); + myFB.getRGB(p, &rp, &gp, &bp); + + // mix current calculated frame with previous displayed frame + uInt8 rn = getPhosphor(rc, rp); + uInt8 gn = getPhosphor(gc, gp); + uInt8 bn = getPhosphor(bc, bp); + + if(shift) + { + // convert RGB to grayscale + rn = gn = bn = (uInt8)(0.2126*rn + 0.7152*gn + 0.0722*bn); + } + + return myFB.mapRGB(rn, gn, bn); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -301,15 +308,14 @@ void TIASurface::render() // In hardware rendering mode, it's faster to just assume that the screen // is dirty and always do an update - uInt8* fbuffer = myTIA->frameBuffer(); - uInt32 width = myTIA->width(); - uInt32 height = myTIA->height(); + uInt8* tiaFrame = myTIA->frameBuffer(); + uInt32* rgbFrame = myTIA->rgbFramebuffer(); + uInt32 width = myTIA->width(); + uInt32 height = myTIA->height(); uInt32 *buffer, pitch; myTiaSurface->basePtr(buffer, pitch); - // TODO - Eventually 'phosphor' won't be a separate mode, and will become - // a post-processing filter by blending several frames. switch(myFilterType) { case kNormal: @@ -320,7 +326,7 @@ void TIASurface::render() { uInt32 pos = screenofsY; for(uInt32 x = 0; x < width; ++x) - buffer[pos++] = myPalette[fbuffer[bufofsY + x]]; + buffer[pos++] = myPalette[tiaFrame[bufofsY + x]]; bufofsY += width; screenofsY += pitch; @@ -329,7 +335,6 @@ void TIASurface::render() } case kPhosphor: { -#if 0 // FIXME uInt32 bufofsY = 0; uInt32 screenofsY = 0; for(uInt32 y = 0; y < height; ++y) @@ -338,17 +343,21 @@ void TIASurface::render() for(uInt32 x = 0; x < width; ++x) { const uInt32 bufofs = bufofsY + x; - buffer[pos++] = myPhosphorPalette[currentFrame[bufofs]][previousFrame[bufofs]]; + const uInt8 c = tiaFrame[bufofs]; + const uInt32 retVal = getRGBPhosphor(myPalette[c], rgbFrame[bufofs]); + + // Store back into displayed frame buffer (for next frame) + rgbFrame[bufofs] = retVal; + buffer[pos++] = retVal; } bufofsY += width; screenofsY += pitch; } -#endif break; } case kBlarggNormal: { - myNTSCFilter.blit_single(fbuffer, width, height, buffer, pitch << 2); + myNTSCFilter.blit_single(tiaFrame, width, height, buffer, pitch << 2); break; } case kBlarggPhosphor: diff --git a/src/emucore/TIASurface.hxx b/src/emucore/TIASurface.hxx index ce8416971..39484eb6b 100644 --- a/src/emucore/TIASurface.hxx +++ b/src/emucore/TIASurface.hxx @@ -118,7 +118,22 @@ class TIASurface @return Averaged value of the two colors */ - uInt8 getPhosphor(uInt8 c1, uInt8 c2) const; + uInt8 getPhosphor(uInt8 c1, uInt8 c2) const { + // Use maximum of current and decayed previous values + c2 *= myPhosphorPercent; + if(c1 > c2) return c1; // raise (assumed immediate) + else return c2; // decay + } + + /** + Used to calculate an averaged color for the 'phosphor' effect. + + @param c RGB Color 1 (current frame) + @param p RGB Color 2 (previous frame) + + @return Averaged value of the two RGB colors + */ + uInt32 getRGBPhosphor(uInt32 c, uInt32 cp, uInt8 shift = 0) const; /** Enable/disable/query NTSC filtering effects. @@ -162,7 +177,7 @@ class TIASurface bool myUsePhosphor; // Amount to blend when using phosphor effect - int myPhosphorBlend; + float myPhosphorPercent; // Use scanlines in TIA rendering mode bool myScanlinesEnabled; @@ -170,9 +185,6 @@ class TIASurface // Palette for normal TIA rendering mode const uInt32* myPalette; - // Palette for phosphor rendering mode - uInt32 myPhosphorPalette[256][256]; - private: // Following constructors and assignment operators not supported TIASurface() = delete; diff --git a/src/emucore/tia/TIA.cxx b/src/emucore/tia/TIA.cxx index bb7390643..6f597418b 100644 --- a/src/emucore/tia/TIA.cxx +++ b/src/emucore/tia/TIA.cxx @@ -86,6 +86,7 @@ TIA::TIA(Console& console, Sound& sound, Settings& settings) ); myFramebuffer = make_ptr(160 * FrameManager::frameBufferHeight); + myRGBFramebuffer = make_ptr(160 * FrameManager::frameBufferHeight); myTIAPinsDriven = mySettings.getBool("tiadriven"); @@ -152,6 +153,7 @@ void TIA::reset() void TIA::frameReset() { memset(myFramebuffer.get(), 0, 160 * FrameManager::frameBufferHeight); + memset(myRGBFramebuffer.get(), 0, 160 * FrameManager::frameBufferHeight); myAutoFrameEnabled = mySettings.getInt("framerate") <= 0; enableColorLoss(mySettings.getBool("colorloss")); } diff --git a/src/emucore/tia/TIA.hxx b/src/emucore/tia/TIA.hxx index 8c653b43d..76495d360 100644 --- a/src/emucore/tia/TIA.hxx +++ b/src/emucore/tia/TIA.hxx @@ -157,9 +157,10 @@ class TIA : public Device void update(); /** - Returns pointer to the internal frame buffer. + Returns a pointer to the internal frame buffer(s). */ - uInt8* frameBuffer() const { return myFramebuffer.get(); } + uInt8* frameBuffer() const { return myFramebuffer.get(); } + uInt32* rgbFramebuffer() const { return myRGBFramebuffer.get(); } /** Answers dimensional info about the framebuffer. @@ -466,6 +467,9 @@ class TIA : public Device // Pointer to the internal frame buffer BytePtr myFramebuffer; + // Pointer to the RGB frame buffer (used for phosphor) + std::unique_ptr myRGBFramebuffer; + bool myTIAPinsDriven; HState myHstate; diff --git a/src/gui/GameInfoDialog.cxx b/src/gui/GameInfoDialog.cxx index 722a760d1..48c9cb73a 100644 --- a/src/gui/GameInfoDialog.cxx +++ b/src/gui/GameInfoDialog.cxx @@ -355,7 +355,7 @@ GameInfoDialog::GameInfoDialog( ypos, 8*fontWidth, lineHeight, "Blend ", font.getStringWidth("Blend "), kPPBlendChanged); - myPPBlend->setMinValue(1); myPPBlend->setMaxValue(100); + myPPBlend->setMinValue(50); myPPBlend->setMaxValue(100); wid.push_back(myPPBlend); myPPBlendLabel = new StaticTextWidget(myTab, font,