The ROM launcher now uses a separate FBSurface for snapshots, so that

any-sized PNG can be loaded and then scaled to the available space.


git-svn-id: svn://svn.code.sf.net/p/stella/code/trunk@2979 8b62c5a3-ac7e-4cc8-8f21-d9a121418aba
This commit is contained in:
stephena 2014-08-28 14:21:44 +00:00
parent 8da55d8eac
commit f6dd1ba27b
10 changed files with 145 additions and 168 deletions

View File

@ -21,6 +21,10 @@
* Added 'MDM' (Menu Driven Megacart) bankswitch scheme as described on
AtariAge and originally developed by Edwin Blink.
* Improved snapshot viewing in the ROM launcher; snapshots are now
scaled to the available space, and can better accommodate sizes
other than those generated by Stella itself.
* Removed the 'bank' command from the debugger prompt, as it only worked
with certain bankswitch types. The bankswitch UI should now be used
to query/set bank state.

View File

@ -33,36 +33,7 @@ FBSurfaceSDL2::FBSurfaceSDL2(FrameBufferSDL2& buffer,
myBlendAlpha(255),
myStaticData(NULL)
{
// Create a surface in the same format as the parent GL class
const SDL_PixelFormat* pf = myFB.myPixelFormat;
mySurface = SDL_CreateRGBSurface(0, width, height,
pf->BitsPerPixel, pf->Rmask, pf->Gmask, pf->Bmask, pf->Amask);
// We start out with the src and dst rectangles containing the same
// dimensions, indicating no scaling or re-positioning
mySrcR.x = mySrcR.y = myDstR.x = myDstR.y = 0;
mySrcR.w = myDstR.w = width;
mySrcR.h = myDstR.h = height;
////////////////////////////////////////////////////
// These *must* be set for the parent class
myPixels = (uInt32*) mySurface->pixels;
myPitch = mySurface->pitch / pf->BytesPerPixel;
////////////////////////////////////////////////////
if(data)
{
myTexAccess = SDL_TEXTUREACCESS_STATIC;
myStaticPitch = mySurface->w * 4; // we need pitch in 'bytes'
myStaticData = new uInt32[mySurface->w * mySurface->h];
SDL_memcpy(myStaticData, data, mySurface->w * mySurface->h * 4);
}
applyAttributes(false);
// To generate texture
reload();
createSurface(width, height, data);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -92,14 +63,6 @@ void FBSurfaceSDL2::fillRect(uInt32 x, uInt32 y, uInt32 w, uInt32 h, uInt32 colo
SDL_FillRect(mySurface, &tmp, myPalette[color]);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FBSurfaceSDL2::drawSurface(const FBSurface* surface)
{
const FBSurfaceSDL2* s = (const FBSurfaceSDL2*) surface;
SDL_Rect dst = s->myDstR;
SDL_BlitSurface(s->mySurface, &(s->mySrcR), mySurface, &dst);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 FBSurfaceSDL2::width() const
{
@ -113,16 +76,14 @@ uInt32 FBSurfaceSDL2::height() const
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const GUI::Rect& FBSurfaceSDL2::srcRect()
const GUI::Rect& FBSurfaceSDL2::srcRect() const
{
mySrcGUIR.setBounds(mySrcR.x, mySrcR.y, mySrcR.x+mySrcR.w, mySrcR.y+mySrcR.h);
return mySrcGUIR;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const GUI::Rect& FBSurfaceSDL2::dstRect()
const GUI::Rect& FBSurfaceSDL2::dstRect() const
{
myDstGUIR.setBounds(myDstR.x, myDstR.y, myDstR.x+myDstR.w, myDstR.y+myDstR.h);
return myDstGUIR;
}
@ -130,24 +91,28 @@ const GUI::Rect& FBSurfaceSDL2::dstRect()
void FBSurfaceSDL2::setSrcPos(uInt32 x, uInt32 y)
{
mySrcR.x = x; mySrcR.y = y;
mySrcGUIR.moveTo(x, y);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FBSurfaceSDL2::setSrcSize(uInt32 w, uInt32 h)
{
mySrcR.w = w; mySrcR.h = h;
mySrcGUIR.setWidth(w); mySrcGUIR.setHeight(h);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FBSurfaceSDL2::setDstPos(uInt32 x, uInt32 y)
{
myDstR.x = x; myDstR.y = y;
myDstGUIR.moveTo(x, y);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FBSurfaceSDL2::setDstSize(uInt32 w, uInt32 h)
{
myDstR.w = w; myDstR.h = h;
myDstGUIR.setWidth(w); myDstGUIR.setHeight(h);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -220,6 +185,57 @@ void FBSurfaceSDL2::reload()
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FBSurfaceSDL2::resize(uInt32 width, uInt32 height)
{
// We will only resize when necessary, and not using static textures
if((myTexAccess == SDL_TEXTUREACCESS_STATIC) ||
(width <= mySurface->w && height <= mySurface->h))
return; // don't need to resize at all
if(mySurface)
SDL_FreeSurface(mySurface);
free();
createSurface(width, height, NULL);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FBSurfaceSDL2::createSurface(uInt32 width, uInt32 height,
const uInt32* data)
{
// Create a surface in the same format as the parent GL class
const SDL_PixelFormat* pf = myFB.myPixelFormat;
mySurface = SDL_CreateRGBSurface(0, width, height,
pf->BitsPerPixel, pf->Rmask, pf->Gmask, pf->Bmask, pf->Amask);
// We start out with the src and dst rectangles containing the same
// dimensions, indicating no scaling or re-positioning
mySrcR.x = mySrcR.y = myDstR.x = myDstR.y = 0;
mySrcR.w = myDstR.w = width;
mySrcR.h = myDstR.h = height;
////////////////////////////////////////////////////
// These *must* be set for the parent class
myPixels = (uInt32*) mySurface->pixels;
myPitch = mySurface->pitch / pf->BytesPerPixel;
////////////////////////////////////////////////////
if(data)
{
myTexAccess = SDL_TEXTUREACCESS_STATIC;
myStaticPitch = mySurface->w * 4; // we need pitch in 'bytes'
myStaticData = new uInt32[mySurface->w * mySurface->h];
SDL_memcpy(myStaticData, data, mySurface->w * mySurface->h * 4);
}
applyAttributes(false);
// To generate texture
reload();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FBSurfaceSDL2::applyAttributes(bool immediate)
{

View File

@ -40,15 +40,14 @@ class FBSurfaceSDL2 : public FBSurface
// the ones implemented here use SDL-specific code for extra performance
//
void fillRect(uInt32 x, uInt32 y, uInt32 w, uInt32 h, uInt32 color);
void drawSurface(const FBSurface* surface);
// With hardware surfaces, it's faster to just update the entire surface
void setDirty() { mySurfaceIsDirty = true; }
uInt32 width() const;
uInt32 height() const;
const GUI::Rect& srcRect();
const GUI::Rect& dstRect();
const GUI::Rect& srcRect() const;
const GUI::Rect& dstRect() const;
void setSrcPos(uInt32 x, uInt32 y);
void setSrcSize(uInt32 w, uInt32 h);
void setDstPos(uInt32 x, uInt32 y);
@ -60,10 +59,14 @@ class FBSurfaceSDL2 : public FBSurface
void invalidate();
void free();
void reload();
void resize(uInt32 width, uInt32 height);
protected:
void applyAttributes(bool immediate);
private:
void createSurface(uInt32 width, uInt32 height, const uInt32* data);
private:
FrameBufferSDL2& myFB;

View File

@ -38,7 +38,6 @@ PNGLibrary::PNGLibrary(const FrameBuffer& fb)
PNGLibrary::~PNGLibrary()
{
delete[] ReadInfo.buffer;
delete[] ReadInfo.line;
delete[] ReadInfo.row_pointers;
}
@ -115,8 +114,8 @@ void PNGLibrary::loadImage(const string& filename, FBSurface& surface)
// We're finished reading
png_read_end(png_ptr, info_ptr);
// Scale image to surface dimensions
scaleImagetoSurface(surface);
// Load image into the surface, setting the correct dimensions
loadImagetoSurface(surface);
// Cleanup
done:
@ -258,16 +257,6 @@ bool PNGLibrary::allocateStorage(png_uint_32 w, png_uint_32 h)
ReadInfo.buffer_size = req_buffer_size;
}
uInt32 req_line_size = w * 3;
if(req_line_size > ReadInfo.line_size)
{
delete[] ReadInfo.line;
ReadInfo.line = new uInt32[req_line_size];
if(ReadInfo.line == NULL)
return false;
ReadInfo.line_size = req_line_size;
}
uInt32 req_row_size = h;
if(req_row_size > ReadInfo.row_size)
{
@ -287,53 +276,30 @@ bool PNGLibrary::allocateStorage(png_uint_32 w, png_uint_32 h)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PNGLibrary::scaleImagetoSurface(FBSurface& surface)
void PNGLibrary::loadImagetoSurface(FBSurface& surface)
{
// Figure out the original zoom level of the snapshot
// All snapshots generated by Stella are at most some multiple of 320
// pixels wide
// The only complication is when the aspect ratio is changed, the width
// can range from 256 (80%) to 320 (100%)
// The following calculation will work up to approx. 16x zoom level,
// but since Stella only generates snapshots at up to 10x, we should
// be fine for a while ...
uInt32 izoom = uInt32(ceil(ReadInfo.width/320.0)),
szoom = surface.width()/320;
// First determine if we need to resize the surface
uInt32 iw = ReadInfo.width, ih = ReadInfo.height;
if(iw > surface.width() || ih > surface.height())
surface.resize(iw, ih);
uInt32 sw = ReadInfo.width / izoom * szoom,
sh = ReadInfo.height / izoom * szoom;
sw = BSPF_min(sw, surface.width());
sh = BSPF_min(sh, surface.height());
surface.setSrcSize(sw, sh);
surface.setDstSize(sw, sh);
// The source dimensions are set here; the destination dimensions are
// set by whoever owns the surface
surface.setSrcPos(0, 0);
surface.setSrcSize(iw, ih);
// Decompress the image, and scale it correctly
uInt32 buf_offset = ReadInfo.pitch * izoom;
uInt32 i_offset = 3 * izoom;
// Convert RGB triples into pixels and store in the surface
uInt32 *s_buf, s_pitch;
surface.basePtr(s_buf, s_pitch);
uInt8* i_buf = ReadInfo.buffer;
uInt32 i_pitch = ReadInfo.pitch;
// We can only scan at most the height of the image to the constraints of
// the surface height (some multiple of 256)
uInt32 iheight = BSPF_min((uInt32)ReadInfo.height, izoom * 256);
// Grab each non-duplicate row of data from the image
uInt8* buffer = ReadInfo.buffer;
for(uInt32 irow = 0, srow = 0; irow < iheight; irow += izoom, buffer += buf_offset)
for(uInt32 irow = 0; irow < ih; ++irow, i_buf += i_pitch, s_buf += s_pitch)
{
// Scale the image data into the temporary line buffer
uInt8* i_ptr = buffer;
uInt32* l_ptr = ReadInfo.line;
for(uInt32 icol = 0; icol < ReadInfo.width; icol += izoom, i_ptr += i_offset)
{
uInt32 pixel = myFB.mapRGB(*i_ptr, *(i_ptr+1), *(i_ptr+2));
uInt32 xstride = szoom;
while(xstride--)
*l_ptr++ = pixel;
}
// Then fill the surface with those bytes
uInt32 ystride = szoom;
while(ystride--)
surface.drawPixels(ReadInfo.line, 0, srow++, sw);
uInt8* i_ptr = i_buf;
uInt32* s_ptr = s_buf;
for(uInt32 icol = 0; icol < ReadInfo.width; ++icol, i_ptr += 3)
*s_ptr++ = myFB.mapRGB(*i_ptr, *(i_ptr+1), *(i_ptr+2));
}
}
@ -394,5 +360,5 @@ void PNGLibrary::png_user_error(png_structp ctx, png_const_charp str)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PNGLibrary::ReadInfoType PNGLibrary::ReadInfo = {
NULL, NULL, 0, 0, 0, NULL, 0, 0, 0
NULL, NULL, 0, 0, 0, 0, 0
};

View File

@ -95,8 +95,7 @@ class PNGLibrary
uInt8* buffer;
png_bytep* row_pointers;
png_uint_32 width, height, pitch;
uInt32* line;
uInt32 buffer_size, line_size, row_size;
uInt32 buffer_size, row_size;
} ReadInfoType;
static ReadInfoType ReadInfo;
@ -128,12 +127,12 @@ class PNGLibrary
const VariantList& comments);
/**
Scale the PNG data from 'ReadInfo' into the FBSurface. For now, scaling
is done on integer boundaries only (ie, 1x, 2x, etc up or down).
Load the PNG data from 'ReadInfo' into the FBSurface. The surface
is resized as necessary to accommodate the data.
@param surface The FBSurface into which to place the PNG data
*/
void scaleImagetoSurface(FBSurface& surface);
void loadImagetoSurface(FBSurface& surface);
/**
Write PNG tEXt chunks to the image.

View File

@ -283,23 +283,5 @@ void FBSurface::drawString(const GUI::Font& font, const string& s,
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FBSurface::drawSurface(const FBSurface* surface)
{
const GUI::Rect& srcR = ((FBSurface*)surface)->srcRect();
const GUI::Rect& dstR = ((FBSurface*)surface)->dstRect();
const uInt32* src = surface->myPixels + srcR.y() * surface->myPitch + srcR.x();
uInt32* dst = myPixels + dstR.y() * myPitch + dstR.x();
uInt32 h = srcR.height(), w = srcR.width() * 4;
while(h--)
{
memcpy(dst, src, w);
src += surface->myPitch;
dst += myPitch;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const uInt32* FBSurface::myPalette = 0;

View File

@ -52,7 +52,7 @@ enum FrameStyle {
class FBSurface
{
friend class TIASurface;
// friend class TIASurface;
public:
/**
@ -208,16 +208,6 @@ class FBSurface
uInt32 color, TextAlignment align = kTextAlignLeft,
int deltax = 0, bool useEllipsis = true);
/**
This method should be called copy the contents of the given
surface into the FrameBuffer surface. No scaling is done; that is,
a straight-forward copy of the surface pixels is done at the specified
destination.
@param surface The data to draw
*/
virtual void drawSurface(const FBSurface* surface);
/**
This method should be called to indicate that the surface has been
modified, and should be redrawn at the next interval.
@ -243,8 +233,8 @@ class FBSurface
These methods answer the current *rendering* dimensions of the
specified surface.
*/
virtual const GUI::Rect& srcRect() = 0;
virtual const GUI::Rect& dstRect() = 0;
virtual const GUI::Rect& srcRect() const = 0;
virtual const GUI::Rect& dstRect() const = 0;
/**
These methods set the origin point and width/height for the
@ -295,9 +285,13 @@ class FBSurface
*/
virtual void reload() = 0;
static void setPalette(const uInt32* palette) { myPalette = palette; }
/**
This method should be called to resize the surface to the
given dimensions and reload data/state. The surface is not
modified if it is larger than the given dimensions.
*/
virtual void resize(uInt32 width, uInt32 height) = 0;
protected:
/**
The rendering attributes that can be modified for this texture.
These probably can only be implemented in child FBSurfaces where
@ -308,6 +302,12 @@ class FBSurface
bool blending; // Blending is enabled
uInt32 blendalpha; // Alpha to use in blending mode (0-100%)
};
/**
Get the currently applied attributes.
*/
Attributes& attributes() { return myAttributes; }
/**
The child class chooses which (if any) of the actual attributes
can be applied.
@ -318,6 +318,8 @@ class FBSurface
*/
virtual void applyAttributes(bool immediate = true) = 0;
static void setPalette(const uInt32* palette) { myPalette = palette; }
protected:
static const uInt32* myPalette;
uInt32* myPixels;

View File

@ -137,9 +137,8 @@ const FBSurface& TIASurface::baseSurface(GUI::Rect& rect)
rect.setBounds(0, 0, width, height);
// Fill the surface with pixels from the TIA, scaled 2x horizontally
uInt32 *buffer, pitch;
myBaseTiaSurface->basePtr(buffer, pitch);
uInt32* buf_ptr = buffer;
uInt32 *buf_ptr, pitch;
myBaseTiaSurface->basePtr(buf_ptr, pitch);
for(uInt32 y = 0; y < height; ++y)
{
@ -219,22 +218,23 @@ void TIASurface::toggleScanlineInterpolation()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 TIASurface::enableScanlines(int relative, int absolute)
{
uInt32& intensity = mySLineSurface->myAttributes.blendalpha;
if(relative == 0) intensity = absolute;
else intensity += relative;
intensity = BSPF_max(0u, intensity);
intensity = BSPF_min(100u, intensity);
FBSurface::Attributes& attr = mySLineSurface->attributes();
if(relative == 0) attr.blendalpha = absolute;
else attr.blendalpha += relative;
attr.blendalpha = BSPF_max(0u, attr.blendalpha);
attr.blendalpha = BSPF_min(100u, attr.blendalpha);
mySLineSurface->applyAttributes();
mySLineSurface->setDirty();
return intensity;
return attr.blendalpha;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIASurface::enableScanlineInterpolation(bool enable)
{
mySLineSurface->myAttributes.smoothing = enable;
FBSurface::Attributes& attr = mySLineSurface->attributes();
attr.smoothing = enable;
mySLineSurface->applyAttributes();
mySLineSurface->setDirty();
}
@ -266,16 +266,15 @@ void TIASurface::enableNTSC(bool enable)
// Normal vs NTSC mode uses different source widths
myTiaSurface->setSrcSize(enable ? ATARI_NTSC_OUT_WIDTH(160) : 160, myTIA->height());
myTiaSurface->myAttributes.smoothing =
myOSystem.settings().getBool("tia.inter");
FBSurface::Attributes& tia_attr = myTiaSurface->attributes();
tia_attr.smoothing = myOSystem.settings().getBool("tia.inter");
myTiaSurface->applyAttributes();
myScanlinesEnabled = enable;
mySLineSurface->myAttributes.smoothing =
myOSystem.settings().getBool("tv.scaninter");
mySLineSurface->myAttributes.blending = myScanlinesEnabled;
mySLineSurface->myAttributes.blendalpha =
myOSystem.settings().getInt("tv.scanlines");
FBSurface::Attributes& sl_attr = mySLineSurface->attributes();
sl_attr.smoothing = myOSystem.settings().getBool("tv.scaninter");
sl_attr.blending = myScanlinesEnabled;
sl_attr.blendalpha = myOSystem.settings().getInt("tv.scanlines");
mySLineSurface->applyAttributes();
myTiaSurface->setDirty();
@ -285,6 +284,8 @@ void TIASurface::enableNTSC(bool enable)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string TIASurface::effectsInfo() const
{
const FBSurface::Attributes& attr = mySLineSurface->attributes();
ostringstream buf;
switch(myFilterType)
{
@ -295,14 +296,12 @@ string TIASurface::effectsInfo() const
buf << "Disabled, phosphor mode";
break;
case kBlarggNormal:
buf << myNTSCFilter.getPreset() << ", scanlines="
<< mySLineSurface->myAttributes.blendalpha << "/"
<< (mySLineSurface->myAttributes.smoothing ? "inter" : "nointer");
buf << myNTSCFilter.getPreset() << ", scanlines=" << attr.blendalpha << "/"
<< (attr.smoothing ? "inter" : "nointer");
break;
case kBlarggPhosphor:
buf << myNTSCFilter.getPreset() << ", phosphor, scanlines="
<< mySLineSurface->myAttributes.blendalpha << "/"
<< (mySLineSurface->myAttributes.smoothing ? "inter" : "nointer");
<< attr.blendalpha << "/" << (attr.smoothing ? "inter" : "nointer");
break;
}
return buf.str();

View File

@ -42,8 +42,6 @@ class VideoMode;
class TIASurface
{
friend class FrameBuffer;
public:
/**
Creates a new TIASurface object

View File

@ -92,9 +92,11 @@ void RomInfoWidget::parseProperties()
// only draw certain parts of it
if(mySurface == NULL)
{
uInt32 ID = instance().frameBuffer().allocateSurface(
320*myZoomLevel, 256*myZoomLevel);
uInt32 ID = instance().frameBuffer().allocateSurface(320*2, 256*2);
mySurface = instance().frameBuffer().surface(ID);
mySurface->attributes().smoothing = true;
mySurface->applyAttributes();
dialog().addSurface(mySurface);
}
@ -111,6 +113,12 @@ void RomInfoWidget::parseProperties()
try
{
instance().png().loadImage(filename, *mySurface);
// Scale surface to available image area
const GUI::Rect& src = mySurface->srcRect();
uInt32 avail_w = 320 * myZoomLevel, avail_h = 256 * myZoomLevel;
float scale = BSPF_min(float(avail_w) / src.width(), float(avail_h) / src.height());
mySurface->setDstSize(src.width() * scale, src.height() * scale);
}
catch(const char* msg)
{
@ -145,9 +153,9 @@ void RomInfoWidget::drawWidget(bool hilite)
if(mySurfaceIsValid)
{
const GUI::Rect& src = mySurface->srcRect();
uInt32 x = _x + ((_w - src.width()) >> 1);
uInt32 y = _y + ((yoff - src.height()) >> 1);
const GUI::Rect& dst = mySurface->dstRect();
uInt32 x = _x + ((_w - dst.width()) >> 1);
uInt32 y = _y + ((yoff - dst.height()) >> 1);
// Make sure when positioning the snapshot surface that we take
// the dialog surface position into account