refactored bezel code

added variable bezel window support
This commit is contained in:
thrust26 2023-08-25 15:57:21 +02:00
parent e0374fe681
commit c31ab36afe
17 changed files with 533 additions and 258 deletions

View File

@ -24,7 +24,7 @@
* Added 2nd UI theme and hotkey for toggling UI theme.
* Adde bezel support.
* Added bezel support. (TODO: Doc)
* Added optional type format detection based on colors used.

View File

@ -101,6 +101,18 @@ class FBBackendSDL2 : public FBBackend
FORCE_INLINE void getRGB(uInt32 pixel, uInt8* r, uInt8* g, uInt8* b) const override
{ SDL_GetRGB(pixel, myPixelFormat, r, g, b); }
/**
This method is called to retrieve the R/G/B/A data from the given pixel.
@param pixel The pixel containing R/G/B data
@param r The red component of the color
@param g The green component of the color
@param b The blue component of the color
@param a The alpha component of the color.
*/
FORCE_INLINE void getRGBA(uInt32 pixel, uInt8* r, uInt8* g, uInt8* b, uInt8* a) const override
{ SDL_GetRGBA(pixel, myPixelFormat, r, g, b, a); }
/**
This method is called to map a given R/G/B triple to the screen palette.
@ -111,6 +123,14 @@ class FBBackendSDL2 : public FBBackend
inline uInt32 mapRGB(uInt8 r, uInt8 g, uInt8 b) const override
{ return SDL_MapRGB(myPixelFormat, r, g, b); }
/**
This method is called to map a given R/G/B/A triple to the screen palette.
@param r The red component of the color.
@param g The green component of the color.
@param b The blue component of the color.
@param a The alpha component of the color.
*/
inline uInt32 mapRGBA(uInt8 r, uInt8 g, uInt8 b, uInt8 a) const override
{ return SDL_MapRGBA(myPixelFormat, r, g, b, a); }

View File

