RetroArch/gfx/shader_glsl.c

1208 lines
35 KiB
C
Raw Normal View History

2011-01-17 19:54:58 +00:00
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
2011-01-23 19:29:28 +00:00
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
2011-01-05 18:07:12 +00:00
*
* Some code herein may be based on code found in BSNES.
*
* SSNES is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with SSNES.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <string.h>
#include "general.h"
2011-03-16 20:48:44 +00:00
#include "shader_glsl.h"
2011-05-18 18:22:27 +00:00
#include "strl.h"
#include "snes_state.h"
#include "dynamic.h"
2011-05-18 18:22:27 +00:00
2011-02-04 12:46:51 +00:00
#ifdef __APPLE__
#include <OpenGL/gl.h>
#else
#define GL_GLEXT_PROTOTYPES
2011-01-05 18:07:12 +00:00
#include <GL/gl.h>
2011-02-04 12:46:51 +00:00
#include <GL/glext.h>
#endif
#define NO_SDL_GLEXT
#include "sdlwrap.h"
#include "SDL_opengl.h"
2011-01-05 18:07:12 +00:00
#include <stdlib.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include "gl_common.h"
2011-05-18 18:22:27 +00:00
#include "image.h"
2011-02-22 22:43:00 +00:00
#ifdef __APPLE__
#define pglCreateProgram glCreateProgram
#define pglUseProgram glUseProgram
#define pglCreateShader glCreateShader
#define pglDeleteShader glDeleteShader
#define pglShaderSource glShaderSource
#define pglCompileShader glCompileShader
#define pglAttachShader glAttachShader
#define pglDetachShader glDetachShader
#define pglLinkProgram glLinkProgram
#define pglGetUniformLocation glGetUniformLocation
#define pglUniform1i glUniform1i
#define pglUniform1f glUniform1f
2011-02-22 22:43:00 +00:00
#define pglUniform2fv glUniform2fv
#define pglUniform4fv glUniform4fv
#define pglGetShaderiv glGetShaderiv
#define pglGetShaderInfoLog glGetShaderInfoLog
#define pglGetProgramiv glGetProgramiv
#define pglGetProgramInfoLog glGetProgramInfoLog
#define pglDeleteProgram glDeleteProgram
#define pglGetAttachedShaders glGetAttachedShaders
#define pglGetAttribLocation glGetAttribLocation
#define pglEnableVertexAttribArray glEnableVertexAttribArray
#define pglVertexAttribPointer glVertexAttribPointer
2011-02-22 22:43:00 +00:00
#else
2011-01-07 20:02:46 +00:00
static PFNGLCREATEPROGRAMPROC pglCreateProgram = NULL;
static PFNGLUSEPROGRAMPROC pglUseProgram = NULL;
static PFNGLCREATESHADERPROC pglCreateShader = NULL;
static PFNGLDELETESHADERPROC pglDeleteShader = NULL;
static PFNGLSHADERSOURCEPROC pglShaderSource = NULL;
static PFNGLCOMPILESHADERPROC pglCompileShader = NULL;
static PFNGLATTACHSHADERPROC pglAttachShader = NULL;
static PFNGLDETACHSHADERPROC pglDetachShader = NULL;
static PFNGLLINKPROGRAMPROC pglLinkProgram = NULL;
static PFNGLGETUNIFORMLOCATIONPROC pglGetUniformLocation = NULL;
static PFNGLUNIFORM1IPROC pglUniform1i = NULL;
static PFNGLUNIFORM1FPROC pglUniform1f = NULL;
2011-01-07 20:02:46 +00:00
static PFNGLUNIFORM2FVPROC pglUniform2fv = NULL;
static PFNGLUNIFORM4FVPROC pglUniform4fv = NULL;
static PFNGLGETSHADERIVPROC pglGetShaderiv = NULL;
static PFNGLGETSHADERINFOLOGPROC pglGetShaderInfoLog = NULL;
static PFNGLGETPROGRAMIVPROC pglGetProgramiv = NULL;
static PFNGLGETPROGRAMINFOLOGPROC pglGetProgramInfoLog = NULL;
static PFNGLDELETEPROGRAMPROC pglDeleteProgram = NULL;
static PFNGLGETATTACHEDSHADERSPROC pglGetAttachedShaders = NULL;
static PFNGLGETATTRIBLOCATIONPROC pglGetAttribLocation = NULL;
static PFNGLENABLEVERTEXATTRIBARRAYPROC pglEnableVertexAttribArray = NULL;
static PFNGLVERTEXATTRIBPOINTERPROC pglVertexAttribPointer = NULL;
2011-02-22 22:43:00 +00:00
#endif
2011-01-05 18:07:12 +00:00
2011-03-12 14:30:57 +00:00
#define MAX_PROGRAMS 16
#define MAX_TEXTURES 8
#define MAX_VARIABLES 256
2011-03-12 14:30:57 +00:00
2011-03-14 20:28:30 +00:00
enum filter_type
{
SSNES_GL_NOFORCE,
SSNES_GL_LINEAR,
SSNES_GL_NEAREST
};
2011-01-05 18:07:12 +00:00
static bool glsl_enable = false;
2011-03-12 14:30:57 +00:00
static GLuint gl_program[MAX_PROGRAMS] = {0};
2011-03-14 20:28:30 +00:00
static enum filter_type gl_filter_type[MAX_PROGRAMS] = {SSNES_GL_NOFORCE};
static struct gl_fbo_scale gl_scale[MAX_PROGRAMS];
2011-03-12 14:30:57 +00:00
static unsigned gl_num_programs = 0;
2011-03-06 18:56:35 +00:00
static unsigned active_index = 0;
2011-01-05 18:07:12 +00:00
2011-05-18 18:22:27 +00:00
static GLuint gl_teximage[MAX_TEXTURES];
static unsigned gl_teximage_cnt = 0;
static char gl_teximage_uniforms[MAX_TEXTURES][64];
static snes_tracker_t *gl_snes_tracker = NULL;
static struct snes_tracker_uniform_info gl_tracker_info[MAX_VARIABLES];
static unsigned gl_tracker_info_cnt = 0;
2011-06-06 16:50:36 +00:00
static char gl_tracker_script[256];
static char gl_tracker_script_class[64];
2011-06-07 13:33:29 +00:00
static xmlChar *gl_script_program = NULL;
2011-03-12 14:30:57 +00:00
struct shader_program
{
char *vertex;
char *fragment;
2011-03-14 20:28:30 +00:00
enum filter_type filter;
float scale_x;
float scale_y;
2011-03-27 18:29:47 +00:00
unsigned abs_x;
unsigned abs_y;
enum gl_scale_type type_x;
enum gl_scale_type type_y;
bool valid_scale;
2011-03-12 14:30:57 +00:00
};
static bool get_xml_attrs(struct shader_program *prog, xmlNodePtr ptr)
{
prog->scale_x = 1.0;
prog->scale_y = 1.0;
2011-03-27 18:29:47 +00:00
prog->type_x = prog->type_y = SSNES_SCALE_INPUT;
prog->valid_scale = false;
// Check if shader forces a certain texture filtering.
xmlChar *attr = xmlGetProp(ptr, (const xmlChar*)"filter");
if (attr)
{
if (strcmp((const char*)attr, "nearest") == 0)
{
prog->filter = SSNES_GL_NEAREST;
SSNES_LOG("XML: Shader forces GL_NEAREST.\n");
}
else if (strcmp((const char*)attr, "linear") == 0)
{
prog->filter = SSNES_GL_LINEAR;
SSNES_LOG("XML: Shader forces GL_LINEAR.\n");
}
else
SSNES_WARN("XML: Invalid property for filter.\n");
xmlFree(attr);
}
else
prog->filter = SSNES_GL_NOFORCE;
2011-03-27 18:29:47 +00:00
// Check for scaling attributes *lots of code <_<*
xmlChar *attr_scale = xmlGetProp(ptr, (const xmlChar*)"scale");
xmlChar *attr_scale_x = xmlGetProp(ptr, (const xmlChar*)"scale_x");
xmlChar *attr_scale_y = xmlGetProp(ptr, (const xmlChar*)"scale_y");
2011-03-27 18:29:47 +00:00
xmlChar *attr_size = xmlGetProp(ptr, (const xmlChar*)"size");
xmlChar *attr_size_x = xmlGetProp(ptr, (const xmlChar*)"size_x");
xmlChar *attr_size_y = xmlGetProp(ptr, (const xmlChar*)"size_y");
xmlChar *attr_outscale = xmlGetProp(ptr, (const xmlChar*)"outscale");
xmlChar *attr_outscale_x = xmlGetProp(ptr, (const xmlChar*)"outscale_x");
xmlChar *attr_outscale_y = xmlGetProp(ptr, (const xmlChar*)"outscale_y");
2011-03-14 22:59:31 +00:00
unsigned x_attr_cnt = 0, y_attr_cnt = 0;
if (attr_scale)
{
float scale = strtod((const char*)attr_scale, NULL);
prog->scale_x = scale;
prog->scale_y = scale;
prog->valid_scale = true;
2011-03-27 18:29:47 +00:00
prog->type_x = prog->type_y = SSNES_SCALE_INPUT;
2011-03-14 22:48:19 +00:00
SSNES_LOG("Got scale attr: %.1f\n", scale);
x_attr_cnt++;
y_attr_cnt++;
}
2011-03-14 22:59:31 +00:00
if (attr_scale_x)
{
2011-03-14 22:48:19 +00:00
float scale = strtod((const char*)attr_scale_x, NULL);
prog->scale_x = scale;
prog->valid_scale = true;
2011-03-27 18:29:47 +00:00
prog->type_x = SSNES_SCALE_INPUT;
2011-03-14 22:48:19 +00:00
SSNES_LOG("Got scale_x attr: %.1f\n", scale);
x_attr_cnt++;
}
2011-03-14 22:59:31 +00:00
if (attr_scale_y)
{
2011-03-14 22:48:19 +00:00
float scale = strtod((const char*)attr_scale_y, NULL);
prog->scale_y = scale;
prog->valid_scale = true;
2011-03-27 18:29:47 +00:00
prog->type_y = SSNES_SCALE_INPUT;
2011-03-14 22:48:19 +00:00
SSNES_LOG("Got scale_y attr: %.1f\n", scale);
y_attr_cnt++;
}
2011-03-27 18:29:47 +00:00
if (attr_size)
{
2011-03-27 19:21:46 +00:00
prog->abs_x = prog->abs_y = strtoul((const char*)attr_size, NULL, 0);
2011-03-27 18:29:47 +00:00
prog->valid_scale = true;
prog->type_x = prog->type_y = SSNES_SCALE_ABSOLUTE;
SSNES_LOG("Got size attr: %u\n", prog->abs_x);
x_attr_cnt++;
y_attr_cnt++;
2011-03-27 18:29:47 +00:00
}
if (attr_size_x)
{
prog->abs_x = strtoul((const char*)attr_size_x, NULL, 0);
prog->valid_scale = true;
prog->type_x = SSNES_SCALE_ABSOLUTE;
SSNES_LOG("Got size_x attr: %u\n", prog->abs_x);
x_attr_cnt++;
2011-03-27 18:29:47 +00:00
}
if (attr_size_y)
{
prog->abs_y = strtoul((const char*)attr_size_y, NULL, 0);
prog->valid_scale = true;
prog->type_y = SSNES_SCALE_ABSOLUTE;
SSNES_LOG("Got size_y attr: %u\n", prog->abs_y);
y_attr_cnt++;
2011-03-27 18:29:47 +00:00
}
if (attr_outscale)
{
float scale = strtod((const char*)attr_outscale, NULL);
prog->scale_x = scale;
prog->scale_y = scale;
prog->valid_scale = true;
prog->type_x = prog->type_y = SSNES_SCALE_VIEWPORT;
SSNES_LOG("Got outscale attr: %.1f\n", scale);
x_attr_cnt++;
y_attr_cnt++;
2011-03-27 18:29:47 +00:00
}
if (attr_outscale_x)
{
float scale = strtod((const char*)attr_outscale_x, NULL);
prog->scale_x = scale;
prog->valid_scale = true;
prog->type_x = SSNES_SCALE_VIEWPORT;
SSNES_LOG("Got outscale_x attr: %.1f\n", scale);
x_attr_cnt++;
2011-03-27 18:29:47 +00:00
}
if (attr_outscale_y)
{
float scale = strtod((const char*)attr_outscale_y, NULL);
prog->scale_y = scale;
prog->valid_scale = true;
prog->type_y = SSNES_SCALE_VIEWPORT;
SSNES_LOG("Got outscale_y attr: %.1f\n", scale);
y_attr_cnt++;
2011-03-27 18:29:47 +00:00
}
if (attr_scale)
xmlFree(attr_scale);
if (attr_scale_x)
xmlFree(attr_scale_x);
if (attr_scale_y)
xmlFree(attr_scale_y);
2011-03-27 18:29:47 +00:00
if (attr_size)
xmlFree(attr_size);
if (attr_size_x)
xmlFree(attr_size_x);
if (attr_size_y)
xmlFree(attr_size_y);
if (attr_outscale)
xmlFree(attr_outscale);
if (attr_outscale_x)
xmlFree(attr_outscale_x);
if (attr_outscale_y)
xmlFree(attr_outscale_y);
if (x_attr_cnt > 1)
return false;
if (y_attr_cnt > 1)
return false;
return true;
}
2011-05-18 18:22:27 +00:00
static bool get_texture_image(const char *shader_path, xmlNodePtr ptr)
{
if (gl_teximage_cnt >= MAX_TEXTURES)
{
SSNES_WARN("Too many texture images! Ignoring ...\n");
return true;
}
2011-05-18 18:22:27 +00:00
bool linear = true;
xmlChar *filename = xmlGetProp(ptr, (const xmlChar*)"file");
xmlChar *filter = xmlGetProp(ptr, (const xmlChar*)"filter");
xmlChar *id = xmlGetProp(ptr, (const xmlChar*)"id");
if (!id)
{
SSNES_ERR("Could not find ID in texture.\n");
goto error;
}
if (!filename)
{
SSNES_ERR("Could not find filename in texture.\n");
goto error;
}
if (filter && strcmp((const char*)filter, "nearest") == 0)
linear = false;
char tex_path[256];
strlcpy(tex_path, shader_path, sizeof(tex_path));
char *last = strrchr(tex_path, '/');
if (!last) last = strrchr(tex_path, '\\');
if (last) last[1] = '\0';
strlcat(tex_path, (const char*)filename, sizeof(tex_path));
struct texture_image img;
SSNES_LOG("Loading texture image from: \"%s\" ...\n", tex_path);
if (!texture_image_load(tex_path, &img))
{
SSNES_ERR("Failed to load texture image from: \"%s\"\n", tex_path);
goto error;
}
strlcpy(gl_teximage_uniforms[gl_teximage_cnt], (const char*)id, sizeof(gl_teximage_uniforms[0]));
glGenTextures(1, &gl_teximage[gl_teximage_cnt]);
2011-05-18 20:11:34 +00:00
pglActiveTexture(GL_TEXTURE0 + gl_teximage_cnt + 1);
2011-05-18 18:22:27 +00:00
glBindTexture(GL_TEXTURE_2D, gl_teximage[gl_teximage_cnt]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, linear ? GL_LINEAR : GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, linear ? GL_LINEAR : GL_NEAREST);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glPixelStorei(GL_UNPACK_ROW_LENGTH, img.width);
glTexImage2D(GL_TEXTURE_2D,
0, GL_RGBA, img.width, img.height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, img.pixels);
2011-05-18 18:22:27 +00:00
2011-05-18 20:11:34 +00:00
pglActiveTexture(GL_TEXTURE0);
2011-05-18 18:22:27 +00:00
glBindTexture(GL_TEXTURE_2D, 0);
free(img.pixels);
xmlFree(filename);
xmlFree(id);
if (filter)
xmlFree(filter);
gl_teximage_cnt++;
return true;
error:
if (filename)
xmlFree(filename);
if (filter)
xmlFree(filter);
if (filter)
xmlFree(id);
return false;
}
2011-06-07 14:28:58 +00:00
#ifdef HAVE_PYTHON
static bool get_script(const char *path, xmlNodePtr ptr)
2011-06-07 13:33:29 +00:00
{
2011-06-07 14:28:58 +00:00
if (*gl_tracker_script || gl_script_program)
2011-06-07 13:33:29 +00:00
{
SSNES_ERR("Script already imported.\n");
return false;
}
xmlChar *script_class = xmlGetProp(ptr, (const xmlChar*)"class");
if (script_class)
{
strlcpy(gl_tracker_script_class, (const char*)script_class, sizeof(gl_tracker_script_class));
xmlFree(script_class);
}
xmlChar *language = xmlGetProp(ptr, (const xmlChar*)"language");
if (!language || strcmp((const char*)language, "python") != 0)
{
SSNES_ERR("Script language is not Python!\n");
if (language)
xmlFree(language);
return false;
}
if (language)
xmlFree(language);
2011-06-07 14:28:58 +00:00
xmlChar *src = xmlGetProp(ptr, (const xmlChar*)"src");
if (src)
2011-06-07 13:33:29 +00:00
{
2011-06-07 14:28:58 +00:00
strlcpy(gl_tracker_script, path, sizeof(gl_tracker_script));
char *dir_ptr = strrchr(gl_tracker_script, '/');
if (!dir_ptr) dir_ptr = strrchr(gl_tracker_script, '\\');
if (dir_ptr) dir_ptr[1] = '\0';
strlcat(gl_tracker_script, (const char*)src, sizeof(gl_tracker_script));
xmlFree(src);
}
else
{
xmlChar *script = xmlNodeGetContent(ptr);
if (!script)
{
SSNES_ERR("No content in script!\n");
return false;
}
gl_script_program = script;
2011-06-07 13:33:29 +00:00
}
2011-06-07 14:28:58 +00:00
2011-06-07 13:33:29 +00:00
return true;
}
2011-06-07 14:28:58 +00:00
#endif
2011-06-07 13:33:29 +00:00
2011-06-07 14:28:58 +00:00
static bool get_import_value(xmlNodePtr ptr)
{
2011-05-25 18:55:38 +00:00
if (gl_tracker_info_cnt >= MAX_VARIABLES)
{
SSNES_ERR("Too many import variables ...\n");
return false;
}
xmlChar *id = xmlGetProp(ptr, (const xmlChar*)"id");
xmlChar *semantic = xmlGetProp(ptr, (const xmlChar*)"semantic");
xmlChar *wram = xmlGetProp(ptr, (const xmlChar*)"wram");
2011-06-11 18:02:17 +00:00
xmlChar *input = xmlGetProp(ptr, (const xmlChar*)"input_slot");
xmlChar *apuram = xmlGetProp(ptr, (const xmlChar*)"apuram");
xmlChar *vram = xmlGetProp(ptr, (const xmlChar*)"vram");
xmlChar *oam = xmlGetProp(ptr, (const xmlChar*)"oam");
xmlChar *cgram = xmlGetProp(ptr, (const xmlChar*)"cgram");
2011-05-29 21:58:04 +00:00
xmlChar *bitmask = xmlGetProp(ptr, (const xmlChar*)"mask");
if (!semantic || !id)
{
SSNES_ERR("No semantic or ID for import value!\n");
goto error;
}
2011-06-06 16:50:36 +00:00
enum snes_tracker_type tracker_type;
2011-06-06 16:50:36 +00:00
if (strcmp((const char*)semantic, "capture") == 0)
tracker_type = SSNES_STATE_CAPTURE;
else if (strcmp((const char*)semantic, "capture_previous") == 0)
tracker_type = SSNES_STATE_CAPTURE_PREV;
else if (strcmp((const char*)semantic, "transition") == 0)
tracker_type = SSNES_STATE_TRANSITION;
2011-06-06 19:32:10 +00:00
else if (strcmp((const char*)semantic, "transition_count") == 0)
tracker_type = SSNES_STATE_TRANSITION_COUNT;
2011-06-06 16:50:36 +00:00
else if (strcmp((const char*)semantic, "transition_previous") == 0)
tracker_type = SSNES_STATE_TRANSITION_PREV;
#ifdef HAVE_PYTHON
else if (strcmp((const char*)semantic, "python") == 0)
tracker_type = SSNES_STATE_PYTHON;
#endif
else
{
2011-06-06 16:50:36 +00:00
SSNES_ERR("Invalid semantic for import value.\n");
goto error;
}
enum snes_ram_type ram_type = SSNES_STATE_NONE;
2011-06-06 16:50:36 +00:00
uint32_t addr = 0;
#ifdef HAVE_PYTHON
if (tracker_type != SSNES_STATE_PYTHON)
#endif
{
2011-06-11 18:02:17 +00:00
if (input)
{
unsigned slot = strtoul((const char*)input, NULL, 0);
switch (slot)
{
case 1:
ram_type = SSNES_STATE_INPUT_SLOT1;
break;
case 2:
ram_type = SSNES_STATE_INPUT_SLOT2;
break;
default:
SSNES_ERR("Invalid input slot for import!\n");
goto error;
}
}
else if (wram) { addr = strtoul((const char*)wram, NULL, 16); ram_type = SSNES_STATE_WRAM; }
2011-06-06 16:50:36 +00:00
else if (apuram) { addr = strtoul((const char*)apuram, NULL, 16); ram_type = SSNES_STATE_APURAM; }
else if (vram) { addr = strtoul((const char*)vram, NULL, 16); ram_type = SSNES_STATE_VRAM; }
else if (oam) { addr = strtoul((const char*)oam, NULL, 16); ram_type = SSNES_STATE_OAM; }
else if (cgram) { addr = strtoul((const char*)cgram, NULL, 16); ram_type = SSNES_STATE_CGRAM; }
else
{
SSNES_ERR("No RAM address specificed for import value.\n");
goto error;
}
}
unsigned memtype;
switch (ram_type)
{
case SSNES_STATE_WRAM:
2011-05-28 13:06:46 +00:00
memtype = SNES_MEMORY_WRAM;
break;
case SSNES_STATE_APURAM:
2011-05-28 13:06:46 +00:00
memtype = SNES_MEMORY_APURAM;
break;
case SSNES_STATE_VRAM:
memtype = SNES_MEMORY_VRAM;
break;
case SSNES_STATE_OAM:
2011-05-28 13:06:46 +00:00
memtype = SNES_MEMORY_OAM;
break;
case SSNES_STATE_CGRAM:
2011-05-28 13:06:46 +00:00
memtype = SNES_MEMORY_CGRAM;
break;
2011-06-06 16:50:36 +00:00
default:
memtype = -1u;
}
if ((memtype != -1u) && (addr >= psnes_get_memory_size(memtype)))
2011-05-28 13:06:46 +00:00
{
SSNES_ERR("Address out of bounds.\n");
goto error;
}
2011-05-29 21:58:04 +00:00
unsigned mask_value = 0;
if (bitmask)
mask_value = strtoul((const char*)bitmask, NULL, 16);
strlcpy(gl_tracker_info[gl_tracker_info_cnt].id, (const char*)id, sizeof(gl_tracker_info[0].id));
gl_tracker_info[gl_tracker_info_cnt].addr = addr;
gl_tracker_info[gl_tracker_info_cnt].type = tracker_type;
gl_tracker_info[gl_tracker_info_cnt].ram_type = ram_type;
2011-05-29 21:58:04 +00:00
gl_tracker_info[gl_tracker_info_cnt].mask = mask_value;
gl_tracker_info_cnt++;
if (id) xmlFree(id);
if (semantic) xmlFree(semantic);
if (wram) xmlFree(wram);
2011-06-11 18:02:17 +00:00
if (input) xmlFree(input);
if (apuram) xmlFree(apuram);
if (vram) xmlFree(vram);
if (oam) xmlFree(oam);
if (cgram) xmlFree(cgram);
2011-05-29 21:58:04 +00:00
if (bitmask) xmlFree(bitmask);
return true;
error:
if (id) xmlFree(id);
if (semantic) xmlFree(semantic);
if (wram) xmlFree(wram);
2011-06-11 18:02:17 +00:00
if (input) xmlFree(input);
if (apuram) xmlFree(apuram);
if (vram) xmlFree(vram);
if (oam) xmlFree(oam);
if (cgram) xmlFree(cgram);
2011-05-29 21:58:04 +00:00
if (bitmask) xmlFree(bitmask);
return false;
}
2011-03-12 14:30:57 +00:00
static unsigned get_xml_shaders(const char *path, struct shader_program *prog, size_t size)
2011-01-05 18:07:12 +00:00
{
LIBXML_TEST_VERSION;
xmlParserCtxtPtr ctx = xmlNewParserCtxt();
if (!ctx)
{
SSNES_ERR("Failed to load libxml2 context.\n");
return false;
}
SSNES_LOG("Loading XML shader: %s\n", path);
xmlDocPtr doc = xmlCtxtReadFile(ctx, path, NULL, 0);
if (!doc)
{
SSNES_ERR("Failed to parse XML file: %s\n", path);
goto error;
}
if (ctx->valid == 0)
{
SSNES_ERR("Cannot validate XML shader: %s\n", path);
goto error;
}
xmlNodePtr head = xmlDocGetRootElement(doc);
xmlNodePtr cur = NULL;
for (cur = head; cur; cur = cur->next)
{
if (cur->type == XML_ELEMENT_NODE && strcmp((const char*)cur->name, "shader") == 0)
{
xmlChar *attr;
attr = xmlGetProp(cur, (const xmlChar*)"language");
if (attr && strcmp((const char*)attr, "GLSL") == 0)
{
xmlFree(attr);
2011-01-05 18:07:12 +00:00
break;
}
if (attr)
xmlFree(attr);
2011-01-05 18:07:12 +00:00
}
}
if (!cur) // We couldn't find any GLSL shader :(
goto error;
2011-03-12 14:30:57 +00:00
unsigned num = 0;
memset(prog, 0, sizeof(struct shader_program) * size);
2011-01-05 18:07:12 +00:00
// Iterate to check if we find fragment and/or vertex shaders.
2011-03-12 14:30:57 +00:00
for (cur = cur->children; cur && num < size; cur = cur->next)
2011-01-05 18:07:12 +00:00
{
if (cur->type != XML_ELEMENT_NODE)
continue;
xmlChar *content = xmlNodeGetContent(cur);
if (!content)
continue;
2011-03-12 14:30:57 +00:00
if (strcmp((const char*)cur->name, "vertex") == 0)
2011-01-05 18:07:12 +00:00
{
2011-03-12 14:30:57 +00:00
if (prog[num].vertex)
{
SSNES_ERR("Cannot have more than one vertex shader in a program.\n");
xmlFree(content);
2011-03-12 14:30:57 +00:00
goto error;
}
prog[num].vertex = (char*)content;
2011-01-05 18:07:12 +00:00
}
2011-03-12 14:30:57 +00:00
else if (strcmp((const char*)cur->name, "fragment") == 0)
2011-01-05 18:07:12 +00:00
{
prog[num].fragment = (char*)content;
if (!get_xml_attrs(&prog[num], cur))
{
SSNES_ERR("XML shader attributes do not comply with specifications.\n");
goto error;
}
2011-03-12 14:30:57 +00:00
num++;
2011-01-05 18:07:12 +00:00
}
2011-05-18 18:22:27 +00:00
else if (strcmp((const char*)cur->name, "texture") == 0)
{
if (!get_texture_image(path, cur))
{
SSNES_ERR("Texture image failed to load.\n");
goto error;
}
}
else if (strcmp((const char*)cur->name, "import") == 0)
{
2011-06-07 14:28:58 +00:00
if (!get_import_value(cur))
{
SSNES_ERR("Import value is invalid.\n");
goto error;
}
}
2011-06-07 14:28:58 +00:00
#ifdef HAVE_PYTHON
2011-06-07 13:33:29 +00:00
else if (strcmp((const char*)cur->name, "script") == 0)
{
2011-06-07 14:28:58 +00:00
if (!get_script(path, cur))
2011-06-07 13:33:29 +00:00
{
SSNES_ERR("Script is invalid.\n");
goto error;
}
}
2011-06-07 14:28:58 +00:00
#endif
2011-01-05 18:07:12 +00:00
}
2011-03-12 14:30:57 +00:00
if (num == 0)
2011-01-05 18:07:12 +00:00
{
SSNES_ERR("Couldn't find vertex shader nor fragment shader in XML file.\n");
goto error;
}
xmlFreeDoc(doc);
xmlFreeParserCtxt(ctx);
2011-03-12 14:30:57 +00:00
return num;
2011-01-05 18:07:12 +00:00
error:
SSNES_ERR("Failed to load XML shader ...\n");
2011-01-05 18:07:12 +00:00
if (doc)
xmlFreeDoc(doc);
xmlFreeParserCtxt(ctx);
2011-03-12 14:30:57 +00:00
return 0;
2011-01-05 18:07:12 +00:00
}
static void print_shader_log(GLuint obj)
{
2011-06-29 01:26:21 +00:00
GLint info_len = 0;
GLint max_len;
pglGetShaderiv(obj, GL_INFO_LOG_LENGTH, &max_len);
char info_log[max_len];
pglGetShaderInfoLog(obj, max_len, &info_len, info_log);
if (info_len > 0)
SSNES_LOG("Shader log: %s\n", info_log);
}
static void print_linker_log(GLuint obj)
{
2011-06-29 01:26:21 +00:00
GLint info_len = 0;
GLint max_len;
pglGetProgramiv(obj, GL_INFO_LOG_LENGTH, &max_len);
char info_log[max_len];
pglGetProgramInfoLog(obj, max_len, &info_len, info_log);
if (info_len > 0)
SSNES_LOG("Linker log: %s\n", info_log);
}
2011-06-02 11:49:22 +00:00
static bool compile_shader(GLuint shader, const char *program)
{
pglShaderSource(shader, 1, &program, 0);
pglCompileShader(shader);
GLint status;
pglGetShaderiv(shader, GL_COMPILE_STATUS, &status);
print_shader_log(shader);
return status == GL_TRUE;
}
2011-06-07 13:58:30 +00:00
static bool link_program(GLuint prog)
{
pglLinkProgram(prog);
GLint status;
pglGetProgramiv(prog, GL_LINK_STATUS, &status);
print_linker_log(prog);
if (status == GL_TRUE)
{
pglUseProgram(prog);
return true;
}
else
return false;
}
2011-03-29 22:10:16 +00:00
static bool compile_programs(GLuint *gl_prog, struct shader_program *progs, size_t num)
2011-01-05 18:07:12 +00:00
{
2011-03-12 14:30:57 +00:00
for (unsigned i = 0; i < num; i++)
{
gl_prog[i] = pglCreateProgram();
2011-06-07 13:58:30 +00:00
if (gl_prog[i] == 0)
{
SSNES_ERR("Failed to create GL program #%u.\n", i);
return false;
}
2011-03-12 14:30:57 +00:00
if (progs[i].vertex)
{
2011-03-12 15:33:01 +00:00
SSNES_LOG("Found GLSL vertex shader.\n");
2011-03-12 14:30:57 +00:00
GLuint shader = pglCreateShader(GL_VERTEX_SHADER);
2011-06-02 11:49:22 +00:00
if (!compile_shader(shader, progs[i].vertex))
{
SSNES_ERR("Failed to compile vertex shader #%u\n", i);
return false;
}
2011-03-12 14:30:57 +00:00
pglAttachShader(gl_prog[i], shader);
free(progs[i].vertex);
}
2011-03-12 15:33:01 +00:00
if (progs[i].fragment)
2011-03-12 14:30:57 +00:00
{
2011-03-12 15:33:01 +00:00
SSNES_LOG("Found GLSL fragment shader.\n");
2011-03-12 14:30:57 +00:00
GLuint shader = pglCreateShader(GL_FRAGMENT_SHADER);
2011-06-02 11:49:22 +00:00
if (!compile_shader(shader, progs[i].fragment))
{
SSNES_ERR("Failed to compile fragment shader #%u\n", i);
return false;
}
2011-03-12 14:30:57 +00:00
pglAttachShader(gl_prog[i], shader);
free(progs[i].fragment);
}
if (progs[i].vertex || progs[i].fragment)
{
2011-03-12 15:33:01 +00:00
SSNES_LOG("Linking GLSL program.\n");
2011-06-07 13:58:30 +00:00
if (!link_program(gl_prog[i]))
{
SSNES_ERR("Failed to link program #%u\n", i);
return false;
}
2011-03-12 14:30:57 +00:00
GLint location = pglGetUniformLocation(gl_prog[i], "rubyTexture");
pglUniform1i(location, 0);
pglUseProgram(0);
}
}
2011-03-29 22:10:16 +00:00
return true;
2011-03-12 14:30:57 +00:00
}
2011-02-22 22:43:00 +00:00
#define LOAD_GL_SYM(SYM) if (!pgl##SYM) { SDL_SYM_WRAP(pgl##SYM, "gl" #SYM) }
2011-03-12 14:30:57 +00:00
bool gl_glsl_init(const char *path)
{
2011-02-22 22:43:00 +00:00
#ifndef __APPLE__
2011-01-05 18:07:12 +00:00
// Load shader functions.
LOAD_GL_SYM(CreateProgram);
LOAD_GL_SYM(UseProgram);
LOAD_GL_SYM(CreateShader);
LOAD_GL_SYM(DeleteShader);
LOAD_GL_SYM(ShaderSource);
LOAD_GL_SYM(CompileShader);
LOAD_GL_SYM(AttachShader);
LOAD_GL_SYM(DetachShader);
LOAD_GL_SYM(LinkProgram);
LOAD_GL_SYM(GetUniformLocation);
LOAD_GL_SYM(Uniform1i);
LOAD_GL_SYM(Uniform1f);
LOAD_GL_SYM(Uniform2fv);
LOAD_GL_SYM(Uniform4fv);
LOAD_GL_SYM(GetShaderiv);
LOAD_GL_SYM(GetShaderInfoLog);
LOAD_GL_SYM(GetProgramiv);
LOAD_GL_SYM(GetProgramInfoLog);
LOAD_GL_SYM(DeleteProgram);
LOAD_GL_SYM(GetAttachedShaders);
LOAD_GL_SYM(GetAttribLocation);
LOAD_GL_SYM(EnableVertexAttribArray);
LOAD_GL_SYM(VertexAttribPointer);
2011-02-22 22:43:00 +00:00
#endif
2011-01-05 18:07:12 +00:00
SSNES_LOG("Checking GLSL shader support ...\n");
2011-02-22 22:43:00 +00:00
#ifdef __APPLE__
const bool shader_support = true;
#else
2011-01-07 20:02:46 +00:00
bool shader_support = pglCreateProgram && pglUseProgram && pglCreateShader
&& pglDeleteShader && pglShaderSource && pglCompileShader && pglAttachShader
&& pglDetachShader && pglLinkProgram && pglGetUniformLocation
&& pglUniform1i && pglUniform1f && pglUniform2fv && pglUniform4fv
&& pglGetShaderiv && pglGetShaderInfoLog && pglGetProgramiv && pglGetProgramInfoLog
&& pglDeleteProgram && pglGetAttachedShaders
&& pglGetAttribLocation && pglEnableVertexAttribArray
&& pglVertexAttribPointer;
2011-02-22 22:43:00 +00:00
#endif
2011-01-05 18:07:12 +00:00
if (!shader_support)
{
SSNES_ERR("GLSL shaders aren't supported by your GL driver.\n");
return false;
}
2011-03-12 14:30:57 +00:00
struct shader_program progs[MAX_PROGRAMS];
unsigned num_progs = get_xml_shaders(path, progs, MAX_PROGRAMS - 1);
2011-01-05 18:07:12 +00:00
2011-03-12 14:30:57 +00:00
if (num_progs == 0)
2011-01-05 18:07:12 +00:00
{
2011-03-12 14:30:57 +00:00
SSNES_ERR("Couldn't find any valid shaders in XML file.\n");
return false;
}
2011-01-05 18:07:12 +00:00
2011-03-14 20:28:30 +00:00
for (unsigned i = 0; i < num_progs; i++)
{
2011-03-14 20:28:30 +00:00
gl_filter_type[i + 1] = progs[i].filter;
2011-03-27 18:29:47 +00:00
gl_scale[i + 1].type_x = progs[i].type_x;
gl_scale[i + 1].type_y = progs[i].type_y;
gl_scale[i + 1].scale_x = progs[i].scale_x;
gl_scale[i + 1].scale_y = progs[i].scale_y;
2011-03-27 18:29:47 +00:00
gl_scale[i + 1].abs_x = progs[i].abs_x;
gl_scale[i + 1].abs_y = progs[i].abs_y;
gl_scale[i + 1].valid = progs[i].valid_scale;
}
2011-03-14 20:28:30 +00:00
2011-03-29 22:10:16 +00:00
if (!compile_programs(&gl_program[1], progs, num_progs))
return false;
2011-03-06 18:56:35 +00:00
2011-03-12 14:30:57 +00:00
// SSNES custom two-pass with two different files.
if (num_progs == 1 && *g_settings.video.second_pass_shader && g_settings.video.render_to_texture)
2011-03-12 14:30:57 +00:00
{
unsigned secondary_progs = get_xml_shaders(g_settings.video.second_pass_shader, progs, 1);
if (secondary_progs == 1)
2011-03-06 18:56:35 +00:00
{
2011-03-12 14:30:57 +00:00
compile_programs(&gl_program[2], progs, 1);
num_progs++;
2011-03-06 18:56:35 +00:00
}
2011-03-12 14:30:57 +00:00
else
2011-03-06 18:56:35 +00:00
{
2011-03-12 14:30:57 +00:00
SSNES_ERR("Did not find valid shader in secondary shader file.\n");
return false;
2011-03-06 18:56:35 +00:00
}
2011-01-05 18:07:12 +00:00
}
2011-06-07 13:58:30 +00:00
//if (!gl_check_error())
// SSNES_WARN("Detected GL error!\n");
if (gl_tracker_info_cnt > 0)
{
struct snes_tracker_info info = {
.wram = psnes_get_memory_data(SNES_MEMORY_WRAM),
.vram = psnes_get_memory_data(SNES_MEMORY_VRAM),
.cgram = psnes_get_memory_data(SNES_MEMORY_CGRAM),
.apuram = psnes_get_memory_data(SNES_MEMORY_APURAM),
.oam = psnes_get_memory_data(SNES_MEMORY_OAM),
.info = gl_tracker_info,
2011-06-06 16:50:36 +00:00
.info_elem = gl_tracker_info_cnt,
2011-06-07 13:33:29 +00:00
};
2011-06-06 16:50:36 +00:00
#ifdef HAVE_PYTHON
2011-06-07 13:33:29 +00:00
if (*gl_tracker_script)
info.script = gl_tracker_script;
else if (gl_script_program)
info.script = (const char*)gl_script_program;
else
info.script = NULL;
info.script_class = *gl_tracker_script_class ? gl_tracker_script_class : NULL;
info.script_is_file = *gl_tracker_script;
2011-06-06 16:50:36 +00:00
#endif
2011-06-07 13:33:29 +00:00
gl_snes_tracker = snes_tracker_init(&info);
if (!gl_snes_tracker)
SSNES_WARN("Failed to init SNES tracker!\n");
}
2011-01-05 18:07:12 +00:00
glsl_enable = true;
2011-03-12 14:30:57 +00:00
gl_num_programs = num_progs;
2011-01-05 18:07:12 +00:00
return true;
}
void gl_glsl_deinit(void)
{
if (glsl_enable)
{
pglUseProgram(0);
for (int i = 1; i < MAX_PROGRAMS; i++)
{
if (gl_program[i] == 0)
break;
GLsizei count;
GLuint shaders[2];
pglGetAttachedShaders(gl_program[i], 2, &count, shaders);
for (GLsizei j = 0; j < count; j++)
{
pglDetachShader(gl_program[i], shaders[j]);
pglDeleteShader(shaders[j]);
}
pglDeleteProgram(gl_program[i]);
}
2011-05-18 18:22:27 +00:00
glDeleteTextures(gl_teximage_cnt, gl_teximage);
gl_teximage_cnt = 0;
memset(gl_teximage_uniforms, 0, sizeof(gl_teximage_uniforms));
}
memset(gl_program, 0, sizeof(gl_program));
glsl_enable = false;
active_index = 0;
gl_tracker_info_cnt = 0;
memset(gl_tracker_info, 0, sizeof(gl_tracker_info));
2011-06-06 16:50:36 +00:00
memset(gl_tracker_script, 0, sizeof(gl_tracker_script));
memset(gl_tracker_script_class, 0, sizeof(gl_tracker_script_class));
2011-06-07 13:33:29 +00:00
if (gl_script_program)
{
xmlFree(gl_script_program);
gl_script_program = NULL;
}
if (gl_snes_tracker)
{
snes_tracker_free(gl_snes_tracker);
gl_snes_tracker = NULL;
}
}
2011-01-05 18:07:12 +00:00
void gl_glsl_set_params(unsigned width, unsigned height,
unsigned tex_width, unsigned tex_height,
unsigned out_width, unsigned out_height,
unsigned frame_count,
const struct gl_tex_info *info,
const struct gl_tex_info *prev_info,
const struct gl_tex_info *fbo_info, unsigned fbo_info_cnt)
2011-01-05 18:07:12 +00:00
{
// We enforce a certain layout for our various texture types in the texunits.
// Unit 0: Regular SNES frame (rubyTexture).
// Unit 1-A: LUT textures.
// Unit A+1: Previous texture.
// Unit A+2: Original texture.
// Unit A+3-B: FBO textures.
2011-03-06 18:56:35 +00:00
if (glsl_enable && gl_program[active_index] > 0)
2011-01-05 18:07:12 +00:00
{
GLint location;
float inputSize[2] = {width, height};
2011-03-06 18:56:35 +00:00
location = pglGetUniformLocation(gl_program[active_index], "rubyInputSize");
2011-01-07 20:02:46 +00:00
pglUniform2fv(location, 1, inputSize);
2011-01-05 18:07:12 +00:00
float outputSize[2] = {out_width, out_height};
2011-03-06 18:56:35 +00:00
location = pglGetUniformLocation(gl_program[active_index], "rubyOutputSize");
2011-01-07 20:02:46 +00:00
pglUniform2fv(location, 1, outputSize);
2011-01-05 18:07:12 +00:00
float textureSize[2] = {tex_width, tex_height};
2011-03-06 18:56:35 +00:00
location = pglGetUniformLocation(gl_program[active_index], "rubyTextureSize");
2011-01-07 20:02:46 +00:00
pglUniform2fv(location, 1, textureSize);
location = pglGetUniformLocation(gl_program[active_index], "rubyFrameCount");
pglUniform1i(location, frame_count);
2011-05-18 18:22:27 +00:00
location = pglGetUniformLocation(gl_program[active_index], "rubyFrameDirection");
pglUniform1i(location, g_extern.frame_is_reverse ? -1 : 1);
2011-05-18 18:22:27 +00:00
for (unsigned i = 0; i < gl_teximage_cnt; i++)
{
location = pglGetUniformLocation(gl_program[active_index], gl_teximage_uniforms[i]);
pglUniform1i(location, i + 1);
}
// Set previous texture.
pglActiveTexture(GL_TEXTURE0 + gl_teximage_cnt + 1);
glBindTexture(GL_TEXTURE_2D, prev_info->tex);
location = pglGetUniformLocation(gl_program[active_index], "rubyPrevTexture");
pglUniform1i(location, gl_teximage_cnt + 1);
location = pglGetUniformLocation(gl_program[active_index], "rubyPrevTextureSize");
pglUniform2fv(location, 1, prev_info->tex_size);
location = pglGetUniformLocation(gl_program[active_index], "rubyPrevInputSize");
pglUniform2fv(location, 1, prev_info->input_size);
// Pass texture coordinates.
location = pglGetAttribLocation(gl_program[active_index], "rubyPrevTexCoord");
if (location >= 0)
{
pglEnableVertexAttribArray(location);
pglVertexAttribPointer(location, 2, GL_FLOAT, GL_FALSE, 0, prev_info->coord);
}
// Set original texture unless we're in first pass (pointless).
if (active_index > 1)
{
// Bind original texture.
pglActiveTexture(GL_TEXTURE0 + gl_teximage_cnt + 2);
glBindTexture(GL_TEXTURE_2D, info->tex);
location = pglGetUniformLocation(gl_program[active_index], "rubyOrigTexture");
pglUniform1i(location, gl_teximage_cnt + 2);
location = pglGetUniformLocation(gl_program[active_index], "rubyOrigTextureSize");
pglUniform2fv(location, 1, info->tex_size);
location = pglGetUniformLocation(gl_program[active_index], "rubyOrigInputSize");
pglUniform2fv(location, 1, info->input_size);
// Pass texture coordinates.
location = pglGetAttribLocation(gl_program[active_index], "rubyOrigTexCoord");
if (location >= 0)
{
pglEnableVertexAttribArray(location);
pglVertexAttribPointer(location, 2, GL_FLOAT, GL_FALSE, 0, info->coord);
}
GLuint base_tex = GL_TEXTURE0 + gl_teximage_cnt + 3;
// Bind new texture in the chain.
if (fbo_info_cnt > 0)
{
pglActiveTexture(base_tex + fbo_info_cnt - 1);
glBindTexture(GL_TEXTURE_2D, fbo_info[fbo_info_cnt - 1].tex);
}
// Bind FBO textures.
for (unsigned i = 0; i < fbo_info_cnt; i++)
{
char attrib_buf[64];
snprintf(attrib_buf, sizeof(attrib_buf), "rubyPass%uTexture", i + 1);
location = pglGetUniformLocation(gl_program[active_index], attrib_buf);
2011-05-23 18:08:16 +00:00
pglUniform1i(location, base_tex + i - GL_TEXTURE0);
snprintf(attrib_buf, sizeof(attrib_buf), "rubyPass%uTextureSize", i + 1);
location = pglGetUniformLocation(gl_program[active_index], attrib_buf);
pglUniform2fv(location, 1, fbo_info[i].tex_size);
snprintf(attrib_buf, sizeof(attrib_buf), "rubyPass%uInputSize", i + 1);
location = pglGetUniformLocation(gl_program[active_index], attrib_buf);
pglUniform2fv(location, 1, fbo_info[i].input_size);
snprintf(attrib_buf, sizeof(attrib_buf), "rubyPass%uTexCoord", i + 1);
location = pglGetAttribLocation(gl_program[active_index], attrib_buf);
if (location >= 0)
{
pglEnableVertexAttribArray(location);
pglVertexAttribPointer(location, 2, GL_FLOAT, GL_FALSE, 0, fbo_info[i].coord);
}
}
}
else
{
// First pass, so unbind everything to avoid collitions.
// Unbind ORIG.
pglActiveTexture(GL_TEXTURE0 + gl_teximage_cnt + 2);
glBindTexture(GL_TEXTURE_2D, 0);
GLuint base_tex = GL_TEXTURE0 + gl_teximage_cnt + 3;
// Unbind any lurking FBO passes.
// Rendering to a texture that is bound to a texture unit
// sounds very shaky ... ;)
for (unsigned i = 0; i < gl_num_programs; i++)
{
pglActiveTexture(GL_TEXTURE0 + base_tex + i);
glBindTexture(GL_TEXTURE_2D, 0);
}
}
pglActiveTexture(GL_TEXTURE0);
if (gl_snes_tracker)
{
static struct snes_tracker_uniform info[MAX_VARIABLES];
static unsigned cnt = 0;
if (active_index == 1)
cnt = snes_get_uniform(gl_snes_tracker, info, MAX_VARIABLES, frame_count);
for (unsigned i = 0; i < cnt; i++)
{
location = pglGetUniformLocation(gl_program[active_index], info[i].id);
pglUniform1f(location, info[i].value);
}
}
2011-01-05 18:07:12 +00:00
}
}
void gl_glsl_set_proj_matrix(void)
{}
2011-03-06 18:56:35 +00:00
void gl_glsl_use(unsigned index)
{
if (glsl_enable)
2011-03-06 18:56:35 +00:00
{
active_index = index;
pglUseProgram(gl_program[index]);
}
}
2011-03-12 14:30:57 +00:00
unsigned gl_glsl_num(void)
{
return gl_num_programs;
}
2011-03-14 20:28:30 +00:00
bool gl_glsl_filter_type(unsigned index, bool *smooth)
{
if (!glsl_enable)
return false;
switch (gl_filter_type[index])
{
case SSNES_GL_NOFORCE:
return false;
case SSNES_GL_NEAREST:
*smooth = false;
return true;
case SSNES_GL_LINEAR:
*smooth = true;
return true;
default:
return false;
}
}
void gl_glsl_shader_scale(unsigned index, struct gl_fbo_scale *scale)
{
if (glsl_enable)
*scale = gl_scale[index];
else
scale->valid = false;
}