mirror of https://github.com/stella-emu/stella.git
Implemented new phosphor mode by Thomas Jentzsch.
- Phosphor mode with Blargg effects is currently broken.
This commit is contained in:
parent
e419bbbcab
commit
f8ea61875c
|
@ -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.
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue