Skip to content

Commit

Permalink
Introducing the OmniScale (beta) algorithm to SameBoy
Browse files Browse the repository at this point in the history
  • Loading branch information
LIJI32 committed Jun 8, 2016
1 parent 8a3e0c3 commit 94ea44d
Show file tree
Hide file tree
Showing 12 changed files with 287 additions and 6 deletions.
2 changes: 2 additions & 0 deletions Cocoa/GBPreferencesWindow.m
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ + (NSArray *)filterList
@"Scale4x",
@"AAScale2x",
@"AAScale4x",
@"OmniScale",
@"AAOmniScale",
];
}
return filters;
Expand Down
1 change: 0 additions & 1 deletion Cocoa/GBShader.m
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ - (instancetype)initWithName:(NSString *) shaderName
if (self) {
// Program
NSString *fragment_shader = [[self class] shaderSourceForName:@"MasterShader"];
fragment_shader = [fragment_shader stringByReplacingOccurrencesOfString:@"\n" withString:@""];
fragment_shader = [fragment_shader stringByReplacingOccurrencesOfString:@"{filter}"
withString:[[self class] shaderSourceForName:shaderName]];
program = [[self class] programWithVertexShader:vertex_shader fragmentShader:fragment_shader];
Expand Down
2 changes: 1 addition & 1 deletion Cocoa/GBView.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
- (void) flip;
- (uint32_t *) pixels;
@property GB_gameboy_t *gb;
@property BOOL shouldBlendFrameWithPrevious;
@property (nonatomic) BOOL shouldBlendFrameWithPrevious;
@property GBShader *shader;
@end
7 changes: 7 additions & 0 deletions Cocoa/GBView.m
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,16 @@ - (void) _init

- (void) filterChanged
{
[self setNeedsDisplay:YES];
self.shader = nil;
}

- (void) setShouldBlendFrameWithPrevious:(BOOL)shouldBlendFrameWithPrevious
{
_shouldBlendFrameWithPrevious = shouldBlendFrameWithPrevious;
[self setNeedsDisplay:YES];
}

- (unsigned char) numberOfBuffers
{
return _shouldBlendFrameWithPrevious? 3 : 2;
Expand Down
10 changes: 8 additions & 2 deletions Cocoa/Preferences.xib
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6pP-kK-EEC">
<rect key="frame" x="30" y="223" width="245" height="26"/>
<popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="I1w-05-lGl">
<popUpButtonCell key="cell" type="push" title="Nearest Neighbor (Pixelated)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="neN-eo-LA7" id="I1w-05-lGl">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="xDC-0T-Qg9">
<items>
<menuItem title="Nearest Neighbor (Pixelated)" id="neN-eo-LA7">
Expand All @@ -54,6 +54,12 @@
<menuItem title="Anti-aliased Scale4x" id="zJR-ER-Ygo">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem title="OmniScale (Beta, any factor)" id="doe-kf-quG">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem title="Anti-aliased OmniScale" id="uZy-BK-VyB">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
</items>
</menu>
</popUpButtonCell>
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Features currently supported only with the Cocoa version:
* Battery save support
* Save states
* Optional frame blending
* Several [scaling algorithms](SCALING.md) (Including exclusive algorithms like OmniScale and Anti-aliased Scale2x)

## Compatibility
While SameBoy passes many of [blargg's test ROMs](http://gbdev.gg8.se/wiki/articles/Test_ROMs#Blargg.27s_tests), some games fail to run correctly. SameBoy is still relatively early in its development and accuracy and compatibility will be improved.
Expand Down
39 changes: 39 additions & 0 deletions SCALING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Scaling

Starting with version 0.4, the Cocoa version of SameBoy supports several GPU-accelerated scaling algorithms, some of which made their premiere at SameBoy. This document describes the algorithms supported by SameBoy.

## General-purpose Scaling Algorithms
Common algorithms that were not made specifically for pixel art

### Nearest Neighbor
A simple pixelated scaling algorithm we all know and love. This is the default filter.

### Bilinear
An algorithm that fills "missing" pixels using a bilinear interpolation, causing a blurry image

### Smooth Bilinear
A variant of bilinear filtering that applies a smooth curve to the bilinear interpolation. The results look similar to the algorithm Apple uses when scaling non-Retina graphics for Retina Displays.

## The ScaleNx Family
The ScaleNx family is a group of algorithm that scales pixel art by the specified factor using simple pattern-based rules. The Scale3x algorithm is not yet supported in SameBoy.

### Scale2x
The most simple algorithm of the family. It scales the image by a 2x factor without introducing new colors.

### Scale4x
This algorithm applies the Scale2x algorithm twice to scale the image by a 4x factor.

### Anti-aliased Scale2x
A variant of Scale2x exclusive to SameBoy that blends the Scale2x output with the Nearest Neighbor output. The specific traits of Scale2x makes this blend produce nicely looking anti-aliased output.

### Anti-aliased Scale4x
Another exclusive algorithm that works by applying the Anti-aliased Scale2x algorithm twice

## The OmniScale Family (beta)
OmniScale is an exclusive algorithm developed for SameBoy. It combines pattern-based rules with a unique locally paletted bilinear filtering technique to scale an image by any factor, including non-integer factors. The algorithm is currently in beta, and its pattern-based rule do not currently detect 30- and 60-degree diagonals, making them look jaggy.

### OmniScale
The base version of the algorithm, which generates aliased output with very few new colors introduced.

### Anti-aliased OmniScale
A variant of OmniScale that produces anti-aliased output using 2x super-sampling.
119 changes: 119 additions & 0 deletions Shaders/AAOmniScale.fsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@

float quickDistance(vec4 a, vec4 b)
{
return abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z);
}

vec4 omniScale(sampler2D image, vec2 texCoord)
{
vec2 pixel = texCoord * textureDimensions - vec2(0.5, 0.5);

vec4 q11 = texture2D(image, vec2(floor(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y));
vec4 q12 = texture2D(image, vec2(floor(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y));
vec4 q21 = texture2D(image, vec2(ceil(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y));
vec4 q22 = texture2D(image, vec2(ceil(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y));

vec2 pos = fract(pixel);

/* Special handling for diaonals */
bool hasDownDiagonal = false;
bool hasUpDiagonal = false;
if (q12 == q21 && q11 != q22) hasUpDiagonal = true;
else if (q12 != q21 && q11 == q22) hasDownDiagonal = true;
else if (q12 == q21 && q11 == q22) {
if (q11 == q12) return q11;
int diagonalBias = 0;
for (float y = -1.0; y < 3.0; y++) {
for (float x = -1.0; x < 3.0; x++) {
vec4 color = texture2D(image, (pixel + vec2(x, y)) / textureDimensions);
if (color == q11) diagonalBias++;
if (color == q12) diagonalBias--;
}
}
if (diagonalBias <= 0) {
hasDownDiagonal = true;
}
if (diagonalBias >= 0) {
hasUpDiagonal = true;
}
}

if (hasUpDiagonal || hasDownDiagonal) {
vec4 downDiagonalResult, upDiagonalResult;

if (hasUpDiagonal) {
float diagonalPos = pos.x + pos.y;

if (diagonalPos < 0.5) {
upDiagonalResult = q11;
}
else if (diagonalPos > 1.5) {
upDiagonalResult = q22;
}
else {
upDiagonalResult = q12;
}
}

if (hasDownDiagonal) {
float diagonalPos = 1.0 - pos.x + pos.y;

if (diagonalPos < 0.5) {
downDiagonalResult = q21;
}
else if (diagonalPos > 1.5) {
downDiagonalResult = q12;
}
else {
downDiagonalResult = q11;
}
}

if (!hasUpDiagonal) return downDiagonalResult;
if (!hasDownDiagonal) return upDiagonalResult;
return mix(downDiagonalResult, upDiagonalResult, 0.5);
}

vec4 r1 = mix(q11, q21, fract(pos.x));
vec4 r2 = mix(q12, q22, fract(pos.x));

vec4 unqunatized = mix(r1, r2, fract(pos.y));

float q11d = quickDistance(unqunatized, q11);
float q21d = quickDistance(unqunatized, q21);
float q12d = quickDistance(unqunatized, q12);
float q22d = quickDistance(unqunatized, q22);

float best = min(q11d,
min(q21d,
min(q12d,
q22d)));

if (q11d == best) {
return q11;
}

if (q21d == best) {
return q21;
}

if (q12d == best) {
return q12;
}

return q22;
}

vec4 filter(sampler2D image)
{
vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution;
vec2 pixel = vec2(1.0, 1.0) / uResolution;
// 4-pixel super sampling

vec4 q11 = omniScale(image, texCoord + pixel * vec2(-0.25, -0.25));
vec4 q21 = omniScale(image, texCoord + pixel * vec2(+0.25, -0.25));
vec4 q12 = omniScale(image, texCoord + pixel * vec2(-0.25, +0.25));
vec4 q22 = omniScale(image, texCoord + pixel * vec2(+0.25, +0.25));

return (q11 + q21 + q12 + q22) / 4.0;
}
2 changes: 1 addition & 1 deletion Shaders/Bilinear.fsh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ vec4 filter(sampler2D image)
{
vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution;

vec2 pixel = texCoord * textureDimensions;
vec2 pixel = texCoord * textureDimensions - vec2(0.5, 0.5);

vec4 q11 = texture2D(image, vec2(floor(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y));
vec4 q12 = texture2D(image, vec2(floor(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y));
Expand Down
1 change: 1 addition & 0 deletions Shaders/MasterShader.fsh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ uniform bool uMixPrevious;
uniform vec2 uResolution;
const vec2 textureDimensions = vec2(160, 144);

#line 1
{filter}

void main() {
Expand Down
107 changes: 107 additions & 0 deletions Shaders/OmniScale.fsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@

float quickDistance(vec4 a, vec4 b)
{
return abs(a.x - b.x) + abs(a.y - b.y) + abs(a.z - b.z);
}

vec4 filter(sampler2D image)
{
vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution;

vec2 pixel = texCoord * textureDimensions - vec2(0.5, 0.5);

vec4 q11 = texture2D(image, (pixel ) / textureDimensions);
vec4 q12 = texture2D(image, (pixel + vec2(0.0, 1.0)) / textureDimensions);
vec4 q21 = texture2D(image, (pixel + vec2(1.0, 0.0)) / textureDimensions);
vec4 q22 = texture2D(image, (pixel + vec2(1.0, 1.0)) / textureDimensions);

vec2 pos = fract(pixel);

/* Special handling for diaonals */
bool hasDownDiagonal = false;
bool hasUpDiagonal = false;
if (q12 == q21 && q11 != q22) hasUpDiagonal = true;
else if (q12 != q21 && q11 == q22) hasDownDiagonal = true;
else if (q12 == q21 && q11 == q22) {
if (q11 == q12) return q11;
int diagonalBias = 0;
for (float y = -1.0; y < 3.0; y++) {
for (float x = -1.0; x < 3.0; x++) {
vec4 color = texture2D(image, (pixel + vec2(x, y)) / textureDimensions);
if (color == q11) diagonalBias++;
if (color == q12) diagonalBias--;
}
}
if (diagonalBias <= 0) {
hasDownDiagonal = true;
}
if (diagonalBias >= 0) {
hasUpDiagonal = true;
}
}

if (hasUpDiagonal || hasDownDiagonal) {
vec4 downDiagonalResult, upDiagonalResult;

if (hasUpDiagonal) {
float diagonalPos = pos.x + pos.y;

if (diagonalPos < 0.5) {
upDiagonalResult = q11;
}
else if (diagonalPos > 1.5) {
upDiagonalResult = q22;
}
else {
upDiagonalResult = q12;
}
}

if (hasDownDiagonal) {
float diagonalPos = 1.0 - pos.x + pos.y;

if (diagonalPos < 0.5) {
downDiagonalResult = q21;
}
else if (diagonalPos > 1.5) {
downDiagonalResult = q12;
}
else {
downDiagonalResult = q11;
}
}

if (!hasUpDiagonal) return downDiagonalResult;
if (!hasDownDiagonal) return upDiagonalResult;
return mix(downDiagonalResult, upDiagonalResult, 0.5);
}

vec4 r1 = mix(q11, q21, fract(pos.x));
vec4 r2 = mix(q12, q22, fract(pos.x));

vec4 unqunatized = mix(r1, r2, fract(pos.y));

float q11d = quickDistance(unqunatized, q11);
float q21d = quickDistance(unqunatized, q21);
float q12d = quickDistance(unqunatized, q12);
float q22d = quickDistance(unqunatized, q22);

float best = min(q11d,
min(q21d,
min(q12d,
q22d)));

if (q11d == best) {
return q11;
}

if (q21d == best) {
return q21;
}

if (q12d == best) {
return q12;
}

return q22;
}
2 changes: 1 addition & 1 deletion Shaders/SmoothBilinear.fsh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ vec4 filter(sampler2D image)
{
vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution;

vec2 pixel = texCoord * textureDimensions;
vec2 pixel = texCoord * textureDimensions - vec2(0.5, 0.5);

vec4 q11 = texture2D(image, vec2(floor(pixel.x) / textureDimensions.x, floor(pixel.y) / textureDimensions.y));
vec4 q12 = texture2D(image, vec2(floor(pixel.x) / textureDimensions.x, ceil(pixel.y) / textureDimensions.y));
Expand Down

0 comments on commit 94ea44d

Please sign in to comment.