@ -16,6 +16,8 @@
//============================================================================
#include "Settings.hxx"
#include "Bezel.hxx"
#include "VideoModeHandler.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -33,7 +35,7 @@ void VideoModeHandler::setDisplaySize(const Common::Size& display, Int32 fsIndex
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const VideoModeHandler::Mode&
VideoModeHandler::buildMode(const Settings& settings, bool inTIAMode, bool showBezel)
VideoModeHandler::buildMode(const Settings& settings, bool inTIAMode, Bezel::Info bezelInfo)
{
const bool windowedRequested = myFSIndex == -1;
@ -42,25 +44,24 @@ const VideoModeHandler::Mode&
{
if(windowedRequested)
{
const float zoom = settings.getFloat("tia.zoom");
const double zoom = settings.getFloat("tia.zoom");
ostringstream desc;
desc << (zoom * 100) << "%";
// Image and screen (aka window) dimensions are the same
// Overscan is not applicable in this mode
myMode = Mode(myImage.w * zoom, myImage.h * zoom, Mode::Stretch::Fill,
myFSIndex, desc.str(), zoom, showBezel);
myMode = Mode(myImage.w, myImage.h,
Mode::Stretch::Fill, myFSIndex,
desc.str(), zoom, bezelInfo);
}
else
{
const float overscan = 1 - settings.getInt("tia.fs_overscan") / 100.0;
const double overscan = 1 - settings.getInt("tia.fs_overscan") / 100.0;
// First calculate maximum zoom that keeps aspect ratio
// Note: We are assuming a 16:9 bezel image here
const float bezelScaleW = showBezel ? (16.F / 9.F) / (4.F / 3.F) : 1;
const float scaleX = myImage.w / (myDisplay.w / bezelScaleW),
scaleY = static_cast<float>(myImage.h) / myDisplay.h;
float zoom = 1.F / std::max(scaleX, scaleY);
const double scaleX = static_cast<double>(myImage.w) / (myDisplay.w / bezelInfo.ratioW()),
scaleY = static_cast<double>(myImage.h) / (myDisplay.h / bezelInfo.ratioH());
double zoom = 1. / std::max(scaleX, scaleY);
// When aspect ratio correction is off, we want pixel-exact images,
// so we default to integer zooming
@ -69,20 +70,19 @@ const VideoModeHandler::Mode&
if(!settings.getBool("tia.fs_stretch")) // preserve aspect, use all space
{
myMode = Mode(myImage.w * zoom, myImage.h * zoom,
myMode = Mode(myImage.w, myImage.h,
myDisplay.w, myDisplay.h,
Mode::Stretch::Preserve, myFSIndex,
"Fullscreen: Preserve aspect, no stretch",
zoom, overscan,
showBezel, showBezel ? settings.getInt("bezel.border") : 0);
zoom, overscan, bezelInfo);
}
else // ignore aspect, use all space
{
myMode = Mode(myImage.w * zoom, myImage.h * zoom,
myMode = Mode(myImage.w, myImage.h,
myDisplay.w, myDisplay.h,
Mode::Stretch::Fill, myFSIndex,
"Fullscreen: Ignore aspect, full stretch",
zoom, overscan, showBezel);
zoom, overscan, bezelInfo);
}
}
}
@ -94,54 +94,48 @@ const VideoModeHandler::Mode&
myMode = Mode(myImage.w, myImage.h, myDisplay.w, myDisplay.h,
Mode::Stretch::None, myFSIndex);
}
return myMode;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
VideoModeHandler::Mode::Mode(uInt32 iw, uInt32 ih, Stretch smode,
Int32 fsindex, string_view desc,
float zoomLevel,
bool showBezel, Int32 bezelBorder)
: Mode(iw, ih, iw, ih, smode, fsindex, desc, zoomLevel, 1.F, showBezel, bezelBorder)
double zoomLevel, Bezel::Info bezelInfo)
: Mode(iw, ih, iw, ih, smode, fsindex, desc, zoomLevel, 1., bezelInfo)
{
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
VideoModeHandler::Mode::Mode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh,
Stretch smode, Int32 fsindex, string_view desc,
float zoomLevel, float overscan,
bool showBezel, Int32 bezelBorder)
double zoomLevel, double overscan, Bezel::Info bezelInfo)
: screenS{sw, sh},
stretch{smode},
description{desc},
zoom{zoomLevel},
zoom{zoomLevel}, //hZoom{zoomLevel}, vZoom{zoomLevel},
fsIndex{fsindex}
{
// Note: We are assuming a 16:9 bezel image here
const float bezelScaleW = showBezel ? (16.F / 9.F) / (4.F / 3.F) : 1;
// Now resize based on windowed/fullscreen mode and stretch factor
if(fsIndex != -1) // fullscreen mode
{
switch(stretch)
{
case Stretch::Preserve:
iw = (iw - bezelBorder * 4.F / 3.F * zoomLevel) * overscan;
ih = (ih - bezelBorder * zoomLevel) * overscan;
//iw *= overscan;
//ih *= overscan;
iw = std::round(iw * overscan * zoomLevel);
ih = std::round(ih * overscan * zoomLevel);
break;
case Stretch::Fill:
{
// Scale to all available space
iw = screenS.w * (overscan / bezelScaleW);
ih = screenS.h * overscan;
iw = std::round(screenS.w * overscan / bezelInfo.ratioW());
ih = std::round(screenS.h * overscan / bezelInfo.ratioH());
break;
}
case Stretch::None: // UI Mode
// Don't do any scaling at all
iw = std::min(iw, screenS.w) * overscan;
ih = std::min(ih, screenS.h) * overscan;
iw = std::min(static_cast<uInt32>(iw * zoomLevel), screenS.w) * overscan;
ih = std::min(static_cast<uInt32>(ih * zoomLevel), screenS.h) * overscan;
break;
}
}
@ -153,8 +147,10 @@ VideoModeHandler::Mode::Mode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh,
{
case Stretch::Preserve:
case Stretch::Fill:
screenS.w = iw * bezelScaleW;
screenS.h = ih;
iw *= zoomLevel;
ih *= zoomLevel;
screenS.w = std::round(iw * bezelInfo.ratioW());
screenS.h = std::round(ih * bezelInfo.ratioH());
break;
case Stretch::None: // UI Mode
@ -166,7 +162,17 @@ VideoModeHandler::Mode::Mode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh,
iw = std::min(iw, screenS.w);
ih = std::min(ih, screenS.h);
imageR.moveTo((screenS.w - iw) >> 1, (screenS.h - ih) >> 1);
// Allow variable image positions in asymmetric bezels
// (works in case of no bezel too)
const uInt32 wx = bezelInfo.window().x() * iw / bezelInfo.window().w();
const uInt32 wy = bezelInfo.window().y() * ih / bezelInfo.window().h();
const uInt32 bezelW = std::min(screenS.w,
static_cast<uInt32>(std::round(iw * bezelInfo.ratioW())));
const uInt32 bezelH = std::min(screenS.h,
static_cast<uInt32>(std::round(ih * bezelInfo.ratioH())));
// Center image (no bezel) or move image relative to centered bezel
imageR.moveTo(((screenS.w - bezelW) >> 1) + wx, ((screenS.h - bezelH) >> 1) + wy);
imageR.setWidth(iw);
imageR.setHeight(ih);

View File

@ -22,6 +22,7 @@ class Settings;
#include "Rect.hxx"
#include "bspf.hxx"
#include "Bezel.hxx"
class VideoModeHandler
{
@ -38,35 +39,23 @@ class VideoModeHandler
Fill, // Stretch to fill all available space
None // No stretching (1x zoom)
};
struct BezelInfo
{
bool enabled{false};
bool windowedMode{false};
uInt32 topBorder{0};
uInt32 bottomBorder{0};
BezelInfo() = default;
BezelInfo(bool _enabled, bool _windowedMode, uInt32 _topBorder, uInt32 _bottomBorder)
: enabled{_enabled}, windowedMode{_windowedMode},
topBorder{_topBorder}, bottomBorder(_bottomBorder) { }
};
Common::Rect imageR;
Common::Rect screenR;
Common::Size screenS;
Stretch stretch{Mode::Stretch::None};
string description;
float zoom{1.F};
double zoom{1.};
Int32 fsIndex{-1}; // -1 indicates windowed mode
Mode() = default;
Mode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh, Stretch smode,
Int32 fsindex = -1, string_view desc = "",
float zoomLevel = 1.F, float overscan = 1.F,
bool showBezel = false, Int32 bezelBorder = 0);
double zoomLevel = 1., double overscan = 1.,
Bezel::Info bezelInfo = Bezel::Info());
Mode(uInt32 iw, uInt32 ih, Stretch smode, Int32 fsindex = -1,
string_view desc = "", float zoomLevel = 1.F,
bool showBezel = false, Int32 bezelBorder = 0);
string_view desc = "", double zoomLevel = 1.,
Bezel::Info bezelInfo = Bezel::Info());
friend ostream& operator<<(ostream& os, const Mode& vm)
{
@ -108,8 +97,8 @@ class VideoModeHandler
@return A video mode based on the given criteria
*/
const VideoModeHandler::Mode& buildMode(const Settings& settings,
bool inTIAMode, bool showBezel);
const VideoModeHandler::Mode& buildMode(const Settings& settings, bool inTIAMode,
Bezel::Info bezelInfo = Bezel::Info());
private:
Common::Size myImage, myDisplay;

198
src/emucore/Bezel.cxx Normal file
View File

@ -0,0 +1,198 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2023 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include "OSystem.hxx"
#include "Console.hxx"
#include "EventHandler.hxx"
#include "FBSurface.hxx"
#include "PNGLibrary.hxx"
#include "Bezel.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Bezel::Bezel(OSystem& osystem)
: myOSystem{osystem},
myFB{osystem.frameBuffer()}
{
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const string Bezel::getName(int& index) const
{
if(++index == 1)
return myOSystem.console().properties().get(PropType::Bezel_Name);
// Try to generate bezel name from cart name
const string& cartName = myOSystem.console().properties().get(PropType::Cart_Name);
const size_t pos = cartName.find_first_of("(");
if(index < 10 && pos != std::string::npos && pos > 0)
{
// The following suffixes are from "The Official No-Intro Convention",
// covering all used combinations by "The Bezel Project" (except single ones)
// (Unl) = unlicensed (Homebrews)
const std::array<string, 8> suffixes = {
" (USA)", " (USA) (Proto)", " (USA) (Unl)", " (USA) (Hack)",
" (Europe)", " (Germany)", " (France) (Unl)", " (Australia)"
};
return cartName.substr(0, pos - 1) + suffixes[index - 2];
}
if(index == 10)
{
index = -1;
return "default";
}
return "";
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 Bezel::borderSize(uInt32 x, uInt32 y, uInt32 size, Int32 step) const
{
uInt32 *pixels{nullptr}, pitch;
uInt32 i;
mySurface->basePtr(pixels, pitch);
pixels += x + y * pitch;
for(i = 0; i < size; ++i, pixels += step)
{
uInt8 r, g, b, a;
myFB.getRGBA(*pixels, &r, &g, &b, &a);
if(a < 255)
return i;
}
return size;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Bezel::load()
{
bool isValid = false;
#ifdef IMAGE_SUPPORT
const bool isShown = myOSystem.eventHandler().inTIAMode() &&
myOSystem.settings().getBool("bezel.show") &&
(myFB.fullScreen() || myOSystem.settings().getBool("bezel.windowed"));
if(mySurface)
myFB.deallocateSurface(mySurface);
mySurface = nullptr;
if(isShown)
{
double aspectRatio = 1;
mySurface = myFB.allocateSurface(1, 1); // dummy size
try
{
const string& path = myOSystem.bezelDir().getPath();
string imageName;
VariantList metaData;
int index = 0;
do
{
const string& name = getName(index);
if(name != EmptyString)
{
imageName = path + name + ".png";
FSNode node(imageName);
if(node.exists())
{
isValid = true;
break;
}
}
} while(index != -1);
if(isValid)
myOSystem.png().loadImage(imageName, *mySurface, &aspectRatio, metaData);
}
catch(const runtime_error&)
{
isValid = false;
}
}
#endif
if(isValid)
{
const Settings& settings = myOSystem.settings();
const Int32 w = mySurface->width();
const Int32 h = mySurface->height();
uInt32 top, bottom, left, right;
if(settings.getBool("bezel.autoborders"))
{
// Determine transparent window inside bezel image
top = borderSize(w >> 1, 0, h, w);
bottom = h - 1 - borderSize(w >> 1, h - 1, h, -w);
left = borderSize(0, (bottom + top) >> 1, w, 1);
right = w - 1 - borderSize(w - 1, (bottom + top) >> 1, w, -1);
}
else
{
left = std::min(w, settings.getInt("bezel.leftborder"));
right = w - 1 - std::min(w, settings.getInt("bezel.rightborder"));
top = std::min(h, settings.getInt("bezel.topborder"));
bottom = h - 1 - std::min(h, settings.getInt("bezel.bottomborder"));
}
//cerr << right - left + 1 << " x " << bottom - top + 1 << " = "
// << double(right - left + 1) / double(bottom - top + 1);
myInfo = Info(Common::Size(w, h), Common::Rect(left, top, right, bottom));
}
else
myInfo = Info();
return isValid;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Bezel::apply()
{
if(isShown())
{
const uInt32 bezelW =
std::min(myFB.screenSize().w,
static_cast<uInt32>(std::round(myFB.imageRect().w() * myInfo.ratioW())));
const uInt32 bezelH =
std::min(myFB.screenSize().h,
static_cast<uInt32>(std::round(myFB.imageRect().h() * myInfo.ratioH())));
// Position and scale bezel
mySurface->setDstSize(bezelW, bezelH);
mySurface->setDstPos((myFB.screenSize().w - bezelW) / 2, // center
(myFB.screenSize().h - bezelH) / 2);
mySurface->setScalingInterpolation(ScalingInterpolation::sharp);
// Note: Variable bezel window positions are handled in VideoModeHandler::Mode
// Enable blending to allow overlaying the bezel over the TIA output
mySurface->attributes().blending = true;
mySurface->attributes().blendalpha = 100;
mySurface->applyAttributes();
mySurface->setVisible(true);
}
else
if(mySurface)
mySurface->setVisible(false);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Bezel::render()
{
if(mySurface)
mySurface->render();
}

140
src/emucore/Bezel.hxx Normal file
View File

@ -0,0 +1,140 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2023 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#ifndef BEZEL_HXX
#define BEZEL_HXX
class OSystem;
class FBSurface;
class FrameBuffer;
#include "Rect.hxx"
/**
This class handles the bezels.
Bezels are loaded using a file name which is either a bezel name property or
is autogenerated from the cart name property. The bezels can be any size and
their transparent emulation window can be at any position. The position of
the window can be determined automatically.
+--------------------------------------+
| | display.h
+--------------------------------------+
| |
| +---------------+ |
| | window | |
| | | |
| | tia.h * zoom | |
| | | | bezel.h * zoom
| | | |
| +---------------+ |
| |
+--------------------------------------+ size
| |
+--------------------------------------+
The bezel and window sizes and their ratios are used for correct scaling.
@author Thomas Jentzsch
*/
class Bezel
{
public:
explicit Bezel(OSystem& osystem);
~Bezel() = default;
struct Info
{
private:
bool _isShown{false}; // Is bezel shown?
Common::Size _size{1, 1}; // Bezel size
Common::Rect _window{1, 1}; // Area of transparent TIA window inside bezel
public:
explicit Info() = default;
explicit Info(Common::Size size, Common::Rect window)
: _isShown{true}, _size{size}, _window{window} { }
bool isShown() const { return _isShown; }
Common::Size size() const { return _size; }
Common::Rect window() const { return _window; }
// Ratios between bezel sizes and TIA window sizes
double ratioW() const { return static_cast<double>(size().w) / window().w(); }
double ratioH() const { return static_cast<double>(size().h) / window().h(); }
};
// Structure access methods
const Info& info() const { return myInfo; }
bool isShown() const { return myInfo.isShown(); }
Common::Size size() const { return myInfo.size(); }
Common::Rect window() const { return myInfo.window(); }
// Ratio between bezel size and TIA window size
double ratioW() const { return myInfo.ratioW(); }
double ratioH() const { return myInfo.ratioH(); }
/*
Calculate size of a bezel border.
*/
uInt32 borderSize(uInt32 x, uInt32 y, uInt32 size, Int32 step) const;
/*
Load the bezel.
*/
bool load();
/*
Display scaled bezel.
*/
void apply();
/*
Render bezel surface
*/
void render();
private:
/*
Generate bezel file name.
*/
const string getName(int& index) const;
private:
// The parent system for the bezel
OSystem& myOSystem;
// Pointer to the FrameBuffer object
FrameBuffer& myFB;
// The bezel surface which blends over the TIA surface
shared_ptr<FBSurface> mySurface;
// Bezel info structure
Info myInfo;
private:
// Following constructors and assignment operators not supported
Bezel() = delete;
Bezel(const Bezel&) = delete;
Bezel(Bezel&&) = delete;
Bezel& operator=(const Bezel&) = delete;
Bezel& operator=(Bezel&&) = delete;
};
#endif

View File

@ -128,6 +128,17 @@ class FBBackend
*/
virtual void getRGB(uInt32 pixel, uInt8* r, uInt8* g, uInt8* b) const = 0;
/**
This method is called to retrieve the R/G/B/A data from the given pixel.
@param pixel The pixel containing R/G/B data
@param r The red component of the color
@param g The green component of the color
@param b The blue component of the color
@param a The alpha component of the color.
*/
virtual void getRGBA(uInt32 pixel, uInt8* r, uInt8* g, uInt8* b, uInt8* a) const = 0;
/**
This method is called to map a given R/G/B triple to the screen palette.
@ -143,6 +154,7 @@ class FBBackend
@param r The red component of the color.
@param g The green component of the color.
@param b The blue component of the color.
@param a The alpha component of the color.
*/
virtual uInt32 mapRGBA(uInt8 r, uInt8 g, uInt8 b, uInt8 a) const = 0;

View File

@ -31,6 +31,7 @@
#include "FBSurface.hxx"
#include "TIASurface.hxx"
#include "Bezel.hxx"
#include "FrameBuffer.hxx"
#include "PaletteHandler.hxx"
#include "StateManager.hxx"
@ -126,6 +127,8 @@ void FrameBuffer::initialize()
// Create a TIA surface; we need it for rendering TIA images
myTIASurface = make_unique<TIASurface>(myOSystem);
// Create a bezel surface for TIA overlays
myBezel = make_unique<Bezel>(myOSystem);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -278,8 +281,10 @@ FBInitStatus FrameBuffer::createDisplay(string_view title, BufferType type,
if(myBufferType == BufferType::Emulator)
{
myBezel->load();
// Determine possible TIA windowed zoom levels
const float currentTIAZoom = myOSystem.settings().getFloat("tia.zoom");
const double currentTIAZoom = myOSystem.settings().getFloat("tia.zoom");
myOSystem.settings().setValue("tia.zoom",
BSPF::clampw(currentTIAZoom, supportedTIAMinZoom(), supportedTIAMaxZoom()));
}
@ -960,8 +965,8 @@ void FrameBuffer::renderTIA(bool shade, bool doClear)
clear(); // TODO - test this: it may cause slowdowns on older systems
myTIASurface->render(shade);
if(myBezelSurface)
myBezelSurface->render();
if(myBezel)
myBezel->render();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -1146,7 +1151,7 @@ void FrameBuffer::toggleFullscreen(bool toggle)
msg << "enabled (" << myBackend->refreshRate() << " Hz, ";
else
msg << "disabled (";
msg << "Zoom " << myActiveVidMode.zoom * 100 << "%)";
msg << "Zoom " << round(myActiveVidMode.zoom * 100) << "%)";
}
else
{
@ -1228,7 +1233,7 @@ void FrameBuffer::switchVideoMode(int direction)
if(!fullScreen())
{
// Windowed TIA modes support variable zoom levels
float zoom = myOSystem.settings().getFloat("tia.zoom");
double zoom = myOSystem.settings().getFloat("tia.zoom");
if(direction == +1) zoom += ZOOM_STEPS;
else if(direction == -1) zoom -= ZOOM_STEPS;
@ -1271,17 +1276,10 @@ FBInitStatus FrameBuffer::applyVideoMode()
myVidModeHandler.setDisplaySize(myAbsDesktopSize[display]);
const bool inTIAMode = myOSystem.eventHandler().inTIAMode();
#ifdef IMAGE_SUPPORT
const bool showBezel = inTIAMode &&
myOSystem.settings().getBool("bezel.show") &&
(fullScreen() || myOSystem.settings().getBool("bezel.windowed")) &&
checkBezel();
#else
const bool showBezel = false;
#endif
// Build the new mode based on current settings
const VideoModeHandler::Mode& mode = myVidModeHandler.buildMode(s, inTIAMode, showBezel);
const VideoModeHandler::Mode& mode
= myVidModeHandler.buildMode(s, inTIAMode, myBezel->info());
if(mode.imageR.size() > mode.screenS)
return FBInitStatus::FailTooLarge;
@ -1306,11 +1304,7 @@ FBInitStatus FrameBuffer::applyVideoMode()
if(inTIAMode)
{
#ifdef IMAGE_SUPPORT
if(myBezelSurface)
deallocateSurface(myBezelSurface);
myBezelSurface = nullptr;
if(showBezel)
loadBezel();
myBezel->apply();
#endif
myTIASurface->initialize(myOSystem.console(), myActiveVidMode);
@ -1334,147 +1328,18 @@ FBInitStatus FrameBuffer::applyVideoMode()
return status;
}
#ifdef IMAGE_SUPPORT
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const string FrameBuffer::getBezelName(int& index) const
{
if(++index == 1)
return myOSystem.console().properties().get(PropType::Bezel_Name);
// Try to generate bezel name from cart name
const string& cartName = myOSystem.console().properties().get(PropType::Cart_Name);
const size_t pos = cartName.find_first_of("(");
if(index < 10 && pos != std::string::npos && pos > 0)
{
// The following suffixes are from "The Official No-Intro Convention",
// covering all used combinations by "The Bezel Project" (except single ones)
// (Unl) = unlicensed (Homebrews)
const std::array<string, 8> suffixes = {
" (USA)", " (USA) (Proto)", " (USA) (Unl)", " (USA) (Hack)",
" (Europe)", " (Germany)", " (France) (Unl)", " (Australia)"
};
return cartName.substr(0, pos - 1) + suffixes[index - 2];
}
if(index == 10)
{
index = -1;
return "default";
}
return "";
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool FrameBuffer::checkBezel()
{
const string& path = myOSystem.bezelDir().getPath();
int index = 0;
do
{
const string& name = getBezelName(index);
if(name != EmptyString)
{
FSNode node(path + name + ".png");
if(node.exists())
return true;
}
} while (index != -1);
return false;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool FrameBuffer::loadBezel()
{
bool isValid = false;
double aspectRatio = 1;
myBezelSurface = allocateSurface(myActiveVidMode.screenS.w, myActiveVidMode.screenS.h);
try
{
const string& path = myOSystem.bezelDir().getPath();
string imageName;
VariantList metaData;
int index = 0;
do
{
const string& name = getBezelName(index);
if(name != EmptyString)
{
imageName = path + name + ".png";
FSNode node(imageName);
if(node.exists())
{
isValid = true;
break;
}
}
} while (index != -1);
if(isValid)
myOSystem.png().loadImage(imageName, *myBezelSurface, &aspectRatio, metaData);
}
catch(const runtime_error&)
{
isValid = false;
}
if(isValid)
{
const float overscan = 1 - myOSystem.settings().getInt("tia.fs_overscan") / 100.F;
uInt32 imageW, imageH;
if(fullScreen())
{
const float bezelBorder = myOSystem.settings().getInt("bezel.border") * overscan * myActiveVidMode.zoom;
imageW = (myActiveVidMode.imageR.w() + static_cast<int>(bezelBorder * 4.F / 3.F)) * (16.F / 9.F) / (4.F / 3.F);
imageH = myActiveVidMode.imageR.h() + static_cast<int>(bezelBorder);
}
else
{
imageW = static_cast<uInt32>(myActiveVidMode.imageR.w() * (16.F / 9.F) / (4.F / 3.F));
imageH = myActiveVidMode.imageR.h();
}
// Scale bezel to fullscreen (preserve or stretch) or window size
const uInt32 bezelW = std::min(
myActiveVidMode.screenS.w, imageW);
//static_cast<uInt32>(myActiveVidMode.imageR.w() * (16.F / 9.F) / (4.F / 3.F)) + static_cast<int>(40 * myActiveVidMode.zoom));
const uInt32 bezelH = std::min(
myActiveVidMode.screenS.h, imageH);
//myActiveVidMode.imageR.h() + static_cast<int>(30 * myActiveVidMode.zoom));
//cerr << bezelW << " x " << bezelH << endl;
myBezelSurface->setDstSize(bezelW, bezelH);
myBezelSurface->setDstPos((myActiveVidMode.screenS.w - bezelW) / 2,
(myActiveVidMode.screenS.h - bezelH) / 2); // center
myBezelSurface->setScalingInterpolation(ScalingInterpolation::sharp);
// Enable blending to allow overlaying the bezel over the TIA output
myBezelSurface->attributes().blending = true;
myBezelSurface->attributes().blendalpha = 100;
myBezelSurface->applyAttributes();
}
if(myBezelSurface)
myBezelSurface->setVisible(isValid);
return isValid;
}
#endif
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
float FrameBuffer::maxWindowZoom() const
double FrameBuffer::maxWindowZoom() const
{
const int display = displayId(BufferType::Emulator);
float multiplier = 1;
const bool showBezel = myOSystem.settings().getBool("bezel.show");
const float scaleW = showBezel ? (16.F / 9.F) / (4.F / 3.F) : 1.F; // = 1.333
double multiplier = 1;
for(;;)
{
// Figure out the zoomed size of the window
const uInt32 width = TIAConstants::viewableWidth * multiplier * scaleW;
const uInt32 height = TIAConstants::viewableHeight * multiplier;
// Figure out the zoomed size of the window (incl. the bezel)
const uInt32 width = static_cast<double>(TIAConstants::viewableWidth) * myBezel->ratioW() * multiplier;
const uInt32 height = static_cast<double>(TIAConstants::viewableHeight) * myBezel->ratioH() * multiplier;
if((width > myAbsDesktopSize[display].w) ||
(height > myAbsDesktopSize[display].h))

View File

@ -25,6 +25,7 @@ class Console;
class Settings;
class FBSurface;
class TIASurface;
class Bezel;
#ifdef GUI_SUPPORT
#include "Font.hxx"
@ -53,7 +54,7 @@ class FrameBuffer
{
public:
// Zoom level step interval
static constexpr float ZOOM_STEPS = 0.25;
static constexpr double ZOOM_STEPS = 0.25;
enum UpdateMode {
NONE = 0,
@ -219,8 +220,8 @@ class FrameBuffer
Get the minimum/maximum supported TIA zoom level (windowed mode)
for the framebuffer.
*/
float supportedTIAMinZoom() const { return myTIAMinZoom * hidpiScaleFactor(); }
float supportedTIAMaxZoom() const { return maxWindowZoom(); }
double supportedTIAMinZoom() const { return myTIAMinZoom * hidpiScaleFactor(); }
double supportedTIAMaxZoom() const { return maxWindowZoom(); }
/**
Get the TIA surface associated with the framebuffer.
@ -349,6 +350,19 @@ class FrameBuffer
myBackend->getRGB(pixel, r, g, b);
}
/**
This method is called to retrieve the R/G/B/A data from the given pixel.
@param pixel The pixel containing R/G/B data
@param r The red component of the color
@param g The green component of the color
@param b The blue component of the color
@param a The alpha component of the color.
*/
void getRGBA(uInt32 pixel, uInt8* r, uInt8* g, uInt8* b, uInt8* a) const {
myBackend->getRGBA(pixel, r, g, b, a);
}
/**
This method is called to map a given R/G/B triple to the screen palette.
@ -361,11 +375,12 @@ class FrameBuffer
}
/**
This method is called to map a given R/G/B triple to the screen palette.
This method is called to map a given R/G/B/A triple to the screen palette.
@param r The red component of the color.
@param g The green component of the color.
@param b The blue component of the color.
@param a The alpha component of the color.
*/
uInt32 mapRGBA(uInt8 r, uInt8 g, uInt8 b, uInt8 a) const {
return myBackend->mapRGBA(r, g, b, a);
@ -462,37 +477,11 @@ class FrameBuffer
*/
FBInitStatus applyVideoMode();
#ifdef IMAGE_SUPPORT
/**
Return bezel names, which are either read from the properties
or generated from the cart name.
@param index The index of the returned bezel name
@return The bezel name for the given index
*/
const string getBezelName(int& index) const;
/**
Check if a bezel for the current ROM name exists.
@return Whether the bezel was found or not
*/
bool checkBezel();
/**
Load the bezel for the current ROM.
@return Whether the bezel was loaded or not
*/
bool loadBezel();
#endif
/**
Calculate the maximum level by which the base window can be zoomed and
still fit in the desktop screen.
*/
float maxWindowZoom() const;
double maxWindowZoom() const;
/**
Enables/disables fullscreen mode.
@ -567,7 +556,7 @@ class FrameBuffer
shared_ptr<TIASurface> myTIASurface;
// The BezelSurface which blends over the TIA surface
shared_ptr<FBSurface> myBezelSurface;
unique_ptr<Bezel> myBezel;
// Used for onscreen messages and frame statistics
// (scanline count and framerate)
@ -594,7 +583,7 @@ class FrameBuffer
vector<bool> myHiDPIEnabled;
// Minimum TIA zoom level that can be used for this framebuffer
float myTIAMinZoom{2.F};
double myTIAMinZoom{2.F};
// Holds a reference to all the surfaces that have been created
std::list<shared_ptr<FBSurface>> mySurfaceList;

View File

@ -64,7 +64,11 @@ Settings::Settings()
setPermanent("pausedim", "true");
setPermanent("bezel.show", "true");
setPermanent("bezel.windowed", "false");
setPermanent("bezel.border", "30");
setPermanent("bezel.autoborders", "true");
setPermanent("bezel.leftborder", "0");
setPermanent("bezel.rightborder", "0");
setPermanent("bezel.topborder", "0");
setPermanent("bezel.bottomborder", "0");
// TIA specific options
setPermanent("tia.inter", "false");
setPermanent("tia.zoom", "3");
@ -544,6 +548,11 @@ void Settings::usage()
<< " -pausedim <1|0> Enable emulation dimming in pause mode\n"
<< " -bezel.show <1|0> Show bezel left and right of emulation\n"
<< " -bezel.windowed <1|0> Show bezel in windowed modes\n"
<< " -bezel.autoborders <1|0> Automatically set bezel window borders\n"
<< " -bezel.leftborder <number> Set left bezel window border\n"
<< " -bezel.rightborder <number> Set right bezel window border\n"
<< " -bezel.topborder <number> Set top bezel window border\n"
<< " -bezel.bottomborder <number> Set bottom bezel window border\n"
<< endl
#ifdef SOUND_SUPPORT
<< " -audio.enabled <1|0> Enable audio\n"

View File

@ -2,6 +2,7 @@ MODULE := src/emucore
MODULE_OBJS := \
src/emucore/AtariVox.o \
src/emucore/Bezel.o
src/emucore/Bankswitch.o \
src/emucore/Booster.o \
src/emucore/Cart.o \

View File

@ -1410,7 +1410,7 @@ void DeveloperDialog::handleDebugColours(int idx, int color)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void DeveloperDialog::handleDebugColours(string_view colors)
{
for(int i = 0; i < DEBUG_COLORS; ++i)
for(int i = 0; i < DEBUG_COLORS && i < colors.length(); ++i)
{
switch(colors[i])
{

View File

@ -478,21 +478,47 @@ void VideoAudioDialog::addBezelTab()
ypos += lineHeight + VGAP * 3;
myBezelShowWindowed = new CheckboxWidget(myTab, _font, xpos, ypos,
"Show in windowed modes");
//myBezelEnableCheckbox->setToolTip(Event::BezelToggle);
//myBezelShowWindowed->setToolTip(Event::BezelToggle);
wid.push_back(myBezelShowWindowed);
// Disable auto borders
ypos += lineHeight + VGAP * 1;
myTopBorderSlider = new SliderWidget(myTab, _font, xpos, ypos,
"Top border ", 0, 0, 6 * fontWidth, "px");
myTopBorderSlider->setMinValue(0); myTopBorderSlider->setMaxValue(50);
myManualBorders = new CheckboxWidget(myTab, _font, xpos, ypos,
"Manual borders", kAutoBordersChanged);
myManualBorders->setToolTip("Enable if automatic border detection fails.");
wid.push_back(myManualBorders);
xpos += INDENT;
const int lWidth = _font.getStringWidth("Bottom ");
const int sWidth = myBezelPath->getRight() - xpos - lWidth - 5.5 * fontWidth; // _w - HBORDER - xpos - lwidth;
ypos += lineHeight + VGAP * 1;
myLeftBorderSlider = new SliderWidget(myTab, _font, xpos, ypos, sWidth, lineHeight,
"Left ", 0, 0, 5 * fontWidth, "px");
myLeftBorderSlider->setMinValue(0); myLeftBorderSlider->setMaxValue(500);
myLeftBorderSlider->setTickmarkIntervals(10);
//myLeftBorderSlider->setToolTip(Event::VolumeDecrease, Event::VolumeIncrease);
wid.push_back(myLeftBorderSlider);
ypos += lineHeight + VGAP * 1;
myRightBorderSlider = new SliderWidget(myTab, _font, xpos, ypos, sWidth, lineHeight,
"Right ", 0, 0, 5 * fontWidth, "px");
myRightBorderSlider->setMinValue(0); myRightBorderSlider->setMaxValue(500);
myRightBorderSlider->setTickmarkIntervals(10);
//myRightBorderSlider->setToolTip(Event::VolumeDecrease, Event::VolumeIncrease);
wid.push_back(myRightBorderSlider);
ypos += lineHeight + VGAP * 1;
myTopBorderSlider = new SliderWidget(myTab, _font, xpos, ypos, sWidth, lineHeight,
"Top ", 0, 0, 5 * fontWidth, "px");
myTopBorderSlider->setMinValue(0); myTopBorderSlider->setMaxValue(250);
myTopBorderSlider->setTickmarkIntervals(5);
//myTopBorderSlider->setToolTip(Event::VolumeDecrease, Event::VolumeIncrease);
wid.push_back(myTopBorderSlider);
ypos += lineHeight + VGAP;
myBtmBorderSlider = new SliderWidget(myTab, _font, xpos, ypos,
"Bottom border ", 0, 0, 6 * fontWidth, "px");
myBtmBorderSlider->setMinValue(0); myBtmBorderSlider->setMaxValue(50);
myBtmBorderSlider = new SliderWidget(myTab, _font, xpos, ypos, sWidth, lineHeight,
"Bottom ", 0, 0, 5 * fontWidth, "px");
myBtmBorderSlider->setMinValue(0); myBtmBorderSlider->setMaxValue(250);
myBtmBorderSlider->setTickmarkIntervals(5);
//myBtmBorderSlider->setToolTip(Event::VolumeDecrease, Event::VolumeIncrease);
wid.push_back(myBtmBorderSlider);
@ -748,6 +774,9 @@ void VideoAudioDialog::loadConfig()
myBezelEnableCheckbox->setState(settings.getBool("bezel.show"));
myBezelPath->setText(settings.getString("bezel.dir"));
myBezelShowWindowed->setState(settings.getBool("bezel.windowed"));
myManualBorders->setState(!settings.getBool("bezel.autoborders"));
myLeftBorderSlider->setValue(settings.getInt("bezel.leftborder"));
myRightBorderSlider->setValue(settings.getInt("bezel.rightborder"));
myTopBorderSlider->setValue(settings.getInt("bezel.topborder"));
myBtmBorderSlider->setValue(settings.getInt("bezel.bottomborder"));
handleBezelChange();
@ -880,9 +909,11 @@ void VideoAudioDialog::saveConfig()
settings.setValue("bezel.show", myBezelEnableCheckbox->getState());
settings.setValue("bezel.dir", myBezelPath->getText());
settings.setValue("bezel.windowed", myBezelShowWindowed->getState());
settings.setValue("bezel.autoborders", !myManualBorders->getState());
settings.setValue("bezel.leftborder", myLeftBorderSlider->getValueLabel());
settings.setValue("bezel.rightborder", myRightBorderSlider->getValueLabel());
settings.setValue("bezel.topborder", myTopBorderSlider->getValueLabel());
settings.setValue("bezel.bottomborder", myBtmBorderSlider->getValueLabel());
cerr << myTopBorderSlider << endl;
// Note: The following has to happen after all video related setting have been saved
if(instance().hasConsole())
@ -1022,8 +1053,7 @@ void VideoAudioDialog::setDefaults()
myBezelEnableCheckbox->setState(true);
myBezelPath->setText(instance().userDir().getShortPath());
myBezelShowWindowed->setState(false);
myTopBorderSlider->setValue(0);
myBtmBorderSlider->setValue(0);
myManualBorders->setState(false);
handleBezelChange();
break;
@ -1193,12 +1223,15 @@ void VideoAudioDialog::handlePhosphorChange()
void VideoAudioDialog::handleBezelChange()
{
const bool enable = myBezelEnableCheckbox->getState();
const bool nonAuto = myManualBorders->getState();
myOpenBrowserButton->setEnabled(enable);
myBezelPath->setEnabled(enable);
myBezelShowWindowed->setEnabled(enable);
myTopBorderSlider->setEnabled(enable);
myBtmBorderSlider->setEnabled(enable);
myLeftBorderSlider->setEnabled(enable && nonAuto);
myRightBorderSlider->setEnabled(enable && nonAuto);
myTopBorderSlider->setEnabled(enable && nonAuto);
myBtmBorderSlider->setEnabled(enable && nonAuto);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -1317,6 +1350,7 @@ void VideoAudioDialog::handleCommand(CommandSender* sender, int cmd,
break;
case kBezelEnableChanged:
case kAutoBordersChanged:
handleBezelChange();
break;

View File

@ -135,6 +135,9 @@ class VideoAudioDialog : public Dialog
ButtonWidget* myOpenBrowserButton{nullptr};
EditTextWidget* myBezelPath{nullptr};
CheckboxWidget* myBezelShowWindowed{nullptr};
CheckboxWidget* myManualBorders{nullptr};
SliderWidget* myLeftBorderSlider{nullptr};
SliderWidget* myRightBorderSlider{nullptr};
SliderWidget* myTopBorderSlider{nullptr};
SliderWidget* myBtmBorderSlider{nullptr};
@ -180,6 +183,7 @@ class VideoAudioDialog : public Dialog
kBezelEnableChanged = 'BZen',
kChooseBezelDirCmd = 'BZsl',
kAutoBordersChanged = 'BZab',
kSoundEnableChanged = 'ADse',
kDeviceChanged = 'ADdc',

View File

@ -993,6 +993,7 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug-NoDebugger|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\emucore\Bankswitch.cxx" />
<ClCompile Include="..\..\emucore\Bezel.cxx" />
<ClCompile Include="..\..\emucore\Cart03E0.cxx" />
<ClCompile Include="..\..\emucore\Cart3EPlus.cxx" />
<ClCompile Include="..\..\emucore\Cart3EX.cxx" />
@ -2320,6 +2321,7 @@
<ClInclude Include="..\..\emucore\AmigaMouse.hxx" />
<ClInclude Include="..\..\emucore\AtariMouse.hxx" />
<ClInclude Include="..\..\emucore\Bankswitch.hxx" />
<ClInclude Include="..\..\emucore\Bezel.hxx" />
<ClInclude Include="..\..\emucore\Cart03E0.hxx" />
<ClInclude Include="..\..\emucore\Cart3EPlus.hxx" />
<ClInclude Include="..\..\emucore\Cart3EX.hxx" />

View File

@ -1209,6 +1209,9 @@
<ClCompile Include="..\..\debugger\gui\Cart03E0Widget.cxx">
<Filter>Source Files\debugger\gui</Filter>
</ClCompile>
<ClCompile Include="..\..\emucore\Bezel.cxx">
<Filter>Source Files\emucore</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\common\bspf.hxx">
@ -2465,6 +2468,9 @@
<ClInclude Include="..\..\debugger\gui\Cart03E0Widget.hxx">
<Filter>Header Files\debugger\gui</Filter>
</ClInclude>
<ClInclude Include="..\..\emucore\Bezel.hxx">
<Filter>Header Files\emucore</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="stella.ico">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 994 KiB

After

Width:  |  Height:  |  Size: 549 KiB