Compare commits

...

2 commits

Author SHA1 Message Date
rdanbrook faf3d38137 Update ChangeLog with list of recent changes 2024-06-05 19:42:58 -06:00
rdanbrook 44d577dfec FLTK: Add MMPX and OmniScale 2024-06-05 19:00:38 -06:00
6 changed files with 602 additions and 3 deletions

View file

@ -1,3 +1,23 @@
----------------------------------------------------------------
Upcoming
----------------------------------------------------------------
Shell:
Changes:
- FLTK: Major rewrite to become a Nestopia-specific "Jolly Good API" frontend
- FLTK: Use libsamplerate directly instead of relying on SDL's abstraction
- FLTK: Better looking theme
- FLTK: Window is freely resizable and the image scales to the window size
Additions:
- FLTK: Support for significantly more input devices
- FLTK: Joystick hotplugging
- FLTK: All UI controls may be assigned to a joystick button
- FLTK: Aspect ratio options
- FLTK: OpenGL 3.1/GLES 3.0 renderer with shaders
- FLTK: Configurable overscan masking on all edges
- FLTK: Drag and Drop support
----------------------------------------------------------------
1.52.1
----------------------------------------------------------------

View file

@ -40,7 +40,7 @@ dist_palette_DATA = palettes/SONY_CXA2025AS_US.pal palettes/Royaltea.pal \
shaderdir = $(datadir)/nestopia/shaders
dist_shader_DATA = shaders/default.vs shaders/default.fs shaders/crtea.fs \
shaders/sharp-bilinear.fs
shaders/sharp-bilinear.fs shaders/mmpx.fs shaders/omniscale.fs
# freedesktop.org-specific files
desktopdir = $(datarootdir)/applications

281
shaders/mmpx.fs Normal file
View file

