1
0
Fork 0

Add output type texturepp for pixel-perfect scaling

The new output type `texturepp' was added, which implements
pixel-perfect scaling using SDL's hardware-accelerated texture output.
In pixel-pefect mode, each original pixel is displayed as a rectangle m
by n pixels, so that m:n yields a reasonably good approximation of the
pixel aspect ratio (PAR) of the emulated graphical mode while using as
much screen space as possible. The balance between the precision of
aspect ratio and the utilisation of screen space is specified as the
`parweight' parameter to pp_getscale() and is currently hard-coded in
sdlmain.cpp.

This implementation emulatates pixel-perfect mode as a special case of
nearest-neighbor interpolation when the horisontal and vertical scaling
factors are integers.
This commit is contained in:
Anton Shepelev 2020-01-29 02:41:33 +03:00 committed by Patryk Obara
parent 83f625178a
commit d1be65b105
10 changed files with 398 additions and 39 deletions

View file

@ -19,7 +19,7 @@
#include <sys/types.h>
#include <assert.h>
#include <math.h>
#include <cmath>
#include <fstream>
#include <sstream>
#include <stdlib.h>
@ -413,10 +413,19 @@ forcenormal:
break;
}
gfx_flags=GFX_GetBestMode(gfx_flags);
if (gfx_flags & GFX_UNITY_SCALE &&
simpleBlock != NULL &&
strstr( simpleBlock->name, "Normal" ) == simpleBlock->name ) {
gfx_scalew = 1.0;
gfx_scaleh = 1.0;
xscale = 1 ;
yscale = 1 ;
simpleBlock = &ScaleNormal1x;
}
if (!gfx_flags) {
if (!complexBlock && simpleBlock == &ScaleNormal1x)
if (!complexBlock && simpleBlock == &ScaleNormal1x)
E_Exit("Failed to create a rendering output");
else
else
goto forcenormal;
}
width *= xscale;
@ -433,10 +442,18 @@ forcenormal:
}
}
/* Setup the scaler variables */
#if C_OPENGL
GFX_SetShader(render.shader_src);
#endif
gfx_flags=GFX_SetSize(width,height,gfx_flags,gfx_scalew,gfx_scaleh,&RENDER_CallBack);
double par; /* the pixel aspect ratio of the source pixel array */
par = (double)width / height / 4 * 3; /* MS-DOS screen is always 4:3 */
if (dblh)
gfx_flags |= GFX_DBL_H;
if (dblw)
gfx_flags |= GFX_DBL_W;
#if C_OPENGL
GFX_SetShader(render.shader_src);
#endif
gfx_flags=GFX_SetSize(width,height,gfx_flags,gfx_scalew,gfx_scaleh,&RENDER_CallBack,par);
if (gfx_flags & GFX_CAN_8)
render.scale.outMode = scalerMode8;
else if (gfx_flags & GFX_CAN_15)
@ -445,7 +462,7 @@ forcenormal:
render.scale.outMode = scalerMode16;
else if (gfx_flags & GFX_CAN_32)
render.scale.outMode = scalerMode32;
else
else
E_Exit("Failed to create a rendering output");
ScalerLineBlock_t *lineBlock;
if (gfx_flags & GFX_HARDWARE) {
@ -532,7 +549,7 @@ static void RENDER_CallBack( GFX_CallBackFunctions_t function ) {
void RENDER_SetSize(Bitu width,Bitu height,Bitu bpp,float fps,double ratio,bool dblw,bool dblh) {
RENDER_Halt( );
if (!width || !height || width > SCALER_MAXWIDTH || height > SCALER_MAXHEIGHT) {
if (!width || !height || width > SCALER_MAXWIDTH || height > SCALER_MAXHEIGHT) {
return;
}
if ( ratio > 1 ) {
@ -641,7 +658,7 @@ static bool RENDER_GetShader(std::string& shader_path) {
if (render.shader_src==NULL || s != render.shader_src) {
src = strdup(s.c_str());
if (src==NULL) LOG_MSG("WARNING: Couldn't copy shader source");
} else {
} else {
src = render.shader_src;
render.shader_src = NULL;
}

View file

@ -30,6 +30,7 @@
#include <unistd.h>
#include <stdarg.h>
#include <sys/types.h>
#include <math.h>
#ifdef WIN32
#include <signal.h>
#include <process.h>
@ -55,6 +56,10 @@
#include "control.h"
#include "render.h"
extern "C" {
#include "../libs/ppscale/ppscale.h"
}
#define MAPPERFILE "mapper-sdl2-" VERSION ".map"
//#define DISABLE_JOYSTICK
@ -203,6 +208,9 @@ enum SCREEN_TYPES {
SCREEN_OPENGL
};
/* Modes of simple pixel scaling, currently encoded in the output type: */
enum ScalingMode {SmNone, SmNearest, SmPerfect};
enum PRIORITY_LEVELS {
PRIORITY_LEVEL_PAUSE,
PRIORITY_LEVEL_LOWEST,
@ -212,18 +220,19 @@ enum PRIORITY_LEVELS {
PRIORITY_LEVEL_HIGHEST
};
struct SDL_Block {
bool inited;
bool active; //If this isn't set don't draw
bool updating;
bool update_display_contents;
bool resizing_window;
ScalingMode scaling_mode;
struct {
Bit32u width;
Bit32u height;
Bitu flags;
double scalex,scaley;
Bit32u width;
Bit32u height;
double pixel_aspect;
Bitu flags;
double scalex, scaley;
GFX_CallBack_t callback;
} draw;
bool wait_on_error;
@ -294,6 +303,8 @@ struct SDL_Block {
MouseControlType control_choice;
bool middle_will_release;
} mouse;
int ppscale_x, ppscale_y; /* x and y scales for pixel-perfect */
bool double_h, double_w; /* double-height and double-width flags */
SDL_Rect updateRects[1024];
Bitu num_joysticks;
#if defined (WIN32)
@ -441,7 +452,10 @@ static void PauseDOSBox(bool pressed) {
}
/* Reset the screen with current values in the sdl structure */
Bitu GFX_GetBestMode(Bitu flags) {
Bitu GFX_GetBestMode(Bitu flags)
{
if (sdl.scaling_mode == SmPerfect)
flags |= GFX_UNITY_SCALE;
switch (sdl.desktop.want_type) {
case SCREEN_SURFACE:
check_surface:
@ -773,17 +787,111 @@ static bool LoadGLShaders(const char *src, GLuint *vertex, GLuint *fragment) {
}
#endif
Bitu GFX_SetSize(Bitu width,Bitu height,Bitu flags,double scalex,double scaley,GFX_CallBack_t callback) {
/* Get the maximum area available for drawing: */
/* TODO: The current implementation will not let the pixel-perfect mode to */
/* to apply even the simplest 1:2 and 2:1 corrections at the original */
/* window resolution, therefore: consider a special case for PP mode. */
static void GetAvailableArea(int &width, int &height)
{
bool fixed;
double par;
fixed = false;
if (sdl.desktop.fullscreen) {
if (sdl.desktop.full.fixed) {
width = sdl.desktop.full.width;
height = sdl.desktop.full.height;
fixed = true;
}
}
else {
if (sdl.desktop.window.width > 0) {
width = sdl.desktop.window.width;
height = sdl.desktop.window.height;
fixed = true;
}
}
if (!fixed) {
par = sdl.draw.pixel_aspect;
if (par > 1.0)
height = static_cast<int>(round(height * par));
if (par < 1.0)
width = static_cast<int>(round(width / par));
}
}
// ATT: aspect is the final aspect ratio of the image including its pixel dimensions and PAR
/*static void GetActualArea( Bit16u av_w, Bit16u av_h, Bit16u *w, Bit16u *h, double aspect )
{ double as_x, as_y;
if( aspect > 1.0 )
{ as_y = aspect; as_x = 1.0; } else
{ as_x = 1.0/aspect; as_y = 1.0; }
if( av_h / as_y < av_w / as_x )
{ *h = av_h; *w = round( (double)av_h / aspect ); } else
{ *w = av_w; *h = round( (double)av_w * aspect ); }
}*/
/* Initialise pixel-perfect mode: */
static bool InitPp(Bit16u avw, Bit16u avh)
{
bool ok;
double newpar;
/* TODO: consider reading apsect importance from the .ini-file */
ok = pp_getscale(
sdl.draw.width, sdl.draw.height,
sdl.draw.pixel_aspect,
avw, avh,
1.14, /* relative importance of aspect ratio */
&sdl.ppscale_x,
&sdl.ppscale_y
) == 0;
if (ok) {
newpar = ( double )sdl.ppscale_y / sdl.ppscale_x;
LOG_MSG( "Pixel-perfect scaling:\n"
"%ix%i (%#4.3g) --[%ix%i]--> %ix%i (%#4.3g)",
sdl.draw.width, sdl.draw.height, sdl.draw.pixel_aspect,
sdl.ppscale_x, sdl.ppscale_y,
sdl.ppscale_x * sdl.draw.width, sdl.ppscale_y * sdl.draw.height,
newpar);
}
return ok;
}
Bitu GFX_SetSize(Bitu width, Bitu height, Bitu flags,
double scalex, double scaley,
GFX_CallBack_t callback,
double pixel_aspect)
{
int avw, avh; /* available width and height */
Bitu retFlags = 0;
if (sdl.updating)
GFX_EndUpdate( 0 );
sdl.draw.pixel_aspect = pixel_aspect;
sdl.draw.width=width;
sdl.draw.height=height;
sdl.draw.callback=callback;
sdl.draw.scalex=scalex;
sdl.draw.scaley=scaley;
Bitu retFlags = 0;
sdl.double_h = (flags & GFX_DBL_H) > 0;
sdl.double_w = (flags & GFX_DBL_W) > 0;
avw = width;
avh = height;
GetAvailableArea(avw, avh);
LOG_MSG("Input image: %ix%i DW: %i DH: %i PAR: %5.3g",
(int)width, (int)height, sdl.double_h, sdl.double_w, pixel_aspect );
LOG_MSG("Available area: %ix%i", avw, avh);
if (sdl.scaling_mode == SmPerfect) {
if (!InitPp(avw, avh)) {
LOG_MSG("Failed to initialise pixel-perfect mode, reverting to surface.");
goto dosurface;
}
}
switch (sdl.desktop.want_type) {
case SCREEN_SURFACE:
dosurface:
@ -860,11 +968,35 @@ dosurface:
SDL_FillRect(sdl.surface, NULL, SDL_MapRGB(sdl.surface->format, 0, 0, 0));
SDL_UpdateWindowSurface(sdl.window);
break;
case SCREEN_TEXTURE:
{
if (!GFX_SetupWindowScaled(sdl.desktop.want_type)) {
LOG_MSG("SDL:Can't set video mode, falling back to surface");
goto dosurface;
case SCREEN_TEXTURE: {
int imgw, imgh, wndw, wndh; /* image and window width and height */
/* TODO: set up all ScalingMode-related settings here. Currently, the */
/* interpolation hint is set at the reading of settings. */
if (sdl.scaling_mode != SmPerfect) {
if (!GFX_SetupWindowScaled(sdl.desktop.want_type)) {
LOG_MSG("SDL:Can't set video mode, falling back to surface");
goto dosurface;
}
} else {
imgw = sdl.ppscale_x * sdl.draw.width;
imgh = sdl.ppscale_y * sdl.draw.height;
if (sdl.desktop.fullscreen) {
wndh = avh; wndw = avw;
} else {
wndh = imgh; wndw = imgw;
}
sdl.clip.w = imgw;
sdl.clip.h = imgh;
sdl.clip.x = (wndw - imgw) / 2;
sdl.clip.y = (wndh - imgh) / 2;
sdl.window = GFX_SetSDLWindowMode(
wndw, wndh, sdl.desktop.fullscreen,
SCREEN_TEXTURE);
}
if (strcmp(sdl.rendererDriver, "auto"))
SDL_SetHint(SDL_HINT_RENDER_DRIVER, sdl.rendererDriver);
@ -1077,7 +1209,11 @@ dosurface:
// No borders
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
if (!sdl.opengl.bilinear || ( (sdl.clip.h % height) == 0 && (sdl.clip.w % width) == 0) ) {
if(
sdl.scaling_mode != SmNone || (
sdl.clip.h % height == 0 &&
sdl.clip.w % width == 0 )
) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
} else {
@ -1648,18 +1784,26 @@ static void GUI_StartUp(Section * sec) {
sdl.desktop.want_type=SCREEN_SURFACE;
} else if (output == "texture") {
sdl.desktop.want_type=SCREEN_TEXTURE;
sdl.scaling_mode = SmNone;
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
} else if (output == "texturenb") {
sdl.desktop.want_type=SCREEN_TEXTURE;
sdl.scaling_mode = SmNearest;
// Currently the default, but... oh well
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest");
} else if (output == "texturepp") {
sdl.desktop.want_type=SCREEN_TEXTURE;
sdl.scaling_mode = SmPerfect;
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest");
#if C_OPENGL
} else if (output == "opengl") {
sdl.desktop.want_type=SCREEN_OPENGL;
sdl.opengl.bilinear=true;
sdl.scaling_mode = SmNone;
sdl.opengl.bilinear = true;
} else if (output == "openglnb") {
sdl.desktop.want_type=SCREEN_OPENGL;
sdl.opengl.bilinear=false;
sdl.scaling_mode = SmNearest;
sdl.opengl.bilinear = false;
#endif
} else {
LOG_MSG("SDL: Unsupported output device %s, switching back to surface",output.c_str());
@ -2212,6 +2356,7 @@ void Config_Add_SDL() {
"surface",
"texture",
"texturenb",
"texturepp",
#if C_OPENGL
"opengl",
"openglnb",