diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index 2b17823..3da9ed8 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -24,6 +24,8 @@ @"GBStart": @"\r", @"GBTurbo": @" ", + + @"GBFilter": @"NearestNeighbor", }]; #undef KEY } diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 7d41360..bf56895 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -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 diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h index 0d83d5a..7e1876d 100644 --- a/Cocoa/GBPreferencesWindow.h +++ b/Cocoa/GBPreferencesWindow.h @@ -2,4 +2,5 @@ @interface GBPreferencesWindow : NSWindow @property IBOutlet NSTableView *controlsTableView; +@property IBOutlet NSPopUpButton *graphicsFilterPopupButton; @end diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 85d545e..362abd1 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -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 diff --git a/Cocoa/GBShader.h b/Cocoa/GBShader.h new file mode 100644 index 0000000..d2c4477 --- /dev/null +++ b/Cocoa/GBShader.h @@ -0,0 +1,6 @@ +#import + +@interface GBShader : NSObject +- (instancetype)initWithName:(NSString *) shaderName; +- (void) renderBitmap: (void *)bitmap previous:(void*) previous inSize:(NSSize)size scale: (double) scale; +@end diff --git a/Cocoa/GBShader.m b/Cocoa/GBShader.m new file mode 100644 index 0000000..a3aa64e --- /dev/null +++ b/Cocoa/GBShader.m @@ -0,0 +1,170 @@ +#import "GBShader.h" +#import + +/* + 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 diff --git a/Cocoa/GBView.h b/Cocoa/GBView.h index a687949..bd042f2 100644 --- a/Cocoa/GBView.h +++ b/Cocoa/GBView.h @@ -1,4 +1,5 @@ #import +#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 diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index f72e01f..0f67aa3 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -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(); } diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 2a58475..4b8f7cd 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -1,5 +1,5 @@ - + @@ -14,11 +14,11 @@ - + - - + + @@ -28,6 +28,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -90,8 +123,9 @@ + - + diff --git a/Makefile b/Makefile index 0fce36f..014c17a 100644 --- a/Makefile +++ b/Makefile @@ -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 $@) diff --git a/Shaders/AAScale2x.fsh b/Shaders/AAScale2x.fsh new file mode 100644 index 0000000..6f4e46e --- /dev/null +++ b/Shaders/AAScale2x.fsh @@ -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); +} \ No newline at end of file diff --git a/Shaders/AAScale4x.fsh b/Shaders/AAScale4x.fsh new file mode 100644 index 0000000..993c319 --- /dev/null +++ b/Shaders/AAScale4x.fsh @@ -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); +} \ No newline at end of file diff --git a/Shaders/Bilinear.fsh b/Shaders/Bilinear.fsh new file mode 100644 index 0000000..6a604e9 --- /dev/null +++ b/Shaders/Bilinear.fsh @@ -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)); +} \ No newline at end of file diff --git a/Shaders/MasterShader.fsh b/Shaders/MasterShader.fsh new file mode 100644 index 0000000..3821e48 --- /dev/null +++ b/Shaders/MasterShader.fsh @@ -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); + } +} \ No newline at end of file diff --git a/Shaders/NearestNeighbor.fsh b/Shaders/NearestNeighbor.fsh new file mode 100644 index 0000000..75a6fc1 --- /dev/null +++ b/Shaders/NearestNeighbor.fsh @@ -0,0 +1,6 @@ +vec4 filter(sampler2D image) +{ + vec2 texCoord = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y) / uResolution; + + return texture2D(image, texCoord); +} \ No newline at end of file diff --git a/Shaders/Scale2x.fsh b/Shaders/Scale2x.fsh new file mode 100644 index 0000000..38efe20 --- /dev/null +++ b/Shaders/Scale2x.fsh @@ -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; + } + } +} \ No newline at end of file diff --git a/Shaders/Scale4x.fsh b/Shaders/Scale4x.fsh new file mode 100644 index 0000000..6c5667f --- /dev/null +++ b/Shaders/Scale4x.fsh @@ -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; + } + } +} \ No newline at end of file diff --git a/Shaders/SmoothBilinear.fsh b/Shaders/SmoothBilinear.fsh new file mode 100644 index 0000000..2489a03 --- /dev/null +++ b/Shaders/SmoothBilinear.fsh @@ -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)); +} \ No newline at end of file