mirror of
https://github.com/0ldsk00l/nestopia.git
synced 2024-06-22 06:02:26 -04:00
FLTK: Modern GL renderer and shaders
This commit is contained in:
parent
a500d95bac
commit
0ab75febef
|
@ -15,12 +15,14 @@ nestopia_CPPFLAGS = \
|
|||
$(LIBARCHIVE_CFLAGS) \
|
||||
$(SAMPLERATE_CFLAGS) \
|
||||
$(SDL2_CFLAGS) \
|
||||
$(LIBEPOXY_CFLAGS) \
|
||||
$(FLTK_CFLAGS)
|
||||
nestopia_LDADD = \
|
||||
$(ZLIB_LIBS) \
|
||||
$(LIBARCHIVE_LIBS) \
|
||||
$(SAMPLERATE_LIBS) \
|
||||
$(SDL2_LIBS) \
|
||||
$(LIBEPOXY_LIBS) \
|
||||
$(FLTK_LIBS)
|
||||
|
||||
################
|
||||
|
@ -36,6 +38,9 @@ dist_palette_DATA = palettes/SONY_CXA2025AS_US.pal palettes/Royaltea.pal \
|
|||
palettes/Magnum_FBX.pal palettes/PVM_Style_D93_FBX.pal \
|
||||
palettes/Smooth_V2_FBX.pal
|
||||
|
||||
shaderdir = $(datadir)/nestopia/shaders
|
||||
dist_shader_DATA = shaders/default.vs shaders/default.fs shaders/crtea.fs \
|
||||
shaders/sharp-bilinear.fs
|
||||
|
||||
# freedesktop.org-specific files
|
||||
desktopdir = $(datarootdir)/applications
|
||||
|
|
|
@ -78,6 +78,9 @@ PKG_CHECK_MODULES([SAMPLERATE], [samplerate])
|
|||
dnl SDL2
|
||||
PKG_CHECK_MODULES([SDL2], [sdl2])
|
||||
|
||||
dnl LibEpoxy
|
||||
PKG_CHECK_MODULES([LIBEPOXY], [epoxy])
|
||||
|
||||
AC_CHECK_PROG(FLTKCONFIG,fltk-config,[fltk-config],[no])
|
||||
test "$FLTKCONFIG" == "no" && AC_MSG_ERROR([Cannot find the fltk-config executable. Is FLTK installed?])
|
||||
|
||||
|
|
330
shaders/crtea.fs
Normal file
330
shaders/crtea.fs
Normal file
|
@ -0,0 +1,330 @@
|
|||
/*
|
||||
MIT License
|
||||
|
||||
CRTea - Configurable CRT Fragment Shader
|
||||
Copyright (c) 2020-2022 Rupert Carmichael
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
// Based on Public Domain work by Timothy Lottes
|
||||
|
||||
precision highp float;
|
||||
|
||||
uniform sampler2D source;
|
||||
uniform vec4 sourceSize;
|
||||
uniform vec4 targetSize;
|
||||
|
||||
uniform int masktype; // Mask Type
|
||||
uniform float maskstr; // Mask Strength
|
||||
uniform float scanstr; // Scanline Strength
|
||||
uniform float sharpness; // Pixel Sharpness
|
||||
uniform float curve; // Screen Curvature
|
||||
uniform float corner; // Screen Curvature
|
||||
uniform float tcurve; // Trinitron Curve
|
||||
|
||||
in vec2 texCoord;
|
||||
|
||||
out vec4 FragColor;
|
||||
|
||||
//#define CRTS_DEBUG 1 // Define to see on/off split screen
|
||||
//#define CRTS_2_TAP 1 // Faster very pixely 2-tap filter (off is 8)
|
||||
#define CRTS_WARP 1 // Apply screen warp
|
||||
|
||||
#define INPUT_MASK (1.0 - maskstr)
|
||||
#define INPUT_SCAN (0.5 + (0.5 * scanstr))
|
||||
#define INPUT_SHRP (-1.0 * sharpness)
|
||||
#define INPUT_X sourceSize.x
|
||||
#define INPUT_Y sourceSize.y
|
||||
|
||||
#define CURVATURE curve
|
||||
#define TRINITRON_CURVE tcurve
|
||||
#define CORNER corner
|
||||
#define CRT_GAMMA 2.4
|
||||
#define CRTS_TONE 1 // Normalize mid-level and process color
|
||||
#define CRTS_CONTRAST 0 // Process color - enable contrast control
|
||||
#define CRTS_SATURATION 0 // Process color - enable saturation control
|
||||
|
||||
#define CrtsRcpF1(x) (1.0/(x))
|
||||
#define CrtsSatF1(x) clamp((x),0.0,1.0)
|
||||
|
||||
// sRGB-Linear Conversions
|
||||
float FromSrgb1(float c) {
|
||||
return (c <= 0.04045) ? c * (1.0 / 12.92) :
|
||||
pow(c * (1.0 / 1.055) + (0.055 / 1.055), CRT_GAMMA);
|
||||
}
|
||||
|
||||
vec3 FromSrgb(vec3 c) {
|
||||
return vec3(FromSrgb1(c.r), FromSrgb1(c.g), FromSrgb1(c.b));
|
||||
}
|
||||
|
||||
float ToSrgb1(float c) {
|
||||
return (c < 0.0031308 ? c * 12.92 : 1.055 * pow(c, 0.41666) - 0.055);
|
||||
}
|
||||
|
||||
vec3 ToSrgb(vec3 c) {
|
||||
return vec3(ToSrgb1(c.r), ToSrgb1(c.g), ToSrgb1(c.b));
|
||||
}
|
||||
|
||||
// Fetch the input image colour
|
||||
vec3 CrtsFetch(vec2 uv) {
|
||||
// Scale to get native texels in the image
|
||||
uv *= vec2(INPUT_X, INPUT_Y) / sourceSize.xy;
|
||||
return FromSrgb(texture(source, uv.xy, -16.0).rgb);
|
||||
}
|
||||
|
||||
// Get the maximum of 3 floats
|
||||
float CrtsMax3F1(float a, float b, float c) {
|
||||
return max(a, max(b, c));
|
||||
}
|
||||
|
||||
// Tonal Control
|
||||
vec4 CrtsTone(float contrast, float saturation, float thin, float mask) {
|
||||
if (masktype == 0) mask = 1.0;
|
||||
|
||||
if (masktype == 1) {
|
||||
// Normal R mask is {1.0,mask,mask}
|
||||
// LITE R mask is {mask,1.0,1.0}
|
||||
mask = 0.5 + mask * 0.5;
|
||||
}
|
||||
|
||||
vec4 ret;
|
||||
float midOut = 0.18 / ((1.5 - thin) * (0.5 * mask + 0.5));
|
||||
float pMidIn = pow(0.18, contrast);
|
||||
ret.x = contrast;
|
||||
ret.y = ((-pMidIn) + midOut) / ((1.0 - pMidIn) * midOut);
|
||||
ret.z = ((-pMidIn) * midOut + pMidIn) / (midOut * (-pMidIn) + midOut);
|
||||
ret.w = contrast + saturation;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Apply the mask
|
||||
vec3 CrtsMask(vec2 pos, float dark) {
|
||||
if (masktype == 0) { // Scanlines (No Mask)
|
||||
return vec3(1.0, 1.0, 1.0);
|
||||
}
|
||||
|
||||
if (masktype == 1) { // Aperture Grille Lite
|
||||
vec3 m = vec3(1.0, 1.0, 1.0);
|
||||
float x = fract(pos.x * (1.0 / 3.0));
|
||||
if (x < (1.0 / 3.0)) m.r = dark;
|
||||
else if (x < (2.0 / 3.0)) m.g = dark;
|
||||
else m.b = dark;
|
||||
return m;
|
||||
}
|
||||
|
||||
if (masktype == 2) { // Aperture Grille
|
||||
vec3 m = vec3(dark, dark, dark);
|
||||
float x = fract(pos.x * (1.0 / 3.0));
|
||||
if (x < (1.0 / 3.0)) m.r = 1.0;
|
||||
else if (x < (2.0 / 3.0)) m.g = 1.0;
|
||||
else m.b = 1.0;
|
||||
return m;
|
||||
}
|
||||
|
||||
if (masktype == 3) { // Shadow Mask
|
||||
pos.x += pos.y * 2.9999;
|
||||
vec3 m = vec3(dark, dark, dark);
|
||||
float x = fract(pos.x * (1.0 / 6.0));
|
||||
if (x < (1.0 / 3.0)) m.r = 1.0;
|
||||
else if (x < (2.0 / 3.0)) m.g = 1.0;
|
||||
else m.b = 1.0;
|
||||
return m;
|
||||
}
|
||||
}
|
||||
|
||||
// The filter function itself
|
||||
// SV_POSITION, fragCoord.xy
|
||||
// sourceSize / targetSize (in pixels)
|
||||
// 0.5 * sourceSize (in pixels)
|
||||
// 1.0 / sourceSize (in pixels)
|
||||
// 1.0 / targetSize (in pixels)
|
||||
// 2.0 / targetSize (in pixels)
|
||||
// Warp scanlines but not phosphor mask
|
||||
// Tonal curve parameters generated by CrtsTone()
|
||||
vec3 CrtsFilter(vec2 ipos, vec2 inputSizeDivOutputSize, vec2 halfInputSize,
|
||||
vec2 rcpInputSize, vec2 rcpOutputSize, vec2 twoDivOutputSize,
|
||||
vec2 warp, vec4 tone) {
|
||||
#ifdef CRTS_DEBUG
|
||||
vec2 uv = ipos * rcpOutputSize;
|
||||
|
||||
// Show second half processed, and first half un-processed
|
||||
if (uv.x < 0.5) {
|
||||
// Force nearest to get squares
|
||||
uv *= 1.0 / rcpInputSize;
|
||||
uv = floor(uv) + vec2(0.5, 0.5);
|
||||
uv *= rcpInputSize;
|
||||
vec3 color = CrtsFetch(uv);
|
||||
return color;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Optional apply warp
|
||||
vec2 pos;
|
||||
|
||||
#ifdef CRTS_WARP
|
||||
// Convert to {-1 to 1} range
|
||||
pos = ipos * twoDivOutputSize - vec2(1.0, 1.0);
|
||||
|
||||
// Distort pushes image outside {-1 to 1} range
|
||||
pos *= vec2(1.0 + (pos.y * pos.y) * warp.x, 1.0 + (pos.x * pos.x) * warp.y);
|
||||
|
||||
// TODO: Vignette needs optimization
|
||||
float vin = (1.0 -
|
||||
((1.0 - CrtsSatF1(pos.x * pos.x)) * (1.0 - CrtsSatF1(pos.y * pos.y)))) *
|
||||
(0.998 + (0.001 * CORNER));
|
||||
vin = CrtsSatF1((-vin) * sourceSize.y + sourceSize.y);
|
||||
|
||||
// Leave in {0 to inputSize}
|
||||
pos = pos * halfInputSize + halfInputSize;
|
||||
#else
|
||||
pos = ipos * inputSizeDivOutputSize;
|
||||
#endif
|
||||
|
||||
// Snap to center of first scanline
|
||||
float y0 = floor(pos.y - 0.5) + 0.5;
|
||||
|
||||
#ifdef CRTS_2_TAP
|
||||
// Using Inigo's "Improved Texture Interpolation"
|
||||
// http://iquilezles.org/www/articles/texture/texture.htm
|
||||
pos.x += 0.5;
|
||||
float xi = floor(pos.x);
|
||||
float xf = pos.x - xi;
|
||||
xf = xf * xf * xf * (xf * (xf * 6.0 - 15.0) + 10.0);
|
||||
float x0 = xi + xf - 0.5;
|
||||
vec2 p = vec2(x0 * rcpInputSize.x, y0 * rcpInputSize.y);
|
||||
|
||||
// Coordinate adjusted bilinear fetch from 2 nearest scanlines
|
||||
vec3 colA = CrtsFetch(p);
|
||||
p.y += rcpInputSize.y;
|
||||
vec3 colB = CrtsFetch(p);
|
||||
#else
|
||||
// Snap to center of one of four pixels
|
||||
float x0 = floor(pos.x - 1.5) + 0.5;
|
||||
|
||||
// Inital UV position
|
||||
vec2 p = vec2(x0 * rcpInputSize.x, y0 * rcpInputSize.y);
|
||||
|
||||
// Fetch 4 nearest texels from 2 nearest scanlines
|
||||
vec3 colA0 = CrtsFetch(p);
|
||||
p.x += rcpInputSize.x;
|
||||
vec3 colA1 = CrtsFetch(p);
|
||||
p.x += rcpInputSize.x;
|
||||
vec3 colA2 = CrtsFetch(p);
|
||||
p.x += rcpInputSize.x;
|
||||
vec3 colA3 = CrtsFetch(p);
|
||||
p.y += rcpInputSize.y;
|
||||
vec3 colB3 = CrtsFetch(p);
|
||||
p.x -= rcpInputSize.x;
|
||||
vec3 colB2 = CrtsFetch(p);
|
||||
p.x -= rcpInputSize.x;
|
||||
vec3 colB1 = CrtsFetch(p);
|
||||
p.x -= rcpInputSize.x;
|
||||
vec3 colB0 = CrtsFetch(p);
|
||||
#endif
|
||||
|
||||
// Vertical filter
|
||||
// Scanline intensity is using sine wave
|
||||
// Easy filter window and integral used later in exposure
|
||||
float off = pos.y - y0;
|
||||
float pi2 = 6.28318530717958;
|
||||
float hlf = 0.5;
|
||||
float scanA = cos(min(0.5, off * INPUT_SCAN) * pi2) * hlf + hlf;
|
||||
float scanB = cos(min(0.5, (-off) * INPUT_SCAN + INPUT_SCAN) * pi2) *
|
||||
hlf + hlf;
|
||||
|
||||
#ifdef CRTS_2_TAP
|
||||
#ifdef CRTS_WARP
|
||||
// Get rid of wrong pixels on edge
|
||||
scanA *= vin;
|
||||
scanB *= vin;
|
||||
#endif
|
||||
// Apply vertical filter
|
||||
vec3 color = (colA * scanA) + (colB * scanB);
|
||||
#else
|
||||
// Horizontal kernel is simple gaussian filter
|
||||
float off0 = pos.x - x0;
|
||||
float off1 = off0 - 1.0;
|
||||
float off2 = off0 - 2.0;
|
||||
float off3 = off0 - 3.0;
|
||||
float pix0 = exp2(INPUT_SHRP * off0 * off0);
|
||||
float pix1 = exp2(INPUT_SHRP * off1 * off1);
|
||||
float pix2 = exp2(INPUT_SHRP * off2 * off2);
|
||||
float pix3 = exp2(INPUT_SHRP * off3 * off3);
|
||||
float pixT = CrtsRcpF1(pix0 + pix1 + pix2 + pix3);
|
||||
#ifdef CRTS_WARP
|
||||
// Get rid of wrong pixels on edge
|
||||
pixT *= vin;
|
||||
#endif
|
||||
scanA *= pixT;
|
||||
scanB *= pixT;
|
||||
|
||||
// Apply horizontal and vertical filters
|
||||
vec3 color =
|
||||
(colA0 * pix0 + colA1 * pix1 + colA2 * pix2 + colA3 * pix3) * scanA +
|
||||
(colB0 * pix0 + colB1 * pix1 + colB2 * pix2 + colB3 * pix3) * scanB;
|
||||
#endif
|
||||
|
||||
// Apply phosphor mask
|
||||
color *= CrtsMask(gl_FragCoord.xy, INPUT_MASK);
|
||||
|
||||
// Optional color processing
|
||||
#ifdef CRTS_TONE
|
||||
// Tonal control, start by protecting from /0
|
||||
float peak = max(1.0 / (256.0 * 65536.0),
|
||||
CrtsMax3F1(color.r, color.g, color.b));
|
||||
|
||||
// Compute the ratios of {R,G,B}
|
||||
vec3 ratio = color * CrtsRcpF1(peak);
|
||||
|
||||
// Apply tonal curve to peak value
|
||||
#ifdef CRTS_CONTRAST
|
||||
peak = pow(peak, tone.x);
|
||||
#endif
|
||||
peak = peak * CrtsRcpF1(peak * tone.y + tone.z);
|
||||
|
||||
// Apply saturation
|
||||
#ifdef CRTS_SATURATION
|
||||
ratio = pow(ratio, vec3(tone.w, tone.w, tone.w));
|
||||
#endif
|
||||
|
||||
// Reconstruct color
|
||||
return ratio * peak;
|
||||
#else
|
||||
return color;
|
||||
#endif
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 warp_factor;
|
||||
warp_factor.x = CURVATURE;
|
||||
warp_factor.y = (3.0 / 4.0) * warp_factor.x; // assume 4:3 aspect
|
||||
warp_factor.x *= (1.0 - TRINITRON_CURVE);
|
||||
|
||||
FragColor.rgb = CrtsFilter(texCoord.xy * targetSize.xy,
|
||||
sourceSize.xy * targetSize.zw,
|
||||
sourceSize.xy * vec2(0.5, 0.5),
|
||||
sourceSize.zw,
|
||||
targetSize.zw,
|
||||
2.0 * targetSize.zw,
|
||||
warp_factor,
|
||||
CrtsTone(1.0, 0.0, INPUT_SCAN, INPUT_MASK));
|
||||
|
||||
// Output non-linear color
|
||||
FragColor.rgb = ToSrgb(FragColor.rgb);
|
||||
}
|
35
shaders/default.fs
Normal file
35
shaders/default.fs
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2022 Rupert Carmichael
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
precision highp float;
|
||||
|
||||
in vec2 texCoord;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform sampler2D source;
|
||||
|
||||
void main() {
|
||||
fragColor = texture(source, texCoord);
|
||||
}
|
33
shaders/default.vs
Normal file
33
shaders/default.vs
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2022 Rupert Carmichael
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
in vec2 position;
|
||||
in vec2 vtxCoord;
|
||||
|
||||
out vec2 texCoord;
|
||||
|
||||
void main() {
|
||||
texCoord = vtxCoord;
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
}
|
54
shaders/sharp-bilinear.fs
Normal file
54
shaders/sharp-bilinear.fs
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
MIT License
|
||||
|
||||
Sharp Bilinear (Modified)
|
||||
Copyright (c) 2020-2022 Rupert Carmichael
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
// Based on Public Domain work by Themaister
|
||||
|
||||
precision highp float;
|
||||
|
||||
uniform sampler2D source;
|
||||
uniform vec4 sourceSize;
|
||||
uniform vec4 targetSize;
|
||||
|
||||
in vec2 texCoord;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
float prescale = floor(targetSize.y / sourceSize.y + 0.01);
|
||||
if (prescale == 0.0) prescale = 1.0;
|
||||
vec2 texel = texCoord.xy * sourceSize.xy;
|
||||
vec2 texel_floored = floor(texel);
|
||||
vec2 s = fract(texel);
|
||||
float region_range = 0.5 - 0.5 / prescale;
|
||||
|
||||
/* Figure out where in the texel to sample to get correct pre-scaled
|
||||
bilinear. Uses the hardware bilinear interpolator to avoid having to
|
||||
sample 4 times manually.
|
||||
*/
|
||||
vec2 center_dist = s - 0.5;
|
||||
vec2 f = (center_dist - clamp(center_dist, -region_range, region_range)) *
|
||||
prescale + 0.5;
|
||||
vec2 mod_texel = texel_floored + f;
|
||||
fragColor = texture(source, mod_texel / sourceSize.xy);
|
||||
}
|
|
@ -27,6 +27,8 @@
|
|||
#include <iostream>
|
||||
#include <set>
|
||||
|
||||
#include <epoxy/gl.h>
|
||||
|
||||
#include <FL/Fl.H>
|
||||
#include <FL/Fl_Button.H>
|
||||
#include <FL/Fl_Double_Window.H>
|
||||
|
@ -125,7 +127,7 @@ static void fltkui_load_file(const char *filename) {
|
|||
jgm->load_game(arcname.c_str(), game);
|
||||
}
|
||||
else {
|
||||
VideoManager::text_print("No valid files in archive", 8, 212, 2, true);
|
||||
LogDriver::log(LogLevel::OSD, "No valid files in archive");
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -367,7 +369,7 @@ void NstGlArea::resize(int x, int y, int w, int h) {
|
|||
}
|
||||
|
||||
void FltkUi::rehash() {
|
||||
videomgr->rehash();
|
||||
videomgr->rehash(true);
|
||||
audiomgr->rehash();
|
||||
}
|
||||
|
||||
|
@ -443,6 +445,7 @@ static void fltkui_about(Fl_Widget* w, void* userdata) {
|
|||
}
|
||||
|
||||
static void quit_cb(Fl_Widget* w, void* userdata) {
|
||||
videomgr->renderer_deinit();
|
||||
nstwin->hide();
|
||||
}
|
||||
|
||||
|
@ -469,7 +472,7 @@ int NstWindow::handle(int e) {
|
|||
}
|
||||
|
||||
void NstGlArea::draw() {
|
||||
videomgr->ogl_render();
|
||||
videomgr->render();
|
||||
}
|
||||
|
||||
int NstGlArea::handle(int e) {
|
||||
|
@ -630,11 +633,12 @@ int main(int argc, char *argv[]) {
|
|||
nstwin->label("Nestopia UE");
|
||||
nstwin->show();
|
||||
menubar->show();
|
||||
|
||||
glarea->make_current();
|
||||
glarea->show();
|
||||
Fl::check();
|
||||
videomgr->renderer_init();
|
||||
|
||||
videomgr->ogl_init();
|
||||
Fl::check();
|
||||
|
||||
// Load a rom from the command line
|
||||
if (argc > 1 && argv[argc - 1][0] != '-') {
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
|
||||
void LogDriver::log(LogLevel level, std::string text) {
|
||||
if (level == LogLevel::OSD) {
|
||||
VideoManager::text_print(text.c_str(), 8, 212, 2, true);
|
||||
VideoRenderer::text_print(text.c_str(), 8, 212, 2, true);
|
||||
}
|
||||
else if (level == LogLevel::Info) {
|
||||
std::cout << text << std::endl;
|
||||
|
@ -52,7 +52,7 @@ void LogDriver::jg_log(int level, const char *fmt, ...) {
|
|||
FILE *fout = level == 1 ? stdout : stderr;
|
||||
|
||||
if (level == JG_LOG_SCR) {
|
||||
VideoManager::text_print(buffer, 8, 212, 2, true);
|
||||
VideoRenderer::text_print(buffer, 8, 212, 2, true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -60,6 +60,6 @@ void LogDriver::jg_log(int level, const char *fmt, ...) {
|
|||
fflush(fout);
|
||||
|
||||
if (level == JG_LOG_ERR) {
|
||||
VideoManager::text_print(buffer, 8, 212, 2, true);
|
||||
VideoRenderer::text_print(buffer, 8, 212, 2, true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,21 +37,26 @@
|
|||
namespace {
|
||||
|
||||
jg_setting_t fe_settings[] = {
|
||||
{ "v_scale", "Initial Window Scale",
|
||||
"N = Window scale factor at startup",
|
||||
"Set the window's initial scale factor (multiple of NES resolution)",
|
||||
2, 1, 16, FLAG_FRONTEND | JG_SETTING_RESTART
|
||||
{ "v_renderer", "Video Renderer",
|
||||
"0 = Modern, 1 = Legacy",
|
||||
"Use Modern (Core Profile) or Legacy (Compatibility Profile) OpenGL",
|
||||
0, 0, 1, FLAG_FRONTEND | JG_SETTING_RESTART
|
||||
},
|
||||
{ "v_linearfilter", "Linear Filter",
|
||||
"0 = Disable, 1 = Enable",
|
||||
"Use the GPU's built-in linear filter for video output",
|
||||
1, 0, 1, FLAG_FRONTEND
|
||||
{ "v_postproc", "Post-processing",
|
||||
"0 = Nearest Neighbour, 1 = Linear, 2 = Sharp Bilinear, 3 = CRT",
|
||||
"Select a video post-processing effect",
|
||||
2, 0, 3, FLAG_FRONTEND
|
||||
},
|
||||
{ "v_aspect", "Aspect Ratio",
|
||||
"0 = TV Correct, 1 = 1:1, 2 = 4:3",
|
||||
"Set the aspect ratio to the correct TV aspect, 1:1 (square pixels), or 4:3",
|
||||
0, 0, 2, FLAG_FRONTEND
|
||||
},
|
||||
{ "v_scale", "Initial Window Scale",
|
||||
"N = Window scale factor at startup",
|
||||
"Set the window's initial scale factor (multiple of NES resolution)",
|
||||
2, 1, 16, FLAG_FRONTEND | JG_SETTING_RESTART
|
||||
},
|
||||
{ "a_rsqual", "Audio Resampler Quality",
|
||||
"0 = Sinc (Best), 1 = Sinc (Medium), 2 = Sinc (Fast), 3 = Zero Order Hold, 4 = Linear",
|
||||
"Set the frontend's audio resampling quality. Use Sinc unless you are on extremely weak hardware.",
|
||||
|
@ -72,6 +77,42 @@ jg_setting_t fe_settings[] = {
|
|||
"Hide the crosshair when a Zapper is present",
|
||||
0, 0, 1, FLAG_FRONTEND
|
||||
},
|
||||
{ "s_crtmasktype", "CRT Mask Type",
|
||||
"0 = No Mask, 1 = Aperture Grille Lite, 2 = Aperture Grille, "
|
||||
"3 = Shadow Mask",
|
||||
"",
|
||||
1, 0, 3, FLAG_FRONTEND
|
||||
},
|
||||
{ "s_crtmaskstr", "CRT Mask Strength",
|
||||
"N = CRT Mask Strength",
|
||||
"",
|
||||
5, 0, 10, FLAG_FRONTEND
|
||||
},
|
||||
{ "s_crtscanstr", "CRT Scanline Strength",
|
||||
"N = CRT Scanline Strength",
|
||||
"",
|
||||
6, 0, 10, FLAG_FRONTEND
|
||||
},
|
||||
{ "s_crtsharp", "CRT Sharpness",
|
||||
"N = CRT Sharpness",
|
||||
"",
|
||||
4, 0, 10, FLAG_FRONTEND
|
||||
},
|
||||
{ "s_crtcurve", "CRT Curve",
|
||||
"N = CRT Curvature",
|
||||
"",
|
||||
3, 0, 10, FLAG_FRONTEND
|
||||
},
|
||||
{ "s_crtcorner", "CRT Corner",
|
||||
"N = CRT Corner",
|
||||
"",
|
||||
3, 0, 10, FLAG_FRONTEND
|
||||
},
|
||||
{ "s_crttcurve", "CRT Trinitron Curve",
|
||||
"N = CRT Trinitron Curvature",
|
||||
"",
|
||||
10, 0, 10, FLAG_FRONTEND
|
||||
},
|
||||
};
|
||||
|
||||
jg_setting_t nullsetting;
|
||||
|
|
|
@ -22,16 +22,20 @@
|
|||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include <FL/gl.h>
|
||||
#include <filesystem>
|
||||
|
||||
#include "videomanager.h"
|
||||
|
||||
#include "logdriver.h"
|
||||
|
||||
#include "font.h"
|
||||
#include "png.h"
|
||||
|
||||
namespace {
|
||||
|
||||
jg_videoinfo_t *vidinfo;
|
||||
uint32_t *videobuf;
|
||||
|
||||
struct osdtext {
|
||||
int xpos;
|
||||
int ypos;
|
||||
|
@ -42,77 +46,22 @@ struct osdtext {
|
|||
bool bg;
|
||||
} osdtext;
|
||||
|
||||
jg_videoinfo_t *vidinfo;
|
||||
uint32_t *videobuf;
|
||||
// Dimensions
|
||||
struct _dimensions {
|
||||
int ww; int wh;
|
||||
float rw; float rh;
|
||||
float xo; float yo;
|
||||
float dpiscale;
|
||||
} dimensions;
|
||||
|
||||
} // namespace
|
||||
|
||||
VideoRenderer::VideoRenderer(SettingManager& setmgr)
|
||||
: setmgr(setmgr) {
|
||||
}
|
||||
|
||||
VideoManager::VideoManager(JGManager& jgm, SettingManager& setmgr)
|
||||
: jgm(jgm), setmgr(setmgr) {
|
||||
// Initialize video
|
||||
vidinfo = jg_get_videoinfo();
|
||||
|
||||
videobuf = (uint32_t*)calloc(1, vidinfo->hmax * vidinfo->wmax * sizeof(uint32_t));
|
||||
vidinfo->buf = (void*)&videobuf[0];
|
||||
|
||||
set_aspect();
|
||||
|
||||
int scale = setmgr.get_setting("v_scale")->val;
|
||||
dimensions.ww = (aspect * vidinfo->h * scale) + 0.5;
|
||||
dimensions.wh = (vidinfo->h * scale) + 0.5;
|
||||
dimensions.rw = dimensions.ww;
|
||||
dimensions.rh = dimensions.wh;
|
||||
dimensions.dpiscale = 1.0;
|
||||
}
|
||||
|
||||
VideoManager::~VideoManager() {
|
||||
ogl_deinit();
|
||||
if (videobuf) {
|
||||
free(videobuf);
|
||||
}
|
||||
}
|
||||
|
||||
void VideoManager::set_aspect() {
|
||||
switch (setmgr.get_setting("v_aspect")->val) {
|
||||
case 0:
|
||||
aspect = vidinfo->aspect;
|
||||
break;
|
||||
case 1:
|
||||
if (jgm.get_setting("ntsc_filter")->val) {
|
||||
aspect = 301/(double)vidinfo->h;
|
||||
}
|
||||
else {
|
||||
aspect = vidinfo->w/(double)vidinfo->h;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
aspect = 4.0/3.0;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoManager::rehash() {
|
||||
GLuint filter = setmgr.get_setting("v_linearfilter")->val ? GL_LINEAR : GL_NEAREST;
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
|
||||
|
||||
set_aspect();
|
||||
dimensions.rw = dimensions.ww;
|
||||
dimensions.rh = dimensions.wh;
|
||||
|
||||
// Check which dimension to optimize
|
||||
if (dimensions.rh * aspect > dimensions.rw)
|
||||
dimensions.rh = dimensions.rw / aspect + 0.5;
|
||||
else if (dimensions.rw / aspect > dimensions.rh)
|
||||
dimensions.rw = dimensions.rh * aspect + 0.5;
|
||||
|
||||
// Store X and Y offsets
|
||||
dimensions.xo = (dimensions.ww - dimensions.rw) / 2;
|
||||
dimensions.yo = (dimensions.wh - dimensions.rh) / 2;
|
||||
}
|
||||
|
||||
void VideoManager::ogl_init() {
|
||||
VideoRendererLegacy::VideoRendererLegacy(SettingManager& setmgr)
|
||||
: VideoRenderer(setmgr) {
|
||||
// Generate texture for raw game output
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glGenTextures(1, &gl_texture_id);
|
||||
|
@ -124,7 +73,7 @@ void VideoManager::ogl_init() {
|
|||
vidinfo->wmax, vidinfo->hmax, 0, GL_BGRA, GL_UNSIGNED_BYTE,
|
||||
videobuf);
|
||||
|
||||
GLuint filter = setmgr.get_setting("v_linearfilter")->val ? GL_LINEAR : GL_NEAREST;
|
||||
GLuint filter = setmgr.get_setting("v_postproc")->val ? GL_LINEAR : GL_NEAREST;
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
@ -132,25 +81,103 @@ void VideoManager::ogl_init() {
|
|||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
|
||||
}
|
||||
|
||||
void VideoManager::ogl_deinit() {
|
||||
// Deinitialize OpenGL
|
||||
VideoRendererLegacy::~VideoRendererLegacy() {
|
||||
if (gl_texture_id) {
|
||||
glDeleteTextures(1, &gl_texture_id);
|
||||
}
|
||||
}
|
||||
|
||||
void VideoManager::ogl_render() {
|
||||
// OSD Text
|
||||
if (osdtext.drawtext) {
|
||||
text_draw(osdtext.textbuf, osdtext.xpos, osdtext.ypos, osdtext.bg);
|
||||
osdtext.drawtext--;
|
||||
VideoRendererModern::VideoRendererModern(SettingManager& setmgr)
|
||||
: VideoRenderer(setmgr) {
|
||||
// Create Vertex Array Objects
|
||||
glGenVertexArrays(1, &vao[0]);
|
||||
glGenVertexArrays(1, &vao[1]);
|
||||
|
||||
// Create Vertex Buffer Objects
|
||||
glGenBuffers(1, &vbo[0]);
|
||||
glGenBuffers(1, &vbo[1]);
|
||||
|
||||
// Bind buffers for vertex buffer objects
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices),
|
||||
vertices, GL_STATIC_DRAW);
|
||||
|
||||
GLfloat vertices_out[] = {
|
||||
-1.0, -1.0, // Vertex 1 (X, Y) Left Bottom
|
||||
-1.0, 1.0, // Vertex 2 (X, Y) Left Top
|
||||
1.0, -1.0, // Vertex 3 (X, Y) Right Bottom
|
||||
1.0, 1.0, // Vertex 4 (X, Y) Right Top
|
||||
0.0, 1.0, // Texture 1 (X, Y) Left Bottom
|
||||
0.0, 0.0, // Texture 2 (X, Y) Left Top
|
||||
1.0, 1.0, // Texture 3 (X, Y) Right Bottom
|
||||
1.0, 0.0, // Texture 4 (X, Y) Right Top
|
||||
};
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_out),
|
||||
vertices_out, GL_STATIC_DRAW);
|
||||
|
||||
// Bind vertex array and specify layout for first pass
|
||||
glBindVertexArray(vao[0]);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
|
||||
|
||||
// Generate texture for raw game output
|
||||
glGenTextures(1, &tex[0]);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, tex[0]);
|
||||
|
||||
// The full sized source image before any clipping
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
|
||||
vidinfo->wmax, vidinfo->hmax, 0, GL_BGRA, GL_UNSIGNED_BYTE,
|
||||
videobuf);
|
||||
|
||||
// Create framebuffer
|
||||
glGenFramebuffers(1, &framebuf);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, framebuf);
|
||||
|
||||
// Create texture to hold colour buffer
|
||||
glGenTextures(1, &tex[1]);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, tex[1]);
|
||||
|
||||
// The framebuffer texture that is being rendered to offscreen, after clip
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
|
||||
vidinfo->w, vidinfo->h, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
|
||||
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
||||
tex[1], 0);
|
||||
|
||||
shader_setup();
|
||||
|
||||
ogl_refresh();
|
||||
}
|
||||
|
||||
VideoRendererModern::~VideoRendererModern() {
|
||||
if (gl_texture_id) {
|
||||
glDeleteTextures(1, &gl_texture_id);
|
||||
}
|
||||
|
||||
if (osdtext.drawtime) {
|
||||
text_draw(osdtext.timebuf, 208, 218, false);
|
||||
if (framebuf) {
|
||||
glDeleteFramebuffers(1, &framebuf);
|
||||
}
|
||||
|
||||
//jgrf_video_gl_refresh(); // Check for changes
|
||||
for (size_t i = 0; i < NUMPASSES; ++i) {
|
||||
if (shaderprog[i]) glDeleteProgram(shaderprog[i]);
|
||||
if (tex[i]) glDeleteTextures(1, &tex[i]);
|
||||
if (vao[i]) glDeleteVertexArrays(1, &vao[i]);
|
||||
if (vbo[i]) glDeleteBuffers(1, &vbo[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void VideoRendererLegacy::rehash(bool reset_shaders) {
|
||||
if (reset_shaders) {
|
||||
GLuint filter = setmgr.get_setting("v_postproc")->val ? GL_LINEAR : GL_NEAREST;
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
|
||||
}
|
||||
}
|
||||
|
||||
void VideoRendererLegacy::ogl_refresh() {
|
||||
float top = (float)vidinfo->y / vidinfo->hmax;
|
||||
float bottom = 1.0 + top -
|
||||
((vidinfo->hmax - (float)vidinfo->h) / vidinfo->hmax);
|
||||
|
@ -166,6 +193,20 @@ void VideoManager::ogl_render() {
|
|||
vertices[8] = vertices[10] = left;
|
||||
vertices[12] = vertices[14] = right;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoRendererLegacy::ogl_render() {
|
||||
// OSD Text
|
||||
if (osdtext.drawtext) {
|
||||
text_draw(osdtext.textbuf, osdtext.xpos, osdtext.ypos, osdtext.bg);
|
||||
osdtext.drawtext--;
|
||||
}
|
||||
|
||||
if (osdtext.drawtime) {
|
||||
text_draw(osdtext.timebuf, 208, 218, false);
|
||||
}
|
||||
|
||||
ogl_refresh(); // Check for changes
|
||||
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, vidinfo->p);
|
||||
|
||||
|
@ -205,25 +246,333 @@ void VideoManager::ogl_render() {
|
|||
glEnd();
|
||||
}
|
||||
|
||||
void VideoManager::get_dimensions(int *w, int *h) {
|
||||
*w = dimensions.rw;
|
||||
*h = dimensions.rh;
|
||||
void VideoRendererModern::rehash(bool reset_shaders) {
|
||||
if (reset_shaders) {
|
||||
shader_setup();
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME maybe use std::tuple here
|
||||
void VideoManager::get_scaled_coords(int x, int y, int *xcoord, int *ycoord) {
|
||||
float xscale = dimensions.rw / (vidinfo->aspect * vidinfo->h) / dimensions.dpiscale;
|
||||
float yscale = dimensions.rh / vidinfo->h / dimensions.dpiscale;
|
||||
float xo = dimensions.xo / dimensions.dpiscale;
|
||||
float yo = dimensions.yo / dimensions.dpiscale;
|
||||
*xcoord = (x - xo) / ((vidinfo->aspect * vidinfo->h * xscale)/(float)vidinfo->w);
|
||||
*ycoord = ((y - yo) / yscale) + vidinfo->y;
|
||||
// Load a shader source file into memory
|
||||
const char* VideoRendererModern::shader_load(const char *filename) {
|
||||
FILE *file = fopen(filename, "rb");
|
||||
|
||||
//if (!file)
|
||||
// jgrf_log(JG_LOG_ERR, "Could not open shader file, exiting...\n");
|
||||
|
||||
// Get the size of the shader source file
|
||||
fseek(file, 0, SEEK_END);
|
||||
size_t size = ftell(file);
|
||||
rewind(file);
|
||||
|
||||
// Allocate memory to store the shader source including version string
|
||||
GLchar *src = (GLchar*)calloc(size + SIZE_GLSLVER, sizeof(GLchar));
|
||||
//if (!src)
|
||||
// jgrf_log(JG_LOG_ERR, "Could not allocate memory, exiting...\n");
|
||||
|
||||
// Allocate memory for the shader source without version string
|
||||
GLchar *shader = (GLchar*)calloc(size + 1, sizeof(GLchar));
|
||||
|
||||
// Write version string into the buffer for the full shader source
|
||||
//snprintf(src, SIZE_GLSLVER, "%s", settings[VIDEO_API].val ?
|
||||
// "#version 300 es\n" : "#version 330 core\n");
|
||||
snprintf(src, SIZE_GLSLVER, "%s", "#version 330 core\n");
|
||||
|
||||
if (!shader || !fread(shader, size, sizeof(GLchar), file)) {
|
||||
free(src);
|
||||
fclose(file);
|
||||
//jgrf_log(JG_LOG_ERR, "Could not open shader file, exiting...\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Close file handle after reading
|
||||
fclose(file);
|
||||
|
||||
// Append shader source to version string
|
||||
src = strncat(src, shader, size + SIZE_GLSLVER);
|
||||
|
||||
// Free the shader source without version string
|
||||
free(shader);
|
||||
|
||||
return src;
|
||||
}
|
||||
|
||||
void VideoManager::resize(int w, int h) {
|
||||
dimensions.ww = w;
|
||||
dimensions.wh = h;
|
||||
rehash();
|
||||
GLuint VideoRendererModern::shader_create(const char *vs, const char *fs) {
|
||||
// If the binary is run from the source directory, shader path is PWD
|
||||
std::string shaderpath{};
|
||||
if (std::filesystem::exists(std::filesystem::path{"shaders/default.vs"})) {
|
||||
shaderpath = std::string(std::getenv("PWD")) + "/shaders/";
|
||||
}
|
||||
else {
|
||||
shaderpath = std::string(NST_DATADIR) + "/shaders/";
|
||||
}
|
||||
|
||||
std::string vspath{shaderpath + std::string(vs)};
|
||||
std::string fspath{shaderpath + std::string(fs)};
|
||||
|
||||
const GLchar *vsrc = shader_load(vspath.c_str());
|
||||
const GLchar *fsrc = shader_load(fspath.c_str());
|
||||
GLint err;
|
||||
|
||||
// Create and compile the vertex shader
|
||||
GLuint vshader = glCreateShader(GL_VERTEX_SHADER);
|
||||
glShaderSource(vshader, 1, &vsrc, NULL);
|
||||
glCompileShader(vshader);
|
||||
|
||||
// Test if the shader compiled
|
||||
glGetShaderiv(vshader, GL_COMPILE_STATUS, &err);
|
||||
if (err == GL_FALSE) {
|
||||
char shaderlog[1024];
|
||||
glGetShaderInfoLog(vshader, 1024, NULL, shaderlog);
|
||||
LogDriver::log(LogLevel::Warn, "Vertex shader: " + std::string(shaderlog));
|
||||
}
|
||||
|
||||
// Create and compile the fragment shader
|
||||
GLuint fshader = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
glShaderSource(fshader, 1, &fsrc, NULL);
|
||||
glCompileShader(fshader);
|
||||
|
||||
// Test if the fragment shader compiled
|
||||
glGetShaderiv(fshader, GL_COMPILE_STATUS, &err);
|
||||
if (err == GL_FALSE) {
|
||||
char shaderlog[1024];
|
||||
glGetShaderInfoLog(fshader, 1024, NULL, shaderlog);
|
||||
LogDriver::log(LogLevel::Warn, "Vertex shader: " + std::string(shaderlog));
|
||||
}
|
||||
|
||||
// Free the allocated memory for shader sources
|
||||
free((GLchar*)vsrc);
|
||||
free((GLchar*)fsrc);
|
||||
|
||||
// Create the shader program
|
||||
GLuint prog = glCreateProgram();
|
||||
glAttachShader(prog, vshader);
|
||||
glAttachShader(prog, fshader);
|
||||
glLinkProgram(prog);
|
||||
|
||||
// Clean up fragment and vertex shaders
|
||||
glDeleteShader(vshader);
|
||||
glDeleteShader(fshader);
|
||||
|
||||
// Return the successfully linked shader program
|
||||
return prog;
|
||||
}
|
||||
|
||||
void VideoRendererModern::shader_setup(void) {
|
||||
for (size_t i = 0; i < NUMPASSES; ++i) {
|
||||
if (shaderprog[i]) {
|
||||
glDeleteProgram(shaderprog[i]);
|
||||
}
|
||||
texfilter[i] = GL_NEAREST;
|
||||
}
|
||||
|
||||
// Create the shader program for the first pass (clipping)
|
||||
shaderprog[0] = shader_create("default.vs", "default.fs");
|
||||
|
||||
GLint posattrib = glGetAttribLocation(shaderprog[0], "position");
|
||||
glEnableVertexAttribArray(posattrib);
|
||||
glVertexAttribPointer(posattrib, 2, GL_FLOAT, GL_FALSE, 0, 0);
|
||||
|
||||
GLint texattrib = glGetAttribLocation(shaderprog[0], "vtxCoord");
|
||||
glEnableVertexAttribArray(texattrib);
|
||||
glVertexAttribPointer(texattrib, 2, GL_FLOAT, GL_FALSE,
|
||||
0, (void*)(8 * sizeof(GLfloat)));
|
||||
|
||||
// Set up uniform for input texture
|
||||
glUseProgram(shaderprog[0]);
|
||||
glUniform1i(glGetUniformLocation(shaderprog[0], "source"), 0);
|
||||
|
||||
// Set up the post-processing shader
|
||||
switch (setmgr.get_setting("v_postproc")->val) {
|
||||
default: case 0: { // Nearest Neighbour
|
||||
shaderprog[1] = shader_create("default.vs", "default.fs");
|
||||
break;
|
||||
}
|
||||
case 1: { // Linear
|
||||
shaderprog[1] = shader_create("default.vs", "default.fs");
|
||||
texfilter[1] = GL_LINEAR;
|
||||
break;
|
||||
}
|
||||
case 2: { // Sharp Bilinear
|
||||
shaderprog[1] = shader_create("default.vs", "sharp-bilinear.fs");
|
||||
texfilter[0] = GL_LINEAR;
|
||||
texfilter[1] = GL_LINEAR;
|
||||
break;
|
||||
}
|
||||
case 3: { // CRTea
|
||||
shaderprog[1] = shader_create("default.vs", "crtea.fs");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Set texture parameters for input texture
|
||||
glBindTexture(GL_TEXTURE_2D, tex[0]);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, texfilter[0]);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, texfilter[0]);
|
||||
|
||||
// Bind vertex array and specify layout for second pass
|
||||
glBindVertexArray(vao[1]);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
|
||||
|
||||
GLint posattrib_out = glGetAttribLocation(shaderprog[1], "position");
|
||||
glEnableVertexAttribArray(posattrib_out);
|
||||
glVertexAttribPointer(posattrib_out, 2, GL_FLOAT, GL_FALSE, 0, 0);
|
||||
|
||||
GLint texattrib_out = glGetAttribLocation(shaderprog[1], "vtxCoord");
|
||||
glEnableVertexAttribArray(texattrib_out);
|
||||
glVertexAttribPointer(texattrib_out, 2, GL_FLOAT, GL_FALSE,
|
||||
0, (void*)(8 * sizeof(GLfloat)));
|
||||
|
||||
// Set up uniforms for post-processing texture
|
||||
glUseProgram(shaderprog[1]);
|
||||
|
||||
glUniform1i(glGetUniformLocation(shaderprog[1], "source"), 0);
|
||||
glUniform4f(glGetUniformLocation(shaderprog[1], "sourceSize"),
|
||||
(float)vidinfo->w, (float)vidinfo->h,
|
||||
1.0/(float)vidinfo->w, 1.0/(float)vidinfo->h);
|
||||
glUniform4f(glGetUniformLocation(shaderprog[1], "targetSize"),
|
||||
dimensions.rw, dimensions.rh,
|
||||
1.0/dimensions.rw, 1.0/dimensions.rh);
|
||||
|
||||
// Settings for CRT
|
||||
glUniform1i(glGetUniformLocation(shaderprog[1], "masktype"),
|
||||
setmgr.get_setting("s_crtmasktype")->val);
|
||||
glUniform1f(glGetUniformLocation(shaderprog[1], "maskstr"),
|
||||
setmgr.get_setting("s_crtmaskstr")->val / 10.0);
|
||||
glUniform1f(glGetUniformLocation(shaderprog[1], "scanstr"),
|
||||
setmgr.get_setting("s_crtscanstr")->val / 10.0);
|
||||
glUniform1f(glGetUniformLocation(shaderprog[1], "sharpness"),
|
||||
float(setmgr.get_setting("s_crtsharp")->val));
|
||||
glUniform1f(glGetUniformLocation(shaderprog[1], "curve"),
|
||||
setmgr.get_setting("s_crtcurve")->val / 100.0);
|
||||
glUniform1f(glGetUniformLocation(shaderprog[1], "corner"),
|
||||
setmgr.get_setting("s_crtcorner")->val ?
|
||||
float(setmgr.get_setting("s_crtcorner")->val) : -3.0);
|
||||
glUniform1f(glGetUniformLocation(shaderprog[1], "tcurve"),
|
||||
setmgr.get_setting("s_crttcurve")->val / 10.0);
|
||||
|
||||
// Set parameters for output texture
|
||||
glBindTexture(GL_TEXTURE_2D, tex[1]);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, texfilter[1]);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, texfilter[1]);
|
||||
}
|
||||
|
||||
void VideoRendererModern::ogl_render() {
|
||||
// OSD Text
|
||||
if (osdtext.drawtext) {
|
||||
text_draw(osdtext.textbuf, osdtext.xpos, osdtext.ypos, osdtext.bg);
|
||||
osdtext.drawtext--;
|
||||
}
|
||||
|
||||
if (osdtext.drawtime) {
|
||||
text_draw(osdtext.timebuf, 208, 218, false);
|
||||
}
|
||||
|
||||
ogl_refresh(); // Check for changes
|
||||
|
||||
// Viewport set to size of the input pixel array
|
||||
glViewport(0, 0, vidinfo->w, vidinfo->h);
|
||||
|
||||
// Make sure first pass shader program is active
|
||||
glUseProgram(shaderprog[0]);
|
||||
|
||||
// Bind user-created framebuffer and draw scene onto it
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, framebuf);
|
||||
glBindVertexArray(vao[0]);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, tex[0]);
|
||||
|
||||
// Render if there is new pixel data, do Black Frame Insertion otherwise
|
||||
glTexSubImage2D(GL_TEXTURE_2D,
|
||||
0,
|
||||
0, // xoffset
|
||||
0, // yoffset
|
||||
vidinfo->w + vidinfo->x, // width
|
||||
vidinfo->h + vidinfo->y, // height
|
||||
GL_BGRA, // format
|
||||
GL_UNSIGNED_BYTE, // type
|
||||
videobuf);
|
||||
|
||||
// Clear the screen to black
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
// Draw a rectangle from the 2 triangles
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
// Now deal with the actual image to be output
|
||||
// Viewport adjusted for output
|
||||
glViewport(dimensions.xo, dimensions.yo, dimensions.rw, dimensions.rh);
|
||||
|
||||
// Make sure second pass shader program is active
|
||||
glUseProgram(shaderprog[1]);
|
||||
|
||||
// Bind default framebuffer and draw contents of user framebuffer
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glBindVertexArray(vao[1]);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, tex[1]);
|
||||
|
||||
// Clear the screen to black again
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
// Draw framebuffer contents
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
void VideoRendererModern::ogl_refresh() {
|
||||
float top = (float)vidinfo->y / vidinfo->hmax;
|
||||
float bottom = 1.0 + top -
|
||||
((vidinfo->hmax - (float)vidinfo->h) / vidinfo->hmax);
|
||||
float left = (float)vidinfo->x / vidinfo->wmax;
|
||||
float right = 1.0 + left -
|
||||
((vidinfo->wmax -(float)vidinfo->w) / vidinfo->wmax);
|
||||
|
||||
// Check if any vertices have changed since last time
|
||||
if (vertices[9] != top || vertices[11] != bottom
|
||||
|| vertices[8] != left || vertices[12] != right) {
|
||||
vertices[9] = vertices[13] = top;
|
||||
vertices[11] = vertices[15] = bottom;
|
||||
vertices[8] = vertices[10] = left;
|
||||
vertices[12] = vertices[14] = right;
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bind the VAO/VBO for the offscreen texture, update with new vertex data
|
||||
glBindVertexArray(vao[0]);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
|
||||
|
||||
// Resize the offscreen texture
|
||||
glBindTexture(GL_TEXTURE_2D, tex[0]);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
|
||||
vidinfo->wmax, vidinfo->hmax, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
|
||||
|
||||
// Set row length
|
||||
glUseProgram(shaderprog[0]);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, vidinfo->p);
|
||||
|
||||
// Resize the output texture
|
||||
glUseProgram(shaderprog[1]);
|
||||
glBindTexture(GL_TEXTURE_2D, tex[1]);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
|
||||
vidinfo->w, vidinfo->h, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
|
||||
|
||||
// Update uniforms for post-processing
|
||||
glUniform4f(glGetUniformLocation(shaderprog[1], "sourceSize"),
|
||||
(float)vidinfo->w, (float)vidinfo->h,
|
||||
1.0/(float)vidinfo->w, 1.0/(float)vidinfo->h);
|
||||
glUniform4f(glGetUniformLocation(shaderprog[1], "targetSize"),
|
||||
dimensions.rw, dimensions.rh,
|
||||
1.0/dimensions.rw, 1.0/dimensions.rh);
|
||||
}
|
||||
|
||||
/*void video_screenshot_flip(unsigned char *pixels, int width, int height, int bytes) {
|
||||
|
@ -266,7 +615,7 @@ void video_screenshot(const char* filename) {
|
|||
free(pixels);
|
||||
}*/
|
||||
|
||||
void VideoManager::text_print(const char *text, int xpos, int ypos, int seconds, bool bg) {
|
||||
void VideoRenderer::text_print(const char *text, int xpos, int ypos, int seconds, bool bg) {
|
||||
snprintf(osdtext.textbuf, sizeof(osdtext.textbuf), "%s", text);
|
||||
osdtext.xpos = xpos;
|
||||
osdtext.ypos = ypos;
|
||||
|
@ -274,12 +623,12 @@ void VideoManager::text_print(const char *text, int xpos, int ypos, int seconds,
|
|||
osdtext.bg = bg;
|
||||
}
|
||||
|
||||
void VideoManager::text_print_time(const char *timebuf, bool drawtime) {
|
||||
void VideoRenderer::text_print_time(const char *timebuf, bool drawtime) {
|
||||
snprintf(osdtext.timebuf, sizeof(osdtext.timebuf), "%s", timebuf);
|
||||
osdtext.drawtime = drawtime;
|
||||
}
|
||||
|
||||
void VideoManager::text_draw(const char *text, int xpos, int ypos, bool bg) {
|
||||
void VideoRenderer::text_draw(const char *text, int xpos, int ypos, bool bg) {
|
||||
// Draw text on screen
|
||||
uint32_t w = 0xc0c0c0c0; // "White", actually Grey
|
||||
uint32_t b = 0x00000000; // Black
|
||||
|
@ -327,7 +676,7 @@ void VideoManager::text_draw(const char *text, int xpos, int ypos, bool bg) {
|
|||
}
|
||||
}
|
||||
|
||||
void VideoManager::text_match(const char *text, int *xpos, int *ypos, int strpos) {
|
||||
void VideoRenderer::text_match(const char *text, int *xpos, int *ypos, int strpos) {
|
||||
// Match letters to draw on screen
|
||||
switch (text[strpos]) {
|
||||
case ' ': *xpos = 0; *ypos = 0; break;
|
||||
|
@ -429,3 +778,108 @@ void VideoManager::text_match(const char *text, int *xpos, int *ypos, int strpos
|
|||
default: *xpos = 0; *ypos = 0; break;
|
||||
}
|
||||
}
|
||||
|
||||
VideoManager::VideoManager(JGManager& jgm, SettingManager& setmgr)
|
||||
: jgm(jgm), setmgr(setmgr) {
|
||||
// Initialize video
|
||||
vidinfo = jg_get_videoinfo();
|
||||
|
||||
videobuf = (uint32_t*)calloc(1, vidinfo->hmax * vidinfo->wmax * sizeof(uint32_t));
|
||||
vidinfo->buf = (void*)&videobuf[0];
|
||||
|
||||
set_aspect();
|
||||
|
||||
int scale = setmgr.get_setting("v_scale")->val;
|
||||
dimensions.ww = (aspect * vidinfo->h * scale) + 0.5;
|
||||
dimensions.wh = (vidinfo->h * scale) + 0.5;
|
||||
dimensions.rw = dimensions.ww;
|
||||
dimensions.rh = dimensions.wh;
|
||||
dimensions.dpiscale = 1.0;
|
||||
}
|
||||
|
||||
VideoManager::~VideoManager() {
|
||||
if (videobuf) {
|
||||
free(videobuf);
|
||||
}
|
||||
}
|
||||
|
||||
void VideoManager::renderer_init() {
|
||||
if (setmgr.get_setting("v_renderer")->val) {
|
||||
renderer = new VideoRendererLegacy(setmgr);
|
||||
}
|
||||
else {
|
||||
renderer = new VideoRendererModern(setmgr);
|
||||
}
|
||||
}
|
||||
|
||||
void VideoManager::renderer_deinit() {
|
||||
if (renderer) {
|
||||
delete renderer;
|
||||
renderer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoManager::render() {
|
||||
renderer->ogl_render();
|
||||
}
|
||||
|
||||
void VideoManager::get_dimensions(int *w, int *h) {
|
||||
*w = dimensions.rw;
|
||||
*h = dimensions.rh;
|
||||
}
|
||||
|
||||
// FIXME maybe use std::tuple here
|
||||
void VideoManager::get_scaled_coords(int x, int y, int *xcoord, int *ycoord) {
|
||||
float xscale = dimensions.rw / (vidinfo->aspect * vidinfo->h) / dimensions.dpiscale;
|
||||
float yscale = dimensions.rh / vidinfo->h / dimensions.dpiscale;
|
||||
float xo = dimensions.xo / dimensions.dpiscale;
|
||||
float yo = dimensions.yo / dimensions.dpiscale;
|
||||
*xcoord = (x - xo) / ((vidinfo->aspect * vidinfo->h * xscale)/(float)vidinfo->w);
|
||||
*ycoord = ((y - yo) / yscale) + vidinfo->y;
|
||||
}
|
||||
|
||||
void VideoManager::rehash(bool reset_shaders) {
|
||||
renderer->rehash(reset_shaders);
|
||||
|
||||
set_aspect();
|
||||
dimensions.rw = dimensions.ww;
|
||||
dimensions.rh = dimensions.wh;
|
||||
|
||||
// Check which dimension to optimize
|
||||
if (dimensions.rh * aspect > dimensions.rw) {
|
||||
dimensions.rh = dimensions.rw / aspect + 0.5;
|
||||
}
|
||||
else if (dimensions.rw / aspect > dimensions.rh) {
|
||||
dimensions.rw = dimensions.rh * aspect + 0.5;
|
||||
}
|
||||
|
||||
// Store X and Y offsets
|
||||
dimensions.xo = (dimensions.ww - dimensions.rw) / 2;
|
||||
dimensions.yo = (dimensions.wh - dimensions.rh) / 2;
|
||||
}
|
||||
|
||||
void VideoManager::resize(int w, int h) {
|
||||
dimensions.ww = w;
|
||||
dimensions.wh = h;
|
||||
rehash();
|
||||
}
|
||||
|
||||
void VideoManager::set_aspect() {
|
||||
switch (setmgr.get_setting("v_aspect")->val) {
|
||||
case 0:
|
||||
aspect = vidinfo->aspect;
|
||||
break;
|
||||
case 1:
|
||||
if (jgm.get_setting("ntsc_filter")->val) {
|
||||
aspect = 301/(double)vidinfo->h;
|
||||
}
|
||||
else {
|
||||
aspect = vidinfo->w/(double)vidinfo->h;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
aspect = 4.0/3.0;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +1,31 @@
|
|||
#pragma once
|
||||
|
||||
#include <epoxy/gl.h>
|
||||
|
||||
#include "setmanager.h"
|
||||
#include "jgmanager.h"
|
||||
|
||||
class VideoManager {
|
||||
class VideoRenderer {
|
||||
public:
|
||||
VideoManager() = delete;
|
||||
VideoManager(JGManager& jgm, SettingManager& setmgr);
|
||||
~VideoManager();
|
||||
VideoRenderer() = delete;
|
||||
VideoRenderer(SettingManager& setmgr);
|
||||
virtual ~VideoRenderer() {};
|
||||
|
||||
void get_dimensions(int *w, int *h);
|
||||
void get_scaled_coords(int x, int y, int *xcoord, int *ycoord);
|
||||
virtual void ogl_render() = 0;
|
||||
virtual void ogl_refresh() = 0;
|
||||
|
||||
void rehash();
|
||||
void resize(int w, int h);
|
||||
void set_aspect();
|
||||
|
||||
void ogl_init();
|
||||
void ogl_deinit();
|
||||
void ogl_render();
|
||||
virtual void rehash(bool reset_shaders = false) = 0;
|
||||
|
||||
static void text_print(const char *text, int xpos, int ypos, int seconds, bool bg);
|
||||
static void text_print_time(const char *timebuf, bool drawtime);
|
||||
|
||||
protected:
|
||||
static void text_draw(const char *text, int xpos, int ypos, bool bg);
|
||||
static void text_print_time(const char *timebuf, bool drawtime);
|
||||
static void text_match(const char *text, int *xpos, int *ypos, int strpos);
|
||||
|
||||
private:
|
||||
JGManager &jgm;
|
||||
SettingManager &setmgr;
|
||||
|
||||
double aspect{1.0};
|
||||
GLuint gl_texture_id{0};
|
||||
|
||||
// Triangle and Texture vertices
|
||||
float vertices[16]{
|
||||
|
@ -42,14 +38,69 @@ private:
|
|||
1.0, 0.0, // Texture 4 (X, Y) Right Top
|
||||
1.0, 1.0, // Texture 3 (X, Y) Right Bottom
|
||||
};
|
||||
|
||||
unsigned int gl_texture_id{0};
|
||||
|
||||
// Dimensions
|
||||
struct _dimensions {
|
||||
int ww; int wh;
|
||||
float rw; float rh;
|
||||
float xo; float yo;
|
||||
float dpiscale;
|
||||
} dimensions;
|
||||
};
|
||||
|
||||
class VideoRendererLegacy : public VideoRenderer {
|
||||
public:
|
||||
VideoRendererLegacy() = delete;
|
||||
VideoRendererLegacy(SettingManager& setmgr);
|
||||
~VideoRendererLegacy() override;
|
||||
|
||||
void ogl_render() override;
|
||||
void ogl_refresh() override;
|
||||
|
||||
void rehash(bool reset_shaders = false) override;
|
||||
};
|
||||
|
||||
class VideoRendererModern : public VideoRenderer {
|
||||
public:
|
||||
VideoRendererModern() = delete;
|
||||
VideoRendererModern(SettingManager& setmgr);
|
||||
~VideoRendererModern() override;
|
||||
|
||||
void ogl_render() override;
|
||||
void ogl_refresh() override;
|
||||
|
||||
void rehash(bool reset_shaders = false) override;
|
||||
|
||||
private:
|
||||
GLuint shader_create(const char *vs, const char *fs);
|
||||
void shader_setup(void);
|
||||
const char* shader_load(const char *filename);
|
||||
|
||||
static constexpr size_t NUMPASSES = 2;
|
||||
#define SIZE_GLSLVER 20
|
||||
|
||||
GLuint vao[NUMPASSES];
|
||||
GLuint vbo[NUMPASSES];
|
||||
GLuint shaderprog[NUMPASSES];
|
||||
GLuint tex[NUMPASSES];
|
||||
GLuint texfilter[NUMPASSES];
|
||||
GLuint framebuf; // Framebuffer for rendering offscreen
|
||||
};
|
||||
|
||||
class VideoManager {
|
||||
public:
|
||||
VideoManager() = delete;
|
||||
VideoManager(JGManager& jgm, SettingManager& setmgr);
|
||||
~VideoManager();
|
||||
|
||||
void get_dimensions(int *w, int *h);
|
||||
void get_scaled_coords(int x, int y, int *xcoord, int *ycoord);
|
||||
|
||||
void rehash(bool reset_shaders = false);
|
||||
void resize(int w, int h);
|
||||
void set_aspect();
|
||||
|
||||
void renderer_init();
|
||||
void renderer_deinit();
|
||||
void render();
|
||||
|
||||
private:
|
||||
JGManager &jgm;
|
||||
SettingManager &setmgr;
|
||||
|
||||
VideoRenderer *renderer{nullptr};
|
||||
|
||||
double aspect{1.0};
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue