SameBoy/Cocoa/GBGLShader.m
2020-03-26 20:54:18 +02:00

191 lines
6.5 KiB
Objective-C

#import "GBGLShader.h"
#import <OpenGL/gl3.h>
/*
Loosely based of https://www.raywenderlich.com/70208/opengl-es-pixel-shaders-tutorial
This code probably makes no sense after I upgraded it to OpenGL 3, since OpenGL makes aboslute no sense and has zero
helpful documentation.
*/
static NSString * const vertex_shader = @"\n\
#version 150 \n\
in vec4 aPosition;\n\
void main(void) {\n\
gl_Position = aPosition;\n\
}\n\
";
@implementation GBGLShader
{
GLuint resolution_uniform;
GLuint texture_uniform;
GLuint previous_texture_uniform;
GLuint frame_blending_mode_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, "output_resolution");
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, "previous_image");
frame_blending_mode_uniform = glGetUniformLocation(program, "frame_blending_mode");
// Configure OpenGL
[self configureOpenGL];
}
return self;
}
- (void) renderBitmap: (void *)bitmap previous:(void*) previous sized:(NSSize)srcSize inSize:(NSSize)dstSize scale: (double) scale withBlendingMode:(GB_frame_blending_mode_t)blendingMode
{
glUseProgram(program);
glUniform2f(resolution_uniform, dstSize.width * scale, dstSize.height * scale);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, srcSize.width, srcSize.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap);
glUniform1i(texture_uniform, 0);
glUniform1i(frame_blending_mode_uniform, blendingMode);
if (blendingMode) {
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, previous_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, srcSize.width, srcSize.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, previous);
glUniform1i(previous_texture_uniform, 1);
}
glBindFragDataLocation(program, 0, "frag_color");
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
- (void)configureOpenGL
{
// Program
glUseProgram(program);
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
GLuint vbo;
glGenBuffers(1, &vbo);
// Attributes
static GLfloat const quad[16] = {
-1.f, -1.f, 0, 1,
-1.f, +1.f, 0, 1,
+1.f, -1.f, 0, 1,
+1.f, +1.f, 0, 1,
};
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(quad), quad, GL_STATIC_DRAW);
glEnableVertexAttribArray(position_attribute);
glVertexAttribPointer(position_attribute, 4, GL_FLOAT, GL_FALSE, 0, 0);
}
+ (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);
/* OpenGL is black magic. Closing one view causes others to be completely black unless we reload their shaders */
/* We're probably not freeing thing in the right place. */
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBFilterChanged" object:nil];
}
+ (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