Filter support for Cocoa port + 7 basic filters

This commit is contained in:
Lior Halphon 2016-04-28 23:07:05 +03:00
parent dde983db8f
commit 8d59bfcbdd
18 changed files with 601 additions and 15 deletions

View File

@ -24,6 +24,8 @@
@"GBStart": @"\r",
@"GBTurbo": @" ",
@"GBFilter": @"NearestNeighbor",
}];
#undef KEY
}

View File

@ -41,7 +41,7 @@ static char *consoleInput(GB_gameboy_t *gb)
static uint32_t rgbEncode(GB_gameboy_t *gb, unsigned char r, unsigned char g, unsigned char b)
{
return (r << 24) | (g << 16) | (b << 8);
return (r << 0) | (g << 8) | (b << 16);
}
@implementation Document

View File

@ -2,4 +2,5 @@
@interface GBPreferencesWindow : NSWindow <NSTableViewDelegate, NSTableViewDataSource>
@property IBOutlet NSTableView *controlsTableView;
@property IBOutlet NSPopUpButton *graphicsFilterPopupButton;
@end

View File

@ -6,6 +6,38 @@
{
bool is_button_being_modified;
NSInteger button_being_modified;
NSPopUpButton *_graphicsFilterPopupButton;
}
+ (NSArray *)filterList
{
/* The filter list as ordered in the popup button */
static NSArray * filters = nil;
if (!filters) {
filters = @[
@"NearestNeighbor",
@"Bilinear",
@"SmoothBilinear",
@"Scale2x",
@"Scale4x",
@"AAScale2x",
@"AAScale4x",
];
}
return filters;
}
- (NSPopUpButton *)graphicsFilterPopupButton
{
return _graphicsFilterPopupButton;
}
- (void)setGraphicsFilterPopupButton:(NSPopUpButton *)graphicsFilterPopupButton
{
_graphicsFilterPopupButton = graphicsFilterPopupButton;
NSString *filter = [[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"];
[_graphicsFilterPopupButton selectItemAtIndex:[[[self class] filterList] indexOfObject:filter]];
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
@ -56,5 +88,12 @@
[self.controlsTableView reloadData];
[self makeFirstResponder:self.controlsTableView];
}
- (IBAction)graphicFilterChanged:(NSPopUpButton *)sender
{
[[NSUserDefaults standardUserDefaults] setObject:[[self class] filterList][[sender indexOfSelectedItem]]
forKey:@"GBFilter"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBFilterChanged" object:nil];
}
@end

6
Cocoa/GBShader.h Normal file
View File

@ -0,0 +1,6 @@
#import <Foundation/Foundation.h>
@interface GBShader : NSObject
- (instancetype)initWithName:(NSString *) shaderName;
- (void) renderBitmap: (void *)bitmap previous:(void*) previous inSize:(NSSize)size scale: (double) scale;
@end

170
Cocoa/GBShader.m Normal file
View File

@ -0,0 +1,170 @@
#import "GBShader.h"
#import <OpenGL/gl.h>
/*
Loosely based of https://www.raywenderlich.com/70208/opengl-es-pixel-shaders-tutorial
*/
static NSString * const vertex_shader = @"\n\
attribute vec2 aPosition;\n\
\n\
void main(void) {\n\
gl_Position = vec4(aPosition, 0., 1.);\n\
}\n\
";
@implementation GBShader
{
GLuint resolution_uniform;
GLuint texture_uniform;
GLuint previous_texture_uniform;
GLuint mix_previous_uniform;
GLuint position_attribute;
GLuint texture;
GLuint previous_texture;
GLuint program;
}
+ (NSString *) shaderSourceForName:(NSString *) name
{
return [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:name
ofType:@"fsh"
inDirectory:@"Shaders"]
encoding:NSUTF8StringEncoding
error:nil];
}
- (instancetype)initWithName:(NSString *) shaderName
{
self = [super init];
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];
// Attributes
position_attribute = glGetAttribLocation(program, "aPosition");
// Uniforms
resolution_uniform = glGetUniformLocation(program, "uResolution");
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, 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);
glBindTexture(GL_TEXTURE_2D, 0);
texture_uniform = glGetUniformLocation(program, "image");
glGenTextures(1, &previous_texture);
glBindTexture(GL_TEXTURE_2D, previous_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, 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);
glBindTexture(GL_TEXTURE_2D, 0);
previous_texture_uniform = glGetUniformLocation(program, "previousImage");
mix_previous_uniform = glGetUniformLocation(program, "uMixPrevious");
// Configure OpenGL ES
[self configureOpenGLES];
}
return self;
}
- (void) renderBitmap: (void *)bitmap previous:(void*) previous inSize:(NSSize)size scale: (double) scale
{
glUseProgram(program);
glUniform2f(resolution_uniform, size.width * scale, size.height * scale);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 160, 144, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap);
glUniform1i(texture_uniform, 0);
glUniform1i(mix_previous_uniform, previous != NULL);
if (previous) {
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, previous_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 160, 144, 0, GL_RGBA, GL_UNSIGNED_BYTE, previous);
glUniform1i(previous_texture_uniform, 1);
}
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
- (void)configureOpenGLES
{
// Program
glUseProgram(program);
// Attributes
glEnableVertexAttribArray(position_attribute);
static GLfloat const quad[8] = {
-1.f, -1.f,
-1.f, +1.f,
+1.f, -1.f,
+1.f, +1.f,
};
glVertexAttribPointer(position_attribute, 2, GL_FLOAT, GL_FALSE, 0, quad);
}
+ (GLuint)programWithVertexShader:(NSString*)vsh fragmentShader:(NSString*)fsh
{
// Build shaders
GLuint vertex_shader = [self shaderWithContents:vsh type:GL_VERTEX_SHADER];
GLuint fragment_shader = [self shaderWithContents:fsh type:GL_FRAGMENT_SHADER];
// Create program
GLuint program = glCreateProgram();
// Attach shaders
glAttachShader(program, vertex_shader);
glAttachShader(program, fragment_shader);
// Link program
glLinkProgram(program);
// Check for errors
GLint status;
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (status == GL_FALSE) {
GLchar messages[1024];
glGetProgramInfoLog(program, sizeof(messages), 0, &messages[0]);
NSLog(@"%@:- GLSL Program Error: %s", self, messages);
}
// Delete shaders
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
return program;
}
- (void)dealloc
{
glDeleteProgram(program);
glDeleteTextures(1, &texture);
glDeleteTextures(1, &previous_texture);
}
+ (GLuint)shaderWithContents:(NSString*)contents type:(GLenum)type
{
const GLchar* source = [contents UTF8String];
// Create the shader object
GLuint shader = glCreateShader(type);
// Load the shader source
glShaderSource(shader, 1, &source, 0);
// Compile the shader
glCompileShader(shader);
// Check for errors
GLint status = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE) {
GLchar messages[1024];
glGetShaderInfoLog(shader, sizeof(messages), 0, &messages[0]);
NSLog(@"%@:- GLSL Shader Error: %s", self, messages);
}
return shader;
}
@end

