Implemented new phosphor mode by Thomas Jentzsch.

- Phosphor mode with Blargg effects is currently broken.
This commit is contained in:
Stephen Anthony 2017-05-28 16:15:39 -02:30
parent e419bbbcab
commit f8ea61875c
7 changed files with 78 additions and 47 deletions

View File

@ -21,6 +21,10 @@
- YStart autodetection - 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. * Proper emulation of RDY during write cycles.
* Much improved RIOT timer emulation never before seen in any emulator. * Much improved RIOT timer emulation never before seen in any emulator.

View File

@ -66,7 +66,7 @@ void Properties::set(PropertyType key, const string& value)
case Display_PPBlend: case Display_PPBlend:
{ {
int blend = atoi(myProperties[key].c_str()); int blend = atoi(myProperties[key].c_str());
if(blend < 0 || blend > 100) blend = 77; if(blend < 50 || blend > 100) blend = 77;
ostringstream buf; ostringstream buf;
buf << blend; buf << blend;
myProperties[key] = buf.str(); myProperties[key] = buf.str();

View File

@ -32,7 +32,7 @@ TIASurface::TIASurface(OSystem& system)
myTIA(nullptr), myTIA(nullptr),
myFilterType(kNormal), myFilterType(kNormal),
myUsePhosphor(false), myUsePhosphor(false),
myPhosphorBlend(77), myPhosphorPercent(0.77),
myScanlinesEnabled(false), myScanlinesEnabled(false),
myPalette(nullptr) myPalette(nullptr)
{ {
@ -95,26 +95,6 @@ void TIASurface::setPalette(const uInt32* tia_palette, const uInt32* rgb_palette
{ {
myPalette = tia_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 // The NTSC filtering needs access to the raw RGB data, since it calculates
// its own internal palette // its own internal palette
myNTSCFilter.setTIAPalette(*this, rgb_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 uInt32 TIASurface::pixel(uInt32 idx, uInt8 shift) const
{ {
// FIXME - use TJ phosphor code uInt8 c = *(myTIA->frameBuffer() + idx) | shift;
return myPalette[*(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) void TIASurface::enablePhosphor(bool enable, int blend)
{ {
myUsePhosphor = enable; myUsePhosphor = enable;
myPhosphorBlend = blend; myPhosphorPercent = blend / 100.0;
myFilterType = FilterType(enable ? myFilterType | 0x01 : myFilterType & 0x10); myFilterType = FilterType(enable ? myFilterType | 0x01 : myFilterType & 0x10);
myTiaSurface->setDirty(); myTiaSurface->setDirty();
mySLineSurface->setDirty(); mySLineSurface->setDirty();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt8 TIASurface::getPhosphor(uInt8 c1, uInt8 c2) const uInt32 TIASurface::getRGBPhosphor(uInt32 c, uInt32 p, uInt8 shift) const
{ {
if(c2 > c1) uInt8 rc, gc, bc, rp, gp, bp;
std::swap(c1, c2);
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 // In hardware rendering mode, it's faster to just assume that the screen
// is dirty and always do an update // is dirty and always do an update
uInt8* fbuffer = myTIA->frameBuffer(); uInt8* tiaFrame = myTIA->frameBuffer();
uInt32 width = myTIA->width(); uInt32* rgbFrame = myTIA->rgbFramebuffer();
uInt32 height = myTIA->height(); uInt32 width = myTIA->width();
uInt32 height = myTIA->height();
uInt32 *buffer, pitch; uInt32 *buffer, pitch;
myTiaSurface->basePtr(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) switch(myFilterType)
{ {
case kNormal: case kNormal:
@ -320,7 +326,7 @@ void TIASurface::render()
{ {
uInt32 pos = screenofsY; uInt32 pos = screenofsY;
for(uInt32 x = 0; x < width; ++x) for(uInt32 x = 0; x < width; ++x)
buffer[pos++] = myPalette[fbuffer[bufofsY + x]]; buffer[pos++] = myPalette[tiaFrame[bufofsY + x]];
bufofsY += width; bufofsY += width;
screenofsY += pitch; screenofsY += pitch;
@ -329,7 +335,6 @@ void TIASurface::render()
} }
case kPhosphor: case kPhosphor:
{ {
#if 0 // FIXME
uInt32 bufofsY = 0; uInt32 bufofsY = 0;
uInt32 screenofsY = 0; uInt32 screenofsY = 0;
for(uInt32 y = 0; y < height; ++y) for(uInt32 y = 0; y < height; ++y)
@ -338,17 +343,21 @@ void TIASurface::render()
for(uInt32 x = 0; x < width; ++x) for(uInt32 x = 0; x < width; ++x)
{ {
const uInt32 bufofs = bufofsY + 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; bufofsY += width;
screenofsY += pitch; screenofsY += pitch;
} }
#endif
break; break;
} }
case kBlarggNormal: case kBlarggNormal:
{ {
myNTSCFilter.blit_single(fbuffer, width, height, buffer, pitch << 2); myNTSCFilter.blit_single(tiaFrame, width, height, buffer, pitch << 2);
break; break;
} }
case kBlarggPhosphor: case kBlarggPhosphor:

View File

@ -118,7 +118,22 @@ class TIASurface
@return Averaged value of the two colors @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. Enable/disable/query NTSC filtering effects.
@ -162,7 +177,7 @@ class TIASurface
bool myUsePhosphor; bool myUsePhosphor;
// Amount to blend when using phosphor effect // Amount to blend when using phosphor effect
int myPhosphorBlend; float myPhosphorPercent;
// Use scanlines in TIA rendering mode // Use scanlines in TIA rendering mode
bool myScanlinesEnabled; bool myScanlinesEnabled;
@ -170,9 +185,6 @@ class TIASurface
// Palette for normal TIA rendering mode // Palette for normal TIA rendering mode
const uInt32* myPalette; const uInt32* myPalette;
// Palette for phosphor rendering mode
uInt32 myPhosphorPalette[256][256];
private: private:
// Following constructors and assignment operators not supported // Following constructors and assignment operators not supported
TIASurface() = delete; TIASurface() = delete;

View File

@ -86,6 +86,7 @@ TIA::TIA(Console& console, Sound& sound, Settings& settings)
); );
myFramebuffer = make_ptr<uInt8[]>(160 * FrameManager::frameBufferHeight); myFramebuffer = make_ptr<uInt8[]>(160 * FrameManager::frameBufferHeight);
myRGBFramebuffer = make_ptr<uInt32[]>(160 * FrameManager::frameBufferHeight);
myTIAPinsDriven = mySettings.getBool("tiadriven"); myTIAPinsDriven = mySettings.getBool("tiadriven");
@ -152,6 +153,7 @@ void TIA::reset()
void TIA::frameReset() void TIA::frameReset()
{ {
memset(myFramebuffer.get(), 0, 160 * FrameManager::frameBufferHeight); memset(myFramebuffer.get(), 0, 160 * FrameManager::frameBufferHeight);
memset(myRGBFramebuffer.get(), 0, 160 * FrameManager::frameBufferHeight);
myAutoFrameEnabled = mySettings.getInt("framerate") <= 0; myAutoFrameEnabled = mySettings.getInt("framerate") <= 0;
enableColorLoss(mySettings.getBool("colorloss")); enableColorLoss(mySettings.getBool("colorloss"));
} }

View File

@ -157,9 +157,10 @@ class TIA : public Device
void update(); 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. Answers dimensional info about the framebuffer.
@ -466,6 +467,9 @@ class TIA : public Device
// Pointer to the internal frame buffer // Pointer to the internal frame buffer
BytePtr myFramebuffer; BytePtr myFramebuffer;
// Pointer to the RGB frame buffer (used for phosphor)
std::unique_ptr<uInt32[]> myRGBFramebuffer;
bool myTIAPinsDriven; bool myTIAPinsDriven;
HState myHstate; HState myHstate;

View File

@ -355,7 +355,7 @@ GameInfoDialog::GameInfoDialog(
ypos, 8*fontWidth, lineHeight, "Blend ", ypos, 8*fontWidth, lineHeight, "Blend ",
font.getStringWidth("Blend "), font.getStringWidth("Blend "),
kPPBlendChanged); kPPBlendChanged);
myPPBlend->setMinValue(1); myPPBlend->setMaxValue(100); myPPBlend->setMinValue(50); myPPBlend->setMaxValue(100);
wid.push_back(myPPBlend); wid.push_back(myPPBlend);
myPPBlendLabel = new StaticTextWidget(myTab, font, myPPBlendLabel = new StaticTextWidget(myTab, font,