From 22f2175becb16a0cfb9ca8397b91f3984fa2f28b Mon Sep 17 00:00:00 2001 From: Bart van Strien Date: Tue, 8 Dec 2015 22:41:19 -0400 Subject: [PATCH] Add initial video playback support for Ogg Theora videos (resolves issue #66.) The basic APIs are: video = love.graphics.newVideo("myvideo.ogv") love.graphics.draw(video, ...) -- Video objects are Drawables. video:play(), video:pause() video:getDuration(), video:tell(), video:rewind(), video:seek(seconds) video:getSource() video:getWidth(), video:getHeight(), video:setFilter(min, mag) More advanced APIs include video:setSource(source), video:getStream(), and videostream:setSync. To use a custom pixel shader when drawing a Video, call the new TexelVideo(texcoords) function instead of Texel(texture, texcoords) in order to get the pixel colors of a video frame. --- CMakeLists.txt | 35 ++ platform/unix/configure.ac | 1 + platform/unix/genmodules | 4 +- src/common/Module.h | 1 + src/common/Object.h | 10 + src/common/Stream.h | 64 +++ src/common/config.h | 2 + src/common/types.cpp | 5 + src/common/types.h | 5 + src/modules/filesystem/wrap_Filesystem.cpp | 19 +- src/modules/filesystem/wrap_Filesystem.h | 2 + src/modules/graphics/opengl/Graphics.cpp | 17 + src/modules/graphics/opengl/Graphics.h | 5 + src/modules/graphics/opengl/Shader.cpp | 60 +++ src/modules/graphics/opengl/Shader.h | 8 + src/modules/graphics/opengl/Video.cpp | 212 +++++++++ src/modules/graphics/opengl/Video.h | 79 ++++ src/modules/graphics/opengl/wrap_Graphics.cpp | 41 +- src/modules/graphics/opengl/wrap_Graphics.h | 1 + src/modules/graphics/opengl/wrap_Graphics.lua | 49 ++- src/modules/graphics/opengl/wrap_Video.cpp | 157 +++++++ src/modules/graphics/opengl/wrap_Video.h | 38 ++ src/modules/graphics/opengl/wrap_Video.lua | 69 +++ src/modules/image/magpie/Image.cpp | 2 + src/modules/love/love.cpp | 6 + src/modules/sound/lullaby/VorbisDecoder.cpp | 2 +- src/modules/timer/Timer.cpp | 11 +- src/modules/timer/Timer.h | 4 +- src/modules/video/Video.h | 53 +++ src/modules/video/VideoStream.cpp | 168 ++++++++ src/modules/video/VideoStream.h | 131 ++++++ src/modules/video/theora/Video.cpp | 122 ++++++ src/modules/video/theora/Video.h | 81 ++++ src/modules/video/theora/VideoStream.cpp | 402 ++++++++++++++++++ src/modules/video/theora/VideoStream.h | 101 +++++ src/modules/video/wrap_Video.cpp | 86 ++++ src/modules/video/wrap_Video.h | 38 ++ src/modules/video/wrap_VideoStream.cpp | 131 ++++++ src/modules/video/wrap_VideoStream.h | 35 ++ src/scripts/boot.lua | 2 + src/scripts/boot.lua.h | 2 + 41 files changed, 2241 insertions(+), 20 deletions(-) create mode 100644 src/common/Stream.h create mode 100644 src/modules/graphics/opengl/Video.cpp create mode 100644 src/modules/graphics/opengl/Video.h create mode 100644 src/modules/graphics/opengl/wrap_Video.cpp create mode 100644 src/modules/graphics/opengl/wrap_Video.h create mode 100644 src/modules/graphics/opengl/wrap_Video.lua create mode 100644 src/modules/video/Video.h create mode 100644 src/modules/video/VideoStream.cpp create mode 100644 src/modules/video/VideoStream.h create mode 100644 src/modules/video/theora/Video.cpp create mode 100644 src/modules/video/theora/Video.h create mode 100644 src/modules/video/theora/VideoStream.cpp create mode 100644 src/modules/video/theora/VideoStream.h create mode 100644 src/modules/video/wrap_Video.cpp create mode 100644 src/modules/video/wrap_Video.h create mode 100644 src/modules/video/wrap_VideoStream.cpp create mode 100644 src/modules/video/wrap_VideoStream.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b373ca61..153636d9c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -308,6 +308,8 @@ set(LOVE_SRC_MODULE_GRAPHICS_OPENGL src/modules/graphics/opengl/SpriteBatch.h src/modules/graphics/opengl/Text.cpp src/modules/graphics/opengl/Text.h + src/modules/graphics/opengl/Video.cpp + src/modules/graphics/opengl/Video.h src/modules/graphics/opengl/wrap_Canvas.cpp src/modules/graphics/opengl/wrap_Canvas.h src/modules/graphics/opengl/wrap_Font.cpp @@ -326,6 +328,8 @@ set(LOVE_SRC_MODULE_GRAPHICS_OPENGL src/modules/graphics/opengl/wrap_SpriteBatch.h src/modules/graphics/opengl/wrap_Text.cpp src/modules/graphics/opengl/wrap_Text.h + src/modules/graphics/opengl/wrap_Video.cpp + src/modules/graphics/opengl/wrap_Video.h ) set(LOVE_SRC_MODULE_GRAPHICS @@ -778,6 +782,35 @@ set(LOVE_SRC_MODULE_TOUCH source_group("modules\\touch" FILES ${LOVE_SRC_MODULE_TOUCH_ROOT}) source_group("modules\\touch\\sdl" FILES ${LOVE_SRC_MODULE_TOUCH_SDL}) +# +# love.video +# + +set(LOVE_SRC_MODULE_VIDEO_ROOT + src/modules/video/Video.h + src/modules/video/VideoStream.cpp + src/modules/video/VideoStream.h + src/modules/video/wrap_Video.cpp + src/modules/video/wrap_Video.h + src/modules/video/wrap_VideoStream.cpp + src/modules/video/wrap_VideoStream.h +) + +set(LOVE_SRC_MODULE_VIDEO_THEORA + src/modules/video/theora/Video.cpp + src/modules/video/theora/Video.h + src/modules/video/theora/VideoStream.cpp + src/modules/video/theora/VideoStream.h +) + +set(LOVE_SRC_MODULE_VIDEO + ${LOVE_SRC_MODULE_VIDEO_ROOT} + ${LOVE_SRC_MODULE_VIDEO_THEORA} +) + +source_group("modules\\video" FILES ${LOVE_SRC_MODULE_VIDEO_ROOT}) +source_group("modules\\video\\theora" FILES ${LOVE_SRC_MODULE_VIDEO_THEORA}) + # # love.window # @@ -1203,6 +1236,7 @@ set(LOVE_LIB_SRC ${LOVE_SRC_MODULE_THREAD} ${LOVE_SRC_MODULE_TIMER} ${LOVE_SRC_MODULE_TOUCH} + ${LOVE_SRC_MODULE_VIDEO} ${LOVE_SRC_MODULE_WINDOW} ) @@ -1222,6 +1256,7 @@ set(LOVE_MEGA_3P ${MEGA_LIBOGG} ${MEGA_LIBVORBISFILE} ${MEGA_LIBVORBIS} + ${MEGA_LIBTHEORA} ${MEGA_LUA} ${MEGA_MODPLUG} ${MEGA_OPENAL} diff --git a/platform/unix/configure.ac b/platform/unix/configure.ac index 2ea733377..8ccf20b24 100644 --- a/platform/unix/configure.ac +++ b/platform/unix/configure.ac @@ -53,6 +53,7 @@ PKG_CHECK_MODULES([openal], [openal], [], [LOVE_MSG_ERROR([OpenAL])]) PKG_CHECK_MODULES([libmodplug], [libmodplug], [], [LOVE_MSG_ERROR([libmodplug])]) PKG_CHECK_MODULES([vorbisfile], [vorbisfile], [], [LOVE_MSG_ERROR([libvorbis and libvorbisfile])]) PKG_CHECK_MODULES([zlib], [zlib], [], [LOVE_MSG_ERROR([zlib])]) +PKG_CHECK_MODULES([theora], [theoradec], [], [LOVE_MSG_ERROR([libtheora])]) # Other libraries AC_SEARCH_LIBS([sqrt], [m], [], [LOVE_MSG_ERROR([the C math library])]) diff --git a/platform/unix/genmodules b/platform/unix/genmodules index 73131a331..ccfb69b3b 100644 --- a/platform/unix/genmodules +++ b/platform/unix/genmodules @@ -114,7 +114,7 @@ cat > src/Makefile.am << EOF AM_CPPFLAGS = -I$inc_current -I$inc_modules -I$inc_libraries -I$inc_libraries/enet/libenet/include \$(LOVE_INCLUDES) \$(FILE_OFFSET)\ \$(SDL_CFLAGS) \$(lua_CFLAGS) \$(freetype2_CFLAGS)\ \$(openal_CFLAGS) \$(zlib_CFLAGS) \$(libmodplug_CFLAGS)\ - \$(vorbisfile_CFLAGS) + \$(vorbisfile_CFLAGS) \$(theora_CFLAGS) AUTOMAKE_OPTIONS = subdir-objects SUBDIRS = SUFFIXES = .lua .lua.h @@ -146,7 +146,7 @@ liblove${love_amsuffix}_la_LDFLAGS = -module -export-dynamic \$(LDFLAGS) liblove${love_amsuffix}_la_LIBADD = \ \$(SDL_LIBS) \$(freetype2_LIBS) \$(lua_LIBS)\ \$(openal_LIBS) \$(zlib_LIBS) \$(libmodplug_LIBS)\ - \$(vorbisfile_LIBS) + \$(vorbisfile_LIBS) \$(theora_LIBS) EOF genmodules >> src/Makefile.am diff --git a/src/common/Module.h b/src/common/Module.h index cac50a3c9..bb77af762 100644 --- a/src/common/Module.h +++ b/src/common/Module.h @@ -53,6 +53,7 @@ public: M_TIMER, M_TOUCH, M_WINDOW, + M_VIDEO, M_MAX_ENUM }; diff --git a/src/common/Object.h b/src/common/Object.h index 624957a9d..dd06ca819 100644 --- a/src/common/Object.h +++ b/src/common/Object.h @@ -117,6 +117,16 @@ public: return object; } + operator bool() const + { + return object != nullptr; + } + + operator T*() const + { + return object; + } + void set(T *obj) { if (obj) obj->retain(); diff --git a/src/common/Stream.h b/src/common/Stream.h new file mode 100644 index 000000000..4b9b1db29 --- /dev/null +++ b/src/common/Stream.h @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2006-2015 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +#ifndef LOVE_STREAM_H +#define LOVE_STREAM_H + +// LOVE +#include +#include "Object.h" + +namespace love +{ + +class Stream : public Object +{ +public: + virtual ~Stream() {} + + // getData and getSize are assumed to talk about + // the buffer + + /** + * A callback, gets called when some Stream consumer exhausts the data + **/ + virtual void fillBackBuffer() {} + + /** + * Get the front buffer, Streams are supposed to be (at least) double-buffered + **/ + virtual const void *getFrontBuffer() const = 0; + + /** + * Get the size of any (and in particular the front) buffer + **/ + virtual size_t getSize() const = 0; + + /** + * Swap buffers. Returns true if there is new data in the front buffer, + * false otherwise. + * NOTE: If there is no back buffer ready, this call must be ignored + **/ + virtual bool swapBuffers() = 0; +}; // Stream + +} // love + +#endif // LOVE_STREAM_H diff --git a/src/common/config.h b/src/common/config.h index 4e13a1bc6..232174d6c 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -141,6 +141,8 @@ # define LOVE_ENABLE_TOUCH # define LOVE_ENABLE_TOUCH_SDL # define LOVE_ENABLE_UTF8 +# define LOVE_ENABLE_VIDEO +# define LOVE_ENABLE_VIDEO_THEORA # define LOVE_ENABLE_WINDOW # define LOVE_ENABLE_WINDOW_SDL # define LOVE_ENABLE_WUFF diff --git a/src/common/types.cpp b/src/common/types.cpp index 073be65b6..87f8cf944 100644 --- a/src/common/types.cpp +++ b/src/common/types.cpp @@ -34,6 +34,7 @@ static const TypeBits *createTypeFlags() b[OBJECT_ID] = one << OBJECT_ID; b[DATA_ID] = (one << DATA_ID) | b[OBJECT_ID]; b[MODULE_ID] = (one << MODULE_ID) | b[OBJECT_ID]; + b[STREAM_ID] = (one << STREAM_ID) | b[OBJECT_ID]; // Filesystem. b[FILESYSTEM_FILE_ID] = (one << FILESYSTEM_FILE_ID) | b[OBJECT_ID]; @@ -55,6 +56,7 @@ static const TypeBits *createTypeFlags() b[GRAPHICS_SHADER_ID] = (one << GRAPHICS_SHADER_ID) | b[OBJECT_ID]; b[GRAPHICS_MESH_ID] = (one << GRAPHICS_MESH_ID) | b[GRAPHICS_DRAWABLE_ID]; b[GRAPHICS_TEXT_ID] = (one << GRAPHICS_TEXT_ID) | b[GRAPHICS_DRAWABLE_ID]; + b[GRAPHICS_VIDEO_ID] = (one << GRAPHICS_VIDEO_ID) | b[GRAPHICS_DRAWABLE_ID]; // Image. b[IMAGE_IMAGE_DATA_ID] = (one << IMAGE_IMAGE_DATA_ID) | b[DATA_ID]; @@ -105,6 +107,9 @@ static const TypeBits *createTypeFlags() b[THREAD_THREAD_ID] = (one << THREAD_THREAD_ID) | b[OBJECT_ID]; b[THREAD_CHANNEL_ID] = (one << THREAD_CHANNEL_ID) | b[OBJECT_ID]; + // Video + b[VIDEO_VIDEO_STREAM_ID] = (one << VIDEO_VIDEO_STREAM_ID) | b[STREAM_ID]; + // Modules. b[MODULE_FILESYSTEM_ID] = (one << MODULE_FILESYSTEM_ID) | b[MODULE_ID]; b[MODULE_GRAPHICS_ID] = (one << MODULE_GRAPHICS_ID) | b[MODULE_ID]; diff --git a/src/common/types.h b/src/common/types.h index 226f4f24d..dbfe3f640 100644 --- a/src/common/types.h +++ b/src/common/types.h @@ -34,6 +34,7 @@ enum Type OBJECT_ID, DATA_ID, MODULE_ID, + STREAM_ID, // Filesystem. FILESYSTEM_FILE_ID, @@ -56,6 +57,7 @@ enum Type GRAPHICS_SHADER_ID, GRAPHICS_MESH_ID, GRAPHICS_TEXT_ID, + GRAPHICS_VIDEO_ID, // Image IMAGE_IMAGE_DATA_ID, @@ -106,6 +108,9 @@ enum Type THREAD_THREAD_ID, THREAD_CHANNEL_ID, + // Video + VIDEO_VIDEO_STREAM_ID, + // The modules themselves. Only add abstracted modules here. MODULE_FILESYSTEM_ID, MODULE_GRAPHICS_ID, diff --git a/src/modules/filesystem/wrap_Filesystem.cpp b/src/modules/filesystem/wrap_Filesystem.cpp index 8e8941d54..9b22b60c2 100644 --- a/src/modules/filesystem/wrap_Filesystem.cpp +++ b/src/modules/filesystem/wrap_Filesystem.cpp @@ -155,19 +155,28 @@ int w_newFile(lua_State *L) return 1; } -FileData *luax_getfiledata(lua_State *L, int idx) +File *luax_getfile(lua_State *L, int idx) { - FileData *data = nullptr; File *file = nullptr; - if (lua_isstring(L, idx)) { const char *filename = luaL_checkstring(L, idx); file = instance()->newFile(filename); } - else if (luax_istype(L, idx, FILESYSTEM_FILE_ID)) - { + else file = luax_checkfile(L, idx); + + return file; +} + +FileData *luax_getfiledata(lua_State *L, int idx) +{ + FileData *data = nullptr; + File *file = nullptr; + + if (lua_isstring(L, idx) || luax_istype(L, idx, FILESYSTEM_FILE_ID)) + { + file = luax_getfile(L, idx); file->retain(); } else if (luax_istype(L, idx, FILESYSTEM_FILE_DATA_ID)) diff --git a/src/modules/filesystem/wrap_Filesystem.h b/src/modules/filesystem/wrap_Filesystem.h index 707d0dad2..3af3c7b3d 100644 --- a/src/modules/filesystem/wrap_Filesystem.h +++ b/src/modules/filesystem/wrap_Filesystem.h @@ -23,6 +23,7 @@ // LOVE #include "common/runtime.h" +#include "File.h" #include "FileData.h" namespace love @@ -38,6 +39,7 @@ namespace filesystem * May trigger a Lua error. **/ FileData *luax_getfiledata(lua_State *L, int idx); +File *luax_getfile(lua_State *L, int idx); bool hack_setupWriteDirectory(); int loader(lua_State *L); diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index 8331c57ca..6215082a2 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -86,6 +86,11 @@ Graphics::~Graphics() Shader::defaultShader->release(); Shader::defaultShader = nullptr; } + if (Shader::defaultVideoShader) + { + Shader::defaultVideoShader->release(); + Shader::defaultVideoShader = nullptr; + } if (quadIndices) delete quadIndices; @@ -320,6 +325,13 @@ bool Graphics::setMode(int width, int height) Shader::defaultShader = newShader(Shader::defaultCode[renderer]); } + // and a default video shader. + if (!Shader::defaultVideoShader) + { + Renderer renderer = GLAD_ES_VERSION_2_0 ? RENDERER_OPENGLES : RENDERER_OPENGL; + Shader::defaultVideoShader = newShader(Shader::defaultVideoCode[renderer]); + } + // A shader should always be active, but the default shader shouldn't be // returned by getShader(), so we don't do setShader(defaultShader). if (!Shader::current) @@ -819,6 +831,11 @@ Text *Graphics::newText(Font *font, const std::vector &text return new Text(font, text); } +Video *Graphics::newVideo(love::video::VideoStream *stream) +{ + return new Video(stream); +} + bool Graphics::isGammaCorrect() const { return love::graphics::isGammaCorrect(); diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index 8a87c33e6..8bf2d982c 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/src/modules/graphics/opengl/Graphics.h @@ -37,6 +37,8 @@ #include "window/Window.h" +#include "video/VideoStream.h" + #include "Font.h" #include "Image.h" #include "graphics/Quad.h" @@ -47,6 +49,7 @@ #include "Shader.h" #include "Mesh.h" #include "Text.h" +#include "Video.h" namespace love { @@ -185,6 +188,8 @@ public: Text *newText(Font *font, const std::vector &text = {}); + Video *newVideo(love::video::VideoStream *stream); + bool isGammaCorrect() const; /** diff --git a/src/modules/graphics/opengl/Shader.cpp b/src/modules/graphics/opengl/Shader.cpp index 226eb37cb..dee541dde 100644 --- a/src/modules/graphics/opengl/Shader.cpp +++ b/src/modules/graphics/opengl/Shader.cpp @@ -64,8 +64,10 @@ namespace Shader *Shader::current = nullptr; Shader *Shader::defaultShader = nullptr; +Shader *Shader::defaultVideoShader = nullptr; Shader::ShaderSource Shader::defaultCode[Graphics::RENDERER_MAX_ENUM]; +Shader::ShaderSource Shader::defaultVideoCode[Graphics::RENDERER_MAX_ENUM]; std::vector Shader::textureCounters; @@ -77,6 +79,7 @@ Shader::Shader(const ShaderSource &source) , lastCanvas((Canvas *) -1) , lastViewport() , lastPointSize(0.0f) + , videoTextureUnits() { if (source.vertex.empty() && source.pixel.empty()) throw love::Exception("Cannot create shader: no source code!"); @@ -225,6 +228,9 @@ bool Shader::loadVolatile() lastProjectionMatrix.setTranslation(nan, nan); lastTransformMatrix.setTranslation(nan, nan); + for (int i = 0; i < 3; i++) + videoTextureUnits[i] = 0; + // zero out active texture list activeTexUnits.clear(); activeTexUnits.insert(activeTexUnits.begin(), gl.getMaxTextureUnits() - 1, 0); @@ -635,6 +641,57 @@ bool Shader::hasVertexAttrib(VertexAttribID attrib) const return builtinAttributes[int(attrib)] != -1; } +void Shader::setVideoTextures(GLuint ytexture, GLuint cbtexture, GLuint crtexture) +{ + TemporaryAttacher attacher(this); + + // Set up the texture units that will be used by the shader to sample from + // the textures, if they haven't been set up yet. + if (videoTextureUnits[0] == 0) + { + const GLint locs[3] = { + builtinUniforms[BUILTIN_VIDEO_Y_CHANNEL], + builtinUniforms[BUILTIN_VIDEO_CB_CHANNEL], + builtinUniforms[BUILTIN_VIDEO_CR_CHANNEL] + }; + + const char *names[3] = {nullptr, nullptr, nullptr}; + builtinNames.find(BUILTIN_VIDEO_Y_CHANNEL, names[0]); + builtinNames.find(BUILTIN_VIDEO_CB_CHANNEL, names[1]); + builtinNames.find(BUILTIN_VIDEO_CR_CHANNEL, names[2]); + + for (int i = 0; i < 3; i++) + { + if (locs[i] >= 0 && names[i] != nullptr) + { + videoTextureUnits[i] = getTextureUnit(names[i]); + + // Increment global shader texture id counter for this texture + // unit, if we haven't already. + if (activeTexUnits[videoTextureUnits[i] - 1] == 0) + ++textureCounters[videoTextureUnits[i] - 1]; + + glUniform1i(locs[i], videoTextureUnits[i]); + } + } + } + + const GLuint textures[3] = {ytexture, cbtexture, crtexture}; + + // Bind the textures to their respective texture units. + for (int i = 0; i < 3; i++) + { + if (videoTextureUnits[i] != 0) + { + // Store texture id so it can be re-bound later. + activeTexUnits[videoTextureUnits[i] - 1] = textures[i]; + gl.bindTextureToUnit(textures[i], videoTextureUnits[i], false); + } + } + + gl.setTextureUnit(0); +} + void Shader::checkSetScreenParams() { OpenGL::Viewport view = gl.getViewport(); @@ -898,6 +955,9 @@ StringMap::Entry Shader::built {"NormalMatrix", Shader::BUILTIN_NORMAL_MATRIX}, {"love_PointSize", Shader::BUILTIN_POINT_SIZE}, {"love_ScreenSize", Shader::BUILTIN_SCREEN_SIZE}, + {"love_VideoYChannel", Shader::BUILTIN_VIDEO_Y_CHANNEL}, + {"love_VideoCbChannel", Shader::BUILTIN_VIDEO_CB_CHANNEL}, + {"love_VideoCrChannel", Shader::BUILTIN_VIDEO_CR_CHANNEL}, }; StringMap Shader::builtinNames(Shader::builtinNameEntries, sizeof(Shader::builtinNameEntries)); diff --git a/src/modules/graphics/opengl/Shader.h b/src/modules/graphics/opengl/Shader.h index 47e7d294b..b69df7b67 100644 --- a/src/modules/graphics/opengl/Shader.h +++ b/src/modules/graphics/opengl/Shader.h @@ -64,6 +64,9 @@ public: BUILTIN_NORMAL_MATRIX, BUILTIN_POINT_SIZE, BUILTIN_SCREEN_SIZE, + BUILTIN_VIDEO_Y_CHANNEL, + BUILTIN_VIDEO_CB_CHANNEL, + BUILTIN_VIDEO_CR_CHANNEL, BUILTIN_MAX_ENUM }; @@ -89,9 +92,11 @@ public: // Pointer to the default Shader. static Shader *defaultShader; + static Shader *defaultVideoShader; // Default shader code (a shader is always required internally.) static ShaderSource defaultCode[Graphics::RENDERER_MAX_ENUM]; + static ShaderSource defaultVideoCode[Graphics::RENDERER_MAX_ENUM]; /** * Creates a new Shader using a list of source codes. @@ -182,6 +187,7 @@ public: **/ bool hasVertexAttrib(VertexAttribID attrib) const; + void setVideoTextures(GLuint ytexture, GLuint cbtexture, GLuint crtexture); void checkSetScreenParams(); void checkSetPointSize(float size); void checkSetBuiltinUniforms(); @@ -263,6 +269,8 @@ private: Matrix4 lastTransformMatrix; Matrix4 lastProjectionMatrix; + GLuint videoTextureUnits[3]; + // Counts total number of textures bound to each texture unit in all shaders static std::vector textureCounters; diff --git a/src/modules/graphics/opengl/Video.cpp b/src/modules/graphics/opengl/Video.cpp new file mode 100644 index 000000000..4f33f4fb9 --- /dev/null +++ b/src/modules/graphics/opengl/Video.cpp @@ -0,0 +1,212 @@ +/** + * Copyright (c) 2006-2015 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +#include "Video.h" + +// LOVE +#include "Shader.h" + +namespace love +{ +namespace graphics +{ +namespace opengl +{ + +Video::Video(love::video::VideoStream *stream) + : stream(stream) + , filter(Texture::getDefaultFilter()) +{ + filter.mipmap = Texture::FILTER_NONE; + + stream->fillBackBuffer(); + + for (int i = 0; i < 4; i++) + vertices[i].r = vertices[i].g = vertices[i].b = vertices[i].a = 255; + + // Vertices are ordered for use with triangle strips: + // 0----2 + // | / | + // | / | + // 1----3 + vertices[0].x = 0.0f; + vertices[0].y = 0.0f; + vertices[1].x = 0.0f; + vertices[1].y = (float) stream->getHeight(); + vertices[2].x = (float) stream->getWidth(); + vertices[2].y = 0.0f; + vertices[3].x = (float) stream->getWidth(); + vertices[3].y = (float) stream->getHeight(); + + vertices[0].s = 0.0f; + vertices[0].t = 0.0f; + vertices[1].s = 0.0f; + vertices[1].t = 1.0f; + vertices[2].s = 1.0f; + vertices[2].t = 0.0f; + vertices[3].s = 1.0f; + vertices[3].t = 1.0f; + + loadVolatile(); +} + +Video::~Video() +{ + unloadVolatile(); +} + +bool Video::loadVolatile() +{ + glGenTextures(3, &textures[0]); + + // Create the textures using the initial frame data. + auto frame = (const love::video::VideoStream::Frame*) stream->getFrontBuffer(); + + gl.bindTexture(textures[0]); + gl.setTextureFilter(filter); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, frame->yw, frame->yh, + 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->yplane); + + gl.bindTexture(textures[1]); + gl.setTextureFilter(filter); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, frame->cw, frame->ch, + 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->cbplane); + + gl.bindTexture(textures[2]); + gl.setTextureFilter(filter); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, frame->cw, frame->ch, + 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->crplane); + + return true; +} + +void Video::unloadVolatile() +{ + for (int i = 0; i < 3; i++) + { + gl.deleteTexture(textures[i]); + textures[i] = 0; + } +} + +love::video::VideoStream *Video::getStream() +{ + return stream; +} + +void Video::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) +{ + update(); + + Shader *shader = Shader::current; + bool defaultShader = (shader == Shader::defaultShader); + if (defaultShader) + { + // If we're still using the default shader, substitute the video version + Shader::defaultVideoShader->attach(); + shader = Shader::defaultVideoShader; + } + + shader->setVideoTextures(textures[0], textures[1], textures[2]); + + OpenGL::TempTransform transform(gl); + transform.get() *= Matrix4(x, y, angle, sx, sy, ox, oy, kx, ky); + + gl.useVertexAttribArrays(ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD); + + glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), &vertices[0].x); + glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), &vertices[0].s); + + gl.prepareDraw(); + gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4); + + // If we were using the default shader, reattach it + if (defaultShader) + Shader::defaultShader->attach(); +} + +void Video::update() +{ + bool bufferschanged = stream->swapBuffers(); + stream->fillBackBuffer(); + + if (bufferschanged) + { + auto frame = (const love::video::VideoStream::Frame*) stream->getFrontBuffer(); + + gl.bindTexture(textures[0]); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, frame->yw, frame->yh, + GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->yplane); + + gl.bindTexture(textures[1]); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, frame->cw, frame->ch, + GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->cbplane); + + gl.bindTexture(textures[2]); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, frame->cw, frame->ch, + GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->crplane); + } +} + +love::audio::Source *Video::getSource() +{ + return source; +} + +void Video::setSource(love::audio::Source *source) +{ + this->source = source; +} + +int Video::getWidth() const +{ + return stream->getWidth(); +} + +int Video::getHeight() const +{ + return stream->getHeight(); +} + +void Video::setFilter(const Texture::Filter &f) +{ + if (!Texture::validateFilter(f, false)) + throw love::Exception("Invalid texture filter."); + + filter = f; + + for (int i = 0; i < 3; i++) + { + gl.bindTexture(textures[i]); + gl.setTextureFilter(filter); + } +} + +const Texture::Filter &Video::getFilter() const +{ + return filter; +} + +} // opengl +} // graphics +} // love diff --git a/src/modules/graphics/opengl/Video.h b/src/modules/graphics/opengl/Video.h new file mode 100644 index 000000000..f1704a48d --- /dev/null +++ b/src/modules/graphics/opengl/Video.h @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2006-2015 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +#pragma once + +// LOVE +#include "common/math.h" +#include "graphics/Drawable.h" +#include "graphics/Volatile.h" +#include "video/VideoStream.h" +#include "audio/Source.h" + +#include "OpenGL.h" + +namespace love +{ +namespace graphics +{ +namespace opengl +{ + +class Video : public Drawable, public Volatile +{ +public: + + Video(love::video::VideoStream *stream); + ~Video(); + + // Volatile + bool loadVolatile(); + void unloadVolatile(); + + love::video::VideoStream *getStream(); + void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky); + + love::audio::Source *getSource(); + void setSource(love::audio::Source *source); + + int getWidth() const; + int getHeight() const; + + void setFilter(const Texture::Filter &f); + const Texture::Filter &getFilter() const; + +private: + + void update(); + + StrongRef stream; + StrongRef source; + + GLuint textures[3]; + + Vertex vertices[4]; + + Texture::Filter filter; + +}; // Video + +} // opengl +} // graphics +} // love diff --git a/src/modules/graphics/opengl/wrap_Graphics.cpp b/src/modules/graphics/opengl/wrap_Graphics.cpp index 3b7d331f6..9d98032bb 100644 --- a/src/modules/graphics/opengl/wrap_Graphics.cpp +++ b/src/modules/graphics/opengl/wrap_Graphics.cpp @@ -25,6 +25,7 @@ #include "image/Image.h" #include "font/Rasterizer.h" #include "filesystem/wrap_Filesystem.h" +#include "video/VideoStream.h" #include "image/wrap_Image.h" #include @@ -862,6 +863,20 @@ int w_newText(lua_State *L) return 1; } +int w_newVideo(lua_State *L) +{ + if (!luax_istype(L, 1, VIDEO_VIDEO_STREAM_ID)) + luax_convobj(L, 1, "video", "newVideoStream"); + + auto stream = luax_checktype(L, 1, VIDEO_VIDEO_STREAM_ID); + Video *video = nullptr; + + luax_catchexcept(L, [&]() { video = instance()->newVideo(stream); }); + luax_pushtype(L, GRAPHICS_VIDEO_ID, video); + video->release(); + return 1; +} + int w_setColor(lua_State *L) { Colorf c; @@ -1274,25 +1289,37 @@ int w_setDefaultShaderCode(lua_State *L) lua_getfield(L, 1, "opengl"); lua_rawgeti(L, -1, 1); lua_rawgeti(L, -2, 2); + lua_rawgeti(L, -3, 3); Shader::ShaderSource openglcode; - openglcode.vertex = luax_checkstring(L, -2); - openglcode.pixel = luax_checkstring(L, -1); + openglcode.vertex = luax_checkstring(L, -3); + openglcode.pixel = luax_checkstring(L, -2); - lua_pop(L, 3); + Shader::ShaderSource openglVideocode; + openglVideocode.vertex = luax_checkstring(L, -3); + openglVideocode.pixel = luax_checkstring(L, -1); + + lua_pop(L, 4); lua_getfield(L, 1, "opengles"); lua_rawgeti(L, -1, 1); lua_rawgeti(L, -2, 2); + lua_rawgeti(L, -3, 3); Shader::ShaderSource openglescode; - openglescode.vertex = luax_checkstring(L, -2); - openglescode.pixel = luax_checkstring(L, -1); + openglescode.vertex = luax_checkstring(L, -3); + openglescode.pixel = luax_checkstring(L, -2); - lua_pop(L, 3); + Shader::ShaderSource openglesVideocode; + openglesVideocode.vertex = luax_checkstring(L, -3); + openglesVideocode.pixel = luax_checkstring(L, -1); + + lua_pop(L, 4); Shader::defaultCode[Graphics::RENDERER_OPENGL] = openglcode; Shader::defaultCode[Graphics::RENDERER_OPENGLES] = openglescode; + Shader::defaultVideoCode[Graphics::RENDERER_OPENGL] = openglVideocode; + Shader::defaultVideoCode[Graphics::RENDERER_OPENGLES] = openglesVideocode; return 0; } @@ -1863,6 +1890,7 @@ static const luaL_Reg functions[] = { "newShader", w_newShader }, { "newMesh", w_newMesh }, { "newText", w_newText }, + { "_newVideo", w_newVideo }, { "setColor", w_setColor }, { "getColor", w_getColor }, @@ -1965,6 +1993,7 @@ static const lua_CFunction types[] = luaopen_shader, luaopen_mesh, luaopen_text, + luaopen_video, 0 }; diff --git a/src/modules/graphics/opengl/wrap_Graphics.h b/src/modules/graphics/opengl/wrap_Graphics.h index a725c6316..5a7b7b78e 100644 --- a/src/modules/graphics/opengl/wrap_Graphics.h +++ b/src/modules/graphics/opengl/wrap_Graphics.h @@ -32,6 +32,7 @@ #include "wrap_Shader.h" #include "wrap_Mesh.h" #include "wrap_Text.h" +#include "wrap_Video.h" #include "Graphics.h" namespace love diff --git a/src/modules/graphics/opengl/wrap_Graphics.lua b/src/modules/graphics/opengl/wrap_Graphics.lua index 07b30ba10..823779cc4 100644 --- a/src/modules/graphics/opengl/wrap_Graphics.lua +++ b/src/modules/graphics/opengl/wrap_Graphics.lua @@ -165,6 +165,28 @@ varying mediump vec4 VaryingColor; uniform sampler2D _tex0_;]], + FUNCTIONS = [[ +uniform sampler2D love_VideoYChannel; +uniform sampler2D love_VideoCbChannel; +uniform sampler2D love_VideoCrChannel; + +vec4 VideoTexel(vec2 texcoords) +{ + vec3 yuv; + yuv[0] = Texel(love_VideoYChannel, texcoords).r; + yuv[1] = Texel(love_VideoCbChannel, texcoords).r; + yuv[2] = Texel(love_VideoCrChannel, texcoords).r; + yuv += vec3(-0.0627451017, -0.501960814, -0.501960814); + + vec4 color; + color.r = dot(yuv, vec3(1.164, 0.000, 1.596)); + color.g = dot(yuv, vec3(1.164, -0.391, -0.813)); + color.b = dot(yuv, vec3(1.164, 2.018, 0.000)); + color.a = 1.0; + + return gammaCorrectColor(color); +}]], + FOOTER = [[ void main() { // fix crashing issue in OSX when _tex0_ is unused within effect() @@ -209,6 +231,7 @@ local function createPixelCode(pixelcode, is_multicanvas, lang) love.graphics.isGammaCorrect() and "#define LOVE_GAMMA_CORRECT 1" or "", GLSL.PIXEL.HEADER, GLSL.UNIFORMS, GLSL.FUNCTIONS, + GLSL.PIXEL.FUNCTIONS, lang == "glsles" and "#line 1" or "#line 0", pixelcode, is_multicanvas and GLSL.PIXEL.FOOTER_MULTI_CANVAS or GLSL.PIXEL.FOOTER, @@ -309,21 +332,45 @@ vec4 position(mat4 transform_proj, vec4 vertpos) { pixel = [[ vec4 effect(mediump vec4 vcolor, Image tex, vec2 texcoord, vec2 pixcoord) { return Texel(tex, texcoord) * vcolor; -}]] +}]], + videopixel = [[ +vec4 effect(mediump vec4 vcolor, Image tex, vec2 texcoord, vec2 pixcoord) { + return VideoTexel(texcoord) * vcolor; +}]], } local defaults = { opengl = { createVertexCode(defaultcode.vertex, "glsl"), createPixelCode(defaultcode.pixel, false, "glsl"), + createPixelCode(defaultcode.videopixel, false, "glsl"), }, opengles = { createVertexCode(defaultcode.vertex, "glsles"), createPixelCode(defaultcode.pixel, false, "glsles"), + createPixelCode(defaultcode.videopixel, false, "glsles"), }, } love.graphics._setDefaultShaderCode(defaults) +function love.graphics.newVideo(file, loadaudio) + local video = love.graphics._newVideo(file) + local source, success + + if loadaudio ~= false then + success, source = pcall(love.audio.newSource, video:getStream():getFilename()) + end + if success then + video:setSource(source) + elseif loadaudio == true then + error("Video had no audio track", 2) + else + video:getStream():setSync(love.video.newRemote()) + end + + return video +end + -- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string. --)luastring"--" diff --git a/src/modules/graphics/opengl/wrap_Video.cpp b/src/modules/graphics/opengl/wrap_Video.cpp new file mode 100644 index 000000000..70c01751c --- /dev/null +++ b/src/modules/graphics/opengl/wrap_Video.cpp @@ -0,0 +1,157 @@ +/** + * Copyright (c) 2006-2015 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +#include "wrap_Video.h" + +// Shove the wrap_Video.lua code directly into a raw string literal. +static const char video_lua[] = +#include "wrap_Video.lua" +; + +namespace love +{ +namespace graphics +{ +namespace opengl +{ + +Video *luax_checkvideo(lua_State *L, int idx) +{ + return luax_checktype