@ -0,0 +1,281 @@
// MMPX
// by Morgan McGuire and Mara Gagiu
// https://casual-effects.com/research/McGuire2021PixelArt/
// License: MIT
// adapted for slang by hunterk
precision highp float;
uniform sampler2D source;
uniform vec4 sourceSize;
uniform vec4 targetSize;
in vec2 texCoord;
out vec4 fragColor;
float luma(vec3 col){
return dot(col, vec3(0.2126, 0.7152, 0.0722));
}
// I tried using a hash to speed these up but it didn't really help
bool same(vec3 B, vec3 A0){
return all(equal(B, A0));
}
bool notsame(vec3 B, vec3 A0){
return any(notEqual(B, A0));
}
bool all_eq2(vec3 B, vec3 A0, vec3 A1) {
return (same(B,A0) && same(B,A1));
}
bool all_eq3(vec3 B, vec3 A0, vec3 A1, vec3 A2) {
return (same(B,A0) && same(B,A1) && same(B,A2));
}
bool all_eq4(vec3 B, vec3 A0, vec3 A1, vec3 A2, vec3 A3) {
return (same(B,A0) && same(B,A1) && same(B,A2) && same(B,A3));
}
bool any_eq3(vec3 B, vec3 A0, vec3 A1, vec3 A2) {
return (same(B,A0) || same(B,A1) || same(B,A2));
}
bool none_eq2(vec3 B, vec3 A0, vec3 A1) {
return (notsame(B,A0) && notsame(B,A1));
}
bool none_eq4(vec3 B, vec3 A0, vec3 A1, vec3 A2, vec3 A3) {
return (notsame(B,A0) && notsame(B,A1) && notsame(B,A2) && notsame(B,A3));
}
#define src(c,d) texture(source, texCoord + vec2(c,d) * sourceSize.zw).rgb
void main()
{
// these do nothing, but just for consistency with the original code...
float srcX = 0.;
float srcY = 0.;
// Our current pixel
vec3 E = src(srcX+0.,srcY+0.);
// Input: A-I central 3x3 grid
vec3 A = src(srcX-1.,srcY-1.);
vec3 B = src(srcX+0.,srcY-1.);
vec3 C = src(srcX+1.,srcY-1.);
vec3 D = src(srcX-1.,srcY+0.);
vec3 F = src(srcX+1.,srcY+0.);
vec3 G = src(srcX-1.,srcY+1.);
vec3 H = src(srcX+0.,srcY+1.);
vec3 I = src(srcX+1.,srcY+1.);
// Default to Nearest magnification
vec3 J = E;
vec3 K = E;
vec3 L = E;
vec3 M = E;
// Go ahead and put this here so we can use an early return on the next
// line to save some cycles
fragColor = vec4(E, 1.0);
// Skip constant 3x3 centers and just use nearest-neighbor
// them. This gives a good speedup on spritesheets with
// lots of padding and full screen images with large
// constant regions such as skies.
// EDIT: this is a wash for me, but we'll keep it around
if(same(E,A) && same(E,B) && same(E,C) && same(E,D) && same(E,F) && same(E,G) && same(E,H) && same(E,I)) return;
// Read additional values at the tips of the diamond pattern
vec3 P = src(srcX+0.,srcY-2.);
vec3 Q = src(srcX-2.,srcY+0.);
vec3 R = src(srcX+2.,srcY+0.);
vec3 S = src(srcX+0.,srcY+2.);
// Precompute luminances
float Bl = luma(B);
float Dl = luma(D);
float El = luma(E);
float Fl = luma(F);
float Hl = luma(H);
// Round some corners and fill in 1:1 slopes, but preserve
// sharp right angles.
//
// In each expression, the left clause is from
// EPX and the others are new. EPX
// recognizes 1:1 single-pixel lines because it
// applies the rounding only to the LINE, and not
// to the background (it looks at the mirrored
// side). It thus fails on thick 1:1 edges
// because it rounds *both* sides and produces an
// aliased edge shifted by 1 dst pixel. (This
// also yields the mushroom-shaped arrow heads,
// where that 1-pixel offset runs up against the
// 2-pixel aligned end; this is an inherent
// problem with 2X in-palette scaling.)
//
// The 2nd clause clauses avoid *double* diagonal
// filling on 1:1 slopes to prevent them becoming
// aliased again. It does this by breaking
// symmetry ties using luminance when working with
// thick features (it allows thin and transparent
// features to pass always).
//
// The 3rd clause seeks to preserve square corners
// by considering the center value before
// rounding.
//
// The 4th clause identifies 1-pixel bumps on
// straight lines that are darker than their
// background, such as the tail on a pixel art
// "4", and prevents them from being rounded. This
// corrects for asymmetry in this case that the
// luminance tie breaker introduced.
// .------------ 1st ------------. .----- 2nd ---------. .------ 3rd -----. .--------------- 4th -----------------------.
if (((same(D,B) && notsame(D,H) && notsame(D,F))) && ((El>=Dl) || same(E,A)) && any_eq3(E,A,C,G) && ((El<Dl) || notsame(A,D) || notsame(E,P) || notsame(E,Q))) J=D;
if (((same(B,F) && notsame(B,D) && notsame(B,H))) && ((El>=Bl) || same(E,C)) && any_eq3(E,A,C,I) && ((El<Bl) || notsame(C,B) || notsame(E,P) || notsame(E,R))) K=B;
if (((same(H,D) && notsame(H,F) && notsame(H,B))) && ((El>=Hl) || same(E,G)) && any_eq3(E,A,G,I) && ((El<Hl) || notsame(G,H) || notsame(E,S) || notsame(E,Q))) L=H;
if (((same(F,H) && notsame(F,B) && notsame(F,D))) && ((El>=Fl) || same(E,I)) && any_eq3(E,C,G,I) && ((El<Fl) || notsame(I,H) || notsame(E,R) || notsame(E,S))) M=F;
// Clean up disconnected line intersections.
//
// The first clause recognizes being on the inside
// of a diagonal corner and ensures that the "foreground"
// has been correctly identified to avoid
// ambiguous cases such as this:
//
// o#o#
// oo##
// o#o#
//
// where trying to fix the center intersection of
// either the "o" or the "#" will leave the other
// one disconnected. This occurs, for example,
// when a pixel-art letter "B" or "R" is next to
// another letter on the right.
//
// The second clause ensures that the pattern is
// not a notch at the edge of a checkerboard
// dithering pattern.
//
// >
// .--------------------- 1st ------------------------. .--------- 2nd -----------.
if ((notsame(E,F) && all_eq4(E,C,I,D,Q) && all_eq2(F,B,H)) && notsame(F,src(srcX+3.,srcY))) K=M=F;
if ((notsame(E,D) && all_eq4(E,A,G,F,R) && all_eq2(D,B,H)) && notsame(D,src(srcX-3.,srcY))) J=L=D;
if ((notsame(E,H) && all_eq4(E,G,I,B,P) && all_eq2(H,D,F)) && notsame(H,src(srcX,srcY+3.))) L=M=H;
if ((notsame(E,B) && all_eq4(E,A,C,H,S) && all_eq2(B,D,F)) && notsame(B,src(srcX,srcY-3.))) J=K=B;
// Remove tips of bright triangles on dark
// backgrounds. The luminance tie breaker for 1:1
// pixel lines leaves these as sticking up squared
// off, which makes bright triangles and diamonds
// look bad.
if ((Bl<El) && all_eq4(E,G,H,I,S) && none_eq4(E,A,D,C,F)) J=K=B;
if ((Hl<El) && all_eq4(E,A,B,C,P) && none_eq4(E,D,G,I,F)) L=M=H;
if ((Fl<El) && all_eq4(E,A,D,G,Q) && none_eq4(E,B,C,I,H)) K=M=F;
if ((Dl<El) && all_eq4(E,C,F,I,R) && none_eq4(E,B,A,G,H)) J=L=D;
//////////////////////////////////////////////////////////////////////////////////
// Do further neighborhood peeking to identify
// 2:1 and 1:2 slopes of constant color.
// The first clause of each rule identifies a 2:1 slope line
// of consistent color.
//
// The second clause verifies that the line is separated from
// every adjacent pixel on one side and not part of a more
// complex pattern. Common subexpressions from the second clause
// are lifted to an outer test on pairs of rules.
//
// The actions taken by rules are unusual in that they extend
// a color assigned by previous rules rather than drawing from
// the original source image.
//
// The comments show a diagram of the local
// neighborhood in which letters shown with the
// same shape and color must match each other and
// everything else without annotation must be
// different from the solid colored, square
// letters.
if (notsame(H,B)) { // Common subexpression
// Above a 2:1 slope or -2:1 slope
// First:
if (notsame(H,A) && notsame(H,E) && notsame(H,C)) {
// Second:
// P
// B C .
// Q D 🄴 🅵 🆁
// 🅶 🅷 I
// S
if (all_eq3(H,G,F,R) && none_eq2(H,D,src(srcX+2.,srcY-1.))) L=M;
// Third:
// P
// . A B
// 🆀 🅳 🄴 F R
// G 🅷 🅸
// S
if (all_eq3(H,I,D,Q) && none_eq2(H,F,src(srcX-2.,srcY-1.))) M=L;
}
// Below a 2:1 (◤) or -2:1 (◥) slope (reflect the above 2:1 patterns vertically)
if (notsame(B,I) && notsame(B,G) && notsame(B,E)) {
// P
// 🅰 🅱 C
// Q D 🄴 🅵 🆁
// H I .
// S
if (all_eq3(B,A,F,R) && none_eq2(B,D,src(srcX+2.,srcY+1.))) J=K;
// P
// A 🅱 🅲
// 🆀 🅳 🄴 F R
// . G H
// S
if (all_eq3(B,C,D,Q) && none_eq2(B,F,src(srcX-2.,srcY+1.))) K=J;
}
}
if (notsame(F,D)) { // Common subexpression
// Right of a -1:2 (\) or -1:2 (/) slope (reflect the left 1:2 patterns horizontally)
if (notsame(D,I) && notsame(D,E) && notsame(D,C)) {
// P
// 🅰 B
// Q 🅳 🄴 F R
// G 🅷 I
// 🆂 .
if (all_eq3(D,A,H,S) && none_eq2(D,B,src(srcX+1.,srcY+2.))) J=L;
// 🅿 .
// A 🅱 C
// Q 🅳 🄴 F R
// 🅶 H
// S
if (all_eq3(D,G,B,P) && none_eq2(D,H,src(srcX+1.,srcY-2.))) L=J;
}
// Left of a 1:2 (/) slope or -1:2 (\) slope (transpose the above 2:1 patterns)
// Pull common none_eq subexpressions out
if (notsame(F,E) && notsame(F,A) && notsame(F,G)) {
// P
// B 🅲
// Q D 🄴 🅵 R
// G 🅷 I
// . 🆂
if (all_eq3(F,C,H,S) && none_eq2(F,B,src(srcX-1.,srcY+2.))) K=M;
// . 🅿
// A 🅱 C
// Q D 🄴 🅵 R
// H 🅸
// S
if (all_eq3(F,I,B,P) && none_eq2(F,H,src(srcX-1.,srcY-2.))) M=K;
}
}
// Determine which of our 4 output pixels we need to use
vec2 a = fract(texCoord * sourceSize.xy);
fragColor.rgb = (a.x < 0.5) ? (a.y < 0.5 ? J : L) : (a.y < 0.5 ? K : M);
}