View File

@ -1,4 +1,5 @@
#import <Cocoa/Cocoa.h>
#import "GBShader.h"
#include "gb.h"
@interface GBView : NSOpenGLView
@ -6,4 +7,5 @@
- (uint32_t *) pixels;
@property GB_gameboy_t *gb;
@property BOOL shouldBlendFrameWithPrevious;
@property GBShader *shader;
@end

View File

@ -3,6 +3,8 @@
#import "GBButtons.h"
#import "NSString+StringForKey.h"
static GBShader *shader = nil;
@implementation GBView
{
uint32_t *image_buffers[3];
@ -15,6 +17,12 @@
image_buffers[1] = malloc(160 * 144 * 4);
image_buffers[2] = malloc(160 * 144 * 4);
_shouldBlendFrameWithPrevious = 1;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(filterChanged) name:@"GBFilterChanged" object:nil];
}
- (void) filterChanged
{
self.shader = nil;
}
- (unsigned char) numberOfBuffers
@ -27,6 +35,7 @@
free(image_buffers[0]);
free(image_buffers[1]);
free(image_buffers[2]);
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
@ -49,16 +58,21 @@
}
- (void)drawRect:(NSRect)dirtyRect {
if (!self.shader) {
self.shader = [[GBShader alloc] initWithName:[[NSUserDefaults standardUserDefaults] objectForKey:@"GBFilter"]];
}
double scale = self.window.backingScaleFactor;
glRasterPos2d(-1, 1);
glPixelZoom(self.bounds.size.width / 160 * scale, self.bounds.size.height / -144 * scale);
glDrawPixels(160, 144, GL_ABGR_EXT, GL_UNSIGNED_BYTE, image_buffers[current_buffer]);
if (_shouldBlendFrameWithPrevious) {
glEnable(GL_BLEND);
glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA);
glBlendColor(1, 1, 1, 0.5);
glDrawPixels(160, 144, GL_ABGR_EXT, GL_UNSIGNED_BYTE, image_buffers[(current_buffer + 2) % self.numberOfBuffers]);
glDisable(GL_BLEND);
[self.shader renderBitmap:image_buffers[current_buffer]
previous:image_buffers[(current_buffer + 2) % self.numberOfBuffers]
inSize:self.bounds.size
scale:scale];
}
else {
[self.shader renderBitmap:image_buffers[current_buffer]
previous:NULL
inSize:self.bounds.size
scale:scale];
}
glFlush();
}

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9531" systemVersion="14F1509" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9531" systemVersion="14F1713" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9531"/>
</dependencies>
@ -14,11 +14,11 @@
<window title="Preferences" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="GBPreferencesWindow">
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="292" height="237"/>
<rect key="contentRect" x="196" y="240" width="292" height="292"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<view key="contentView" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="292" height="237"/>
<autoresizingMask key="autoresizingMask"/>
<rect key="frame" x="0.0" y="0.0" width="292" height="292"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Utu-t4-cLx">
<rect key="frame" x="18" y="201" width="256" height="17"/>
@ -28,6 +28,39 @@
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T91-rh-rRp">
<rect key="frame" x="18" y="255" width="256" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Graphics Filter:" id="pXg-WY-8Q5">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</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">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<menu key="menu" id="xDC-0T-Qg9">
<items>
<menuItem title="Nearest Neighbor (Pixelated)" id="neN-eo-LA7">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem title="Bilinear (Blurry)" id="iDe-si-atu"/>
<menuItem title="Smooth Bilinear (Less blurry)" id="1jN-pO-1iD"/>
<menuItem title="Scale2x" id="C1I-L2-Up1"/>
<menuItem title="Scale4x" id="uWA-Zp-JY9"/>
<menuItem title="Anti-aliased Scale2x" id="iP6-DJ-CVH"/>
<menuItem title="Anti-aliased Scale4x" id="zJR-ER-Ygo">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="graphicFilterChanged:" target="QvC-M9-y7g" id="n87-t4-fbV"/>
</connections>
</popUpButton>
<scrollView focusRingType="none" fixedFrame="YES" autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" verticalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="PBp-dj-EIa">
<rect key="frame" x="20" y="20" width="252" height="173"/>
<clipView key="contentView" focusRingType="none" ambiguous="YES" drawsBackground="NO" id="AMs-PO-nid">
@ -90,8 +123,9 @@
<connections>
<outlet property="controlsTableView" destination="UDd-IJ-fxX" id="a1D-Md-yXv"/>
<outlet property="delegate" destination="-2" id="ASc-vN-Zbq"/>
<outlet property="graphicsFilterPopupButton" destination="6pP-kK-EEC" id="LS7-HY-kHC"/>
</connections>
<point key="canvasLocation" x="179" y="403.5"/>
<point key="canvasLocation" x="179" y="431"/>
</window>
</objects>
</document>

