diff --git a/configure.ac b/configure.ac index d268e519..9273417f 100644 --- a/configure.ac +++ b/configure.ac @@ -641,6 +641,7 @@ src/libs/zmbv/Makefile src/libs/gui_tk/Makefile src/libs/decoders/Makefile src/libs/nuked/Makefile +src/libs/ppscale/Makefile src/misc/Makefile src/shell/Makefile src/platform/Makefile diff --git a/include/video.h b/include/video.h index fe206b4e..8a84f346 100644 --- a/include/video.h +++ b/include/video.h @@ -30,28 +30,34 @@ typedef enum { typedef void (*GFX_CallBack_t)( GFX_CallBackFunctions_t function ); -#define GFX_CAN_8 0x0001 -#define GFX_CAN_15 0x0002 -#define GFX_CAN_16 0x0004 -#define GFX_CAN_32 0x0008 +#define GFX_CAN_8 0x0001 +#define GFX_CAN_15 0x0002 +#define GFX_CAN_16 0x0004 +#define GFX_CAN_32 0x0008 -#define GFX_LOVE_8 0x0010 -#define GFX_LOVE_15 0x0020 -#define GFX_LOVE_16 0x0040 -#define GFX_LOVE_32 0x0080 +#define GFX_LOVE_8 0x0010 +#define GFX_LOVE_15 0x0020 +#define GFX_LOVE_16 0x0040 +#define GFX_LOVE_32 0x0080 -#define GFX_RGBONLY 0x0100 +#define GFX_RGBONLY 0x0100 +#define GFX_DBL_H 0x0200 /* double-width flag */ +#define GFX_DBL_W 0x0400 /* double-height flag */ #define GFX_SCALING 0x1000 #define GFX_HARDWARE 0x2000 -#define GFX_CAN_RANDOM 0x4000 //If the interface can also do random access surface +#define GFX_CAN_RANDOM 0x4000 //If the interface can also do random access surface +#define GFX_UNITY_SCALE 0x8000 /* turn of all scaling in render.cpp */ void GFX_Events(void); Bitu GFX_GetBestMode(Bitu flags); Bitu GFX_GetRGB(Bit8u red,Bit8u green,Bit8u blue); -Bitu GFX_SetSize(Bitu width,Bitu height,Bitu flags,double scalex,double scaley,GFX_CallBack_t cb); void GFX_SetShader(const char* src); +Bitu GFX_SetSize(Bitu width, Bitu height, Bitu flags, + double scalex, double scaley, + GFX_CallBack_t callback, + double pixel_aspect); void GFX_ResetScreen(void); void GFX_Start(void); diff --git a/src/Makefile.am b/src/Makefile.am index 21f810ce..6d5bb82f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -17,6 +17,7 @@ dosbox_LDADD = cpu/libcpu.a debug/libdebug.a dos/libdos.a fpu/libfpu.a hardware hardware/serialport/libserial.a \ libs/decoders/libdecoders.a \ libs/gui_tk/libgui_tk.a \ - libs/nuked/libnuked.a + libs/nuked/libnuked.a \ + libs/ppscale/libppscale.a EXTRA_DIST = winres.rc dosbox.ico diff --git a/src/gui/render.cpp b/src/gui/render.cpp index 3ef27577..c2d1e2aa 100644 --- a/src/gui/render.cpp +++ b/src/gui/render.cpp @@ -19,7 +19,7 @@ #include #include -#include +#include #include #include #include @@ -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; } diff --git a/src/gui/sdlmain.cpp b/src/gui/sdlmain.cpp index a14663c4..fdf5d975 100644 --- a/src/gui/sdlmain.cpp +++ b/src/gui/sdlmain.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #ifdef WIN32 #include #include @@ -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(round(height * par)); + if (par < 1.0) + width = static_cast(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", diff --git a/src/libs/Makefile.am b/src/libs/Makefile.am index 06c434e6..4208233d 100644 --- a/src/libs/Makefile.am +++ b/src/libs/Makefile.am @@ -1,3 +1,3 @@ AM_CPPFLAGS = -I$(top_srcdir)/include -SUBDIRS = zmbv gui_tk decoders nuked +SUBDIRS = zmbv gui_tk decoders nuked ppscale diff --git a/src/libs/ppscale/Makefile.am b/src/libs/ppscale/Makefile.am new file mode 100644 index 00000000..c7b0b4df --- /dev/null +++ b/src/libs/ppscale/Makefile.am @@ -0,0 +1,7 @@ +AM_CPPFLAGS = -I$(top_srcdir)/include + +EXTRA_DIST = ppscale.h + +noinst_LIBRARIES = libppscale.a + +libppscale_a_SOURCES = ppscale.c diff --git a/src/libs/ppscale/ppscale.c b/src/libs/ppscale/ppscale.c new file mode 100644 index 00000000..a2fb66a8 --- /dev/null +++ b/src/libs/ppscale/ppscale.c @@ -0,0 +1,147 @@ +/* Copyright 2018-2020 Anton Shepelev (anton.txt@gmail.com) */ +/* Usage of the works is permitted provided that this instrument is retained */ +/* with the works, so that any entity that uses the works is notified of this */ +/* instrument. DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY. */ + +/* ----------------------- Pixel-perfect scaling unit ----------------------- */ +/* This unit uses the Horstmann indentation style. */ + +#include +#include + +static double min( double a, double b ) +{ double res; + if( a < b ) res = a; + else res = b; + return res; +} + +int pp_getscale /* calculate integer scales for pixel-perfect magnification */ +( int win, int hin, /* input dimensions */ + double par, /* input pixel aspect ratio */ + int wout, int hout, /* output dimensions */ + double parweight, /* weight of PAR in scale estimation */ + int *sx, int *sy /* horisontal and vertical scales */ +) /* returns -1 on error and 0 on success */ +{ int sxc, syc, sxm, sym; /* current and maximum x and y scales */ + int exactpar; /* whether to enforce exact aspect ratio */ + double parrat; /* ratio of current PAR and target PAR */ + double errpar, errsize, err; /* PAR error, size error, and total error */ + double errmin; /* minimal error so far */ + double parnorm; /* target PAR "normalised" to exceed 1.0 */ + double srat; /* ratio of maximum size to current */ + + if /* sanity checks: */ + ( win <= 0 || hin <= 0 || + win > wout || hin > hout || + par <= 0.0 + ) + return -1; + + /* enforce aspect ratio priority for 1:n and 1:n pixel proportions: */ + if( par > 1.0 ) parnorm = par; + else parnorm = 1.0 / par; + /* whether our PAR is an integer ratio: */ + exactpar = parnorm - floor( parnorm ) < 0.01; + + sxm = sxc = (int)floor( ( double )wout / win ); + sym = syc = (int)floor( ( double )hout / hin ); + + errmin = -1; /* this value marks the first iteration */ + while( 1 ) + { parrat = (double)syc / sxc / par; + + /* calculate aspect-ratio error: */ + if( parrat > 1.0 ) errpar = parrat; + else errpar = 1.0 / parrat; + + srat = min( (double)sym/syc, (double)sxm/sxc ); + + /* calculate size error: */ + /* if PAR is exact, exclude size error from the fitness function: */ + if( exactpar ) errsize = 1.0; + else errsize = pow( srat, parweight ); + + err = errpar * errsize; /* total error */ + + /* check for a new optimum: */ + if( err < errmin || errmin == -1 ) + { *sx = sxc; + *sy = syc; + errmin = err; + } + + /* try a smaller magnification: */ + if( parrat < 1.0 ) sxc--; + else syc--; + + /* do not explore magnifications smaller than half the screen: */ + if( srat >= 2.0 ) break; + } + + return 0; +} + +/* RAT: the many scalar arguments are not unifined into one of more stucts */ +/* because doing so somehow thwarts GCC's O3 optimisations, and the */ +/* alrorithm works up to 30% slower. */ +int pp_scale /* magnify an image in a pixel-perfect manner */ +( char* simg, int spitch, /* source image and pitch */ + int *rx, int *ry, /* location and */ + int *rw, int *rh, /* size of the rectangle to process */ + char* dimg, int dpitch, /* destination image and pitch */ + int bypp, /* bytes per pixel */ + int sx, int sy /* horisontal and vertical scales */ +) /* return -1 on error and 0 on success */ +{ int x, y, /* coordinate of a source pixel */ + ix, iy, /* horisontal and vertical subpixel indices */ + b, /* index of byte in a source pixel */ + drowsz; /* destination row size in bytes */ + + char *srow, *spix, *spos, /* source row, current pixel, and position */ + *drow, *drow0, *dpos; /* dest. row, base row, and position */ + + if /* minimal sanity checks: */ + ( bypp < 1 || sx < 1 || sy < 1 || *rw < 1 || *rh < 1 || + simg == NULL || dimg == NULL || + spitch < *rw * bypp || dpitch < *rw * sx * bypp + ) + return -1; + + drowsz = bypp * *rw * sx; + srow = simg + *ry * spitch + *rx * bypp; + drow = dimg + sy * *ry * dpitch + sx * *rx * bypp; + for( y = 0; y < *rh; y += 1 ) + { drow0 = drow; + dpos = drow; + spos = srow; + spix = srow; + + for( x = 0; x < *rw; x += 1 ) + { for( ix = 0; ix < sx; ix += 1 ) + { for( b = 0; b < bypp; b += 1 ) + { *dpos = *spos; + dpos += 1; + spos += 1; + } + spos = spix; + } + spix += bypp; + } + + iy = 1; + while( 1 ) + { drow = drow + dpitch; /* next destination row */ + if( iy == sy ) break; /* terminate if source row scaled */ + memcpy( drow, drow0, drowsz ); /* duplicate base row below */ + iy += 1; + } + + srow = srow + spitch; /* next source row */ + } + + /* return the output rectagle: */ + *rx *= sx; * ry *= sy; *rw *= sx; *rh *= sy; + + return 0; +} diff --git a/src/libs/ppscale/ppscale.h b/src/libs/ppscale/ppscale.h new file mode 100644 index 00000000..a910e418 --- /dev/null +++ b/src/libs/ppscale/ppscale.h @@ -0,0 +1,33 @@ +/* Copyright 2018-2020 Anton Shepelev (anton.txt@gmail.com) */ +/* Usage of the works is permitted provided that this instrument is retained */ +/* with the works, so that any entity that uses the works is notified of this */ +/* instrument. DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY. */ + +/* ----------------------- Pixel-perfect scaling unit ----------------------- */ +/* This unit uses the Horstmann indentation style. */ + +#ifndef PPSCALE_H +#define PPSCALE_H + +int pp_getscale /* calculate integer scales for pixel-perfect magnification */ +( int win, int hin, /* input dimensions */ + double par, /* input pixel aspect ratio */ + int wout, int hout, /* output dimensions */ + double parweight, /* weight of PAR in scale estimation */ + int *sx, int *sy /* horisontal and vertical scales */ +); /* returns -1 on error and 0 on success */ + + +/* RAT: the many scalar arguments are not unifined into one of more stucts */ +/* because doing so somehow thwarts GCC's O3 optimisations, and the */ +/* alrorithm works up to 30% slower. */ +int pp_scale /* magnify an image in a pixel-perfect manner */ +( char* simg, int spitch, /* source image and pitch */ + int *rx, int *ry, /* location and */ + int *rw, int *rh, /* size of the rectangle to process */ + char* dimg, int dpitch, /* destination image and pitch */ + int bypp, /* bytes per pixel */ + int sx, int sy /* horisontal and vertical scales */ +); /* return -1 on error and 0 on success */ + +#endif /* PPSCALE_H */ diff --git a/vs/dosbox.vcxproj b/vs/dosbox.vcxproj index ce671a9f..c79dd53d 100644 --- a/vs/dosbox.vcxproj +++ b/vs/dosbox.vcxproj @@ -252,6 +252,7 @@ + @@ -375,6 +376,7 @@ +