Get rid of smoothing in favor of scaling settings.

This commit is contained in:
Christian Speckner 2019-12-13 22:46:08 +01:00
parent 31408864ee
commit c4d1473f81
17 changed files with 115 additions and 45 deletions

View File

@ -20,10 +20,32 @@
#include "ThreadDebugging.hxx" #include "ThreadDebugging.hxx"
#include "sdl_blitter/BlitterFactory.hxx" #include "sdl_blitter/BlitterFactory.hxx"
namespace {
BlitterFactory::ScalingAlgorithm scalingAlgorithm(FrameBuffer::ScalingInterpolation interpolation)
{
switch (interpolation) {
case FrameBuffer::ScalingInterpolation::none:
return BlitterFactory::ScalingAlgorithm::nearestNeighbour;
case FrameBuffer::ScalingInterpolation::blur:
return BlitterFactory::ScalingAlgorithm::bilinear;
case FrameBuffer::ScalingInterpolation::sharp:
return BlitterFactory::ScalingAlgorithm::quasiInteger;
default:
throw runtime_error("unreachable");
}
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FBSurfaceSDL2::FBSurfaceSDL2(FrameBufferSDL2& buffer, FBSurfaceSDL2::FBSurfaceSDL2(FrameBufferSDL2& buffer,
uInt32 width, uInt32 height, const uInt32* data) uInt32 width, uInt32 height,
FrameBuffer::ScalingInterpolation interpolation,
const uInt32* data)
: myFB(buffer), : myFB(buffer),
myInterpolationMode(interpolation),
mySurface(nullptr), mySurface(nullptr),
myIsVisible(true), myIsVisible(true),
myIsStatic(false) myIsStatic(false)
@ -215,7 +237,7 @@ void FBSurfaceSDL2::createSurface(uInt32 width, uInt32 height,
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FBSurfaceSDL2::reinitializeBlitter() void FBSurfaceSDL2::reinitializeBlitter()
{ {
if (!myBlitter && myFB.isInitialized()) myBlitter = BlitterFactory::createBlitter(myFB); if (!myBlitter && myFB.isInitialized()) myBlitter = BlitterFactory::createBlitter(myFB, scalingAlgorithm(myInterpolationMode));
if (myBlitter) myBlitter->reinitialize(mySrcR, myDstR, myAttributes, myIsStatic ? mySurface : nullptr); if (myBlitter) myBlitter->reinitialize(mySrcR, myDstR, myAttributes, myIsStatic ? mySurface : nullptr);
} }

View File

@ -33,6 +33,7 @@ class FBSurfaceSDL2 : public FBSurface
{ {
public: public:
FBSurfaceSDL2(FrameBufferSDL2& buffer, uInt32 width, uInt32 height, FBSurfaceSDL2(FrameBufferSDL2& buffer, uInt32 width, uInt32 height,
FrameBuffer::ScalingInterpolation interpolation,
const uInt32* data); const uInt32* data);
virtual ~FBSurfaceSDL2(); virtual ~FBSurfaceSDL2();
@ -80,6 +81,7 @@ class FBSurfaceSDL2 : public FBSurface
FrameBufferSDL2& myFB; FrameBufferSDL2& myFB;
unique_ptr<Blitter> myBlitter; unique_ptr<Blitter> myBlitter;
const FrameBuffer::ScalingInterpolation myInterpolationMode;
SDL_Surface* mySurface; SDL_Surface* mySurface;
SDL_Rect mySrcR, myDstR; SDL_Rect mySrcR, myDstR;

View File

@ -402,10 +402,14 @@ void FrameBufferSDL2::setWindowIcon()
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
unique_ptr<FBSurface> unique_ptr<FBSurface> FrameBufferSDL2::createSurface(
FrameBufferSDL2::createSurface(uInt32 w, uInt32 h, const uInt32* data) const uInt32 w,
uInt32 h,
FrameBuffer::ScalingInterpolation interpolation,
const uInt32* data
) const
{ {
return make_unique<FBSurfaceSDL2>(const_cast<FrameBufferSDL2&>(*this), w, h, data); return make_unique<FBSurfaceSDL2>(const_cast<FrameBufferSDL2&>(*this), w, h, interpolation, data);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -159,12 +159,18 @@ class FrameBufferSDL2 : public FrameBuffer
/** /**
This method is called to create a surface with the given attributes. This method is called to create a surface with the given attributes.
@param w The requested width of the new surface. @param w The requested width of the new surface.
@param h The requested height of the new surface. @param h The requested height of the new surface.
@param data If non-null, use the given data values as a static surface @param interpolation Interpolation mode
@param data If non-null, use the given data values as a static surface
*/ */
unique_ptr<FBSurface> unique_ptr<FBSurface>
createSurface(uInt32 w, uInt32 h, const uInt32* data) const override; createSurface(
uInt32 w,
uInt32 h,
FrameBuffer::ScalingInterpolation interpolation,
const uInt32* data
) const override;
/** /**
Grabs or ungrabs the mouse based on the given boolean value. Grabs or ungrabs the mouse based on the given boolean value.

View File

@ -20,9 +20,10 @@
#include "ThreadDebugging.hxx" #include "ThreadDebugging.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
BilinearBlitter::BilinearBlitter(FrameBufferSDL2& fb) : BilinearBlitter::BilinearBlitter(FrameBufferSDL2& fb, bool interpolate) :
myTexture(nullptr), myTexture(nullptr),
mySecondaryTexture(nullptr), mySecondaryTexture(nullptr),
myInterpolate(interpolate),
myTexturesAreAllocated(false), myTexturesAreAllocated(false),
myRecreateTextures(false), myRecreateTextures(false),
myStaticData(nullptr), myStaticData(nullptr),
@ -111,7 +112,7 @@ void BilinearBlitter::recreateTexturesIfNecessary()
SDL_TextureAccess texAccess = myStaticData == nullptr ? SDL_TEXTUREACCESS_STREAMING : SDL_TEXTUREACCESS_STATIC; SDL_TextureAccess texAccess = myStaticData == nullptr ? SDL_TEXTUREACCESS_STREAMING : SDL_TEXTUREACCESS_STATIC;
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, myAttributes.smoothing ? "1" : "0"); SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, myInterpolate ? "1" : "0");
myTexture = SDL_CreateTexture(myFB.renderer(), myFB.pixelFormat().format, myTexture = SDL_CreateTexture(myFB.renderer(), myFB.pixelFormat().format,
texAccess, mySrcRect.w, mySrcRect.h); texAccess, mySrcRect.w, mySrcRect.h);

View File

@ -26,7 +26,7 @@ class BilinearBlitter : public Blitter {
public: public:
BilinearBlitter(FrameBufferSDL2& fb); BilinearBlitter(FrameBufferSDL2& fb, bool interpolate);
virtual ~BilinearBlitter(); virtual ~BilinearBlitter();
@ -48,6 +48,7 @@ class BilinearBlitter : public Blitter {
SDL_Rect mySrcRect, myDstRect; SDL_Rect mySrcRect, myDstRect;
FBSurface::Attributes myAttributes; FBSurface::Attributes myAttributes;
bool myInterpolate;
bool myTexturesAreAllocated; bool myTexturesAreAllocated;
bool myRecreateTextures; bool myRecreateTextures;

View File

@ -21,12 +21,24 @@
#include "BilinearBlitter.hxx" #include "BilinearBlitter.hxx"
#include "HqBlitter.hxx" #include "HqBlitter.hxx"
unique_ptr<Blitter> BlitterFactory::createBlitter(FrameBufferSDL2& fb) unique_ptr<Blitter> BlitterFactory::createBlitter(FrameBufferSDL2& fb, ScalingAlgorithm scaling)
{ {
if (!fb.isInitialized()) { if (!fb.isInitialized()) {
throw runtime_error("BlitterFactory requires an initialized framebuffer!"); throw runtime_error("BlitterFactory requires an initialized framebuffer!");
} }
return HqBlitter::isSupported(fb) ? switch (scaling) {
unique_ptr<Blitter>(new HqBlitter(fb)) : unique_ptr<Blitter>(new BilinearBlitter(fb)); case ScalingAlgorithm::nearestNeighbour:
return unique_ptr<Blitter>(new BilinearBlitter(fb, false));
case ScalingAlgorithm::bilinear:
return unique_ptr<Blitter>(new BilinearBlitter(fb, true));
case ScalingAlgorithm::quasiInteger:
return HqBlitter::isSupported(fb) ?
unique_ptr<Blitter>(new HqBlitter(fb)) : unique_ptr<Blitter>(new BilinearBlitter(fb, true));
default:
throw runtime_error("unreachable");
}
} }

View File

@ -18,6 +18,8 @@
#ifndef BLITTER_FACTORY_HXX #ifndef BLITTER_FACTORY_HXX
#define BLITTER_FACTORY_HXX #define BLITTER_FACTORY_HXX
#include <string>
#include "Blitter.hxx" #include "Blitter.hxx"
#include "bspf.hxx" #include "bspf.hxx"
#include "FrameBufferSDL2.hxx" #include "FrameBufferSDL2.hxx"
@ -25,7 +27,15 @@
class BlitterFactory { class BlitterFactory {
public: public:
static unique_ptr<Blitter> createBlitter(FrameBufferSDL2& fb); enum class ScalingAlgorithm {
nearestNeighbour,
bilinear,
quasiInteger
};
public:
static unique_ptr<Blitter> createBlitter(FrameBufferSDL2& fb, ScalingAlgorithm scaling);
}; };
#endif // BLITTER_FACTORY_HXX #endif // BLITTER_FACTORY_HXX

View File

@ -170,7 +170,7 @@ void HqBlitter::recreateTexturesIfNecessary()
myBlankBuffer = make_unique<uInt32[]>(4 * myIntermediateRect.w * myIntermediateRect.h); myBlankBuffer = make_unique<uInt32[]>(4 * myIntermediateRect.w * myIntermediateRect.h);
memset(myBlankBuffer.get(), 0, 4 * myIntermediateRect.w * myIntermediateRect.h); memset(myBlankBuffer.get(), 0, 4 * myIntermediateRect.w * myIntermediateRect.h);
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, myAttributes.smoothing ? "1" : "0"); SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
myIntermediateTexture = SDL_CreateTexture(myFB.renderer(), myFB.pixelFormat().format, myIntermediateTexture = SDL_CreateTexture(myFB.renderer(), myFB.pixelFormat().format,
SDL_TEXTUREACCESS_TARGET, myIntermediateRect.w, myIntermediateRect.h); SDL_TEXTUREACCESS_TARGET, myIntermediateRect.w, myIntermediateRect.h);

View File

@ -34,7 +34,6 @@ FBSurface::FBSurface()
// from this class // from this class
// Set default attributes // Set default attributes
myAttributes.smoothing = false;
myAttributes.blending = false; myAttributes.blending = false;
myAttributes.blendalpha = 100; myAttributes.blendalpha = 100;
} }
@ -455,5 +454,5 @@ const uInt32* FBSurface::myPalette = nullptr;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool operator==(const FBSurface::Attributes& a1, const FBSurface::Attributes& a2) bool operator==(const FBSurface::Attributes& a1, const FBSurface::Attributes& a2)
{ {
return a1.blendalpha == a2.blendalpha && a1.blending == a2.blending && a1.smoothing && a2.smoothing; return a1.blendalpha == a2.blendalpha && a1.blending == a2.blending;
} }

View File

@ -326,7 +326,6 @@ class FBSurface
the specific functionality actually exists. the specific functionality actually exists.
*/ */
struct Attributes { struct Attributes {
bool smoothing; // Scaling is smoothed or blocky
bool blending; // Blending is enabled bool blending; // Blending is enabled
uInt32 blendalpha; // Alpha to use in blending mode (0-100%) uInt32 blendalpha; // Alpha to use in blending mode (0-100%)
}; };

View File

@ -643,10 +643,10 @@ void FrameBuffer::setPauseDelay()
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
shared_ptr<FBSurface> FrameBuffer::allocateSurface(int w, int h, const uInt32* data) shared_ptr<FBSurface> FrameBuffer::allocateSurface(int w, int h, ScalingInterpolation interpolation, const uInt32* data)
{ {
// Add new surface to the list // Add new surface to the list
mySurfaceList.push_back(createSurface(w, h, data)); mySurfaceList.push_back(createSurface(w, h, interpolation, data));
// And return a pointer to it (pointer should be treated read-only) // And return a pointer to it (pointer should be treated read-only)
return mySurfaceList.at(mySurfaceList.size() - 1); return mySurfaceList.at(mySurfaceList.size() - 1);

View File

@ -81,6 +81,12 @@ class FrameBuffer
} }
}; };
enum class ScalingInterpolation {
none,
sharp,
blur
};
// Zoom level step interval // Zoom level step interval
static constexpr float ZOOM_STEPS = 0.25; static constexpr float ZOOM_STEPS = 0.25;
@ -164,13 +170,19 @@ class FrameBuffer
Allocate a new surface. The FrameBuffer class takes all responsibility Allocate a new surface. The FrameBuffer class takes all responsibility
for freeing this surface (ie, other classes must not delete it directly). for freeing this surface (ie, other classes must not delete it directly).
@param w The requested width of the new surface. @param w The requested width of the new surface.
@param h The requested height of the new surface. @param h The requested height of the new surface.
@param data If non-null, use the given data values as a static surface @param interpolation Interpolation mode
@param data If non-null, use the given data values as a static surface
@return A pointer to a valid surface object, or nullptr. @return A pointer to a valid surface object, or nullptr.
*/ */
shared_ptr<FBSurface> allocateSurface(int w, int h, const uInt32* data = nullptr); shared_ptr<FBSurface> allocateSurface(
int w,
int h,
ScalingInterpolation interpolation = ScalingInterpolation::none,
const uInt32* data = nullptr
);
/** /**
Returns the current dimensions of the framebuffer image. Returns the current dimensions of the framebuffer image.
@ -395,13 +407,19 @@ class FrameBuffer
/** /**
This method is called to create a surface with the given attributes. This method is called to create a surface with the given attributes.
z
@param w The requested width of the new surface. @param w The requested width of the new surface.
@param h The requested height of the new surface. @param h The requested height of the new surface.
@param data If non-null, use the given data values as a static surface @param interpolation Interpolation mode
@param data If non-null, use the given data values as a static surface
*/ */
virtual unique_ptr<FBSurface> virtual unique_ptr<FBSurface>
createSurface(uInt32 w, uInt32 h, const uInt32* data) const = 0; createSurface(
uInt32 w,
uInt32 h,
ScalingInterpolation interpolation = ScalingInterpolation::none,
const uInt32* data = nullptr
) const = 0;
/** /**
Calls 'free()' on all surfaces that the framebuffer knows about. Calls 'free()' on all surfaces that the framebuffer knows about.

View File

@ -41,8 +41,11 @@ TIASurface::TIASurface(OSystem& system)
myNTSCFilter.loadConfig(myOSystem.settings()); myNTSCFilter.loadConfig(myOSystem.settings());
// Create a surface for the TIA image and scanlines; we'll need them eventually // Create a surface for the TIA image and scanlines; we'll need them eventually
myTiaSurface = myFB.allocateSurface(AtariNTSC::outWidth(TIAConstants::frameBufferWidth), myTiaSurface = myFB.allocateSurface(
TIAConstants::frameBufferHeight); AtariNTSC::outWidth(TIAConstants::frameBufferWidth),
TIAConstants::frameBufferHeight,
FrameBuffer::ScalingInterpolation::sharp
);
// Generate scanline data, and a pre-defined scanline surface // Generate scanline data, and a pre-defined scanline surface
constexpr uInt32 scanHeight = TIAConstants::frameBufferHeight * 2; constexpr uInt32 scanHeight = TIAConstants::frameBufferHeight * 2;
@ -52,7 +55,7 @@ TIASurface::TIASurface(OSystem& system)
scanData[i] = 0x00000000; scanData[i] = 0x00000000;
scanData[i+1] = 0xff000000; scanData[i+1] = 0xff000000;
} }
mySLineSurface = myFB.allocateSurface(1, scanHeight, scanData); mySLineSurface = myFB.allocateSurface(1, scanHeight, FrameBuffer::ScalingInterpolation::sharp, scanData);
// Base TIA surface for use in taking snapshots in 1x mode // Base TIA surface for use in taking snapshots in 1x mode
myBaseTiaSurface = myFB.allocateSurface(TIAConstants::frameBufferWidth*2, myBaseTiaSurface = myFB.allocateSurface(TIAConstants::frameBufferWidth*2,
@ -255,13 +258,8 @@ void TIASurface::enableNTSC(bool enable)
myTiaSurface->setSrcSize(enable ? AtariNTSC::outWidth(TIAConstants::frameBufferWidth) myTiaSurface->setSrcSize(enable ? AtariNTSC::outWidth(TIAConstants::frameBufferWidth)
: TIAConstants::frameBufferWidth, myTIA->height()); : TIAConstants::frameBufferWidth, myTIA->height());
FBSurface::Attributes& tia_attr = myTiaSurface->attributes();
tia_attr.smoothing = myOSystem.settings().getBool("tia.inter");
myTiaSurface->applyAttributes();
myScanlinesEnabled = myOSystem.settings().getInt("tv.scanlines") > 0; myScanlinesEnabled = myOSystem.settings().getInt("tv.scanlines") > 0;
FBSurface::Attributes& sl_attr = mySLineSurface->attributes(); FBSurface::Attributes& sl_attr = mySLineSurface->attributes();
sl_attr.smoothing = true;
sl_attr.blending = myScanlinesEnabled; sl_attr.blending = myScanlinesEnabled;
sl_attr.blendalpha = myOSystem.settings().getInt("tv.scanlines"); sl_attr.blendalpha = myOSystem.settings().getInt("tv.scanlines");
mySLineSurface->applyAttributes(); mySLineSurface->applyAttributes();
@ -284,12 +282,11 @@ string TIASurface::effectsInfo() const
buf << "Disabled, phosphor mode"; buf << "Disabled, phosphor mode";
break; break;
case Filter::BlarggNormal: case Filter::BlarggNormal:
buf << myNTSCFilter.getPreset() << ", scanlines=" << attr.blendalpha << "/" buf << myNTSCFilter.getPreset() << ", scanlines=" << attr.blendalpha;
<< (attr.smoothing ? "inter" : "nointer");
break; break;
case Filter::BlarggPhosphor: case Filter::BlarggPhosphor:
buf << myNTSCFilter.getPreset() << ", phosphor, scanlines=" buf << myNTSCFilter.getPreset() << ", phosphor, scanlines="
<< attr.blendalpha << "/" << (attr.smoothing ? "inter" : "nointer"); << attr.blendalpha;
break; break;
} }
return buf.str(); return buf.str();

View File

@ -87,7 +87,6 @@ void RomInfoWidget::parseProperties(const FilesystemNode& node)
{ {
mySurface = instance().frameBuffer().allocateSurface( mySurface = instance().frameBuffer().allocateSurface(
TIAConstants::viewableWidth*2, TIAConstants::viewableHeight*2); TIAConstants::viewableWidth*2, TIAConstants::viewableHeight*2);
mySurface->attributes().smoothing = true;
mySurface->applyAttributes(); mySurface->applyAttributes();
dialog().addSurface(mySurface); dialog().addSurface(mySurface);

View File

@ -44,7 +44,7 @@ void FrameBufferLIBRETRO::queryHardware(vector<Common::Size>& fullscreenRes,
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
unique_ptr<FBSurface> unique_ptr<FBSurface>
FrameBufferLIBRETRO::createSurface(uInt32 w, uInt32 h, const uInt32* data) const FrameBufferLIBRETRO::createSurface(uInt32 w, uInt32 h, FrameBuffer::ScalingInterpolation, const uInt32* data) const
{ {
unique_ptr<FBSurface> ptr = make_unique<FBSurfaceLIBRETRO> unique_ptr<FBSurface> ptr = make_unique<FBSurfaceLIBRETRO>
(const_cast<FrameBufferLIBRETRO&>(*this), w, h, data); (const_cast<FrameBufferLIBRETRO&>(*this), w, h, data);

View File

@ -147,7 +147,7 @@ class FrameBufferLIBRETRO : public FrameBuffer
@param data If non-null, use the given data values as a static surface @param data If non-null, use the given data values as a static surface
*/ */
unique_ptr<FBSurface> unique_ptr<FBSurface>
createSurface(uInt32 w, uInt32 h, const uInt32* data) const override; createSurface(uInt32 w, uInt32 h, FrameBuffer::ScalingInterpolation, const uInt32* data) const override;
/** /**
Grabs or ungrabs the mouse based on the given boolean value. Grabs or ungrabs the mouse based on the given boolean value.