diff --git a/src/modules/graphics/opengl/PixelEffect.cpp b/src/modules/graphics/opengl/PixelEffect.cpp new file mode 100644 index 000000000..90dfeb411 --- /dev/null +++ b/src/modules/graphics/opengl/PixelEffect.cpp @@ -0,0 +1,268 @@ +#include "PixelEffect.h" +#include "GLee.h" +#include +#include +#include + +namespace +{ + // temporarily attaches a shader program (for setting uniforms, etc) + // reattaches the originally active program when destroyed + struct TemporaryAttacher + { + TemporaryAttacher(love::graphics::opengl::PixelEffect* sp) : s(sp) + { + glGetIntegerv(GL_CURRENT_PROGRAM, &activeProgram); + s->attach(); + } + ~TemporaryAttacher() { glUseProgram(activeProgram); } + love::graphics::opengl::PixelEffect* s; + GLint activeProgram; + }; +} // anonymous namespace + +namespace love +{ +namespace graphics +{ +namespace opengl +{ + std::map PixelEffect::_texture_unit_pool; + GLint PixelEffect::_current_texture_unit = 0; + GLint PixelEffect::_max_texture_units = 0; + + GLint PixelEffect::getTextureUnit(const std::string& name) + { + std::map::const_iterator it = _texture_unit_pool.find(name); + + if (it != _texture_unit_pool.end()) + return it->second; + + if (++_current_texture_unit >= _max_texture_units) + throw love::Exception("No more texture units available"); + + _texture_unit_pool[name] = _current_texture_unit; + return _current_texture_unit; + } + + + PixelEffect::PixelEffect(const std::string& code) + : _program(0), _code(code) + { + glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &_max_texture_units); + loadVolatile(); + } + + bool PixelEffect::loadVolatile() + { + _program = glCreateProgram(); + // should only fail if this is called between a glBegin()/glEnd() pair + if (_program == 0) + throw love::Exception("Cannot create shader program object."); + + GLuint shader = glCreateShader(GL_FRAGMENT_SHADER); + // should only fail if this is called between a glBegin()/glEnd() pair + if (shader == 0) { + glDeleteProgram(_program); + throw love::Exception("Cannot create shader object."); + } + + // compile fragment shader code + const char* src = _code.c_str(); + GLint strlen = _code.length(); + glShaderSource(shader, 1, (const GLchar**)&src, &strlen); + glCompileShader(shader); + + GLint compile_ok; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_ok); + if (GL_FALSE == compile_ok) { + // get compiler error + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &strlen); + char *error_str = new char[strlen]; + glGetShaderInfoLog(shader, strlen, NULL, error_str); + + std::stringstream error_message; + error_message << "Cannot compile shader:" << std::endl << std::string(error_str); + + // cleanup before throw + delete[] error_str; + glDeleteShader(shader); + glDeleteProgram(_program); + throw love::Exception(error_message.str().c_str()); + } + + // link fragment shader + GLint link_ok; + glAttachShader(_program, shader); + glLinkProgram(_program); + glGetProgramiv(_program, GL_LINK_STATUS, &link_ok); + if (GL_FALSE == link_ok) { + // this should not happen if compiling is ok, but one can never be too careful + // get linker error + std::stringstream error_message; + error_message << "Cannot compile shader:" << std::endl << getWarnings(); + + // cleanup before throw + glDeleteShader(shader); + glDeleteProgram(_program); + throw love::Exception(error_message.str().c_str()); + } + + glDeleteShader(shader); + return true; + } + + PixelEffect::~PixelEffect() + { + unloadVolatile(); + } + + void PixelEffect::unloadVolatile() + { + glDeleteProgram(_program); + } + + bool PixelEffect::isSupported() + { + return GLEE_VERSION_2_0 && GLEE_ARB_shader_objects && GLEE_ARB_fragment_shader; + } + + std::string PixelEffect::getWarnings() const + { + GLint strlen; + glGetProgramiv(_program, GL_INFO_LOG_LENGTH, &strlen); + char *temp_str = new char[strlen]; + glGetProgramInfoLog(_program, strlen, NULL, temp_str); + + std::string warnings(temp_str); + delete[] temp_str; + return warnings; + } + + void PixelEffect::attach() + { + glUseProgram(_program); + } + + void PixelEffect::detach() + { + glUseProgram(0); + } + + void PixelEffect::sendFloat(const std::string& name, int count, const GLfloat* vec) + { + TemporaryAttacher attacher(this); + GLint location = getUniformLocation(name); + + if (count < 1 || count > 4) { + throw love::Exception("Invalid variable count: %d (expected 1-4).", count); + } + + switch (count) { + case 4: + glUniform4fv(location, 1, vec); + break; + case 3: + glUniform3fv(location, 1, vec); + break; + case 2: + glUniform2fv(location, 1, vec); + break; + case 1: + default: + glUniform1fv(location, 1, vec); + break; + } + + // throw error if needed + checkSetUniformError(); + } + + void PixelEffect::sendMatrix(const std::string& name, int size, const GLfloat* m) + { + TemporaryAttacher attacher(this); + GLint location = getUniformLocation(name); + + if (size < 2 || size > 4) { + throw love::Exception("Invalid matrix size: %dx%d " + "(can only set 2x2, 3x3 or 4x4 matrices).", size,size); + } + + switch (size) { + case 4: + glUniformMatrix4fv(location, 1, GL_FALSE, m); + break; + case 3: + glUniformMatrix3fv(location, 1, GL_FALSE, m); + break; + case 2: + default: + glUniformMatrix2fv(location, 1, GL_FALSE, m); + break; + } + + // throw error if needed + checkSetUniformError(); + } + + void PixelEffect::sendImage(const std::string& name, const Image& image) + { + GLint texture_unit = getTextureUnit(name); + + TemporaryAttacher attacher(this); + GLint location = getUniformLocation(name); + + glActiveTexture(GL_TEXTURE0 + texture_unit); + glBindTexture(GL_TEXTURE_2D, image.getTextureName()); + glUniform1i(location, texture_unit); + + // reset texture unit + glActiveTexture(GL_TEXTURE0); + + // throw error if needed + checkSetUniformError(); + } + + void PixelEffect::sendFramebuffer(const std::string& name, const Framebuffer& fb) + { + GLint texture_unit = getTextureUnit(name); + + TemporaryAttacher attacher(this); + GLint location = getUniformLocation(name); + + glActiveTexture(GL_TEXTURE0 + texture_unit); + glBindTexture(GL_TEXTURE_2D, fb.getTextureName()); + glUniform1i(location, texture_unit); + + // reset texture unit + glActiveTexture(GL_TEXTURE0); + + // throw error if needed + checkSetUniformError(); + } + + GLint PixelEffect::getUniformLocation(const std::string& name) + { + GLint location = glGetUniformLocation(_program, name.c_str()); + if (location == -1) { + throw love::Exception( + "Cannot get location of shader variable `%s'.\n" + "A common error is to define but not use the variable.", name.c_str()); + } + return location; + } + + void PixelEffect::checkSetUniformError() + { + GLenum error_code = glGetError(); + if (GL_INVALID_OPERATION == error_code) { + throw love::Exception( + "Invalid operation:\n" + "Trying to send more than one value to a float variable, or" + "Size of shader variable does not match indicated size, or" + "Invalid variable name."); + } + } +} // opengl +} // graphics +} // love diff --git a/src/modules/graphics/opengl/PixelEffect.h b/src/modules/graphics/opengl/PixelEffect.h new file mode 100644 index 000000000..8e959b075 --- /dev/null +++ b/src/modules/graphics/opengl/PixelEffect.h @@ -0,0 +1,53 @@ +#ifndef LOVE_GRAPHICS_EFFECT_H +#define LOVE_GRAPHICS_EFFECT_H + +#include +#include +#include +#include "Image.h" +#include "Framebuffer.h" + +namespace love +{ +namespace graphics +{ +namespace opengl +{ + // A fragment shader + class PixelEffect : public Volatile // Volatile? + { + public: + PixelEffect(const std::string& code); + virtual ~PixelEffect(); + std::string getWarnings() const; + + virtual bool loadVolatile(); + virtual void unloadVolatile(); + + void attach(); + static void detach(); + static bool isSupported(); + + void sendFloat(const std::string& name, int count, const GLfloat* vec); + void sendMatrix(const std::string& name, int size, const GLfloat* m); + void sendImage(const std::string& name, const Image& image); + void sendFramebuffer(const std::string& name, const Framebuffer& fb); + + private: + GLint getUniformLocation(const std::string& name); + void checkSetUniformError(); + GLuint _program; + std::string _code; // volatile and stuff + + // texture unit pool for setting images + static std::map _texture_unit_pool; + static GLint _current_texture_unit; + static GLint _max_texture_units; + static GLint getTextureUnit(const std::string& name); + }; + +} // opengl +} // graphics +} // love + +#endif // LOVE_GRAPHICS_EFFECT_H diff --git a/src/modules/graphics/opengl/wrap_Graphics.cpp b/src/modules/graphics/opengl/wrap_Graphics.cpp index 4b6b19e12..56df859fd 100644 --- a/src/modules/graphics/opengl/wrap_Graphics.cpp +++ b/src/modules/graphics/opengl/wrap_Graphics.cpp @@ -24,7 +24,6 @@ #include #include -#include "GLInfo.h" namespace love { diff --git a/src/modules/graphics/opengl/wrap_PixelEffect.cpp b/src/modules/graphics/opengl/wrap_PixelEffect.cpp new file mode 100644 index 000000000..ad7de2f38 --- /dev/null +++ b/src/modules/graphics/opengl/wrap_PixelEffect.cpp @@ -0,0 +1,117 @@ +#include "wrap_PixelEffect.h" +#include "wrap_Image.h" +#include "wrap_Framebuffer.h" +#include + +namespace love +{ +namespace graphics +{ +namespace opengl +{ + PixelEffect * luax_checkpixeleffect(lua_State * L, int idx) + { + return luax_checktype(L, idx, "PixelEffect", GRAPHICS_PIXELEFFECT_T); + } + + int w_PixelEffect_getWarnings(lua_State * L) + { + PixelEffect * effect = luax_checkpixeleffect(L, 1); + lua_pushstring(L, effect->getWarnings().c_str()); + return 1; + } + + int w_PixelEffect_sendFloat(lua_State * L) + { + size_t count = lua_gettop(L) - 2; + PixelEffect * effect = luax_checkpixeleffect(L, 1); + const char* name = luaL_checkstring(L, 2); + + if (count < 1 || count > 4) + return luaL_error(L, "Invalid variable count (expected 1-4, got %d).", count); + + float values[4] = {0,0,0,0}; + for (int i = 0; i < count; ++i) + values[i] = luaL_checknumber(L, i+1 + 2); + + try { + effect->sendFloat(name, count, values); + } catch(love::Exception& e) { + luaL_error(L, e.what()); + } + + return 0; + } + + int w_PixelEffect_sendMatrix(lua_State * L) + { + size_t count = lua_gettop(L) - 3; + PixelEffect * effect = luax_checkpixeleffect(L, 1); + const char* name = luaL_checkstring(L, 2); + int size = luaL_checkinteger(L, 3); + + if (size < 2 || size > 4) + return luaL_error(L, "Invalid matrix size: %dx%d (only 2x2, 3x3 and 4x4 matrices are supported).", count, count); + + float values[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + for (int i = 0; i < count; ++i) + values[i] = luaL_checknumber(L, i+1 + 3); + + try { + effect->sendFloat(name, size, values); + } catch(love::Exception& e) { + luaL_error(L, e.what()); + } + + return 0; + } + + int w_PixelEffect_sendImage(lua_State * L) + { + PixelEffect * effect = luax_checkpixeleffect(L, 1); + const char* name = luaL_checkstring(L, 2); + Image* img = luax_checkimage(L, 3); + + try { + effect->sendImage(name, *img); + } catch(love::Exception& e) { + luaL_error(L, e.what()); + } + + return 0; + } + + int w_PixelEffect_sendFramebuffer(lua_State * L) + { + PixelEffect * effect = luax_checkpixeleffect(L, 1); + const char* name = luaL_checkstring(L, 2); + Framebuffer* fb = luax_checkfbo(L, 3); + + try { + effect->sendFramebuffer(name, *fb); + } catch(love::Exception& e) { + luaL_error(L, e.what()); + } + + return 0; + } + + + static const luaL_Reg functions[] = { + { "getWarnings", w_PixelEffect_getWarnings }, + { "sendFloat", w_PixelEffect_sendFloat }, + { "sendMatrix", w_PixelEffect_sendMatrix }, + { "sendImage", w_PixelEffect_sendImage }, + { "sendFramebuffer", w_PixelEffect_sendFramebuffer }, + { 0, 0 } + }; + + int luaopen_pixeleffect(lua_State * L) + { + return luax_register_type(L, "PixelEffect", functions); + } + +} // opengl +} // graphics +} // love + diff --git a/src/modules/graphics/opengl/wrap_PixelEffect.h b/src/modules/graphics/opengl/wrap_PixelEffect.h new file mode 100644 index 000000000..06b15a928 --- /dev/null +++ b/src/modules/graphics/opengl/wrap_PixelEffect.h @@ -0,0 +1,23 @@ +#ifndef LOVE_GRAPHICS_OPENGL_WRAP_PROGRAM_H +#define LOVE_GRAPHICS_OPENGL_WRAP_PROGRAM_H + +#include +#include "PixelEffect.h" + +namespace love +{ +namespace graphics +{ +namespace opengl +{ + PixelEffect * luax_checkpixeleffect(lua_State * L, int idx); + int w_PixelEffect_getWarnings(lua_State * L); + int w_PixelEffect_sendFloat(lua_State * L); + int w_PixelEffect_sendMatrix(lua_State * L); + int w_PixelEffect_sendImage(lua_State * L); + int luaopen_pixeleffect(lua_State * L); +} // opengl +} // graphics +} // love + +#endif