View File

@ -67,6 +67,8 @@ $(OBJ)/%.m.o: %.m
# Cocoa Port
Shaders:$(shell echo Shaders/*.fsh)
$(BIN)/Sameboy.app: $(BIN)/Sameboy.app/Contents/MacOS/Sameboy \
$(shell echo Cocoa/*.icns) \
Cocoa/License.html \
@ -75,11 +77,14 @@ $(BIN)/Sameboy.app: $(BIN)/Sameboy.app/Contents/MacOS/Sameboy \
$(BIN)/BootROMs/cgb_boot.bin \
$(BIN)/Sameboy.app/Contents/Resources/Base.lproj/Document.nib \
$(BIN)/Sameboy.app/Contents/Resources/Base.lproj/MainMenu.nib \
$(BIN)/Sameboy.app/Contents/Resources/Base.lproj/Preferences.nib
$(BIN)/Sameboy.app/Contents/Resources/Base.lproj/Preferences.nib \
Shaders
mkdir -p $(BIN)/Sameboy.app/Contents/Resources
cp Cocoa/*.icns $(BIN)/BootROMs/dmg_boot.bin $(BIN)/BootROMs/cgb_boot.bin $(BIN)/Sameboy.app/Contents/Resources/
sed s/@VERSION/$(VERSION)/ < Cocoa/info.plist > $(BIN)/Sameboy.app/Contents/info.plist
cp Cocoa/License.html $(BIN)/Sameboy.app/Contents/Resources/Credits.html
mkdir -p $(BIN)/Sameboy.app/Contents/Resources/Shaders
cp Shaders/*.fsh $(BIN)/Sameboy.app/Contents/Resources/Shaders
$(BIN)/Sameboy.app/Contents/MacOS/Sameboy: $(CORE_OBJECTS) $(COCOA_OBJECTS)
-@mkdir -p $(dir $@)

47
Shaders/AAScale2x.fsh Normal file
View File

@ -0,0 +1,47 @@
vec4 scale2x(sampler2D image)
{
// o = offset, the width of a pixel
vec2 o = 1.0 / textureDimensions;
vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution;
// texel arrangement
// A B C
// D E F
// G H I
vec4 A = texture2D(image, texCoord + vec2( -o.x, o.y));
vec4 B = texture2D(image, texCoord + vec2( 0, o.y));
vec4 C = texture2D(image, texCoord + vec2( o.x, o.y));
vec4 D = texture2D(image, texCoord + vec2( -o.x, 0));
vec4 E = texture2D(image, texCoord + vec2( 0, 0));
vec4 F = texture2D(image, texCoord + vec2( o.x, 0));
vec4 G = texture2D(image, texCoord + vec2( -o.x, -o.y));
vec4 H = texture2D(image, texCoord + vec2( 0, -o.y));
vec4 I = texture2D(image, texCoord + vec2( o.x, -o.y));
vec2 p = texCoord * textureDimensions;
// p = the position within a pixel [0...1]
p = fract(p);
if (p.x > .5) {
if (p.y > .5) {
// Top Right
return B == F && B != D && F != H ? F : E;
} else {
// Bottom Right
return H == F && D != H && B != F ? F : E;
}
} else {
if (p.y > .5) {
// Top Left
return D == B && B != F && D != H ? D : E;
} else {
// Bottom Left
return D == H && D != B && H != F ? D : E;
}
}
}
vec4 filter(sampler2D image)
{
vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution;
return mix(texture2D(image, texCoord), scale2x(image), 0.5);
}

87
Shaders/AAScale4x.fsh Normal file
View File

@ -0,0 +1,87 @@
vec4 scale2x(sampler2D image, vec2 texCoord)
{
// o = offset, the width of a pixel
vec2 o = 1.0 / textureDimensions;
// texel arrangement
// A B C
// D E F
// G H I
vec4 A = texture2D(image, texCoord + vec2( -o.x, o.y));
vec4 B = texture2D(image, texCoord + vec2( 0, o.y));
vec4 C = texture2D(image, texCoord + vec2( o.x, o.y));
vec4 D = texture2D(image, texCoord + vec2( -o.x, 0));
vec4 E = texture2D(image, texCoord + vec2( 0, 0));
vec4 F = texture2D(image, texCoord + vec2( o.x, 0));
vec4 G = texture2D(image, texCoord + vec2( -o.x, -o.y));
vec4 H = texture2D(image, texCoord + vec2( 0, -o.y));
vec4 I = texture2D(image, texCoord + vec2( o.x, -o.y));
vec2 p = texCoord * textureDimensions;
// p = the position within a pixel [0...1]
p = fract(p);
if (p.x > .5) {
if (p.y > .5) {
// Top Right
return B == F && B != D && F != H ? F : E;
} else {
// Bottom Right
return H == F && D != H && B != F ? F : E;
}
} else {
if (p.y > .5) {
// Top Left
return D == B && B != F && D != H ? D : E;
} else {
// Bottom Left
return D == H && D != B && H != F ? D : E;
}
}
}
vec4 aaScale2x(sampler2D image, vec2 texCoord)
{
return mix(texture2D(image, texCoord), scale2x(image, texCoord), 0.5);
}
vec4 filter(sampler2D image)
{
// o = offset, the width of a pixel
vec2 o = 1.0 / (textureDimensions * 2.);
vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution;
// texel arrangement
// A B C
// D E F
// G H I
vec4 A = aaScale2x(image, texCoord + vec2( -o.x, o.y));
vec4 B = aaScale2x(image, texCoord + vec2( 0, o.y));
vec4 C = aaScale2x(image, texCoord + vec2( o.x, o.y));
vec4 D = aaScale2x(image, texCoord + vec2( -o.x, 0));
vec4 E = aaScale2x(image, texCoord + vec2( 0, 0));
vec4 F = aaScale2x(image, texCoord + vec2( o.x, 0));
vec4 G = aaScale2x(image, texCoord + vec2( -o.x, -o.y));
vec4 H = aaScale2x(image, texCoord + vec2( 0, -o.y));
vec4 I = aaScale2x(image, texCoord + vec2( o.x, -o.y));
vec4 R;
vec2 p = texCoord * textureDimensions * 2.;
// p = the position within a pixel [0...1]
p = fract(p);
if (p.x > .5) {
if (p.y > .5) {
// Top Right
R = B == F && B != D && F != H ? F : E;
} else {
// Bottom Right
R = H == F && D != H && B != F ? F : E;
}
} else {
if (p.y > .5) {
// Top Left
R = D == B && B != F && D != H ? D : E;
} else {
// Bottom Left
R = D == H && D != B && H != F ? D : E;
}
}
return mix(R, E, 0.5);
}

16
Shaders/Bilinear.fsh Normal file
View File

@ -0,0 +1,16 @@
vec4 filter(sampler2D image)
{
vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution;
vec2 pixel = texCoord * textureDimensions;
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));
vec4 r1 = mix(q11, q21, fract(pixel.x));
vec4 r2 = mix(q12, q22, fract(pixel.x));
return mix (r1, r2, fract(pixel.y));
}

17
Shaders/MasterShader.fsh Normal file
View File

@ -0,0 +1,17 @@
uniform sampler2D image;
uniform sampler2D previousImage;
uniform bool uMixPrevious;
uniform vec2 uResolution;
const vec2 textureDimensions = vec2(160, 144);
{filter}
void main() {
if (uMixPrevious) {
gl_FragColor = mix(filter(image), filter(previousImage), 0.5);
}
else {
gl_FragColor = filter(image);
}
}

View File

@ -0,0 +1,6 @@
vec4 filter(sampler2D image)
{
vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution;
return texture2D(image, texCoord);
}

42
Shaders/Scale2x.fsh Normal file
View File

@ -0,0 +1,42 @@
/* Shader implementation of Scale2x is adapted from https://gist.github.com/singron/3161079 */
vec4 filter(sampler2D image)
{
// o = offset, the width of a pixel
vec2 o = 1.0 / textureDimensions;
vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution;
// texel arrangement
// A B C
// D E F
// G H I
vec4 A = texture2D(image, texCoord + vec2( -o.x, o.y));
vec4 B = texture2D(image, texCoord + vec2( 0, o.y));
vec4 C = texture2D(image, texCoord + vec2( o.x, o.y));
vec4 D = texture2D(image, texCoord + vec2( -o.x, 0));
vec4 E = texture2D(image, texCoord + vec2( 0, 0));
vec4 F = texture2D(image, texCoord + vec2( o.x, 0));
vec4 G = texture2D(image, texCoord + vec2( -o.x, -o.y));
vec4 H = texture2D(image, texCoord + vec2( 0, -o.y));
vec4 I = texture2D(image, texCoord + vec2( o.x, -o.y));
vec2 p = texCoord * textureDimensions;
// p = the position within a pixel [0...1]
p = fract(p);
if (p.x > .5) {
if (p.y > .5) {
// Top Right
return B == F && B != D && F != H ? F : E;
} else {
// Bottom Right
return H == F && D != H && B != F ? F : E;
}
} else {
if (p.y > .5) {
// Top Left
return D == B && B != F && D != H ? D : E;
} else {
// Bottom Left
return D == H && D != B && H != F ? D : E;
}
}
}

80
Shaders/Scale4x.fsh Normal file
View File

@ -0,0 +1,80 @@
vec4 scale2x(sampler2D image, vec2 texCoord)
{
// o = offset, the width of a pixel
vec2 o = 1.0 / textureDimensions;
// texel arrangement
// A B C
// D E F
// G H I
vec4 A = texture2D(image, texCoord + vec2( -o.x, o.y));
vec4 B = texture2D(image, texCoord + vec2( 0, o.y));
vec4 C = texture2D(image, texCoord + vec2( o.x, o.y));
vec4 D = texture2D(image, texCoord + vec2( -o.x, 0));
vec4 E = texture2D(image, texCoord + vec2( 0, 0));
vec4 F = texture2D(image, texCoord + vec2( o.x, 0));
vec4 G = texture2D(image, texCoord + vec2( -o.x, -o.y));
vec4 H = texture2D(image, texCoord + vec2( 0, -o.y));
vec4 I = texture2D(image, texCoord + vec2( o.x, -o.y));
vec2 p = texCoord * textureDimensions;
// p = the position within a pixel [0...1]
vec4 R;
p = fract(p);
if (p.x > .5) {
if (p.y > .5) {
// Top Right
return B == F && B != D && F != H ? F : E;
} else {
// Bottom Right
return H == F && D != H && B != F ? F : E;
}
} else {
if (p.y > .5) {
// Top Left
return D == B && B != F && D != H ? D : E;
} else {
// Bottom Left
return D == H && D != B && H != F ? D : E;
}
}
}
vec4 filter(sampler2D image)
{
// o = offset, the width of a pixel
vec2 o = 1.0 / (textureDimensions * 2.);
vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution;
// texel arrangement
// A B C
// D E F
// G H I
vec4 A = scale2x(image, texCoord + vec2( -o.x, o.y));
vec4 B = scale2x(image, texCoord + vec2( 0, o.y));
vec4 C = scale2x(image, texCoord + vec2( o.x, o.y));
vec4 D = scale2x(image, texCoord + vec2( -o.x, 0));
vec4 E = scale2x(image, texCoord + vec2( 0, 0));
vec4 F = scale2x(image, texCoord + vec2( o.x, 0));
vec4 G = scale2x(image, texCoord + vec2( -o.x, -o.y));
vec4 H = scale2x(image, texCoord + vec2( 0, -o.y));
vec4 I = scale2x(image, texCoord + vec2( o.x, -o.y));
vec2 p = texCoord * textureDimensions * 2.;
// p = the position within a pixel [0...1]
p = fract(p);
if (p.x > .5) {
if (p.y > .5) {
// Top Right
return B == F && B != D && F != H ? F : E;
} else {
// Bottom Right
return H == F && D != H && B != F ? F : E;
}
} else {
if (p.y > .5) {
// Top Left
return D == B && B != F && D != H ? D : E;
} else {
// Bottom Left
return D == H && D != B && H != F ? D : E;
}
}
}

View File

@ -0,0 +1,18 @@
vec4 filter(sampler2D image)
{
vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution;
vec2 pixel = texCoord * textureDimensions;
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 smooth = smoothstep(0., 1., fract(pixel));
vec4 r1 = mix(q11, q21, fract(smooth.x));
vec4 r2 = mix(q12, q22, fract(smooth.x));
return mix (r1, r2, fract(smooth.y));
}