290
shaders/omniscale.fs Normal file
View file

@ -0,0 +1,290 @@
// OmniScale
// by Lior Halphon
// ported to slang by hunterk
/*
MIT License
Copyright (c) 2015-2016 Lior Halphon
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;
uniform sampler2D source;
uniform vec4 sourceSize;
uniform vec4 targetSize;
in vec2 texCoord;
out vec4 fragColor;
/* We use the same colorspace as the HQ algorithms. */
vec3 rgb_to_hq_colospace(vec4 rgb)
{
return vec3( 0.250 * rgb.r + 0.250 * rgb.g + 0.250 * rgb.b,
0.250 * rgb.r - 0.000 * rgb.g - 0.250 * rgb.b,
-0.125 * rgb.r + 0.250 * rgb.g - 0.125 * rgb.b);
}
bool is_different(vec4 a, vec4 b)
{
vec3 diff = abs(rgb_to_hq_colospace(a) - rgb_to_hq_colospace(b));
return diff.x > 0.125 || diff.y > 0.027 || diff.z > 0.031;
}
#define P(m, r) ((pattern & (m)) == (r))
#define uResolution targetSize.xy
#define textureDimensions sourceSize.xy
vec4 scale(sampler2D image, vec2 coord)
{
// o = offset, the width of a pixel
vec2 o = 1.0 / textureDimensions;
vec2 texCoord = coord;
/* We always calculate the top left quarter. If we need a different quarter, we flip our co-ordinates */
// p = the position within a pixel [0...1]
vec2 p = fract(texCoord * textureDimensions);
if (p.x > 0.5) {
o.x = -o.x;
p.x = 1.0 - p.x;
}
if (p.y > 0.5) {
o.y = -o.y;
p.y = 1.0 - p.y;
}
vec4 w0 = texture(image, texCoord + vec2( -o.x, -o.y));
vec4 w1 = texture(image, texCoord + vec2( 0, -o.y));
vec4 w2 = texture(image, texCoord + vec2( o.x, -o.y));
vec4 w3 = texture(image, texCoord + vec2( -o.x, 0));
vec4 w4 = texture(image, texCoord + vec2( 0, 0));
vec4 w5 = texture(image, texCoord + vec2( o.x, 0));
vec4 w6 = texture(image, texCoord + vec2( -o.x, o.y));
vec4 w7 = texture(image, texCoord + vec2( 0, o.y));
vec4 w8 = texture(image, texCoord + vec2( o.x, o.y));
int pattern = 0;
if (is_different(w0, w4)) pattern |= 1 << 0;
if (is_different(w1, w4)) pattern |= 1 << 1;
if (is_different(w2, w4)) pattern |= 1 << 2;
if (is_different(w3, w4)) pattern |= 1 << 3;
if (is_different(w5, w4)) pattern |= 1 << 4;
if (is_different(w6, w4)) pattern |= 1 << 5;
if (is_different(w7, w4)) pattern |= 1 << 6;
if (is_different(w8, w4)) pattern |= 1 << 7;
if ((P(0xbf,0x37) || P(0xdb,0x13)) && is_different(w1, w5))
return mix(w4, w3, 0.5 - p.x);
if ((P(0xdb,0x49) || P(0xef,0x6d)) && is_different(w7, w3))
return mix(w4, w1, 0.5 - p.y);
if ((P(0x0b,0x0b) || P(0xfe,0x4a) || P(0xfe,0x1a)) && is_different(w3, w1))
return w4;
if ((P(0x6f,0x2a) || P(0x5b,0x0a) || P(0xbf,0x3a) || P(0xdf,0x5a) ||
P(0x9f,0x8a) || P(0xcf,0x8a) || P(0xef,0x4e) || P(0x3f,0x0e) ||
P(0xfb,0x5a) || P(0xbb,0x8a) || P(0x7f,0x5a) || P(0xaf,0x8a) ||
P(0xeb,0x8a)) && is_different(w3, w1))
return mix(w4, mix(w4, w0, 0.5 - p.x), 0.5 - p.y);
if (P(0x0b,0x08))
return mix(mix(w0 * 0.375 + w1 * 0.25 + w4 * 0.375, w4 * 0.5 + w1 * 0.5, p.x * 2.0), w4, p.y * 2.0);
if (P(0x0b,0x02))
return mix(mix(w0 * 0.375 + w3 * 0.25 + w4 * 0.375, w4 * 0.5 + w3 * 0.5, p.y * 2.0), w4, p.x * 2.0);
if (P(0x2f,0x2f)) {
float dist = length(p - vec2(0.5));
float pixel_size = length(1.0 / (uResolution / textureDimensions));
if (dist < 0.5 - pixel_size / 2.0) {
return w4;
}
vec4 r;
if (is_different(w0, w1) || is_different(w0, w3)) {
r = mix(w1, w3, p.y - p.x + 0.5);
}
else {
r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0);
}
if (dist > 0.5 + pixel_size / 2.0) {
return r;
}
return mix(w4, r, (dist - 0.5 + pixel_size / 2.0) / pixel_size);
}
if (P(0xbf,0x37) || P(0xdb,0x13)) {
float dist = p.x - 2.0 * p.y;
float pixel_size = length(1.0 / (uResolution / textureDimensions)) * sqrt(5.0);
if (dist > pixel_size / 2.0) {
return w1;
}
vec4 r = mix(w3, w4, p.x + 0.5);
if (dist < -pixel_size / 2.0) {
return r;
}
return mix(r, w1, (dist + pixel_size / 2.0) / pixel_size);
}
if (P(0xdb,0x49) || P(0xef,0x6d)) {
float dist = p.y - 2.0 * p.x;
float pixel_size = length(1.0 / (uResolution / textureDimensions)) * sqrt(5.0);
if (p.y - 2.0 * p.x > pixel_size / 2.0) {
return w3;
}
vec4 r = mix(w1, w4, p.x + 0.5);
if (dist < -pixel_size / 2.0) {
return r;
}
return mix(r, w3, (dist + pixel_size / 2.0) / pixel_size);
}
if (P(0xbf,0x8f) || P(0x7e,0x0e)) {
float dist = p.x + 2.0 * p.y;
float pixel_size = length(1.0 / (uResolution / textureDimensions)) * sqrt(5.0);
if (dist > 1.0 + pixel_size / 2.0) {
return w4;
}
vec4 r;
if (is_different(w0, w1) || is_different(w0, w3)) {
r = mix(w1, w3, p.y - p.x + 0.5);
}
else {
r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0);
}
if (dist < 1.0 - pixel_size / 2.0) {
return r;
}
return mix(r, w4, (dist + pixel_size / 2.0 - 1.0) / pixel_size);
}
if (P(0x7e,0x2a) || P(0xef,0xab)) {
float dist = p.y + 2.0 * p.x;
float pixel_size = length(1.0 / (uResolution / textureDimensions)) * sqrt(5.0);
if (p.y + 2.0 * p.x > 1.0 + pixel_size / 2.0) {
return w4;
}
vec4 r;
if (is_different(w0, w1) || is_different(w0, w3)) {
r = mix(w1, w3, p.y - p.x + 0.5);
}
else {
r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0);
}
if (dist < 1.0 - pixel_size / 2.0) {
return r;
}
return mix(r, w4, (dist + pixel_size / 2.0 - 1.0) / pixel_size);
}
if (P(0x1b,0x03) || P(0x4f,0x43) || P(0x8b,0x83) || P(0x6b,0x43))
return mix(w4, w3, 0.5 - p.x);
if (P(0x4b,0x09) || P(0x8b,0x89) || P(0x1f,0x19) || P(0x3b,0x19))
return mix(w4, w1, 0.5 - p.y);
if (P(0xfb,0x6a) || P(0x6f,0x6e) || P(0x3f,0x3e) || P(0xfb,0xfa) ||
P(0xdf,0xde) || P(0xdf,0x1e))
return mix(w4, w0, (1.0 - p.x - p.y) / 2.0);
if (P(0x4f,0x4b) || P(0x9f,0x1b) || P(0x2f,0x0b) ||
P(0xbe,0x0a) || P(0xee,0x0a) || P(0x7e,0x0a) || P(0xeb,0x4b) ||
P(0x3b,0x1b)) {
float dist = p.x + p.y;
float pixel_size = length(1.0 / (uResolution / textureDimensions));
if (dist > 0.5 + pixel_size / 2.0) {
return w4;
}
vec4 r;
if (is_different(w0, w1) || is_different(w0, w3)) {
r = mix(w1, w3, p.y - p.x + 0.5);
}
else {
r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0);
}
if (dist < 0.5 - pixel_size / 2.0) {
return r;
}
return mix(r, w4, (dist + pixel_size / 2.0 - 0.5) / pixel_size);
}
if (P(0x0b,0x01))
return mix(mix(w4, w3, 0.5 - p.x), mix(w1, (w1 + w3) / 2.0, 0.5 - p.x), 0.5 - p.y);
if (P(0x0b,0x00))
return mix(mix(w4, w3, 0.5 - p.x), mix(w1, w0, 0.5 - p.x), 0.5 - p.y);
float dist = p.x + p.y;
float pixel_size = length(1.0 / (uResolution / textureDimensions));
if (dist > 0.5 + pixel_size / 2.0)
return w4;
/* We need more samples to "solve" this diagonal */
vec4 x0 = texture(image, texCoord + vec2( -o.x * 2.0, -o.y * 2.0));
vec4 x1 = texture(image, texCoord + vec2( -o.x , -o.y * 2.0));
vec4 x2 = texture(image, texCoord + vec2( 0.0 , -o.y * 2.0));
vec4 x3 = texture(image, texCoord + vec2( o.x , -o.y * 2.0));
vec4 x4 = texture(image, texCoord + vec2( -o.x * 2.0, -o.y ));
vec4 x5 = texture(image, texCoord + vec2( -o.x * 2.0, 0.0 ));
vec4 x6 = texture(image, texCoord + vec2( -o.x * 2.0, o.y ));
if (is_different(x0, w4)) pattern |= 1 << 8;
if (is_different(x1, w4)) pattern |= 1 << 9;
if (is_different(x2, w4)) pattern |= 1 << 10;
if (is_different(x3, w4)) pattern |= 1 << 11;
if (is_different(x4, w4)) pattern |= 1 << 12;
if (is_different(x5, w4)) pattern |= 1 << 13;
if (is_different(x6, w4)) pattern |= 1 << 14;
int diagonal_bias = -7;
while (pattern != 0) {
diagonal_bias += pattern & 1;
pattern >>= 1;
}
if (diagonal_bias <= 0) {
vec4 r = mix(w1, w3, p.y - p.x + 0.5);
if (dist < 0.5 - pixel_size / 2.0) {
return r;
}
return mix(r, w4, (dist + pixel_size / 2.0 - 0.5) / pixel_size);
}
return w4;
}
void main()
{
fragColor = scale(source, texCoord);
}

View file

@ -44,9 +44,9 @@ jg_setting_t fe_settings[] = {
0, 0, 1, FLAG_FRONTEND | JG_SETTING_RESTART
},
{ "v_postproc", "Post-processing",
"0 = Nearest Neighbour, 1 = Linear, 2 = Sharp Bilinear, 3 = CRT",
"0 = Nearest Neighbour, 1 = Linear, 2 = Sharp Bilinear, 3 = CRT, 4 = MMPX, 5 = OmniScale",
"Select a video post-processing effect. Advanced effects are only available to the Modern renderer.",
2, 0, 3, FLAG_FRONTEND
2, 0, 5, FLAG_FRONTEND
},
{ "v_aspect", "Aspect Ratio",
"0 = Auto, 1 = 1:1, 2 = 4:3, 3 = 5:4",

View file

@ -373,6 +373,14 @@ void VideoRendererModern::shader_setup(void) {
shaderprog[1] = shader_create("default.vs", "crtea.fs");
break;
}
case 4: { // MMPX
shaderprog[1] = shader_create("default.vs", "mmpx.fs");
break;
}
case 5: { // Omniscale
shaderprog[1] = shader_create("default.vs", "omniscale.fs");
break;
}
}
// Set texture parameters for input texture