mirror of https://github.com/stella-emu/stella.git
experimenting with not clearing the framebuffer during emulation
added rounded bezel auto-detection and handling
This commit is contained in:
parent
37422a838f
commit
bd778e3c2b
|
@ -63,6 +63,19 @@ const string Bezel::getName(int& index) const
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
bool Bezel::checkPixel(uInt32 x, uInt32 y) const
|
||||||
|
{
|
||||||
|
uInt32 *pixels{nullptr}, pitch;
|
||||||
|
uInt8 r, g, b, a;
|
||||||
|
|
||||||
|
mySurface->basePtr(pixels, pitch);
|
||||||
|
pixels += x + y * pitch;
|
||||||
|
myFB.getRGBA(*pixels, &r, &g, &b, &a);
|
||||||
|
|
||||||
|
return a != 0; // pixel is not fully transparent
|
||||||
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
uInt32 Bezel::borderSize(uInt32 x, uInt32 y, uInt32 size, Int32 step) const
|
uInt32 Bezel::borderSize(uInt32 x, uInt32 y, uInt32 size, Int32 step) const
|
||||||
{
|
{
|
||||||
|
@ -136,6 +149,7 @@ bool Bezel::load()
|
||||||
const Int32 w = mySurface->width();
|
const Int32 w = mySurface->width();
|
||||||
const Int32 h = mySurface->height();
|
const Int32 h = mySurface->height();
|
||||||
uInt32 top, bottom, left, right;
|
uInt32 top, bottom, left, right;
|
||||||
|
bool isRounded;
|
||||||
|
|
||||||
if(settings.getBool("bezel.win.auto"))
|
if(settings.getBool("bezel.win.auto"))
|
||||||
{
|
{
|
||||||
|
@ -148,6 +162,12 @@ bool Bezel::load()
|
||||||
yCenter = (bottom + top) >> 1;
|
yCenter = (bottom + top) >> 1;
|
||||||
left = borderSize(0, yCenter, w, 1);
|
left = borderSize(0, yCenter, w, 1);
|
||||||
right = w - 1 - borderSize(w - 1, yCenter, w, -1);
|
right = w - 1 - borderSize(w - 1, yCenter, w, -1);
|
||||||
|
|
||||||
|
// Check if pixels close to the borders are not fully transparent
|
||||||
|
isRounded = checkPixel(left + w / 100, top + h / 100)
|
||||||
|
|| checkPixel(right - w / 100, top + h / 100)
|
||||||
|
|| checkPixel(left + w / 100, bottom - h / 100)
|
||||||
|
|| checkPixel(right - w / 100, bottom - h / 100);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -159,14 +179,15 @@ bool Bezel::load()
|
||||||
right = w - 1 - std::min(w - 1, static_cast<Int32>(w * settings.getInt("bezel.win.right") / 100. + .5));
|
right = w - 1 - std::min(w - 1, static_cast<Int32>(w * settings.getInt("bezel.win.right") / 100. + .5));
|
||||||
top = std::min(h - 1, static_cast<Int32>(h * settings.getInt("bezel.win.top") / 100. + .5));
|
top = std::min(h - 1, static_cast<Int32>(h * settings.getInt("bezel.win.top") / 100. + .5));
|
||||||
bottom = h - 1 - std::min(h - 1, static_cast<Int32>(h * settings.getInt("bezel.win.bottom") / 100. + .5));
|
bottom = h - 1 - std::min(h - 1, static_cast<Int32>(h * settings.getInt("bezel.win.bottom") / 100. + .5));
|
||||||
|
isRounded = settings.getBool("bezel.win.rounded");
|
||||||
}
|
}
|
||||||
|
|
||||||
//cerr << (int)(right - left + 1) << " x " << (int)(bottom - top + 1) << " = "
|
//cerr << (int)(right - left + 1) << " x " << (int)(bottom - top + 1) << " = "
|
||||||
// << double((int)(right - left + 1)) / double((int)(bottom - top + 1));
|
// << double((int)(right - left + 1)) / double((int)(bottom - top + 1)) << endl;
|
||||||
|
|
||||||
// Disable bezel is no transparent window was found
|
// Disable bezel is no transparent window was found
|
||||||
if (left < right && top < bottom)
|
if (left < right && top < bottom)
|
||||||
myInfo = Info(Common::Size(w, h), Common::Rect(left, top, right, bottom));
|
myInfo = Info(Common::Size(w, h), Common::Rect(left, top, right + 1, bottom + 1), isRounded);
|
||||||
else
|
else
|
||||||
myInfo = Info();
|
myInfo = Info();
|
||||||
}
|
}
|
||||||
|
@ -188,6 +209,11 @@ void Bezel::apply()
|
||||||
std::min(myFB.screenSize().h,
|
std::min(myFB.screenSize().h,
|
||||||
static_cast<uInt32>(std::round(myFB.imageRect().h() * myInfo.ratioH())));
|
static_cast<uInt32>(std::round(myFB.imageRect().h() * myInfo.ratioH())));
|
||||||
|
|
||||||
|
//cerr << myInfo.ratioW() << ", " << myInfo.ratioH() << endl;
|
||||||
|
//cerr << myInfo.size() << "; " << myInfo.window() << endl;
|
||||||
|
//cerr << myFB.imageRect().size() << "; " << std::round(imageW * myInfo.ratioW()) << endl;
|
||||||
|
//cerr << "dbl: " << bezelW << " x " << bezelH << endl;
|
||||||
|
|
||||||
// Position and scale bezel
|
// Position and scale bezel
|
||||||
mySurface->setDstSize(bezelW, bezelH);
|
mySurface->setDstSize(bezelW, bezelH);
|
||||||
mySurface->setDstPos((myFB.screenSize().w - bezelW) / 2, // center
|
mySurface->setDstPos((myFB.screenSize().w - bezelW) / 2, // center
|
||||||
|
@ -204,11 +230,22 @@ void Bezel::apply()
|
||||||
else
|
else
|
||||||
if(mySurface)
|
if(mySurface)
|
||||||
mySurface->setVisible(false);
|
mySurface->setVisible(false);
|
||||||
|
|
||||||
|
// If the bezel window is not rounded, it has to be rendered only once for each buffer
|
||||||
|
if(mySurface && !myInfo.isRounded())
|
||||||
|
myRenderCount = 2; // double buffering
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
void Bezel::render()
|
void Bezel::render(bool force)
|
||||||
{
|
{
|
||||||
if(mySurface)
|
if(myRenderCount)
|
||||||
|
{
|
||||||
|
force = true;
|
||||||
|
myRenderCount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only bezels with rounded windows have to be rendered each frame
|
||||||
|
if(mySurface && (myInfo.isRounded() || force))
|
||||||
mySurface->render();
|
mySurface->render();
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,15 +65,17 @@ class Bezel
|
||||||
bool _isShown{false}; // Is bezel shown?
|
bool _isShown{false}; // Is bezel shown?
|
||||||
Common::Size _size{1, 1}; // Bezel size
|
Common::Size _size{1, 1}; // Bezel size
|
||||||
Common::Rect _window{1, 1}; // Area of transparent TIA window inside bezel
|
Common::Rect _window{1, 1}; // Area of transparent TIA window inside bezel
|
||||||
|
bool _isRounded{false}; // true, if the bezel window has rounded corners
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Info() = default;
|
explicit Info() = default;
|
||||||
explicit Info(Common::Size size, Common::Rect window)
|
explicit Info(const Common::Size& size, const Common::Rect& window, bool isRounded)
|
||||||
: _isShown{true}, _size{size}, _window{window} { }
|
: _isShown{true}, _size{size}, _window{window}, _isRounded{isRounded} { }
|
||||||
|
|
||||||
bool isShown() const { return _isShown; }
|
bool isShown() const { return _isShown; }
|
||||||
Common::Size size() const { return _size; }
|
const Common::Size& size() const { return _size; }
|
||||||
Common::Rect window() const { return _window; }
|
const Common::Rect& window() const { return _window; }
|
||||||
|
bool isRounded() const { return _isRounded; }
|
||||||
|
|
||||||
// Ratios between bezel sizes and TIA window sizes
|
// Ratios between bezel sizes and TIA window sizes
|
||||||
double ratioW() const { return static_cast<double>(size().w) / window().w(); }
|
double ratioW() const { return static_cast<double>(size().w) / window().w(); }
|
||||||
|
@ -83,12 +85,20 @@ class Bezel
|
||||||
// Structure access methods
|
// Structure access methods
|
||||||
const Info& info() const { return myInfo; }
|
const Info& info() const { return myInfo; }
|
||||||
bool isShown() const { return myInfo.isShown(); }
|
bool isShown() const { return myInfo.isShown(); }
|
||||||
Common::Size size() const { return myInfo.size(); }
|
const Common::Size& size() const { return myInfo.size(); }
|
||||||
Common::Rect window() const { return myInfo.window(); }
|
const Common::Rect& window() const { return myInfo.window(); }
|
||||||
|
bool isRounded() const { return myInfo.isRounded(); }
|
||||||
// Ratio between bezel size and TIA window size
|
// Ratio between bezel size and TIA window size
|
||||||
double ratioW() const { return myInfo.ratioW(); }
|
double ratioW() const { return myInfo.ratioW(); }
|
||||||
double ratioH() const { return myInfo.ratioH(); }
|
double ratioH() const { return myInfo.ratioH(); }
|
||||||
|
|
||||||
|
/*
|
||||||
|
Check if a pixel is not fully transparent.
|
||||||
|
|
||||||
|
@return true, if pixel is not fully transparent
|
||||||
|
*/
|
||||||
|
bool checkPixel(uInt32 x, uInt32 y) const;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Calculate size of a bezel border.
|
Calculate size of a bezel border.
|
||||||
*/
|
*/
|
||||||
|
@ -107,7 +117,7 @@ class Bezel
|
||||||
/*
|
/*
|
||||||
Render bezel surface
|
Render bezel surface
|
||||||
*/
|
*/
|
||||||
void render();
|
void render(bool force);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/*
|
/*
|
||||||
|
@ -125,6 +135,8 @@ class Bezel
|
||||||
// The bezel surface which blends over the TIA surface
|
// The bezel surface which blends over the TIA surface
|
||||||
shared_ptr<FBSurface> mySurface;
|
shared_ptr<FBSurface> mySurface;
|
||||||
|
|
||||||
|
uInt32 myRenderCount{0};
|
||||||
|
|
||||||
// Bezel info structure
|
// Bezel info structure
|
||||||
Info myInfo;
|
Info myInfo;
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,8 @@ const VideoModeHandler::Mode&
|
||||||
// First calculate maximum zoom that keeps aspect ratio
|
// First calculate maximum zoom that keeps aspect ratio
|
||||||
const double scaleX = static_cast<double>(myImage.w) / (myDisplay.w / bezelInfo.ratioW()),
|
const double scaleX = static_cast<double>(myImage.w) / (myDisplay.w / bezelInfo.ratioW()),
|
||||||
scaleY = static_cast<double>(myImage.h) / (myDisplay.h / bezelInfo.ratioH());
|
scaleY = static_cast<double>(myImage.h) / (myDisplay.h / bezelInfo.ratioH());
|
||||||
|
// Note: Since the scale value may differ, the bezel may not be scaled to fully size.
|
||||||
|
// One value might be tiny bit smaller.
|
||||||
double zoom = 1. / std::max(scaleX, scaleY);
|
double zoom = 1. / std::max(scaleX, scaleY);
|
||||||
|
|
||||||
// When aspect ratio correction is off, we want pixel-exact images,
|
// When aspect ratio correction is off, we want pixel-exact images,
|
||||||
|
@ -114,7 +116,7 @@ VideoModeHandler::Mode::Mode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh,
|
||||||
: screenS{sw, sh},
|
: screenS{sw, sh},
|
||||||
stretch{smode},
|
stretch{smode},
|
||||||
description{desc},
|
description{desc},
|
||||||
zoom{zoomLevel}, //hZoom{zoomLevel}, vZoom{zoomLevel},
|
zoom{zoomLevel},
|
||||||
fsIndex{fsindex}
|
fsIndex{fsindex}
|
||||||
{
|
{
|
||||||
// Now resize based on windowed/fullscreen mode and stretch factor
|
// Now resize based on windowed/fullscreen mode and stretch factor
|
||||||
|
|
|
@ -588,7 +588,7 @@ void FrameBuffer::updateInEmulationMode(float framesPerSecond)
|
||||||
// We don't worry about selective rendering here; the rendering
|
// We don't worry about selective rendering here; the rendering
|
||||||
// always happens at the full framerate
|
// always happens at the full framerate
|
||||||
|
|
||||||
renderTIA();
|
renderTIA(false);
|
||||||
|
|
||||||
// Show frame statistics
|
// Show frame statistics
|
||||||
if(myStatsMsg.enabled)
|
if(myStatsMsg.enabled)
|
||||||
|
@ -967,7 +967,7 @@ void FrameBuffer::renderTIA(bool doClear, bool shade)
|
||||||
|
|
||||||
myTIASurface->render(shade);
|
myTIASurface->render(shade);
|
||||||
if(myBezel)
|
if(myBezel)
|
||||||
myBezel->render();
|
myBezel->render(doClear); // force rendering if framebuffer was cleared
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
|
@ -69,6 +69,7 @@ Settings::Settings()
|
||||||
setPermanent("bezel.win.right", "12");
|
setPermanent("bezel.win.right", "12");
|
||||||
setPermanent("bezel.win.top", "0");
|
setPermanent("bezel.win.top", "0");
|
||||||
setPermanent("bezel.win.bottom", "0");
|
setPermanent("bezel.win.bottom", "0");
|
||||||
|
setPermanent("bezel.win.rounded", "false");
|
||||||
// TIA specific options
|
// TIA specific options
|
||||||
setPermanent("tia.inter", "false");
|
setPermanent("tia.inter", "false");
|
||||||
setPermanent("tia.zoom", "3");
|
setPermanent("tia.zoom", "3");
|
||||||
|
@ -554,6 +555,7 @@ void Settings::usage()
|
||||||
<< " -bezel.win.right <0-40> Set right bezel window position [%]\n"
|
<< " -bezel.win.right <0-40> Set right bezel window position [%]\n"
|
||||||
<< " -bezel.win.top <0-40> Set top bezel window position [%]\n"
|
<< " -bezel.win.top <0-40> Set top bezel window position [%]\n"
|
||||||
<< " -bezel.win.bottom <0-40> Set bottom bezel window position [%]\n"
|
<< " -bezel.win.bottom <0-40> Set bottom bezel window position [%]\n"
|
||||||
|
<< " -bezel.win.rounded <1|0> Set if the bezel window has rounded borders\n"
|
||||||
<< endl
|
<< endl
|
||||||
#ifdef SOUND_SUPPORT
|
#ifdef SOUND_SUPPORT
|
||||||
<< " -audio.enabled <1|0> Enable audio\n"
|
<< " -audio.enabled <1|0> Enable audio\n"
|
||||||
|
|
|
@ -519,6 +519,13 @@ void VideoAudioDialog::addBezelTab()
|
||||||
myWinBottomSlider->setTickmarkIntervals(4);
|
myWinBottomSlider->setTickmarkIntervals(4);
|
||||||
wid.push_back(myWinBottomSlider);
|
wid.push_back(myWinBottomSlider);
|
||||||
|
|
||||||
|
// Mark bezel windows as rounded (requires rendering each frame)
|
||||||
|
ypos += lineHeight + VGAP;
|
||||||
|
myBezelWinRounded = new CheckboxWidget(myTab, _font, xpos, ypos,
|
||||||
|
"Rounded borders");
|
||||||
|
myBezelWinRounded->setToolTip("Enable if the bezel window has rounded borders");
|
||||||
|
wid.push_back(myBezelWinRounded);
|
||||||
|
|
||||||
// Add items for tab 4
|
// Add items for tab 4
|
||||||
addToFocusList(wid, myTab, tabID);
|
addToFocusList(wid, myTab, tabID);
|
||||||
|
|
||||||
|
@ -775,6 +782,7 @@ void VideoAudioDialog::loadConfig()
|
||||||
myWinRightSlider->setValue(settings.getInt("bezel.win.right"));
|
myWinRightSlider->setValue(settings.getInt("bezel.win.right"));
|
||||||
myWinTopSlider->setValue(settings.getInt("bezel.win.top"));
|
myWinTopSlider->setValue(settings.getInt("bezel.win.top"));
|
||||||
myWinBottomSlider->setValue(settings.getInt("bezel.win.bottom"));
|
myWinBottomSlider->setValue(settings.getInt("bezel.win.bottom"));
|
||||||
|
myBezelWinRounded->setState(settings.getBool("bezel.win.rounded"));
|
||||||
handleBezelChange();
|
handleBezelChange();
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -910,6 +918,7 @@ void VideoAudioDialog::saveConfig()
|
||||||
settings.setValue("bezel.win.right", myWinRightSlider->getValueLabel());
|
settings.setValue("bezel.win.right", myWinRightSlider->getValueLabel());
|
||||||
settings.setValue("bezel.win.top", myWinTopSlider->getValueLabel());
|
settings.setValue("bezel.win.top", myWinTopSlider->getValueLabel());
|
||||||
settings.setValue("bezel.win.bottom", myWinBottomSlider->getValueLabel());
|
settings.setValue("bezel.win.bottom", myWinBottomSlider->getValueLabel());
|
||||||
|
settings.setValue("bezel.win.rounded", myBezelWinRounded->getState());
|
||||||
|
|
||||||
// Note: The following has to happen after all video related setting have been saved
|
// Note: The following has to happen after all video related setting have been saved
|
||||||
if(instance().hasConsole())
|
if(instance().hasConsole())
|
||||||
|
@ -1228,6 +1237,7 @@ void VideoAudioDialog::handleBezelChange()
|
||||||
myWinRightSlider->setEnabled(enable && nonAuto);
|
myWinRightSlider->setEnabled(enable && nonAuto);
|
||||||
myWinTopSlider->setEnabled(enable && nonAuto);
|
myWinTopSlider->setEnabled(enable && nonAuto);
|
||||||
myWinBottomSlider->setEnabled(enable && nonAuto);
|
myWinBottomSlider->setEnabled(enable && nonAuto);
|
||||||
|
myBezelWinRounded->setEnabled(enable && nonAuto);
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
|
@ -140,6 +140,7 @@ class VideoAudioDialog : public Dialog
|
||||||
SliderWidget* myWinRightSlider{nullptr};
|
SliderWidget* myWinRightSlider{nullptr};
|
||||||
SliderWidget* myWinTopSlider{nullptr};
|
SliderWidget* myWinTopSlider{nullptr};
|
||||||
SliderWidget* myWinBottomSlider{nullptr};
|
SliderWidget* myWinBottomSlider{nullptr};
|
||||||
|
CheckboxWidget* myBezelWinRounded{nullptr};
|
||||||
|
|
||||||
// Audio
|
// Audio
|
||||||
CheckboxWidget* mySoundEnableCheckbox{nullptr};
|
CheckboxWidget* mySoundEnableCheckbox{nullptr};
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 276 KiB |
Binary file not shown.
After Width: | Height: | Size: 295 KiB |
Binary file not shown.
After Width: | Height: | Size: 413 KiB |
|
@ -0,0 +1,5 @@
|
||||||
|
The bezels test some features of Stella's bezel code:
|
||||||
|
|
||||||
|
- Combat uses a rounded bezel, this should be autodetected and the bezel rendered each frame
|
||||||
|
- River Raid uses a non-rounded bezel, this should be autodetected and the bezel rendered only when loading it
|
||||||
|
- default should be used a fallback for all games, it has wide borders and tests the correct window position and the scaling
|
Loading…
Reference in New Issue