SameBoy/Cocoa/GBShader.m

170 lines
5.6 KiB
Objective-C

#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:@"{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