From a01ac1c1680ea93368e24cd63fba38ba8c0ee747 Mon Sep 17 00:00:00 2001 From: Wengier Date: Sun, 20 Sep 2020 17:48:33 -0400 Subject: [PATCH] support for GLSL shader on both SDL1 and SDL2 builds --- CHANGELOG | 7 + contrib/linux/dosbox-x.appdata.xml.in | 2 +- dosbox-x.reference.conf | 5 + include/build_timestamp.h | 4 +- include/render.h | 6 +- include/video.h | 2 +- src/dosbox.cpp | 8 + src/gui/render.cpp | 92 +++- src/gui/render_glsl.h | 576 ++++++++++++++++++++++++++ src/gui/sdlmain.cpp | 142 ++++++- src/output/output_opengl.cpp | 286 +++++++++++-- src/output/output_opengl.h | 54 +++ 12 files changed, 1146 insertions(+), 38 deletions(-) create mode 100644 src/gui/render_glsl.h diff --git a/CHANGELOG b/CHANGELOG index e0c766249..744b4a44e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,11 @@ 0.83.6 + - Added GLSL shader support by porting the feature + from DOSBox SVN. Worked for SDL1 and SDL2 builds. + The config option "glshader" (in [render] section) + can be used to specify a GLSL shader file or a + built-in GLSL shader when the output is set to + "opengl" or "openglnb". For example, you can use + "glshader=sharp" for pixel-perfect mode. (Wengier) - Support for FluidSynth MIDI Synthesizer is now activated by default for Windows Visual Studio x86/x64 builds. (Wengier) diff --git a/contrib/linux/dosbox-x.appdata.xml.in b/contrib/linux/dosbox-x.appdata.xml.in index 534cd40bc..bfab617a2 100644 --- a/contrib/linux/dosbox-x.appdata.xml.in +++ b/contrib/linux/dosbox-x.appdata.xml.in @@ -10,7 +10,7 @@ Emulation - + diff --git a/dosbox-x.reference.conf b/dosbox-x.reference.conf index 8c24b4afa..9d27eb4d8 100644 --- a/dosbox-x.reference.conf +++ b/dosbox-x.reference.conf @@ -737,6 +737,10 @@ pc-98 show graphics layer on initialize = true # To fit a scaler in the resolution used at full screen may require a border or side bars. # To fill the screen entirely, depending on your hardware, a different scaler/fullresolution might work. # Possible values: none, normal2x, normal3x, normal4x, normal5x, advmame2x, advmame3x, advinterp2x, advinterp3x, hq2x, hq3x, 2xsai, super2xsai, supereagle, tv2x, tv3x, rgb2x, rgb3x, scan2x, scan3x, gray, gray2x, hardware_none, hardware2x, hardware3x, hardware4x, hardware5x, xbrz, xbrz_bilinear. +# glshader: Path to GLSL shader source to use with OpenGL output ("none" to disable). +# Can be either an absolute path, a file in the "glshaders" subdirectory of the DOSBox-X configuration directory, +# or one of the built-in shaders (use "sharp" for the pixel-perfect mode): +# advinterp2x, advinterp3x, advmame2x, advmame3x, rgb2x, rgb3x, scan2x, scan3x, tv2x, tv3x, sharp. # xbrz slice: Number of screen lines to process in single xBRZ scaler taskset task, affects xBRZ performance, 16 is the default # xbrz fixed scale factor: To use fixed xBRZ scale factor (i.e. to attune performance), set it to 2-6, 0 - use automatic calculation (default) # xbrz max scale factor: To cap maximum xBRZ scale factor used (i.e. to attune performance), set it to 2-6, 0 - use scaler allowed maximum (default) @@ -753,6 +757,7 @@ char9 = true euro = -1 doublescan = true scaler = normal2x +glshader = none xbrz slice = 16 xbrz fixed scale factor = 0 xbrz max scale factor = 0 diff --git a/include/build_timestamp.h b/include/build_timestamp.h index d7eedbcc3..6bd04fc40 100644 --- a/include/build_timestamp.h +++ b/include/build_timestamp.h @@ -1,4 +1,4 @@ /*auto-generated*/ -#define UPDATED_STR "Sep 19, 2020 6:54:21pm" -#define GIT_COMMIT_HASH "9ea356c" +#define UPDATED_STR "Sep 20, 2020 7:16:36pm" +#define GIT_COMMIT_HASH "8bcc19e" #define COPYRIGHT_END_YEAR "2020" diff --git a/include/render.h b/include/render.h index dde5475c6..fcccd77d7 100644 --- a/include/render.h +++ b/include/render.h @@ -91,6 +91,9 @@ typedef struct { Bit8u *cacheRead; Bitu inHeight, inLine, outLine; } scale; +#if C_OPENGL + char* shader_src; +#endif RenderPal_t pal; bool updating; bool active; @@ -108,6 +111,7 @@ void RENDER_SetSize(Bitu width,Bitu height,Bitu bpp,float fps,double scrn_ratio) bool RENDER_StartUpdate(void); void RENDER_EndUpdate(bool abort); void RENDER_SetPal(Bit8u entry,Bit8u red,Bit8u green,Bit8u blue); - +bool RENDER_GetForceUpdate(void); +void RENDER_SetForceUpdate(bool); #endif diff --git a/include/video.h b/include/video.h index b2fb6e240..967445d89 100644 --- a/include/video.h +++ b/include/video.h @@ -60,7 +60,7 @@ 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 callback); void GFX_TearDown(void); - +void GFX_SetShader(const char* src); void GFX_ResetScreen(void); void GFX_RestoreMode(void); void GFX_Start(void); diff --git a/src/dosbox.cpp b/src/dosbox.cpp index 79543c347..9b9e3dbb3 100644 --- a/src/dosbox.cpp +++ b/src/dosbox.cpp @@ -2235,6 +2235,14 @@ void DOSBOX_SetupConfigSections(void) { Pstring = Pmulti->GetSection()->Add_string("force",Property::Changeable::Always,""); Pstring->Set_values(force); +#if C_OPENGL + Pstring = secprop->Add_path("glshader",Property::Changeable::Always,"none"); + Pstring->Set_help("Path to GLSL shader source to use with OpenGL output (\"none\" to disable).\n" + "Can be either an absolute path, a file in the \"glshaders\" subdirectory of the DOSBox-X configuration directory,\n" + "or one of the built-in shaders (use \"sharp\" for the pixel-perfect mode):\n" + "advinterp2x, advinterp3x, advmame2x, advmame3x, rgb2x, rgb3x, scan2x, scan3x, tv2x, tv3x, sharp."); +#endif + #if C_XBRZ Pint = secprop->Add_int("xbrz slice",Property::Changeable::OnlyAtStart,16); Pint->SetMinMax(1,1024); diff --git a/src/gui/render.cpp b/src/gui/render.cpp index 24d18da6d..4d1b8fc87 100644 --- a/src/gui/render.cpp +++ b/src/gui/render.cpp @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include "dosbox.h" #include "video.h" @@ -31,8 +33,10 @@ #include "hardware.h" #include "support.h" #include "sdlmain.h" +#include "shell.h" #include "render_scalers.h" +#include "render_glsl.h" #if defined(__SSE__) #include #include @@ -366,7 +370,7 @@ void RENDER_EndUpdate( bool abort ) { #endif // Force output to update the screen even if nothing changed... // works only with Direct3D output (GFX_StartUpdate() was probably not even called) - if (render.forceUpdate) GFX_EndUpdate( 0 ); + if (RENDER_GetForceUpdate()) GFX_EndUpdate( 0 ); } render.frameskip.index = (render.frameskip.index + 1) & (RENDER_SKIP_CACHE - 1); render.updating=false; @@ -666,6 +670,9 @@ forcenormal: sdl.srcAspect.yToX = (double)sdl.srcAspect.y / sdl.srcAspect.x; LOG_MSG("Aspect ratio: %u x %u xToY=%.3f yToX=%.3f",sdl.srcAspect.x,sdl.srcAspect.y,sdl.srcAspect.xToY,sdl.srcAspect.yToX); /* 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); if (gfx_flags & GFX_CAN_8) render.scale.outMode = scalerMode8; @@ -846,10 +853,72 @@ static void ChangeScaler(bool pressed) { void RENDER_UpdateFromScalerSetting(void); +bool RENDER_GetForceUpdate(void) { + return render.forceUpdate; +} + void RENDER_SetForceUpdate(bool f) { render.forceUpdate = f; } +#if C_OPENGL +static bool RENDER_GetShader(std::string& shader_path, char *old_src) { + char* src; + std::stringstream buf; + std::ifstream fshader(shader_path.c_str(), std::ios_base::binary); + if (!fshader.is_open()) fshader.open((shader_path + ".glsl").c_str(), std::ios_base::binary); + if (fshader.is_open()) { + buf << fshader.rdbuf(); + fshader.close(); + } + else if (shader_path == "advinterp2x") buf << advinterp2x_glsl; + else if (shader_path == "advinterp3x") buf << advinterp3x_glsl; + else if (shader_path == "advmame2x") buf << advmame2x_glsl; + else if (shader_path == "advmame3x") buf << advmame3x_glsl; + else if (shader_path == "rgb2x") buf << rgb2x_glsl; + else if (shader_path == "rgb3x") buf << rgb3x_glsl; + else if (shader_path == "scan2x") buf << scan2x_glsl; + else if (shader_path == "scan3x") buf << scan3x_glsl; + else if (shader_path == "tv2x") buf << tv2x_glsl; + else if (shader_path == "tv3x") buf << tv3x_glsl; + else if (shader_path == "sharp") buf << sharp_glsl; + + if (!buf.str().empty()) { + std::string s = buf.str(); + if (first_shell) { + std::string pre_defs; + Bitu count = first_shell->GetEnvCount(); + for (Bitu i=0; i < count; i++) { + std::string env; + if (!first_shell->GetEnvNum(i, env)) + continue; + if (env.compare(0, 9, "GLSHADER_")==0) { + size_t brk = env.find('='); + if (brk == std::string::npos) continue; + env[brk] = ' '; + pre_defs += "#define " + env.substr(9) + '\n'; + } + } + if (!pre_defs.empty()) { + // if "#version" occurs it must be before anything except comments and whitespace + size_t pos = buf.str().find("#version "); + if (pos != std::string::npos) pos = buf.str().find('\n', pos+9); + if (pos == std::string::npos) pos = 0; + else ++pos; + s = buf.str().insert(pos, pre_defs); + } + } + // keep the same buffer if contents aren't different + if (old_src==NULL || s != old_src) { + src = strdup(s.c_str()); + if (src==NULL) LOG_MSG("WARNING: Couldn't copy shader source"); + } else src = old_src; + } else src = NULL; + render.shader_src = src; + return src != NULL; +} +#endif + void RENDER_UpdateFrameskipMenu(void) { char tmp[64]; @@ -1091,10 +1160,31 @@ void RENDER_Init() { render.autofit=section->Get_bool("autofit"); +#if C_OPENGL + char* shader_src = render.shader_src; + Prop_path *sh = section->Get_path("glshader"); + std::string f = (std::string)sh->GetValue(); + if (f.empty() || f=="none") render.shader_src = NULL; + else if (!RENDER_GetShader(sh->realpath,shader_src)) { + std::string path; + Cross::GetPlatformConfigDir(path); + path = path + "glshaders" + CROSS_FILESPLIT + f; + if (!RENDER_GetShader(path,shader_src) && (sh->realpath==f || !RENDER_GetShader(f,shader_src))) { + sh->SetValue("none"); + LOG_MSG("Shader file \"%s\" not found", f.c_str()); + } + } else + LOG_MSG("Loaded GLSL shader: %s\n", f.c_str()); + if (shader_src!=render.shader_src) free(shader_src); +#endif + //If something changed that needs a ReInit // Only ReInit when there is a src.bpp (fixes crashes on startup and directly changing the scaler without a screen specified yet) if(running && render.src.bpp && ((render.aspect != aspect) || (render.scale.op != scaleOp) || (render.scale.size != scalersize) || (render.scale.forced != scalerforced) || +#if C_OPENGL + (render.shader_src != shader_src) || +#endif render.scale.forced)) RENDER_CallBack( GFX_CallBackReset ); diff --git a/src/gui/render_glsl.h b/src/gui/render_glsl.h new file mode 100644 index 000000000..1d247e93c --- /dev/null +++ b/src/gui/render_glsl.h @@ -0,0 +1,576 @@ +/* + * Copyright (C) 2002-2020 The DOSBox Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#if C_OPENGL + +static const char advinterp2x_glsl[] = +"varying vec2 v_texCoord;\n" +"uniform sampler2D rubyTexture;\n" +"uniform vec2 rubyInputSize;\n" +"uniform vec2 rubyOutputSize;\n" +"uniform vec2 rubyTextureSize;\n" +"" +"#if defined(VERTEX)\n" +"attribute vec4 a_position;\n" +"" +"void main() {\n" +" gl_Position = a_position;\n" +" v_texCoord = vec2(a_position.x+1.0,1.0-a_position.y)*rubyInputSize;\n" +"}\n" +"" +"#elif defined(FRAGMENT)\n" +"" +"vec3 getadvinterp2xtexel(vec2 coord) {\n" +" vec2 base = floor(coord/vec2(2.0))+vec2(0.5);\n" +" vec3 c4 = texture2D(rubyTexture, base/rubyTextureSize).xyz;\n" +" vec3 c1 = texture2D(rubyTexture, (base-vec2(0.0,1.0))/rubyTextureSize).xyz;\n" +" vec3 c7 = texture2D(rubyTexture, (base+vec2(0.0,1.0))/rubyTextureSize).xyz;\n" +" vec3 c3 = texture2D(rubyTexture, (base-vec2(1.0,0.0))/rubyTextureSize).xyz;\n" +" vec3 c5 = texture2D(rubyTexture, (base+vec2(1.0,0.0))/rubyTextureSize).xyz;\n" +"" +" bool outer = c1 != c7 && c3 != c5;\n" +" bool c3c1 = outer && c3==c1;\n" +" bool c1c5 = outer && c1==c5;\n" +" bool c3c7 = outer && c3==c7;\n" +" bool c7c5 = outer && c7==c5;\n" +"" +" vec3 l00 = mix(c3,c4,c3c1?3.0/8.0:1.0);\n" +" vec3 l01 = mix(c5,c4,c1c5?3.0/8.0:1.0);\n" +" vec3 l10 = mix(c3,c4,c3c7?3.0/8.0:1.0);\n" +" vec3 l11 = mix(c5,c4,c7c5?3.0/8.0:1.0);\n" +"" +" coord = max(floor(mod(coord, 2.0)), 0.0);\n" +" /* 2x2 output:\n" +" * |x=0|x=1\n" +" * y=0|l00|l01\n" +" * y=1|l10|l11\n" +" */\n" +"" +" return mix(mix(l00,l01,coord.x), mix(l10,l11,coord.x), coord.y);\n" +"}\n" +"" +"void main()\n" +"{\n" +" vec2 coord = v_texCoord;\n" +"#if defined(OPENGLNB)\n" +" gl_FragColor = getadvinterp2xtexel(coord);\n" +"#else\n" +" coord -= 0.5;\n" +" vec3 c0 = getadvinterp2xtexel(coord);\n" +" vec3 c1 = getadvinterp2xtexel(coord+vec2(1.0,0.0));\n" +" vec3 c2 = getadvinterp2xtexel(coord+vec2(0.0,1.0));\n" +" vec3 c3 = getadvinterp2xtexel(coord+vec2(1.0));\n" +"" +" coord = fract(max(coord,0.0));\n" +" gl_FragColor = vec4(mix(mix(c0,c1,coord.x), mix(c2,c3,coord.x), coord.y), 1.0);\n" +"#endif\n" +"}\n" +"#endif"; + +static const char advinterp3x_glsl[] = +"varying vec2 v_texCoord;\n" +"uniform sampler2D rubyTexture;\n" +"uniform vec2 rubyInputSize;\n" +"uniform vec2 rubyOutputSize;\n" +"uniform vec2 rubyTextureSize;\n" +"" +"#if defined(VERTEX)\n" +"attribute vec4 a_position;\n" +"" +"void main() {\n" +" gl_Position = a_position;\n" +" v_texCoord = vec2(a_position.x+1.0,1.0-a_position.y)/2.0*rubyInputSize*3.0;\n" +"}\n" +"" +"#elif defined(FRAGMENT)\n" +"" +"vec3 getadvinterp3xtexel(vec2 coord) {\n" +" vec2 base = floor(coord/vec2(3.0))+vec2(0.5);\n" +" vec3 c0 = texture2D(rubyTexture, (base-vec2(1.0,1.0))/rubyTextureSize).xyz;\n" +" vec3 c1 = texture2D(rubyTexture, (base-vec2(0.0,1.0))/rubyTextureSize).xyz;\n" +" vec3 c2 = texture2D(rubyTexture, (base-vec2(-1.0,1.0))/rubyTextureSize).xyz;\n" +" vec3 c3 = texture2D(rubyTexture, (base-vec2(1.0,0.0))/rubyTextureSize).xyz;\n" +" vec3 c4 = texture2D(rubyTexture, base/rubyTextureSize).xyz;\n" +" vec3 c5 = texture2D(rubyTexture, (base+vec2(1.0,0.0))/rubyTextureSize).xyz;\n" +" vec3 c6 = texture2D(rubyTexture, (base+vec2(-1.0,1.0))/rubyTextureSize).xyz;\n" +" vec3 c7 = texture2D(rubyTexture, (base+vec2(0.0,1.0))/rubyTextureSize).xyz;\n" +" vec3 c8 = texture2D(rubyTexture, (base+vec2(1.0,1.0))/rubyTextureSize).xyz;\n" +"" +" bool outer = c1 != c7 && c3 != c5;\n" +"" +" vec3 l00 = mix(c3,c4,(outer && c3==c1) ? 3.0/8.0:1.0);\n" +" vec3 l01 = (outer && ((c3==c1&&c4!=c2)||(c5==c1&&c4!=c0))) ? c1 : c4;\n" +" vec3 l02 = mix(c5,c4,(outer && c5==c1) ? 3.0/8.0:1.0);\n" +" vec3 l10 = (outer && ((c3==c1&&c4!=c6)||(c3==c7&&c4!=c0))) ? c3 : c4;\n" +" vec3 l11 = c4;\n" +" vec3 l12 = (outer && ((c5==c1&&c4!=c8)||(c5==c7&&c4!=c2))) ? c5 : c4;\n" +" vec3 l20 = mix(c3,c4,(outer && c3==c7) ? 3.0/8.0:1.0);\n" +" vec3 l21 = (outer && ((c3==c7&&c4!=c8)||(c5==c7&&c4!=c6))) ? c7 : c4;\n" +" vec3 l22 = mix(c5,c4,(outer && c5==c7) ? 3.0/8.0:1.0);\n" +"" +" coord = mod(coord, 3.0);\n" +" bvec2 l = lessThan(coord, vec2(1.0));\n" +" bvec2 h = greaterThanEqual(coord, vec2(2.0));\n" +"" +" if (h.x) {\n" +" l01 = l02;\n" +" l11 = l12;\n" +" l21 = l22;\n" +" }\n" +" if (h.y) {\n" +" l10 = l20;\n" +" l11 = l21;\n" +" }\n" +" if (l.x) {\n" +" l01 = l00;\n" +" l11 = l10;\n" +" }\n" +" return l.y ? l01 : l11;\n" +"}\n" +"" +"void main()\n" +"{\n" +" vec2 coord = v_texCoord;\n" +"#if defined(OPENGLNB)\n" +" gl_FragColor = getadvinterp3xtexel(coord);\n" +"#else\n" +" coord -= 0.5;\n" +" vec3 c0 = getadvinterp3xtexel(coord);\n" +" vec3 c1 = getadvinterp3xtexel(coord+vec2(1.0,0.0));\n" +" vec3 c2 = getadvinterp3xtexel(coord+vec2(0.0,1.0));\n" +" vec3 c3 = getadvinterp3xtexel(coord+vec2(1.0));\n" +"" +" coord = fract(max(coord,0.0));\n" +" gl_FragColor = vec4(mix(mix(c0,c1,coord.x), mix(c2,c3,coord.x), coord.y), 1.0);\n" +"#endif\n" +"}\n" +"#endif"; + +static const char advmame2x_glsl[] = +"varying vec2 v_texCoord;\n" +"uniform sampler2D rubyTexture;\n" +"uniform vec2 rubyInputSize;\n" +"uniform vec2 rubyOutputSize;\n" +"uniform vec2 rubyTextureSize;\n" +"" +"#if defined(VERTEX)\n" +"attribute vec4 a_position;\n" +"" +"void main() {\n" +" gl_Position = a_position;\n" +" v_texCoord = vec2(a_position.x+1.0,1.0-a_position.y)*rubyInputSize;\n" +"}\n" +"" +"#elif defined(FRAGMENT)\n" +"" +"vec3 getadvmame2xtexel(vec2 coord) {\n" +" vec2 base = floor(coord/vec2(2.0))+vec2(0.5);\n" +" vec3 c4 = texture2D(rubyTexture, base/rubyTextureSize).xyz;\n" +" vec3 c1 = texture2D(rubyTexture, (base-vec2(0.0,1.0))/rubyTextureSize).xyz;\n" +" vec3 c7 = texture2D(rubyTexture, (base+vec2(0.0,1.0))/rubyTextureSize).xyz;\n" +" vec3 c3 = texture2D(rubyTexture, (base-vec2(1.0,0.0))/rubyTextureSize).xyz;\n" +" vec3 c5 = texture2D(rubyTexture, (base+vec2(1.0,0.0))/rubyTextureSize).xyz;\n" +"" +" bool outer = c1 != c7 && c3 != c5;\n" +" bool c3c1 = outer && c3==c1;\n" +" bool c1c5 = outer && c1==c5;\n" +" bool c3c7 = outer && c3==c7;\n" +" bool c7c5 = outer && c7==c5;\n" +"" +" vec3 l00 = mix(c4,c3,c3c1?1.0:0.0);\n" +" vec3 l01 = mix(c4,c5,c1c5?1.0:0.0);\n" +" vec3 l10 = mix(c4,c3,c3c7?1.0:0.0);\n" +" vec3 l11 = mix(c4,c5,c7c5?1.0:0.0);\n" +"" +" coord = max(floor(mod(coord, 2.0)), 0.0);\n" +" /* 2x2 output:\n" +" * |x=0|x=1\n" +" * y=0|l00|l01\n" +" * y=1|l10|l11\n" +" */\n" +"" +" return mix(mix(l00,l01,coord.x), mix(l10,l11,coord.x), coord.y);\n" +"}\n" +"" +"void main()\n" +"{\n" +" vec2 coord = v_texCoord;\n" +"#if defined(OPENGLNB)\n" +" gl_FragColor = getadvmame2xtexel(coord);\n" +"#else\n" +" coord -= 0.5;\n" +" vec3 c0 = getadvmame2xtexel(coord);\n" +" vec3 c1 = getadvmame2xtexel(coord+vec2(1.0,0.0));\n" +" vec3 c2 = getadvmame2xtexel(coord+vec2(0.0,1.0));\n" +" vec3 c3 = getadvmame2xtexel(coord+vec2(1.0));\n" +"" +" coord = fract(max(coord,0.0));\n" +" gl_FragColor = vec4(mix(mix(c0,c1,coord.x), mix(c2,c3,coord.x), coord.y), 1.0);\n" +"#endif\n" +"}\n" +"#endif"; + +static const char advmame3x_glsl[] = +"varying vec2 v_texCoord;\n" +"uniform sampler2D rubyTexture;\n" +"uniform vec2 rubyInputSize;\n" +"uniform vec2 rubyOutputSize;\n" +"uniform vec2 rubyTextureSize;\n" +"" +"#if defined(VERTEX)\n" +"attribute vec4 a_position;\n" +"" +"void main() {\n" +" gl_Position = a_position;\n" +" v_texCoord = vec2(a_position.x+1.0,1.0-a_position.y)/2.0*rubyInputSize*3.0;\n" +"}\n" +"" +"#elif defined(FRAGMENT)\n" +"" +"vec3 getadvmame3xtexel(vec2 coord) {\n" +" vec2 base = floor(coord/vec2(3.0))+vec2(0.5);\n" +" coord = mod(coord, 3.0);\n" +" bvec2 l = lessThan(coord, vec2(1.0));\n" +" bvec2 h = greaterThanEqual(coord, vec2(2.0));\n" +" bvec2 m = equal(l,h);\n" +"" +" vec2 left = vec2(h.x?1.0:-1.0, 0.0);\n" +" vec2 up = vec2(0.0, h.y?1.0:-1.0);\n" +" if (l==h) left.x = 0.0; // hack for center pixel, will ensure outer==false\n" +" if (m.y) {\n" +" // swap\n" +" left -= up;\n" +" up += left;\n" +" left = up - left;\n" +" }\n" +"" +" vec3 c0 = texture2D(rubyTexture, (base+up+left)/rubyTextureSize).xyz;\n" +" vec3 c1 = texture2D(rubyTexture, (base+up)/rubyTextureSize).xyz;\n" +" vec3 c2 = texture2D(rubyTexture, (base+up-left)/rubyTextureSize).xyz;\n" +" vec3 c3 = texture2D(rubyTexture, (base+left)/rubyTextureSize).xyz;\n" +" vec3 c4 = texture2D(rubyTexture, base/rubyTextureSize).xyz;\n" +" vec3 c5 = texture2D(rubyTexture, (base-left)/rubyTextureSize).xyz;\n" +" vec3 c7 = texture2D(rubyTexture, (base-up)/rubyTextureSize).xyz;\n" +"" +" bool outer = c1 != c7 && c3 != c5;\n" +" bool check1 = c3==c1 && (!any(m) || c4!=c2);\n" +" bool check2 = any(m) && c5==c1 && c4!=c0;\n" +"" +" return (outer && (check1 || check2)) ? c1 : c4;\n" +"}\n" +"" +"void main()\n" +"{\n" +" vec2 coord = v_texCoord;\n" +"#if defined(OPENGLNB)\n" +" gl_FragColor = getadvmame3xtexel(coord);\n" +"#else\n" +" coord -= 0.5;\n" +" vec3 c0 = getadvmame3xtexel(coord);\n" +" vec3 c1 = getadvmame3xtexel(coord+vec2(1.0,0.0));\n" +" vec3 c2 = getadvmame3xtexel(coord+vec2(0.0,1.0));\n" +" vec3 c3 = getadvmame3xtexel(coord+vec2(1.0));\n" +"" +" coord = fract(max(coord,0.0));\n" +" gl_FragColor = vec4(mix(mix(c0,c1,coord.x), mix(c2,c3,coord.x), coord.y), 1.0);\n" +"#endif\n" +"}\n" +"#endif"; + +static const char rgb2x_glsl[] = +"varying vec2 v_texCoord;\n" +"uniform sampler2D rubyTexture;\n" +"uniform vec2 rubyInputSize;\n" +"uniform vec2 rubyOutputSize;\n" +"uniform vec2 rubyTextureSize;\n" +"" +"#if defined(VERTEX)\n" +"attribute vec4 a_position;\n" +"" +"void main() {\n" +" gl_Position = a_position;\n" +" v_texCoord = vec2(a_position.x+1.0,1.0-a_position.y)/2.0*rubyInputSize * 2.0;\n" +"}\n" +"" +"#elif defined(FRAGMENT)\n" +"" +"vec4 getRGB2xtexel(vec2 coord) {\n" +" vec2 mid = vec2(0.5);\n" +" vec4 s = texture2D(rubyTexture, (floor(coord/vec2(2.0))+mid)/rubyTextureSize);\n" +"" +" coord = max(floor(mod(coord, 2.0)), 0.0);\n" +" /* 2x2 output:\n" +" * |x=0|x=1\n" +" * y=0| r | g\n" +" * y=1| b |rgb\n" +" */\n" +"" +" s.r *= 1.0 - abs(coord.x - coord.y);\n" +" s.g *= coord.x;\n" +" s.b *= coord.y;\n" +" return s;\n" +"}\n" +"" +"void main()\n" +"{\n" +" vec2 coord = v_texCoord;\n" +"#if defined(OPENGLNB)\n" +" gl_FragColor = getRGB2xtexel(coord);\n" +"#else\n" +" coord -= 0.5;\n" +" vec4 c0 = getRGB2xtexel(coord);\n" +" vec4 c1 = getRGB2xtexel(coord+vec2(1.0,0.0));\n" +" vec4 c2 = getRGB2xtexel(coord+vec2(0.0,1.0));\n" +" vec4 c3 = getRGB2xtexel(coord+vec2(1.0));\n" +"" +" coord = fract(max(coord,0.0));\n" +" gl_FragColor = mix(mix(c0,c1,coord.x), mix(c2,c3,coord.x), coord.y);\n" +"#endif\n" +"}\n" +"#endif"; + +static const char rgb3x_glsl[] = +"varying vec2 v_texCoord;\n" +"uniform sampler2D rubyTexture;\n" +"uniform vec2 rubyInputSize;\n" +"uniform vec2 rubyOutputSize;\n" +"uniform vec2 rubyTextureSize;\n" +"" +"#if defined(VERTEX)\n" +"attribute vec4 a_position;\n" +"" +"void main() {\n" +" gl_Position = a_position;\n" +" v_texCoord = vec2(a_position.x+1.0,1.0-a_position.y)/2.0*rubyInputSize * 3.0;\n" +"}\n" +"" +"#elif defined(FRAGMENT)\n" +"" +"vec4 getRGB3xtexel(vec2 coord) {\n" +" vec2 mid = vec2(0.5);\n" +" vec4 s = texture2D(rubyTexture, (floor(coord/vec2(3.0))+mid)/rubyTextureSize);\n" +"" +" coord = max(floor(mod(coord, 3.0)), 0.0);\n" +" /* 3x3 output:\n" +" * | l | m | h\n" +" * l|rgb| g | b\n" +" * m| g | r |rgb\n" +" * h|rgb| b | r\n" +" */\n" +" vec2 l = step(0.0, -coord);\n" +" vec2 m = vec2(1.0) - abs(coord-1.0);\n" +" vec2 h = step(2.0, coord);\n" +"" +" s.r *= l.x + m.y - 2.0*l.x*m.y + h.x*h.y;\n" +" s.g *= l.x + l.y*m.x + h.x*m.y;\n" +" s.b *= l.x*l.y + h.x + h.y - 2.0*h.x*h.y;\n" +" return s;\n" +"}\n" +"" +"void main()\n" +"{\n" +" vec2 coord = v_texCoord;\n" +"#if defined(OPENGLNB)\n" +" gl_FragColor = getRGB3xtexel(coord);\n" +"#else\n" +" coord -= 0.5;\n" +" vec4 c0 = getRGB3xtexel(coord);\n" +" vec4 c1 = getRGB3xtexel(coord+vec2(1.0,0.0));\n" +" vec4 c2 = getRGB3xtexel(coord+vec2(0.0,1.0));\n" +" vec4 c3 = getRGB3xtexel(coord+vec2(1.0));\n" +"" +" coord = fract(max(coord,0.0));\n" +" gl_FragColor = mix(mix(c0,c1,coord.x), mix(c2,c3,coord.x), coord.y);\n" +"#endif\n" +"}\n" +"#endif"; + +static const char scan2x_glsl[] = +"varying vec2 v_texCoord;\n" +"uniform vec2 rubyInputSize;\n" +"uniform vec2 rubyOutputSize;\n" +"uniform vec2 rubyTextureSize;\n" +"" +"#if defined(VERTEX)\n" +"attribute vec4 a_position;\n" +"void main() {\n" +" gl_Position = a_position;\n" +" v_texCoord = vec2(a_position.x+1.0,1.0-a_position.y)/2.0*rubyInputSize;\n" +"}\n" +"" +"#elif defined(FRAGMENT)\n" +"uniform sampler2D rubyTexture;\n" +"" +"void main() {\n" +" vec2 prescale = vec2(2.0);\n" +" vec2 texel = v_texCoord;\n" +" vec2 texel_floored = floor(texel);\n" +" vec2 s = fract(texel);\n" +" vec2 region_range = vec2(0.5) - vec2(0.5) / prescale;\n" +"" +" vec2 center_dist = s - vec2(0.5);\n" +" vec2 f = (center_dist - clamp(center_dist, -region_range, region_range)) * prescale + vec2(0.5);\n" +"" +" vec2 mod_texel = min(texel_floored + f, rubyInputSize-0.5);\n" +" vec4 p = texture2D(rubyTexture, mod_texel/rubyTextureSize);\n" +" float ss = abs(s.y*2.0-1.0);\n" +" p -= p*ss;\n" +"" +" gl_FragColor = p;\n" +"}\n" +"#endif"; + +static const char scan3x_glsl[] = +"varying vec2 v_texCoord;\n" +"uniform vec2 rubyInputSize;\n" +"uniform vec2 rubyOutputSize;\n" +"uniform vec2 rubyTextureSize;\n" +"" +"#if defined(VERTEX)\n" +"attribute vec4 a_position;\n" +"void main() {\n" +" gl_Position = a_position;\n" +" v_texCoord = vec2(a_position.x+1.0,1.0-a_position.y)/2.0*rubyInputSize;\n" +"}\n" +"" +"#elif defined(FRAGMENT)\n" +"uniform sampler2D rubyTexture;\n" +"" +"void main() {\n" +" vec2 prescale = vec2(3.0);\n" +" vec2 texel = v_texCoord;\n" +" vec2 texel_floored = floor(texel);\n" +" vec2 s = fract(texel);\n" +" vec2 region_range = vec2(0.5) - vec2(0.5) / prescale;\n" +"" +" vec2 center_dist = s - 0.5;\n" +" vec2 f = (center_dist - clamp(center_dist, -region_range, region_range)) * prescale + vec2(0.5);\n" +"" +" vec2 mod_texel = min(texel_floored + f, rubyInputSize-0.5);\n" +" vec4 p = texture2D(rubyTexture, mod_texel/rubyTextureSize);\n" +" float m = s.y*6.0;\n" +" m -= clamp(m, 2.0, 4.0);\n" +" m = abs(m/2.0);\n" +" gl_FragColor = p - p*m;\n" +"}\n" +"#endif"; + +static const char tv2x_glsl[] = +"varying vec2 v_texCoord;\n" +"uniform vec2 rubyInputSize;\n" +"uniform vec2 rubyOutputSize;\n" +"uniform vec2 rubyTextureSize;\n" +"" +"#if defined(VERTEX)\n" +"attribute vec4 a_position;\n" +"void main() {\n" +" gl_Position = a_position;\n" +" v_texCoord = vec2(a_position.x+1.0,1.0-a_position.y)/2.0*rubyInputSize;\n" +"}\n" +"" +"#elif defined(FRAGMENT)\n" +"uniform sampler2D rubyTexture;\n" +"" +"void main() {\n" +" vec2 prescale = vec2(2.0);\n" +" vec2 texel = v_texCoord;\n" +" vec2 texel_floored = floor(texel);\n" +" vec2 s = fract(texel);\n" +" vec2 region_range = vec2(0.5) - vec2(0.5) / prescale;\n" +"" +" vec2 center_dist = s - 0.5;\n" +" vec2 f = (center_dist - clamp(center_dist, -region_range, region_range)) * prescale + vec2(0.5);\n" +"" +" vec2 mod_texel = min(texel_floored + f, rubyInputSize-0.5);\n" +" vec4 p = texture2D(rubyTexture, mod_texel/rubyTextureSize);\n" +" float ss = abs(s.y*2.0-1.0);\n" +" p -= p*ss*3.0/8.0;\n" +"" +" gl_FragColor = p;\n" +"}\n" +"#endif"; + +static const char tv3x_glsl[] = +"varying vec2 v_texCoord;\n" +"uniform vec2 rubyInputSize;\n" +"uniform vec2 rubyOutputSize;\n" +"uniform vec2 rubyTextureSize;\n" +"" +"#if defined(VERTEX)\n" +"attribute vec4 a_position;\n" +"void main() {\n" +" gl_Position = a_position;\n" +" v_texCoord = vec2(a_position.x+1.0,1.0-a_position.y)/2.0*rubyInputSize;\n" +"}\n" +"" +"#elif defined(FRAGMENT)\n" +"uniform sampler2D rubyTexture;\n" +"" +"void main() {\n" +" vec2 prescale = vec2(3.0);\n" +" vec2 texel = v_texCoord;\n" +" vec2 texel_floored = floor(texel);\n" +" vec2 s = fract(texel);\n" +" vec2 region_range = vec2(0.5) - vec2(0.5) / prescale;\n" +"" +" vec2 center_dist = s - 0.5;\n" +" vec2 f = (center_dist - clamp(center_dist, -region_range, region_range)) * prescale + vec2(0.5);\n" +"" +" vec2 mod_texel = min(texel_floored + f, rubyInputSize-0.5);\n" +" vec4 p = texture2D(rubyTexture, mod_texel/rubyTextureSize);\n" +" float ss = abs(s.y*2.0-1.0);\n" +" p -= p*ss*11.0/16.0;\n" +"" +" gl_FragColor = p;\n" +"}\n" +"#endif"; + +static const char sharp_glsl[] = +"varying vec2 v_texCoord;\n" +"uniform vec2 rubyInputSize;\n" +"uniform vec2 rubyOutputSize;\n" +"uniform vec2 rubyTextureSize;\n" +"varying vec2 prescale; // const set by vertex shader\n" +"" +"#if defined(VERTEX)\n" +"attribute vec4 a_position;\n" +"void main() {\n" +" gl_Position = a_position;\n" +" v_texCoord = vec2(a_position.x+1.0,1.0-a_position.y)/2.0*rubyInputSize;\n" +" prescale = ceil(rubyOutputSize / rubyInputSize);\n" +"}\n" +"" +"#elif defined(FRAGMENT)\n" +"uniform sampler2D rubyTexture;\n" +"" +"void main() {\n" +" const vec2 halfp = vec2(0.5);\n" +" vec2 texel_floored = floor(v_texCoord);\n" +" vec2 s = fract(v_texCoord);\n" +" vec2 region_range = halfp - halfp / prescale;\n" +"" +" vec2 center_dist = s - halfp;\n" +" vec2 f = (center_dist - clamp(center_dist, -region_range, region_range)) * prescale + halfp;\n" +"" +" vec2 mod_texel = min(texel_floored + f, rubyInputSize-halfp);\n" +" gl_FragColor = texture2D(rubyTexture, mod_texel / rubyTextureSize);\n" +"}\n" +"#endif"; + + +#endif \ No newline at end of file diff --git a/src/gui/sdlmain.cpp b/src/gui/sdlmain.cpp index 203a59013..76f43cc49 100644 --- a/src/gui/sdlmain.cpp +++ b/src/gui/sdlmain.cpp @@ -174,6 +174,48 @@ typedef enum PROCESS_DPI_AWARENESS { #include "sdlmain.h" #include "build_timestamp.h" +namespace gl2 { +extern PFNGLATTACHSHADERPROC glAttachShader; +extern PFNGLCOMPILESHADERPROC glCompileShader; +extern PFNGLCREATEPROGRAMPROC glCreateProgram; +extern PFNGLCREATESHADERPROC glCreateShader; +extern PFNGLDELETEPROGRAMPROC glDeleteProgram; +extern PFNGLDELETESHADERPROC glDeleteShader; +extern PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray; +extern PFNGLGETATTRIBLOCATIONPROC glGetAttribLocation; +extern PFNGLGETPROGRAMIVPROC glGetProgramiv; +extern PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog; +extern PFNGLGETSHADERIVPROC glGetShaderiv; +extern PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog; +extern PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation; +extern PFNGLLINKPROGRAMPROC glLinkProgram; +extern PFNGLSHADERSOURCEPROC_NP glShaderSource; +extern PFNGLUNIFORM2FPROC glUniform2f; +extern PFNGLUNIFORM1IPROC glUniform1i; +extern PFNGLUSEPROGRAMPROC glUseProgram; +extern PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer; +} + +#define glAttachShader gl2::glAttachShader +#define glCompileShader gl2::glCompileShader +#define glCreateProgram gl2::glCreateProgram +#define glCreateShader gl2::glCreateShader +#define glDeleteProgram gl2::glDeleteProgram +#define glDeleteShader gl2::glDeleteShader +#define glEnableVertexAttribArray gl2::glEnableVertexAttribArray +#define glGetAttribLocation gl2::glGetAttribLocation +#define glGetProgramiv gl2::glGetProgramiv +#define glGetProgramInfoLog gl2::glGetProgramInfoLog +#define glGetShaderiv gl2::glGetShaderiv +#define glGetShaderInfoLog gl2::glGetShaderInfoLog +#define glGetUniformLocation gl2::glGetUniformLocation +#define glLinkProgram gl2::glLinkProgram +#define glShaderSource gl2::glShaderSource +#define glUniform2f gl2::glUniform2f +#define glUniform1i gl2::glUniform1i +#define glUseProgram gl2::glUseProgram +#define glVertexAttribPointer gl2::glVertexAttribPointer + #ifdef MACOSX extern bool has_touch_bar_support; bool osx_detect_nstouchbar(void); @@ -2618,6 +2660,19 @@ void DoExtendedKeyboardHook(bool enable) { #endif } +void GFX_SetShader(const char* src) { +#if C_OPENGL + if (!sdl_opengl.use_shader || src == sdl_opengl.shader_src) + return; + + sdl_opengl.shader_src = src; + if (sdl_opengl.program_object) { + glDeleteProgram(sdl_opengl.program_object); + sdl_opengl.program_object = 0; + } +#endif +} + void GFX_ReleaseMouse(void) { if (sdl.mouse.locked) GFX_CaptureMouse(); @@ -3262,9 +3317,9 @@ void GFX_EndUpdate(const Bit16u *changedLines) { if (d3d && d3d->getForceUpdate()); else #endif - if (!sdl.updating) + if (((sdl.desktop.type != SCREEN_OPENGL) || !RENDER_GetForceUpdate()) && !sdl.updating) return; - + bool actually_updating = sdl.updating; sdl.updating = false; switch (sdl.desktop.type) { @@ -3274,6 +3329,17 @@ void GFX_EndUpdate(const Bit16u *changedLines) { #if C_OPENGL case SCREEN_OPENGL: + // Clear drawing area. Some drivers (on Linux) have more than 2 buffers and the screen might + // be dirty because of other programs. + if (!actually_updating) { + /* Don't really update; Just increase the frame counter. + * If we tried to update it may have not worked so well + * with VSync... + * (Think of 60Hz on the host with 70Hz on the client.) + */ + sdl_opengl.actual_frame_count++; + return; + } OUTPUT_OPENGL_EndUpdate(changedLines); break; #endif @@ -3834,8 +3900,77 @@ static void GUI_StartUp() { sdl.overscan_width=(unsigned int)section->Get_int("overscan"); // sdl.overscan_color=section->Get_int("overscancolor"); +#if C_OPENGL + if (sdl.desktop.want_type == SCREEN_OPENGL) { /* OPENGL is requested */ +#if defined(C_SDL2) + GFX_SetResizeable(true); + if (!(sdl.window = GFX_SetSDLWindowMode(640,400, SCREEN_OPENGL)) || !(sdl_opengl.context = SDL_GL_CreateContext(sdl.window))) { +#else + sdl.surface = SDL_SetVideoMode(640,400,0,SDL_OPENGL); + if (sdl.surface == NULL) { +#endif + LOG_MSG("Could not initialize OpenGL, switching back to surface"); + sdl.desktop.want_type = SCREEN_SURFACE; + } else { + sdl_opengl.program_object = 0; + glAttachShader = (PFNGLATTACHSHADERPROC)SDL_GL_GetProcAddress("glAttachShader"); + glCompileShader = (PFNGLCOMPILESHADERPROC)SDL_GL_GetProcAddress("glCompileShader"); + glCreateProgram = (PFNGLCREATEPROGRAMPROC)SDL_GL_GetProcAddress("glCreateProgram"); + glCreateShader = (PFNGLCREATESHADERPROC)SDL_GL_GetProcAddress("glCreateShader"); + glDeleteProgram = (PFNGLDELETEPROGRAMPROC)SDL_GL_GetProcAddress("glDeleteProgram"); + glDeleteShader = (PFNGLDELETESHADERPROC)SDL_GL_GetProcAddress("glDeleteShader"); + glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)SDL_GL_GetProcAddress("glEnableVertexAttribArray"); + glGetAttribLocation = (PFNGLGETATTRIBLOCATIONPROC)SDL_GL_GetProcAddress("glGetAttribLocation"); + glGetProgramiv = (PFNGLGETPROGRAMIVPROC)SDL_GL_GetProcAddress("glGetProgramiv"); + glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)SDL_GL_GetProcAddress("glGetProgramInfoLog"); + glGetShaderiv = (PFNGLGETSHADERIVPROC)SDL_GL_GetProcAddress("glGetShaderiv"); + glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)SDL_GL_GetProcAddress("glGetShaderInfoLog"); + glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)SDL_GL_GetProcAddress("glGetUniformLocation"); + glLinkProgram = (PFNGLLINKPROGRAMPROC)SDL_GL_GetProcAddress("glLinkProgram"); + glShaderSource = (PFNGLSHADERSOURCEPROC_NP)SDL_GL_GetProcAddress("glShaderSource"); + glUniform2f = (PFNGLUNIFORM2FPROC)SDL_GL_GetProcAddress("glUniform2f"); + glUniform1i = (PFNGLUNIFORM1IPROC)SDL_GL_GetProcAddress("glUniform1i"); + glUseProgram = (PFNGLUSEPROGRAMPROC)SDL_GL_GetProcAddress("glUseProgram"); + glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)SDL_GL_GetProcAddress("glVertexAttribPointer"); + sdl_opengl.use_shader = (glAttachShader && glCompileShader && glCreateProgram && glDeleteProgram && glDeleteShader && \ + glEnableVertexAttribArray && glGetAttribLocation && glGetProgramiv && glGetProgramInfoLog && \ + glGetShaderiv && glGetShaderInfoLog && glGetUniformLocation && glLinkProgram && glShaderSource && \ + glUniform2f && glUniform1i && glUseProgram && glVertexAttribPointer); + + sdl_opengl.buffer=0; + sdl_opengl.framebuf=0; + sdl_opengl.texture=0; + sdl_opengl.displaylist=0; + glGetIntegerv (GL_MAX_TEXTURE_SIZE, &sdl_opengl.max_texsize); + glGenBuffersARB = (PFNGLGENBUFFERSARBPROC)SDL_GL_GetProcAddress("glGenBuffersARB"); + glBindBufferARB = (PFNGLBINDBUFFERARBPROC)SDL_GL_GetProcAddress("glBindBufferARB"); + glDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC)SDL_GL_GetProcAddress("glDeleteBuffersARB"); + glBufferDataARB = (PFNGLBUFFERDATAARBPROC)SDL_GL_GetProcAddress("glBufferDataARB"); + glMapBufferARB = (PFNGLMAPBUFFERARBPROC)SDL_GL_GetProcAddress("glMapBufferARB"); + glUnmapBufferARB = (PFNGLUNMAPBUFFERARBPROC)SDL_GL_GetProcAddress("glUnmapBufferARB"); + const char * gl_ext = (const char *)glGetString (GL_EXTENSIONS); + if(gl_ext && *gl_ext){ + sdl_opengl.packed_pixel=(strstr(gl_ext,"EXT_packed_pixels") != NULL); + sdl_opengl.paletted_texture=(strstr(gl_ext,"EXT_paletted_texture") != NULL); + sdl_opengl.pixel_buffer_object=(strstr(gl_ext,"GL_ARB_pixel_buffer_object") != NULL ) && + glGenBuffersARB && glBindBufferARB && glDeleteBuffersARB && glBufferDataARB && + glMapBufferARB && glUnmapBufferARB; + } else { + sdl_opengl.packed_pixel = false; + sdl_opengl.paletted_texture = false; + sdl_opengl.pixel_buffer_object = false; + } +#ifdef DB_DISABLE_DBO + sdl_opengl.pixel_buffer_object = false; +#endif + LOG_MSG("OpenGL extension: pixel_bufer_object %d",sdl_opengl.pixel_buffer_object); + } + } /* OPENGL is requested end */ + +#endif //OPENGL + +/* Initialize screen for first time */ #if defined(C_SDL2) - /* Initialize screen for first time */ GFX_SetResizeable(true); if (!GFX_SetSDLSurfaceWindow(640,400)) E_Exit("Could not initialize video: %s",SDL_GetError()); @@ -3847,7 +3982,6 @@ static void GUI_StartUp() { if (SDL_BITSPERPIXEL(sdl.desktop.pixelFormat) == 24) LOG_MSG("SDL: You are running in 24 bpp mode, this will slow down things!"); #else - /* Initialize screen for first time */ sdl.surface=SDL_SetVideoMode(640,400,0,SDL_RESIZABLE); if (sdl.surface == NULL) E_Exit("Could not initialize video: %s",SDL_GetError()); sdl.deferred_resize = false; diff --git a/src/output/output_opengl.cpp b/src/output/output_opengl.cpp index 6a23c366d..5195988f1 100644 --- a/src/output/output_opengl.cpp +++ b/src/output/output_opengl.cpp @@ -14,6 +14,7 @@ #include #include "sdlmain.h" +#include "render.h" using namespace std; @@ -27,6 +28,56 @@ PFNGLBUFFERDATAARBPROC glBufferDataARB = NULL; PFNGLMAPBUFFERARBPROC glMapBufferARB = NULL; PFNGLUNMAPBUFFERARBPROC glUnmapBufferARB = NULL; +/* Apple defines these functions in their GL header (as core functions) + * so we can't use their names as function pointers. We can't link + * directly as some platforms may not have them. So they get their own + * namespace here to keep the official names but avoid collisions. + */ +namespace gl2 { +PFNGLATTACHSHADERPROC glAttachShader = NULL; +PFNGLCOMPILESHADERPROC glCompileShader = NULL; +PFNGLCREATEPROGRAMPROC glCreateProgram = NULL; +PFNGLCREATESHADERPROC glCreateShader = NULL; +PFNGLDELETEPROGRAMPROC glDeleteProgram = NULL; +PFNGLDELETESHADERPROC glDeleteShader = NULL; +PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray = NULL; +PFNGLGETATTRIBLOCATIONPROC glGetAttribLocation = NULL; +PFNGLGETPROGRAMIVPROC glGetProgramiv = NULL; +PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog = NULL; +PFNGLGETSHADERIVPROC glGetShaderiv = NULL; +PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog = NULL; +PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation = NULL; +PFNGLLINKPROGRAMPROC glLinkProgram = NULL; +PFNGLSHADERSOURCEPROC_NP glShaderSource = NULL; +PFNGLUNIFORM2FPROC glUniform2f = NULL; +PFNGLUNIFORM1IPROC glUniform1i = NULL; +PFNGLUSEPROGRAMPROC glUseProgram = NULL; +PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer = NULL; +} + +/* "using" is meant to hide identical names declared in outer scope + * but is unreliable, so just redefine instead. + */ +#define glAttachShader gl2::glAttachShader +#define glCompileShader gl2::glCompileShader +#define glCreateProgram gl2::glCreateProgram +#define glCreateShader gl2::glCreateShader +#define glDeleteProgram gl2::glDeleteProgram +#define glDeleteShader gl2::glDeleteShader +#define glEnableVertexAttribArray gl2::glEnableVertexAttribArray +#define glGetAttribLocation gl2::glGetAttribLocation +#define glGetProgramiv gl2::glGetProgramiv +#define glGetProgramInfoLog gl2::glGetProgramInfoLog +#define glGetShaderiv gl2::glGetShaderiv +#define glGetShaderInfoLog gl2::glGetShaderInfoLog +#define glGetUniformLocation gl2::glGetUniformLocation +#define glLinkProgram gl2::glLinkProgram +#define glShaderSource gl2::glShaderSource +#define glUniform2f gl2::glUniform2f +#define glUniform1i gl2::glUniform1i +#define glUseProgram gl2::glUseProgram +#define glVertexAttribPointer gl2::glVertexAttribPointer + #if C_OPENGL && DOSBOXMENU_TYPE == DOSBOXMENU_SDLDRAW extern unsigned int SDLDrawGenFontTextureUnitPerRow; extern unsigned int SDLDrawGenFontTextureRows; @@ -221,6 +272,76 @@ Bitu OUTPUT_OPENGL_GetBestMode(Bitu flags) return flags; } +/* Create a GLSL shader object, load the shader source, and compile the shader. */ +static GLuint BuildShader ( GLenum type, const char *shaderSrc ) { + GLuint shader; + GLint compiled; + const char* src_strings[2]; + std::string top; + + // look for "#version" because it has to occur first + const char *ver = strstr(shaderSrc, "#version "); + if (ver) { + const char *endline = strchr(ver+9, '\n'); + if (endline) { + top.assign(shaderSrc, endline-shaderSrc+1); + shaderSrc = endline+1; + } + } + + top += (type==GL_VERTEX_SHADER) ? "#define VERTEX 1\n":"#define FRAGMENT 1\n"; + if (!sdl_opengl.bilinear) + top += "#define OPENGLNB 1\n"; + + src_strings[0] = top.c_str(); + src_strings[1] = shaderSrc; + + // Create the shader object + shader = glCreateShader(type); + if (shader == 0) return 0; + + // Load the shader source + glShaderSource(shader, 2, src_strings, NULL); + + // Compile the shader + glCompileShader(shader); + + // Check the compile status + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + + if (!compiled) { + char* infoLog = NULL; + GLint infoLen = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); + + if (infoLen>1) infoLog = (char*)malloc(infoLen); + if (infoLog) { + glGetShaderInfoLog(shader, infoLen, NULL, infoLog); + LOG_MSG("Error compiling shader: %s", infoLog); + free(infoLog); + } else LOG_MSG("Error getting shader compilation log"); + + glDeleteShader(shader); + return 0; + } + + return shader; +} + +static bool LoadGLShaders(const char *src, GLuint *vertex, GLuint *fragment) { + GLuint s = BuildShader(GL_VERTEX_SHADER, src); + if (s) { + *vertex = s; + s = BuildShader(GL_FRAGMENT_SHADER, src); + if (s) { + *fragment = s; + return true; + } + glDeleteShader(*vertex); + } + return false; +} + Bitu OUTPUT_OPENGL_SetSize() { Bitu retFlags = 0; @@ -256,10 +377,11 @@ Bitu OUTPUT_OPENGL_SetSize() # endif #endif + // try 32 bits first then 16 #if defined(C_SDL2) - SetupSurfaceScaledOpenGL(SDL_WINDOW_RESIZABLE, 0); + if (SetupSurfaceScaledOpenGL(SDL_WINDOW_RESIZABLE,32)==NULL) SetupSurfaceScaledOpenGL(SDL_WINDOW_RESIZABLE,16); #else - SetupSurfaceScaledOpenGL(SDL_RESIZABLE, 0); + if (SetupSurfaceScaledOpenGL(SDL_RESIZABLE,32)==NULL) SetupSurfaceScaledOpenGL(SDL_RESIZABLE,16); #endif if (!sdl.surface || sdl.surface->format->BitsPerPixel < 15) { @@ -290,6 +412,100 @@ Bitu OUTPUT_OPENGL_SetSize() return 0; } + if (sdl_opengl.use_shader) { + GLuint prog=0; + // reset error + glGetError(); + glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&prog); + // if there was an error this context doesn't support shaders + if (glGetError()==GL_NO_ERROR && (sdl_opengl.program_object==0 || prog!=sdl_opengl.program_object)) { + // check if existing program is valid + if (sdl_opengl.program_object) { + glUseProgram(sdl_opengl.program_object); + if (glGetError() != GL_NO_ERROR) { + // program is not usable (probably new context), purge it + glDeleteProgram(sdl_opengl.program_object); + sdl_opengl.program_object = 0; + } + } + + // does program need to be rebuilt? + if (sdl_opengl.program_object == 0) { + GLuint vertexShader, fragmentShader; + const char *src = sdl_opengl.shader_src; + if (src && !LoadGLShaders(src, &vertexShader, &fragmentShader)) { + LOG_MSG("SDL:OPENGL:Failed to compile shader, falling back to default"); + src = NULL; + } + if (src == NULL && !LoadGLShaders(shader_src_default, &vertexShader, &fragmentShader)) { + LOG_MSG("SDL:OPENGL:Failed to compile default shader!"); + return 0; + } + + sdl_opengl.program_object = glCreateProgram(); + if (!sdl_opengl.program_object) { + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + LOG_MSG("SDL:OPENGL:Can't create program object, falling back to surface"); + return 0; + } + glAttachShader(sdl_opengl.program_object, vertexShader); + glAttachShader(sdl_opengl.program_object, fragmentShader); + // Link the program + glLinkProgram(sdl_opengl.program_object); + // Even if we *are* successful, we may delete the shader objects + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + + // Check the link status + GLint isProgramLinked; + glGetProgramiv(sdl_opengl.program_object, GL_LINK_STATUS, &isProgramLinked); + if (!isProgramLinked) { + char * infoLog = NULL; + GLint infoLen = 0; + + glGetProgramiv(sdl_opengl.program_object, GL_INFO_LOG_LENGTH, &infoLen); + if (infoLen>1) infoLog = (char*)malloc(infoLen); + if (infoLog) { + glGetProgramInfoLog(sdl_opengl.program_object, infoLen, NULL, infoLog); + LOG_MSG("SDL:OPENGL:Error linking program:\n %s", infoLog); + free(infoLog); + } else LOG_MSG("SDL:OPENGL:Failed to retrieve program link log"); + + glDeleteProgram(sdl_opengl.program_object); + sdl_opengl.program_object = 0; + return 0; + } + + glUseProgram(sdl_opengl.program_object); + + GLint u = glGetAttribLocation(sdl_opengl.program_object, "a_position"); + // upper left + sdl_opengl.vertex_data[0] = -1.0f; + sdl_opengl.vertex_data[1] = 1.0f; + // lower left + sdl_opengl.vertex_data[2] = -1.0f; + sdl_opengl.vertex_data[3] = -3.0f; + // upper right + sdl_opengl.vertex_data[4] = 3.0f; + sdl_opengl.vertex_data[5] = 1.0f; + // Load the vertex positions + glVertexAttribPointer(u, 2, GL_FLOAT, GL_FALSE, 0, sdl_opengl.vertex_data); + glEnableVertexAttribArray(u); + + u = glGetUniformLocation(sdl_opengl.program_object, "rubyTexture"); + glUniform1i(u, 0); + + sdl_opengl.ruby.texture_size = glGetUniformLocation(sdl_opengl.program_object, "rubyTextureSize"); + sdl_opengl.ruby.input_size = glGetUniformLocation(sdl_opengl.program_object, "rubyInputSize"); + sdl_opengl.ruby.output_size = glGetUniformLocation(sdl_opengl.program_object, "rubyOutputSize"); + sdl_opengl.ruby.frame_count = glGetUniformLocation(sdl_opengl.program_object, "rubyFrameCount"); + // Don't force updating unless a shader depends on frame_count + RENDER_SetForceUpdate(sdl_opengl.ruby.frame_count != (GLint)-1); + } + } + } + /* Create the texture and display list */ if (sdl_opengl.pixel_buffer_object) { @@ -341,7 +557,7 @@ Bitu OUTPUT_OPENGL_SetSize() glClear(GL_COLOR_BUFFER_BIT); // SDL_GL_SwapBuffers(); // glClear(GL_COLOR_BUFFER_BIT); - glShadeModel(GL_FLAT); + //glShadeModel(GL_FLAT); glBlendFunc(GL_ONE, GL_ZERO); glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); @@ -353,36 +569,45 @@ Bitu OUTPUT_OPENGL_SetSize() glDisable(GL_STENCIL_TEST); glEnable(GL_TEXTURE_2D); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); + if (sdl_opengl.program_object) { + // Set shader variables + glUniform2f(sdl_opengl.ruby.texture_size, (float)texsize, (float)texsize); + glUniform2f(sdl_opengl.ruby.input_size, (float)adjTexWidth, (float)adjTexHeight); + glUniform2f(sdl_opengl.ruby.output_size, sdl.clip.w, sdl.clip.h); + // The following uniform is *not* set right now + sdl_opengl.actual_frame_count = 0; + } else { + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0, sdl.surface->w, sdl.surface->h, 0, -1, 1); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, sdl.surface->w, sdl.surface->h, 0, -1, 1); - glMatrixMode(GL_TEXTURE); - glLoadIdentity(); - glScaled(1.0 / texsize, 1.0 / texsize, 1.0); + glMatrixMode(GL_TEXTURE); + glLoadIdentity(); + glScaled(1.0 / texsize, 1.0 / texsize, 1.0); - // if (glIsList(sdl_opengl.displaylist)) - // glDeleteLists(sdl_opengl.displaylist, 1); - // sdl_opengl.displaylist = glGenLists(1); - sdl_opengl.displaylist = 1; + // if (glIsList(sdl_opengl.displaylist)) + // glDeleteLists(sdl_opengl.displaylist, 1); + // sdl_opengl.displaylist = glGenLists(1); + sdl_opengl.displaylist = 1; - glNewList(sdl_opengl.displaylist, GL_COMPILE); - glBindTexture(GL_TEXTURE_2D, sdl_opengl.texture); + glNewList(sdl_opengl.displaylist, GL_COMPILE); + glBindTexture(GL_TEXTURE_2D, sdl_opengl.texture); - glBegin(GL_QUADS); + glBegin(GL_QUADS); - glTexCoord2i(0, 0); glVertex2i((GLint)sdl.clip.x, (GLint)sdl.clip.y); // lower left - glTexCoord2i((GLint)adjTexWidth, 0); glVertex2i((GLint)sdl.clip.x + (GLint)sdl.clip.w, (GLint)sdl.clip.y); // lower right - glTexCoord2i((GLint)adjTexWidth, (GLint)adjTexHeight); glVertex2i((GLint)sdl.clip.x + (GLint)sdl.clip.w, (GLint)sdl.clip.y + (GLint)sdl.clip.h); // upper right - glTexCoord2i(0, (GLint)adjTexHeight); glVertex2i((GLint)sdl.clip.x, (GLint)sdl.clip.y + (GLint)sdl.clip.h); // upper left + glTexCoord2i(0, 0); glVertex2i((GLint)sdl.clip.x, (GLint)sdl.clip.y); // lower left + glTexCoord2i((GLint)adjTexWidth, 0); glVertex2i((GLint)sdl.clip.x + (GLint)sdl.clip.w, (GLint)sdl.clip.y); // lower right + glTexCoord2i((GLint)adjTexWidth, (GLint)adjTexHeight); glVertex2i((GLint)sdl.clip.x + (GLint)sdl.clip.w, (GLint)sdl.clip.y + (GLint)sdl.clip.h); // upper right + glTexCoord2i(0, (GLint)adjTexHeight); glVertex2i((GLint)sdl.clip.x, (GLint)sdl.clip.y + (GLint)sdl.clip.h); // upper left - glEnd(); - glEndList(); + glEnd(); + glEndList(); - glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(GL_TEXTURE_2D, 0); + } #if DOSBOXMENU_TYPE == DOSBOXMENU_SDLDRAW void GFX_DrawSDLMenu(DOSBoxMenu &menu, DOSBoxMenu::displaylist &dl); @@ -591,8 +816,8 @@ void OUTPUT_OPENGL_EndUpdate(const Bit16u *changedLines) #endif (void*)0); glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_EXT, 0); - glCallList(sdl_opengl.displaylist); - SDL_GL_SwapBuffers(); + //glCallList(sdl_opengl.displaylist); + //SDL_GL_SwapBuffers(); } else if (changedLines) { @@ -625,6 +850,12 @@ void OUTPUT_OPENGL_EndUpdate(const Bit16u *changedLines) } index++; } + } else + return; + if (sdl_opengl.program_object) { + glUniform1i(sdl_opengl.ruby.frame_count, sdl_opengl.actual_frame_count++); + glDrawArrays(GL_TRIANGLES, 0, 3); + } else glCallList(sdl_opengl.displaylist); #if 0 /* DEBUG Prove to me that you're drawing the damn texture */ @@ -659,7 +890,6 @@ void OUTPUT_OPENGL_EndUpdate(const Bit16u *changedLines) #endif SDL_GL_SwapBuffers(); - } if (!menu.hidecycles && !sdl.desktop.fullscreen) frames++; } diff --git a/src/output/output_opengl.h b/src/output/output_opengl.h index 15cd61d8d..80e4c758c 100644 --- a/src/output/output_opengl.h +++ b/src/output/output_opengl.h @@ -38,6 +38,32 @@ extern PFNGLBUFFERDATAARBPROC glBufferDataARB; extern PFNGLMAPBUFFERARBPROC glMapBufferARB; extern PFNGLUNMAPBUFFERARBPROC glUnmapBufferARB; + +/* Don't guard these with GL_VERSION_2_0 - Apple defines it but not these typedefs. + * If they're already defined they should match these definitions, so no conflicts. + */ +typedef void (APIENTRYP PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (APIENTRYP PFNGLCOMPILESHADERPROC) (GLuint shader); +typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC) (void); +typedef GLuint (APIENTRYP PFNGLCREATESHADERPROC) (GLenum type); +typedef void (APIENTRYP PFNGLDELETEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLDELETESHADERPROC) (GLuint shader); +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (APIENTRYP PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLLINKPROGRAMPROC) (GLuint program); +//Change to NP, as Khronos changes include guard :( +typedef void (APIENTRYP PFNGLSHADERSOURCEPROC_NP) (GLuint shader, GLsizei count, const GLchar **string, const GLint *length); +typedef void (APIENTRYP PFNGLUNIFORM2FPROC) (GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLUNIFORM1IPROC) (GLint location, GLint v0); +typedef void (APIENTRYP PFNGLUSEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); + + #if defined(C_SDL2) # include #endif @@ -56,11 +82,39 @@ struct SDL_OpenGL { bool pixel_buffer_object; int menudraw_countdown; int clear_countdown; + bool use_shader; + GLuint program_object; + const char *shader_src; + struct { + GLint texture_size; + GLint input_size; + GLint output_size; + GLint frame_count; + } ruby; + GLuint actual_frame_count; + GLfloat vertex_data[2*3]; #if defined(C_SDL2) SDL_GLContext context; #endif }; +static char const shader_src_default[] = + "varying vec2 v_texCoord;\n" + "#if defined(VERTEX)\n" + "uniform vec2 rubyTextureSize;\n" + "uniform vec2 rubyInputSize;\n" + "attribute vec4 a_position;\n" + "void main() {\n" + " gl_Position = a_position;\n" + " v_texCoord = vec2(a_position.x+1.0,1.0-a_position.y)/2.0*rubyInputSize/rubyTextureSize;\n" + "}\n" + "#elif defined(FRAGMENT)\n" + "uniform sampler2D rubyTexture;\n\n" + "void main() {\n" + " gl_FragColor = texture2D(rubyTexture, v_texCoord);\n" + "}\n" + "#endif\n"; + extern SDL_OpenGL sdl_opengl; // output API