From 8529b2f925c2a0f0cfa3ffe9218519b55223107e Mon Sep 17 00:00:00 2001 From: Benoit Bolsee Date: Thu, 9 Jun 2016 21:07:58 +0200 Subject: BGE: alpha on frame buffer and precedence of MSAA over swap. A new option '-a' can be passed to the blenderplayer. It forces the framebuffer to have an alpha channel. This can be used in VideoTexture to return a image with alpha channel with ImageViewport (provided alpha is set to True on the ImageViewport object and that the background color alpha channel is 0, which is the default). Without the -a option, the frame buffer has no alpha channel and ImageViewport always returns an opaque image, no matter what. In Linux, the player window will be rendered transparently over the desktop. In Windows, the player window is still rendered opaque because transparency of the window is only possible using the 'compositing' functions of Windows. The code is there but not enabled (look for WIN32_COMPOSITING) because 1) it doesn't work so well 2) it requires a DLL that is only available on Vista and up. give precedence to AA over Swap copy: Certain GPU (intel) will not allow MSAA together with swap copy. Previously, swap copy had priority over MSAA: fewer AA samples would be chosen if it was the condition to get swap copy. This patch reverse the logic: swap copy will be abandonned if another swap method (undefined or exchange) will provide the number of AA samples requested. If no AA samples is requested, swap copy still has the priority of course. --- source/gameengine/GamePlayer/ghost/GPG_Application.cpp | 11 +++++++++-- source/gameengine/GamePlayer/ghost/GPG_Application.h | 6 +++--- source/gameengine/GamePlayer/ghost/GPG_ghost.cpp | 11 +++++++++-- 3 files changed, 21 insertions(+), 7 deletions(-) (limited to 'source/gameengine') diff --git a/source/gameengine/GamePlayer/ghost/GPG_Application.cpp b/source/gameengine/GamePlayer/ghost/GPG_Application.cpp index f0a7bd47ca3..19eef82439e 100644 --- a/source/gameengine/GamePlayer/ghost/GPG_Application.cpp +++ b/source/gameengine/GamePlayer/ghost/GPG_Application.cpp @@ -301,7 +301,7 @@ bool GPG_Application::startScreenSaverFullScreen( const int stereoMode, const GHOST_TUns16 samples) { - bool ret = startFullScreen(width, height, bpp, frequency, stereoVisual, stereoMode, samples); + bool ret = startFullScreen(width, height, bpp, frequency, stereoVisual, stereoMode, 0, samples); if (ret) { HWND ghost_hwnd = findGhostWindowHWND(m_mainWindow); @@ -325,6 +325,7 @@ bool GPG_Application::startWindow( int windowHeight, const bool stereoVisual, const int stereoMode, + const int alphaBackground, const GHOST_TUns16 samples) { GHOST_GLSettings glSettings = {0}; @@ -333,6 +334,8 @@ bool GPG_Application::startWindow( //STR_String title ("Blender Player - GHOST"); if (stereoVisual) glSettings.flags |= GHOST_glStereoVisual; + if (alphaBackground) + glSettings.flags |= GHOST_glAlphaBackground; glSettings.numOfAASamples = samples; m_mainWindow = fSystem->createWindow(title, windowLeft, windowTop, windowWidth, windowHeight, GHOST_kWindowStateNormal, @@ -360,6 +363,7 @@ bool GPG_Application::startEmbeddedWindow( const GHOST_TEmbedderWindowID parentWindow, const bool stereoVisual, const int stereoMode, + const int alphaBackground, const GHOST_TUns16 samples) { GHOST_TWindowState state = GHOST_kWindowStateNormal; @@ -367,6 +371,8 @@ bool GPG_Application::startEmbeddedWindow( if (stereoVisual) glSettings.flags |= GHOST_glStereoVisual; + if (alphaBackground) + glSettings.flags |= GHOST_glAlphaBackground; glSettings.numOfAASamples = samples; if (parentWindow != 0) @@ -394,6 +400,7 @@ bool GPG_Application::startFullScreen( int bpp,int frequency, const bool stereoVisual, const int stereoMode, + const int alphaBackground, const GHOST_TUns16 samples, bool useDesktop) { @@ -407,7 +414,7 @@ bool GPG_Application::startFullScreen( setting.bpp = bpp; setting.frequency = frequency; - fSystem->beginFullScreen(setting, &m_mainWindow, stereoVisual, samples); + fSystem->beginFullScreen(setting, &m_mainWindow, stereoVisual, alphaBackground, samples); m_mainWindow->setCursorVisibility(false); /* note that X11 ignores this (it uses a window internally for fullscreen) */ m_mainWindow->setState(GHOST_kWindowStateFullScreen); diff --git a/source/gameengine/GamePlayer/ghost/GPG_Application.h b/source/gameengine/GamePlayer/ghost/GPG_Application.h index b6f545c2de8..e757cc10e32 100644 --- a/source/gameengine/GamePlayer/ghost/GPG_Application.h +++ b/source/gameengine/GamePlayer/ghost/GPG_Application.h @@ -65,13 +65,13 @@ public: bool startWindow(STR_String& title, int windowLeft, int windowTop, int windowWidth, int windowHeight, - const bool stereoVisual, const int stereoMode, const GHOST_TUns16 samples=0); + const bool stereoVisual, const int stereoMode, const int alphaBackground=0, const GHOST_TUns16 samples=0); bool startFullScreen(int width, int height, int bpp, int frequency, - const bool stereoVisual, const int stereoMode, + const bool stereoVisual, const int stereoMode, const int alphaBackground = 0, const GHOST_TUns16 samples=0, bool useDesktop=false); bool startEmbeddedWindow(STR_String& title, const GHOST_TEmbedderWindowID parent_window, - const bool stereoVisual, const int stereoMode, const GHOST_TUns16 samples=0); + const bool stereoVisual, const int stereoMode, const int alphaBackground=0, const GHOST_TUns16 samples=0); #ifdef WIN32 bool startScreenSaverFullScreen(int width, int height, int bpp, int frequency, diff --git a/source/gameengine/GamePlayer/ghost/GPG_ghost.cpp b/source/gameengine/GamePlayer/ghost/GPG_ghost.cpp index ac2e4dfa563..30ad5b37777 100644 --- a/source/gameengine/GamePlayer/ghost/GPG_ghost.cpp +++ b/source/gameengine/GamePlayer/ghost/GPG_ghost.cpp @@ -444,6 +444,7 @@ int main( int validArguments=0; bool samplesParFound = false; GHOST_TUns16 aasamples = 0; + int alphaBackground = 0; #ifdef WIN32 char **argv; @@ -838,6 +839,12 @@ int main( } break; } + case 'a': // allow window to blend with display background + { + i++; + alphaBackground = 1; + break; + } default: //not recognized { printf("Unknown argument: %s\n", argv[i++]); @@ -1041,7 +1048,7 @@ int main( #endif { app.startFullScreen(fullScreenWidth, fullScreenHeight, fullScreenBpp, fullScreenFrequency, - stereoWindow, stereomode, aasamples, (scene->gm.playerflag & GAME_PLAYER_DESKTOP_RESOLUTION)); + stereoWindow, stereomode, alphaBackground, aasamples, (scene->gm.playerflag & GAME_PLAYER_DESKTOP_RESOLUTION)); } } else @@ -1088,7 +1095,7 @@ int main( app.startEmbeddedWindow(title, parentWindow, stereoWindow, stereomode, aasamples); else app.startWindow(title, windowLeft, windowTop, windowWidth, windowHeight, - stereoWindow, stereomode, aasamples); + stereoWindow, stereomode, alphaBackground, aasamples); if (SYS_GetCommandLineInt(syshandle, "nomipmap", 0)) { GPU_set_mipmap(0); -- cgit v1.2.3 From 40f1c4f34337d7dfb3fa5bcbd2daa2f602e12011 Mon Sep 17 00:00:00 2001 From: Benoit Bolsee Date: Thu, 9 Jun 2016 23:56:45 +0200 Subject: BGE: Various render improvements. bge.logic.setRender(flag) to enable/disable render. The render pass is enabled by default but it can be disabled with bge.logic.setRender(False). Once disabled, the render pass is skipped and a new logic frame starts immediately. Note that VSync no longer limits the fps when render is off but the 'Use Frame Rate' option in the Render Properties still does. To run as many frames as possible, untick the option This function is useful when you don't need the default render, e.g. when doing offscreen render to an alternate device than the monitor. Note that without VSync, you must limit the frame rate by other means. fbo = bge.render.offScreenCreate(width,height,[,samples=0][,target=bge.render.RAS_OFS_RENDER_BUFFER]) Use this method to create an offscreen buffer of given size, with given MSAA samples and targetting either a render buffer (bge.render.RAS_OFS_RENDER_BUFFER) or a texture (bge.render.RAS_OFS_RENDER_TEXTURE). Use the former if you want to retrieve the frame buffer on the host and the latter if you want to pass the render to another context (texture are proper OGL object, render buffers aren't) The object created by this function can only be used as a parameter of the bge.texture.ImageRender() constructor to send the the render to the FBO rather than to the frame buffer. This is best suited when you want to create a render of specific size, or if you need an image with an alpha channel. bge.texture..refresh(buffer=None, format="RGBA", ts=-1.0) Without arg, the refresh method of the image objects is pretty much a no-op, it simply invalidates the image so that on next texture refresh, the image will be recalculated. It is now possible to pass an optional buffer object to transfer the image (and recalculate it if it was invalid) to an external object. The object must implement the 'buffer protocol'. The image will be transfered as "RGBA" or "BGRA" pixels depending on format argument (only those 2 formats are supported) and ts is an optional timestamp in the image depends on it (e.g. VideoFFmpeg playing a video file). With this function you don't need anymore to link the image object to a Texture object to use: the image object is self-sufficient. bge.texture.ImageRender(scene, camera, fbo=None) Render to buffer is possible by passing a FBO object (see offScreenCreate). bge.texture.ImageRender.render() Allows asynchronous render: call this method to render the scene but without extracting the pixels yet. The function returns as soon as the render commands have been send to the GPU. The render will proceed asynchronously in the GPU while the host can perform other tasks. To complete the render, you can either call refresh() directly of refresh the texture to which this object is the source. Asynchronous render is useful to achieve optimal performance: call render() on frame N and refresh() on frame N+1 to give as much as time as possible to the GPU to render the frame while the game engine can perform other tasks. Support negative scale on camera. Camera scale was previously ignored in the BGE. It is now injected in the modelview matrix as a vertical or horizontal flip of the scene (respectively if scaleY<0 and scaleX<0). Note that the actual value of the scale is not used, only the sign. This allows to flip the image produced by ImageRender() without any performance degradation: the flip is integrated in the render itself. Optimized image transfer from ImageRender to buffer. Previously, images that were transferred to the host were always going through buffers in VideoTexture. It is now possible to transfer ImageRender images to external buffer without intermediate copy (i.e. directly from OGL to buffer) if the attributes of the ImageRender objects are set as follow: flip=False, alpha=True, scale=False, depth=False, zbuff=False. (if you need to flip the image, use camera negative scale) --- .../BlenderRoutines/BL_KetsjiEmbedStart.cpp | 1 + .../GamePlayer/ghost/GPG_Application.cpp | 1 + source/gameengine/Ketsji/KX_Dome.cpp | 4 +- source/gameengine/Ketsji/KX_KetsjiEngine.cpp | 32 +- source/gameengine/Ketsji/KX_KetsjiEngine.h | 20 +- source/gameengine/Ketsji/KX_PythonInit.cpp | 178 +++++++++++ source/gameengine/Ketsji/KX_Scene.cpp | 1 + source/gameengine/Ketsji/KX_Scene.h | 7 + source/gameengine/Rasterizer/CMakeLists.txt | 2 + source/gameengine/Rasterizer/RAS_IOffScreen.h | 84 +++++ source/gameengine/Rasterizer/RAS_IRasterizer.h | 16 +- source/gameengine/Rasterizer/RAS_ISync.h | 48 +++ .../Rasterizer/RAS_OpenGLRasterizer/CMakeLists.txt | 4 + .../RAS_OpenGLRasterizer/RAS_OpenGLLight.cpp | 2 +- .../RAS_OpenGLRasterizer/RAS_OpenGLOffScreen.cpp | 347 +++++++++++++++++++++ .../RAS_OpenGLRasterizer/RAS_OpenGLOffScreen.h | 65 ++++ .../RAS_OpenGLRasterizer/RAS_OpenGLRasterizer.cpp | 41 ++- .../RAS_OpenGLRasterizer/RAS_OpenGLRasterizer.h | 11 +- .../RAS_OpenGLRasterizer/RAS_OpenGLSync.cpp | 82 +++++ .../RAS_OpenGLRasterizer/RAS_OpenGLSync.h | 50 +++ source/gameengine/VideoTexture/Exception.cpp | 15 + source/gameengine/VideoTexture/Exception.h | 18 +- source/gameengine/VideoTexture/FilterBase.h | 7 + source/gameengine/VideoTexture/FilterSource.h | 24 ++ source/gameengine/VideoTexture/ImageBase.cpp | 99 +++++- source/gameengine/VideoTexture/ImageBase.h | 10 +- source/gameengine/VideoTexture/ImageMix.cpp | 2 +- source/gameengine/VideoTexture/ImageRender.cpp | 200 ++++++++++-- source/gameengine/VideoTexture/ImageRender.h | 28 +- source/gameengine/VideoTexture/ImageViewport.cpp | 99 +++++- source/gameengine/VideoTexture/ImageViewport.h | 11 +- source/gameengine/VideoTexture/Texture.cpp | 7 +- source/gameengine/VideoTexture/VideoBase.cpp | 47 ++- source/gameengine/VideoTexture/VideoBase.h | 2 +- source/gameengine/VideoTexture/VideoFFmpeg.cpp | 4 +- 35 files changed, 1474 insertions(+), 95 deletions(-) create mode 100644 source/gameengine/Rasterizer/RAS_IOffScreen.h create mode 100644 source/gameengine/Rasterizer/RAS_ISync.h create mode 100644 source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLOffScreen.cpp create mode 100644 source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLOffScreen.h create mode 100644 source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLSync.cpp create mode 100644 source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLSync.h (limited to 'source/gameengine') diff --git a/source/gameengine/BlenderRoutines/BL_KetsjiEmbedStart.cpp b/source/gameengine/BlenderRoutines/BL_KetsjiEmbedStart.cpp index c5fc55a8e68..91683f4d6e7 100644 --- a/source/gameengine/BlenderRoutines/BL_KetsjiEmbedStart.cpp +++ b/source/gameengine/BlenderRoutines/BL_KetsjiEmbedStart.cpp @@ -341,6 +341,7 @@ extern "C" void StartKetsjiShell(struct bContext *C, struct ARegion *ar, rcti *c ketsjiengine->SetUseFixedTime(usefixed); ketsjiengine->SetTimingDisplay(frameRate, profile, properties); ketsjiengine->SetRestrictAnimationFPS(restrictAnimFPS); + ketsjiengine->SetRender(true); KX_KetsjiEngine::SetExitKey(ConvertKeyCode(startscene->gm.exitkey)); //set the global settings (carried over if restart/load new files) diff --git a/source/gameengine/GamePlayer/ghost/GPG_Application.cpp b/source/gameengine/GamePlayer/ghost/GPG_Application.cpp index 19eef82439e..1b52c61b816 100644 --- a/source/gameengine/GamePlayer/ghost/GPG_Application.cpp +++ b/source/gameengine/GamePlayer/ghost/GPG_Application.cpp @@ -678,6 +678,7 @@ bool GPG_Application::initEngine(GHOST_IWindow* window, const int stereoMode) //set the global settings (carried over if restart/load new files) m_ketsjiengine->SetGlobalSettings(m_globalSettings); + m_ketsjiengine->SetRender(true); m_engineInitialized = true; } diff --git a/source/gameengine/Ketsji/KX_Dome.cpp b/source/gameengine/Ketsji/KX_Dome.cpp index 6585b9dc831..d08372e47d4 100644 --- a/source/gameengine/Ketsji/KX_Dome.cpp +++ b/source/gameengine/Ketsji/KX_Dome.cpp @@ -1600,7 +1600,7 @@ void KX_Dome::RotateCamera(KX_Camera* cam, int i) MT_Transform camtrans(cam->GetWorldToCamera()); MT_Matrix4x4 viewmat(camtrans); - m_rasterizer->SetViewMatrix(viewmat, cam->NodeGetWorldOrientation(), cam->NodeGetWorldPosition(), cam->GetCameraData()->m_perspective); + m_rasterizer->SetViewMatrix(viewmat, cam->NodeGetWorldOrientation(), cam->NodeGetWorldPosition(), cam->NodeGetLocalScaling(), cam->GetCameraData()->m_perspective); cam->SetModelviewMatrix(viewmat); // restore the original orientation @@ -2035,7 +2035,7 @@ void KX_Dome::RenderDomeFrame(KX_Scene* scene, KX_Camera* cam, int i) MT_Transform camtrans(cam->GetWorldToCamera()); MT_Matrix4x4 viewmat(camtrans); - m_rasterizer->SetViewMatrix(viewmat, cam->NodeGetWorldOrientation(), cam->NodeGetWorldPosition(), 1.0f); + m_rasterizer->SetViewMatrix(viewmat, cam->NodeGetWorldOrientation(), cam->NodeGetWorldPosition(), cam->NodeGetLocalScaling(), 1.0f); cam->SetModelviewMatrix(viewmat); // restore the original orientation diff --git a/source/gameengine/Ketsji/KX_KetsjiEngine.cpp b/source/gameengine/Ketsji/KX_KetsjiEngine.cpp index 7237c473332..b0a8e376eb6 100644 --- a/source/gameengine/Ketsji/KX_KetsjiEngine.cpp +++ b/source/gameengine/Ketsji/KX_KetsjiEngine.cpp @@ -108,7 +108,7 @@ double KX_KetsjiEngine::m_suspendeddelta = 0.0; double KX_KetsjiEngine::m_average_framerate = 0.0; bool KX_KetsjiEngine::m_restrict_anim_fps = false; short KX_KetsjiEngine::m_exitkey = 130; // ESC Key - +bool KX_KetsjiEngine::m_doRender = true; /** * Constructor of the Ketsji Engine @@ -173,6 +173,7 @@ KX_KetsjiEngine::KX_KetsjiEngine(KX_ISystem* system) m_overrideFrameColorR(0.0f), m_overrideFrameColorG(0.0f), m_overrideFrameColorB(0.0f), + m_overrideFrameColorA(0.0f), m_usedome(false) { @@ -381,7 +382,7 @@ void KX_KetsjiEngine::RenderDome() m_overrideFrameColorR, m_overrideFrameColorG, m_overrideFrameColorB, - 1.0 + m_overrideFrameColorA ); } else @@ -749,6 +750,9 @@ bool KX_KetsjiEngine::NextFrame() scene->setSuspendedTime(m_clockTime); m_logger->StartLog(tc_services, m_kxsystem->GetTimeInSeconds(), true); + + // invalidates the shadow buffer from previous render/ImageRender because the scene has changed + scene->SetShadowDone(false); } // update system devices @@ -771,7 +775,7 @@ bool KX_KetsjiEngine::NextFrame() // Start logging time spent outside main loop m_logger->StartLog(tc_outside, m_kxsystem->GetTimeInSeconds(), true); - return doRender; + return doRender && m_doRender; } @@ -805,7 +809,7 @@ void KX_KetsjiEngine::Render() m_overrideFrameColorR, m_overrideFrameColorG, m_overrideFrameColorB, - 1.0 + m_overrideFrameColorA ); } else @@ -1133,6 +1137,8 @@ void KX_KetsjiEngine::RenderShadowBuffers(KX_Scene *scene) cam->Release(); } } + /* remember that we have a valid shadow buffer for that scene */ + scene->SetShadowDone(true); } // update graphics @@ -1252,7 +1258,7 @@ void KX_KetsjiEngine::RenderFrame(KX_Scene* scene, KX_Camera* cam) MT_Transform camtrans(cam->GetWorldToCamera()); MT_Matrix4x4 viewmat(camtrans); - m_rasterizer->SetViewMatrix(viewmat, cam->NodeGetWorldOrientation(), cam->NodeGetWorldPosition(), cam->GetCameraData()->m_perspective); + m_rasterizer->SetViewMatrix(viewmat, cam->NodeGetWorldOrientation(), cam->NodeGetWorldPosition(), cam->NodeGetLocalScaling(), cam->GetCameraData()->m_perspective); cam->SetModelviewMatrix(viewmat); // The following actually reschedules all vertices to be @@ -1925,6 +1931,16 @@ short KX_KetsjiEngine::GetExitKey() return m_exitkey; } +void KX_KetsjiEngine::SetRender(bool render) +{ + m_doRender = render; +} + +bool KX_KetsjiEngine::GetRender() +{ + return m_doRender; +} + void KX_KetsjiEngine::SetShowFramerate(bool frameRate) { m_show_framerate = frameRate; @@ -2023,19 +2039,21 @@ bool KX_KetsjiEngine::GetUseOverrideFrameColor(void) const } -void KX_KetsjiEngine::SetOverrideFrameColor(float r, float g, float b) +void KX_KetsjiEngine::SetOverrideFrameColor(float r, float g, float b, float a) { m_overrideFrameColorR = r; m_overrideFrameColorG = g; m_overrideFrameColorB = b; + m_overrideFrameColorA = a; } -void KX_KetsjiEngine::GetOverrideFrameColor(float& r, float& g, float& b) const +void KX_KetsjiEngine::GetOverrideFrameColor(float& r, float& g, float& b, float& a) const { r = m_overrideFrameColorR; g = m_overrideFrameColorG; b = m_overrideFrameColorB; + a = m_overrideFrameColorA; } diff --git a/source/gameengine/Ketsji/KX_KetsjiEngine.h b/source/gameengine/Ketsji/KX_KetsjiEngine.h index 3b8cec2ac82..1756214b6dd 100644 --- a/source/gameengine/Ketsji/KX_KetsjiEngine.h +++ b/source/gameengine/Ketsji/KX_KetsjiEngine.h @@ -129,6 +129,8 @@ private: static short m_exitkey; /* Key used to exit the BGE */ + static bool m_doRender; /* whether or not the scene should be rendered after the logic frame */ + int m_exitcode; STR_String m_exitstring; @@ -199,6 +201,8 @@ private: float m_overrideFrameColorG; /** Blue component of framing bar color. */ float m_overrideFrameColorB; + /** alpha component of framing bar color. */ + float m_overrideFrameColorA; /** Settings that doesn't go away with Game Actuator */ GlobalSettings m_globalsettings; @@ -209,7 +213,6 @@ private: void RenderFrame(KX_Scene* scene, KX_Camera* cam); void PostRenderScene(KX_Scene* scene); void RenderDebugProperties(); - void RenderShadowBuffers(KX_Scene *scene); public: KX_KetsjiEngine(class KX_ISystem* system); @@ -249,6 +252,7 @@ public: ///returns true if an update happened to indicate -> Render bool NextFrame(); void Render(); + void RenderShadowBuffers(KX_Scene *scene); void StartEngine(bool clearIpo); void StopEngine(); @@ -400,6 +404,16 @@ public: static short GetExitKey(); + /** + * Activate or deactivates the render of the scene after the logic frame + * \param render true (render) or false (do not render) + */ + static void SetRender(bool render); + /** + * Get the current render flag value + */ + static bool GetRender(); + /** * \Sets the display for frame rate on or off. */ @@ -485,7 +499,7 @@ public: * \param g Green component of the override color. * \param b Blue component of the override color. */ - void SetOverrideFrameColor(float r, float g, float b); + void SetOverrideFrameColor(float r, float g, float b, float a); /** * Returns the color used for framing bar color instead of the one in the Blender file's scenes. @@ -493,7 +507,7 @@ public: * \param g Green component of the override color. * \param b Blue component of the override color. */ - void GetOverrideFrameColor(float& r, float& g, float& b) const; + void GetOverrideFrameColor(float& r, float& g, float& b, float& a) const; KX_Scene* CreateScene(const STR_String& scenename); KX_Scene* CreateScene(Scene *scene, bool libloading=false); diff --git a/source/gameengine/Ketsji/KX_PythonInit.cpp b/source/gameengine/Ketsji/KX_PythonInit.cpp index 9f173a567ee..cdc2f9f3644 100644 --- a/source/gameengine/Ketsji/KX_PythonInit.cpp +++ b/source/gameengine/Ketsji/KX_PythonInit.cpp @@ -104,6 +104,7 @@ extern "C" { #include "BL_ArmatureObject.h" #include "RAS_IRasterizer.h" #include "RAS_ICanvas.h" +#include "RAS_IOffScreen.h" #include "RAS_BucketManager.h" #include "RAS_2DFilterManager.h" #include "MT_Vector3.h" @@ -469,6 +470,21 @@ static PyObject *gPyGetExitKey(PyObject *) return PyLong_FromLong(KX_KetsjiEngine::GetExitKey()); } +static PyObject *gPySetRender(PyObject *, PyObject *args) +{ + int render; + if (!PyArg_ParseTuple(args, "i:setRender", &render)) + return NULL; + KX_KetsjiEngine::SetRender(render); + Py_RETURN_NONE; +} + +static PyObject *gPyGetRender(PyObject *) +{ + return PyBool_FromLong(KX_KetsjiEngine::GetRender()); +} + + static PyObject *gPySetMaxLogicFrame(PyObject *, PyObject *args) { int frame; @@ -909,6 +925,8 @@ static struct PyMethodDef game_methods[] = { {"setAnimRecordFrame", (PyCFunction) gPySetAnimRecordFrame, METH_VARARGS, (const char *)"Sets the current frame number used for animation recording"}, {"getExitKey", (PyCFunction) gPyGetExitKey, METH_NOARGS, (const char *)"Gets the key used to exit the game engine"}, {"setExitKey", (PyCFunction) gPySetExitKey, METH_VARARGS, (const char *)"Sets the key used to exit the game engine"}, + {"setRender", (PyCFunction) gPySetRender, METH_VARARGS, (const char *)"Set the global render flag"}, + {"getRender", (PyCFunction) gPyGetRender, METH_NOARGS, (const char *)"get the global render flag value"}, {"getUseExternalClock", (PyCFunction) gPyGetUseExternalClock, METH_NOARGS, (const char *)"Get if we use the time provided by an external clock"}, {"setUseExternalClock", (PyCFunction) gPySetUseExternalClock, METH_VARARGS, (const char *)"Set if we use the time provided by an external clock"}, {"getClockTime", (PyCFunction) gPyGetClockTime, METH_NOARGS, (const char *)"Get the last BGE render time. " @@ -1457,6 +1475,158 @@ static PyObject *gPyGetDisplayDimensions(PyObject *) return result; } + +/* python wrapper around RAS_IOffScreen + * Should eventually gets its own file + */ + +static void PyRASOffScreen__tp_dealloc(PyRASOffScreen *self) +{ + if (self->ofs) + delete self->ofs; + Py_TYPE(self)->tp_free((PyObject *)self); +} + +PyDoc_STRVAR(py_RASOffScreen_doc, +"RASOffscreen(width, height) -> new GPU Offscreen object" +"initialized to hold a framebuffer object of ``width`` x ``height``.\n" +"" +); + +PyDoc_STRVAR(RASOffScreen_width_doc, "Offscreen buffer width.\n\n:type: integer"); +static PyObject *RASOffScreen_width_get(PyRASOffScreen *self, void *UNUSED(type)) +{ + return PyLong_FromLong(self->ofs->GetWidth()); +} + +PyDoc_STRVAR(RASOffScreen_height_doc, "Offscreen buffer height.\n\n:type: GLsizei"); +static PyObject *RASOffScreen_height_get(PyRASOffScreen *self, void *UNUSED(type)) +{ + return PyLong_FromLong(self->ofs->GetHeight()); +} + +PyDoc_STRVAR(RASOffScreen_color_doc, "Offscreen buffer texture object (if target is RAS_OFS_RENDER_TEXTURE).\n\n:type: GLuint"); +static PyObject *RASOffScreen_color_get(PyRASOffScreen *self, void *UNUSED(type)) +{ + return PyLong_FromLong(self->ofs->GetColor()); +} + +static PyGetSetDef RASOffScreen_getseters[] = { + {(char *)"width", (getter)RASOffScreen_width_get, (setter)NULL, RASOffScreen_width_doc, NULL}, + {(char *)"height", (getter)RASOffScreen_height_get, (setter)NULL, RASOffScreen_height_doc, NULL}, + {(char *)"color", (getter)RASOffScreen_color_get, (setter)NULL, RASOffScreen_color_doc, NULL}, + {NULL, NULL, NULL, NULL, NULL} /* Sentinel */ +}; + +static int PyRASOffScreen__tp_init(PyRASOffScreen *self, PyObject *args, PyObject *kwargs) +{ + int width, height, samples, target; + const char *keywords[] = {"width", "height", "samples", "target", NULL}; + + samples = 0; + target = RAS_IOffScreen::RAS_OFS_RENDER_BUFFER; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii|ii:RASOffscreen", (char **)keywords, &width, &height, &samples, &target)) { + return -1; + } + + if (width <= 0) { + PyErr_SetString(PyExc_ValueError, "negative 'width' given"); + return -1; + } + + if (height <= 0) { + PyErr_SetString(PyExc_ValueError, "negative 'height' given"); + return -1; + } + + if (samples < 0) { + PyErr_SetString(PyExc_ValueError, "negative 'samples' given"); + return -1; + } + + if (target != RAS_IOffScreen::RAS_OFS_RENDER_BUFFER && target != RAS_IOffScreen::RAS_OFS_RENDER_TEXTURE) + { + PyErr_SetString(PyExc_ValueError, "invalid 'target' given, can only be RAS_OFS_RENDER_BUFFER or RAS_OFS_RENDER_TEXTURE"); + return -1; + } + if (!gp_Rasterizer) + { + PyErr_SetString(PyExc_SystemError, "no rasterizer"); + return -1; + } + self->ofs = gp_Rasterizer->CreateOffScreen(width, height, samples, target); + if (!self->ofs) { + PyErr_SetString(PyExc_SystemError, "creation failed"); + return -1; + } + return 0; +} + +PyTypeObject PyRASOffScreen_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "RASOffScreen", /* tp_name */ + sizeof(PyRASOffScreen), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + (destructor)PyRASOffScreen__tp_dealloc, /* tp_dealloc */ + NULL, /* tp_print */ + NULL, /* tp_getattr */ + NULL, /* tp_setattr */ + NULL, /* tp_compare */ + NULL, /* tp_repr */ + NULL, /* tp_as_number */ + NULL, /* tp_as_sequence */ + NULL, /* tp_as_mapping */ + NULL, /* tp_hash */ + NULL, /* tp_call */ + NULL, /* tp_str */ + NULL, /* tp_getattro */ + NULL, /* tp_setattro */ + NULL, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + py_RASOffScreen_doc, /* Documentation string */ + NULL, /* tp_traverse */ + NULL, /* tp_clear */ + NULL, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + NULL, /* tp_iter */ + NULL, /* tp_iternext */ + NULL, /* tp_methods */ + NULL, /* tp_members */ + RASOffScreen_getseters, /* tp_getset */ + NULL, /* tp_base */ + NULL, /* tp_dict */ + NULL, /* tp_descr_get */ + NULL, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)PyRASOffScreen__tp_init, /* tp_init */ + (allocfunc)PyType_GenericAlloc, /* tp_alloc */ + (newfunc)PyType_GenericNew, /* tp_new */ + (freefunc)0, /* tp_free */ + NULL, /* tp_is_gc */ + NULL, /* tp_bases */ + NULL, /* tp_mro */ + NULL, /* tp_cache */ + NULL, /* tp_subclasses */ + NULL, /* tp_weaklist */ + (destructor) NULL /* tp_del */ +}; + + +static PyObject *gPyOffScreenCreate(PyObject *UNUSED(self), PyObject *args) +{ + int width; + int height; + int samples; + int target; + + samples = 0; + if (!PyArg_ParseTuple(args, "ii|ii:offScreenCreate", &width, &height, &samples, &target)) + return NULL; + + return PyObject_CallObject((PyObject *) &PyRASOffScreen_Type, args); +} + PyDoc_STRVAR(Rasterizer_module_documentation, "This is the Python API for the game engine of Rasterizer" ); @@ -1511,6 +1681,7 @@ static struct PyMethodDef rasterizer_methods[] = { {"showProperties",(PyCFunction) gPyShowProperties, METH_VARARGS, "show or hide the debug properties"}, {"autoDebugList",(PyCFunction) gPyAutoDebugList, METH_VARARGS, "enable or disable auto adding debug properties to the debug list"}, {"clearDebugList",(PyCFunction) gPyClearDebugList, METH_NOARGS, "clears the debug property list"}, + {"offScreenCreate", (PyCFunction) gPyOffScreenCreate, METH_VARARGS, "create an offscreen buffer object, arguments are width and height in pixels"}, { NULL, (PyCFunction) NULL, 0, NULL } }; @@ -2330,6 +2501,8 @@ PyMODINIT_FUNC initRasterizerPythonBinding() PyObject *m; PyObject *d; + PyType_Ready(&PyRASOffScreen_Type); + m = PyModule_Create(&Rasterizer_module_def); PyDict_SetItemString(PySys_GetObject("modules"), Rasterizer_module_def.m_name, m); @@ -2357,6 +2530,11 @@ PyMODINIT_FUNC initRasterizerPythonBinding() KX_MACRO_addTypesToDict(d, LEFT_EYE, RAS_IRasterizer::RAS_STEREO_LEFTEYE); KX_MACRO_addTypesToDict(d, RIGHT_EYE, RAS_IRasterizer::RAS_STEREO_RIGHTEYE); + /* offscreen render */ + KX_MACRO_addTypesToDict(d, RAS_OFS_RENDER_BUFFER, RAS_IOffScreen::RAS_OFS_RENDER_BUFFER); + KX_MACRO_addTypesToDict(d, RAS_OFS_RENDER_TEXTURE, RAS_IOffScreen::RAS_OFS_RENDER_TEXTURE); + + // XXXX Add constants here // Check for errors diff --git a/source/gameengine/Ketsji/KX_Scene.cpp b/source/gameengine/Ketsji/KX_Scene.cpp index a5a418b5e78..47ba2c4343f 100644 --- a/source/gameengine/Ketsji/KX_Scene.cpp +++ b/source/gameengine/Ketsji/KX_Scene.cpp @@ -172,6 +172,7 @@ KX_Scene::KX_Scene(class SCA_IInputDevice* keyboarddevice, m_activity_culling = false; m_suspend = false; m_isclearingZbuffer = true; + m_isShadowDone = false; m_tempObjectList = new CListValue(); m_objectlist = new CListValue(); m_parentlist = new CListValue(); diff --git a/source/gameengine/Ketsji/KX_Scene.h b/source/gameengine/Ketsji/KX_Scene.h index c43b7be45dc..6d8ae8a321b 100644 --- a/source/gameengine/Ketsji/KX_Scene.h +++ b/source/gameengine/Ketsji/KX_Scene.h @@ -171,6 +171,11 @@ protected: */ bool m_isclearingZbuffer; + /** + * Does the shadow buffer needs calculing + */ + bool m_isShadowDone; + /** * The name of the scene */ @@ -572,6 +577,8 @@ public: bool IsSuspended(); bool IsClearingZBuffer(); void EnableZBufferClearing(bool isclearingZbuffer); + bool IsShadowDone() { return m_isShadowDone; } + void SetShadowDone(bool b) { m_isShadowDone = b; } // use of DBVT tree for camera culling void SetDbvtCulling(bool b) { m_dbvt_culling = b; } bool GetDbvtCulling() { return m_dbvt_culling; } diff --git a/source/gameengine/Rasterizer/CMakeLists.txt b/source/gameengine/Rasterizer/CMakeLists.txt index 496a864244b..c65fcac5161 100644 --- a/source/gameengine/Rasterizer/CMakeLists.txt +++ b/source/gameengine/Rasterizer/CMakeLists.txt @@ -65,6 +65,8 @@ set(SRC RAS_IPolygonMaterial.h RAS_IRasterizer.h RAS_ILightObject.h + RAS_IOffScreen.h + RAS_ISync.h RAS_MaterialBucket.h RAS_MeshObject.h RAS_ObjectColor.h diff --git a/source/gameengine/Rasterizer/RAS_IOffScreen.h b/source/gameengine/Rasterizer/RAS_IOffScreen.h new file mode 100644 index 00000000000..e5f3dc43e5f --- /dev/null +++ b/source/gameengine/Rasterizer/RAS_IOffScreen.h @@ -0,0 +1,84 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program 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 Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * +* The Original Code is Copyright (C) 2015, Blender Foundation +* All rights reserved. +* +* The Original Code is: all of this file. +* +* Contributor(s): Blender Foundation. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file RAS_IOffScreen.h + * \ingroup bgerast + */ + +#ifndef __RAS_OFFSCREEN_H__ +#define __RAS_OFFSCREEN_H__ + +#include "EXP_Python.h" + +class RAS_ICanvas; + +class MT_Transform; + +struct Image; + +class RAS_IOffScreen +{ +public: + enum RAS_OFS_BIND_MODE { + RAS_OFS_BIND_RENDER = 0, + RAS_OFS_BIND_READ, + }; + enum RAS_OFS_RENDER_TARGET { + RAS_OFS_RENDER_BUFFER = 0, // use render buffer as render target + RAS_OFS_RENDER_TEXTURE, // use texture as render target + }; + + int m_width; + int m_height; + int m_samples; + int m_color; // if used, holds the texture object, 0 if not used + + virtual ~RAS_IOffScreen() {} + + virtual bool Create(int width, int height, int samples, RAS_OFS_RENDER_TARGET target) = 0; + virtual void Destroy() = 0; + virtual void Bind(RAS_OFS_BIND_MODE mode) = 0; + virtual void Blit() = 0; + virtual void Unbind() = 0; + virtual void MipMap() = 0; + + virtual int GetWidth() { return m_width; } + virtual int GetHeight() { return m_height; } + virtual int GetSamples() { return m_samples; } + virtual int GetColor() { return m_color; } +}; + +#ifdef WITH_PYTHON +typedef struct { + PyObject_HEAD + RAS_IOffScreen *ofs; +} PyRASOffScreen; + +extern PyTypeObject PyRASOffScreen_Type; +#endif + +#endif /* __RAS_OFFSCREEN_H__ */ diff --git a/source/gameengine/Rasterizer/RAS_IRasterizer.h b/source/gameengine/Rasterizer/RAS_IRasterizer.h index a92b87773c7..dc92408915b 100644 --- a/source/gameengine/Rasterizer/RAS_IRasterizer.h +++ b/source/gameengine/Rasterizer/RAS_IRasterizer.h @@ -55,6 +55,8 @@ class RAS_IPolyMaterial; class RAS_MeshSlot; class RAS_ILightObject; class SCA_IScene; +class RAS_IOffScreen; +class RAS_ISync; typedef vector KX_IndexArray; typedef vector KX_VertexArray; @@ -257,6 +259,18 @@ public: virtual void SetFocalLength(const float focallength) = 0; virtual float GetFocalLength() = 0; + /** + * Create an offscreen render buffer that can be used as target for render. + * For the time being, it is only used in VideoTexture for custom render. + */ + virtual RAS_IOffScreen *CreateOffScreen(int width, int height, int samples, int target) = 0; + + /** + * Create a sync object + * For use with offscreen render + */ + virtual RAS_ISync *CreateSync(int type) = 0; + /** * SwapBuffers swaps the back buffer with the front buffer. */ @@ -287,7 +301,7 @@ public: * Sets the modelview matrix. */ virtual void SetViewMatrix(const MT_Matrix4x4 &mat, const MT_Matrix3x3 &ori, - const MT_Point3 &pos, bool perspective) = 0; + const MT_Point3 &pos, const MT_Vector3 &scale, bool perspective) = 0; /** */ diff --git a/source/gameengine/Rasterizer/RAS_ISync.h b/source/gameengine/Rasterizer/RAS_ISync.h new file mode 100644 index 00000000000..b9987dc1cad --- /dev/null +++ b/source/gameengine/Rasterizer/RAS_ISync.h @@ -0,0 +1,48 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program 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 Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * +* The Original Code is Copyright (C) 2015, Blender Foundation +* All rights reserved. +* +* The Original Code is: all of this file. +* +* Contributor(s): Blender Foundation. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file RAS_ISync.h + * \ingroup bgerast + */ + +#ifndef __RAS_ISYNC_H__ +#define __RAS_ISYNC_H__ + +class RAS_ISync +{ +public: + enum RAS_SYNC_TYPE { + RAS_SYNC_TYPE_FENCE = 0, + }; + virtual ~RAS_ISync() {} + + virtual bool Create(RAS_SYNC_TYPE type) = 0; + virtual void Destroy() = 0; + virtual void Wait() = 0; +}; + +#endif /* __RAS_ISYNC_H__ */ diff --git a/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/CMakeLists.txt b/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/CMakeLists.txt index 9f95e2c82af..89e31b62b41 100644 --- a/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/CMakeLists.txt +++ b/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/CMakeLists.txt @@ -51,6 +51,8 @@ set(INC_SYS set(SRC RAS_ListRasterizer.cpp RAS_OpenGLLight.cpp + RAS_OpenGLOffScreen.cpp + RAS_OpenGLSync.cpp RAS_OpenGLRasterizer.cpp RAS_StorageVA.cpp RAS_StorageVBO.cpp @@ -58,6 +60,8 @@ set(SRC RAS_IStorage.h RAS_ListRasterizer.h RAS_OpenGLLight.h + RAS_OpenGLOffScreen.h + RAS_OpenGLSync.h RAS_OpenGLRasterizer.h RAS_StorageVA.h RAS_StorageVBO.h diff --git a/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLLight.cpp b/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLLight.cpp index e15ae4bd0d7..fff988a07c5 100644 --- a/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLLight.cpp +++ b/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLLight.cpp @@ -242,7 +242,7 @@ void RAS_OpenGLLight::BindShadowBuffer(RAS_ICanvas *canvas, KX_Camera *cam, MT_T RAS_IRasterizer::StereoMode stereomode = m_rasterizer->GetStereoMode(); m_rasterizer->SetStereoMode(RAS_IRasterizer::RAS_STEREO_NOSTEREO); m_rasterizer->SetProjectionMatrix(projectionmat); - m_rasterizer->SetViewMatrix(modelviewmat, cam->NodeGetWorldOrientation(), cam->NodeGetWorldPosition(), cam->GetCameraData()->m_perspective); + m_rasterizer->SetViewMatrix(modelviewmat, cam->NodeGetWorldOrientation(), cam->NodeGetWorldPosition(), cam->NodeGetLocalScaling(), cam->GetCameraData()->m_perspective); m_rasterizer->SetStereoMode(stereomode); } diff --git a/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLOffScreen.cpp b/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLOffScreen.cpp new file mode 100644 index 00000000000..26ece47d8b3 --- /dev/null +++ b/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLOffScreen.cpp @@ -0,0 +1,347 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program 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 Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * +* The Original Code is Copyright (C) 2015, Blender Foundation +* All rights reserved. +* +* The Original Code is: all of this file. +* +* Contributor(s): Blender Foundation. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#include "glew-mx.h" + +#include + +#include "RAS_OpenGLOffScreen.h" +#include "RAS_ICanvas.h" + +RAS_OpenGLOffScreen::RAS_OpenGLOffScreen(RAS_ICanvas *canvas) + :m_canvas(canvas), m_depthrb(0), m_colorrb(0), m_depthtx(0), m_colortx(0), + m_fbo(0), m_blitfbo(0), m_blitrbo(0), m_blittex(0), m_target(RAS_OFS_RENDER_BUFFER), m_bound(false) +{ + m_width = 0; + m_height = 0; + m_samples = 0; + m_color = 0; +} + +RAS_OpenGLOffScreen::~RAS_OpenGLOffScreen() +{ + Destroy(); +} + +bool RAS_OpenGLOffScreen::Create(int width, int height, int samples, RAS_OFS_RENDER_TARGET target) +{ + GLenum status; + GLuint glo[2], fbo; + GLint max_samples; + GLenum textarget; + + if (m_fbo) { + printf("RAS_OpenGLOffScreen::Create(): buffer exists already, destroy first\n"); + return false; + } + if (target != RAS_IOffScreen::RAS_OFS_RENDER_BUFFER && + target != RAS_IOffScreen::RAS_OFS_RENDER_TEXTURE) + { + printf("RAS_OpenGLOffScreen::Create(): invalid offscren target\n"); + return false; + } + if (!GLEW_EXT_framebuffer_object) { + printf("RAS_OpenGLOffScreen::Create(): frame buffer not supported\n"); + return false; + } + if (samples) { + if (!GLEW_EXT_framebuffer_multisample || + !GLEW_EXT_framebuffer_blit) + { + samples = 0; + } + } + if (samples && target == RAS_OFS_RENDER_TEXTURE) { + // we need this in addition if we use multisample textures + if (!GLEW_ARB_texture_multisample || + !GLEW_EXT_framebuffer_multisample_blit_scaled) + { + samples = 0; + } + } + if (samples) { + max_samples = 0; + glGetIntegerv(GL_MAX_SAMPLES_EXT , &max_samples); + if (samples > max_samples) + samples = max_samples; + } + m_target = target; + fbo = 0; + glGenFramebuffersEXT(1, &fbo); + if (fbo == 0) { + printf("RAS_OpenGLOffScreen::Create(): frame buffer creation failed: %d\n", (int)glGetError()); + return false; + } + m_fbo = fbo; + glo[0] = glo[1] = 0; + if (target == RAS_OFS_RENDER_TEXTURE) { + glGenTextures(2, glo); + if (glo[0] == 0 || glo[1] == 0) { + printf("RAS_OpenGLOffScreen::Create(): texture creation failed: %d\n", (int)glGetError()); + goto L_ERROR; + } + m_depthtx = glo[0]; + m_color = m_colortx = glo[1]; + if (samples) { + textarget = GL_TEXTURE_2D_MULTISAMPLE; + glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, m_depthtx); + glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, GL_DEPTH_COMPONENT, width, height, true); + glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, m_colortx); + glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, GL_RGBA8, width, height, true); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + 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_MULTISAMPLE, 0); + } + else { + textarget = GL_TEXTURE_2D; + glBindTexture(GL_TEXTURE_2D, m_depthtx); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL); + glBindTexture(GL_TEXTURE_2D, m_colortx); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + } + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_fbo); + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, textarget, m_depthtx, 0); + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, textarget, m_colortx, 0); + } + else { + glGenRenderbuffersEXT(2, glo); + if (glo[0] == 0 || glo[1] == 0) { + printf("RAS_OpenGLOffScreen::Create(): render buffer creation failed: %d\n", (int)glGetError()); + goto L_ERROR; + } + m_depthrb = glo[0]; + m_colorrb = glo[1]; + glBindRenderbufferEXT(GL_RENDERBUFFER, m_depthrb); + glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, samples, GL_DEPTH_COMPONENT, width, height); + glBindRenderbufferEXT(GL_RENDERBUFFER, m_colorrb); + glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, samples, GL_RGBA8, width, height); + glBindRenderbufferEXT(GL_RENDERBUFFER, 0); + + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_fbo); + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER, m_depthrb); + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER, m_colorrb); + } + status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + + if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { + printf("RAS_OpenGLOffScreen::Create(): frame buffer incomplete: %d\n", (int)status); + goto L_ERROR; + } + m_width = width; + m_height = height; + + if (samples > 0) { + GLuint blit_tex; + GLuint blit_fbo; + // create a secondary FBO to blit to before the pixel can be read + + /* write into new single-sample buffer */ + glGenFramebuffersEXT(1, &blit_fbo); + if (!blit_fbo) { + printf("RAS_OpenGLOffScreen::Create(): failed creating a FBO for multi-sample offscreen buffer\n"); + goto L_ERROR; + } + m_blitfbo = blit_fbo; + blit_tex = 0; + if (target == RAS_OFS_RENDER_TEXTURE) { + glGenTextures(1, &blit_tex); + if (!blit_tex) { + printf("RAS_OpenGLOffScreen::Create(): failed creating a texture for multi-sample offscreen buffer\n"); + goto L_ERROR; + } + // m_color is the texture where the final render goes, the blit texture in this case + m_color = m_blittex = blit_tex; + glBindTexture(GL_TEXTURE_2D, m_blittex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + 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); + glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, m_blitfbo); + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_blittex, 0); + } + else { + /* create render buffer for new 'fbo_blit' */ + glGenRenderbuffersEXT(1, &blit_tex); + if (!blit_tex) { + printf("RAS_OpenGLOffScreen::Create(): failed creating a render buffer for multi-sample offscreen buffer\n"); + goto L_ERROR; + } + m_blitrbo = blit_tex; + glBindRenderbufferEXT(GL_RENDERBUFFER, m_blitrbo); + glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 0, GL_RGBA8, width, height); + glBindRenderbufferEXT(GL_RENDERBUFFER, 0); + glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, m_blitfbo); + glFramebufferRenderbufferEXT(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER, m_blitrbo); + } + status = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER_EXT); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, 0); + if (status != GL_FRAMEBUFFER_COMPLETE) { + printf("RAS_OpenGLOffScreen::Create(): frame buffer for multi-sample offscreen buffer incomplete: %d\n", (int)status); + goto L_ERROR; + } + // remember that multisample is enabled + m_samples = 1; + } + return true; + +L_ERROR: + Destroy(); + return false; +} + +void RAS_OpenGLOffScreen::Destroy() +{ + GLuint globj; + Unbind(); + if (m_fbo) { + globj = m_fbo; + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_fbo); + if (m_target == RAS_OFS_RENDER_TEXTURE) { + GLenum textarget = (m_samples) ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D; + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, textarget, 0, 0); + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, textarget, 0, 0); + } + else { + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, 0); + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, 0); + } + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + glDeleteFramebuffersEXT(1, &globj); + m_fbo = 0; + } + if (m_depthrb) { + globj = m_depthrb; + glDeleteRenderbuffers(1, &globj); + m_depthrb = 0; + } + if (m_colorrb) { + globj = m_colorrb; + glDeleteRenderbuffers(1, &globj); + m_colorrb = 0; + } + if (m_depthtx) { + globj = m_depthtx; + glDeleteTextures(1, &globj); + m_depthtx = 0; + } + if (m_colortx) { + globj = m_colortx; + glDeleteTextures(1, &globj); + m_colortx = 0; + } + if (m_blitfbo) { + globj = m_blitfbo; + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_blitfbo); + if (m_target == RAS_OFS_RENDER_TEXTURE) { + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, 0, 0); + } + else { + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, 0); + } + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + glDeleteFramebuffersEXT(1, &globj); + m_blitfbo = 0; + } + if (m_blitrbo) { + globj = m_blitrbo; + glDeleteRenderbuffers(1, &globj); + m_blitrbo = 0; + } + if (m_blittex) { + globj = m_blittex; + glDeleteTextures(1, &globj); + m_blittex = 0; + } + m_width = 0; + m_height = 0; + m_samples = 0; + m_color = 0; + m_target = RAS_OFS_RENDER_BUFFER; +} + +void RAS_OpenGLOffScreen::Bind(RAS_OFS_BIND_MODE mode) +{ + if (m_fbo) { + if (mode == RAS_OFS_BIND_RENDER) { + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_fbo); + glReadBuffer(GL_COLOR_ATTACHMENT0_EXT); + glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT); + glViewport(0, 0, m_width, m_height); + glDisable(GL_SCISSOR_TEST); + } + else if (!m_blitfbo) { + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_fbo); + glReadBuffer(GL_COLOR_ATTACHMENT0_EXT); + } + else { + glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, m_blitfbo); + glReadBuffer(GL_COLOR_ATTACHMENT0_EXT); + } + m_bound = true; + } +} + +void RAS_OpenGLOffScreen::Unbind() +{ + if (!m_bound) + return; + + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + glEnable(GL_SCISSOR_TEST); + glReadBuffer(GL_BACK); + glDrawBuffer(GL_BACK); + m_bound = false; +} + +void RAS_OpenGLOffScreen::MipMap() +{ + if (m_color) { + glBindTexture(GL_TEXTURE_2D, m_color); + glGenerateMipmap(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, 0); + } +} + +void RAS_OpenGLOffScreen::Blit() +{ + if (m_bound && m_blitfbo) { + // set the draw target to the secondary FBO, the read target is still the multisample FBO + glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, m_blitfbo); + + // sample the primary + glBlitFramebufferEXT(0, 0, m_width, m_height, 0, 0, m_width, m_height, GL_COLOR_BUFFER_BIT, GL_NEAREST); + + // make sure the next glReadPixels will read from the secondary buffer + glBindFramebufferEXT(GL_READ_FRAMEBUFFER, m_blitfbo); + } +} diff --git a/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLOffScreen.h b/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLOffScreen.h new file mode 100644 index 00000000000..94d0d4aa105 --- /dev/null +++ b/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLOffScreen.h @@ -0,0 +1,65 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program 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 Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * +* The Original Code is Copyright (C) 2015, Blender Foundation +* All rights reserved. +* +* The Original Code is: all of this file. +* +* Contributor(s): Blender Foundation. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#ifndef __RAS_OPENGLOFFSCREEN__ +#define __RAS_OPENGLOFFSCREEN__ + +#include "RAS_IOffScreen.h" +#include "GPU_extensions.h" + +class RAS_ICanvas; + +class RAS_OpenGLOffScreen : public RAS_IOffScreen +{ + RAS_ICanvas *m_canvas; + // these are GL objects + unsigned int m_depthrb; + unsigned int m_colorrb; + unsigned int m_depthtx; + unsigned int m_colortx; + unsigned int m_fbo; + unsigned int m_blitfbo; + unsigned int m_blitrbo; + unsigned int m_blittex; + RAS_OFS_RENDER_TARGET m_target; + bool m_bound; + + +public: + RAS_OpenGLOffScreen(RAS_ICanvas *canvas); + ~RAS_OpenGLOffScreen(); + + bool Create(int width, int height, int samples, RAS_OFS_RENDER_TARGET target); + void Destroy(); + void Bind(RAS_OFS_BIND_MODE mode); + void Blit(); + void Unbind(); + void MipMap(); +}; + +#endif /* __RAS_OPENGLOFFSCREEN__ */ + diff --git a/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLRasterizer.cpp b/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLRasterizer.cpp index 34f0ef04a58..fcb11ce2355 100644 --- a/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLRasterizer.cpp +++ b/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLRasterizer.cpp @@ -46,6 +46,8 @@ #include "MT_CmMatrix4x4.h" #include "RAS_OpenGLLight.h" +#include "RAS_OpenGLOffScreen.h" +#include "RAS_OpenGLSync.h" #include "RAS_StorageVA.h" #include "RAS_StorageVBO.h" @@ -92,6 +94,7 @@ RAS_OpenGLRasterizer::RAS_OpenGLRasterizer(RAS_ICanvas* canvas, RAS_STORAGE_TYPE m_time(0.0f), m_campos(0.0f, 0.0f, 0.0f), m_camortho(false), + m_camnegscale(false), m_stereomode(RAS_STEREO_NOSTEREO), m_curreye(RAS_STEREO_LEFTEYE), m_eyeseparation(0.0f), @@ -207,7 +210,7 @@ void RAS_OpenGLRasterizer::SetBackColor(float color[3]) m_redback = color[0]; m_greenback = color[1]; m_blueback = color[2]; - m_alphaback = 1.0f; + m_alphaback = 0.0f; } void RAS_OpenGLRasterizer::SetFog(short type, float start, float dist, float intensity, float color[3]) @@ -600,6 +603,31 @@ float RAS_OpenGLRasterizer::GetFocalLength() return m_focallength; } +RAS_IOffScreen *RAS_OpenGLRasterizer::CreateOffScreen(int width, int height, int samples, int target) +{ + RAS_IOffScreen *ofs; + + ofs = new RAS_OpenGLOffScreen(m_2DCanvas); + + if (!ofs->Create(width, height, samples, (RAS_IOffScreen::RAS_OFS_RENDER_TARGET)target)) { + delete ofs; + return NULL; + } + return ofs; +} + +RAS_ISync *RAS_OpenGLRasterizer::CreateSync(int type) +{ + RAS_ISync *sync; + + sync = new RAS_OpenGLSync(); + + if (!sync->Create((RAS_ISync::RAS_SYNC_TYPE)type)) { + delete sync; + return NULL; + } + return sync; +} void RAS_OpenGLRasterizer::SwapBuffers() { @@ -924,6 +952,7 @@ MT_Matrix4x4 RAS_OpenGLRasterizer::GetOrthoMatrix( void RAS_OpenGLRasterizer::SetViewMatrix(const MT_Matrix4x4 &mat, const MT_Matrix3x3 & camOrientMat3x3, const MT_Point3 & pos, + const MT_Vector3 &scale, bool perspective) { m_viewmatrix = mat; @@ -966,6 +995,12 @@ void RAS_OpenGLRasterizer::SetViewMatrix(const MT_Matrix4x4 &mat, } } + bool negX = (scale[0] < 0.0f); + bool negY = (scale[0] < 0.0f); + bool negZ = (scale[0] < 0.0f); + if (negX || negY || negZ) { + m_viewmatrix.tscale((negX)?-1.0f:1.0f, (negY)?-1.0f:1.0f, (negZ)?-1.0f:1.0f, 1.0); + } m_viewinvmatrix = m_viewmatrix; m_viewinvmatrix.invert(); @@ -976,6 +1011,7 @@ void RAS_OpenGLRasterizer::SetViewMatrix(const MT_Matrix4x4 &mat, glMatrixMode(GL_MODELVIEW); glLoadMatrixf(glviewmat); m_campos = pos; + m_camnegscale = negX ^ negY ^ negZ; } @@ -1108,6 +1144,9 @@ void RAS_OpenGLRasterizer::SetAlphaBlend(int alphablend) void RAS_OpenGLRasterizer::SetFrontFace(bool ccw) { + if (m_camnegscale) + ccw = !ccw; + if (m_last_frontface == ccw) return; diff --git a/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLRasterizer.h b/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLRasterizer.h index 4c22d1de611..9561e207dba 100644 --- a/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLRasterizer.h +++ b/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLRasterizer.h @@ -96,6 +96,7 @@ class RAS_OpenGLRasterizer : public RAS_IRasterizer MT_Matrix4x4 m_viewinvmatrix; MT_Point3 m_campos; bool m_camortho; + bool m_camnegscale; StereoMode m_stereomode; StereoEye m_curreye; @@ -180,7 +181,8 @@ public: virtual float GetEyeSeparation(); virtual void SetFocalLength(const float focallength); virtual float GetFocalLength(); - + virtual RAS_IOffScreen *CreateOffScreen(int width, int height, int samples, int target); + virtual RAS_ISync *CreateSync(int type); virtual void SwapBuffers(); virtual void IndexPrimitives(class RAS_MeshSlot &ms); @@ -189,7 +191,12 @@ public: virtual void SetProjectionMatrix(MT_CmMatrix4x4 &mat); virtual void SetProjectionMatrix(const MT_Matrix4x4 &mat); - virtual void SetViewMatrix(const MT_Matrix4x4 &mat, const MT_Matrix3x3 &ori, const MT_Point3 &pos, bool perspective); + virtual void SetViewMatrix( + const MT_Matrix4x4 &mat, + const MT_Matrix3x3 &ori, + const MT_Point3 &pos, + const MT_Vector3 &scale, + bool perspective); virtual const MT_Point3& GetCameraPosition(); virtual bool GetCameraOrtho(); diff --git a/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLSync.cpp b/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLSync.cpp new file mode 100644 index 00000000000..ebb4a9a3ca1 --- /dev/null +++ b/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLSync.cpp @@ -0,0 +1,82 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program 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 Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * +* The Original Code is Copyright (C) 2015, Blender Foundation +* All rights reserved. +* +* The Original Code is: all of this file. +* +* Contributor(s): Blender Foundation. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#include "glew-mx.h" + +#include + +#include "RAS_OpenGLSync.h" + +RAS_OpenGLSync::RAS_OpenGLSync() + :m_sync(NULL) +{ +} + +RAS_OpenGLSync::~RAS_OpenGLSync() +{ + Destroy(); +} + +bool RAS_OpenGLSync::Create(RAS_SYNC_TYPE type) +{ + if (m_sync) { + printf("RAS_OpenGLSync::Create(): sync already exists, destroy first\n"); + return false; + } + if (type != RAS_SYNC_TYPE_FENCE) { + printf("RAS_OpenGLSync::Create(): only RAS_SYNC_TYPE_FENCE are currently supported\n"); + return false; + } + if (!GLEW_ARB_sync) { + printf("RAS_OpenGLSync::Create(): ARB_sync extension is needed to create sync object\n"); + return false; + } + m_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + if (!m_sync) { + printf("RAS_OpenGLSync::Create(): glFenceSync() failed"); + return false; + } + return true; +} + +void RAS_OpenGLSync::Destroy() +{ + if (m_sync) { + glDeleteSync(m_sync); + m_sync = NULL; + } +} + +void RAS_OpenGLSync::Wait() +{ + if (m_sync) { + // this is needed to ensure that the sync is in the GPU + glFlush(); + // block until the operation have completed + glWaitSync(m_sync, 0, GL_TIMEOUT_IGNORED); + } +} diff --git a/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLSync.h b/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLSync.h new file mode 100644 index 00000000000..9b6340b04ac --- /dev/null +++ b/source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLSync.h @@ -0,0 +1,50 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program 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 Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * +* The Original Code is Copyright (C) 2015, Blender Foundation +* All rights reserved. +* +* The Original Code is: all of this file. +* +* Contributor(s): Blender Foundation. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#ifndef __RAS_OPENGLSYNC__ +#define __RAS_OPENGLSYNC__ + + +#include "RAS_ISync.h" + +struct __GLsync; + +class RAS_OpenGLSync : public RAS_ISync +{ +private: + struct __GLsync *m_sync; + +public: + RAS_OpenGLSync(); + ~RAS_OpenGLSync(); + + virtual bool Create(RAS_SYNC_TYPE type); + virtual void Destroy(); + virtual void Wait(); +}; + +#endif /* __RAS_OPENGLSYNC__ */ diff --git a/source/gameengine/VideoTexture/Exception.cpp b/source/gameengine/VideoTexture/Exception.cpp index 08616e0c41c..322b7f9aef3 100644 --- a/source/gameengine/VideoTexture/Exception.cpp +++ b/source/gameengine/VideoTexture/Exception.cpp @@ -213,6 +213,7 @@ void registerAllExceptions(void) ImageSizesNotMatchDesc.registerDesc(); ImageHasExportsDesc.registerDesc(); InvalidColorChannelDesc.registerDesc(); + InvalidImageModeDesc.registerDesc(); SceneInvalidDesc.registerDesc(); CameraInvalidDesc.registerDesc(); ObserverInvalidDesc.registerDesc(); @@ -223,4 +224,18 @@ void registerAllExceptions(void) MirrorTooSmallDesc.registerDesc(); SourceVideoEmptyDesc.registerDesc(); SourceVideoCreationDesc.registerDesc(); + OffScreenInvalidDesc.registerDesc(); +#ifdef WITH_DECKLINK + AutoDetectionNotAvailDesc.registerDesc(); + DeckLinkBadDisplayModeDesc.registerDesc(); + DeckLinkBadPixelFormatDesc.registerDesc(); + DeckLinkOpenCardDesc.registerDesc(); + DeckLinkBadFormatDesc.registerDesc(); + DeckLinkInternalErrorDesc.registerDesc(); + SourceVideoOnlyCaptureDesc.registerDesc(); + VideoDeckLinkBadFormatDesc.registerDesc(); + VideoDeckLinkOpenCardDesc.registerDesc(); + VideoDeckLinkDvpInternalErrorDesc.registerDesc(); + VideoDeckLinkPinMemoryErrorDesc.registerDesc(); +#endif } diff --git a/source/gameengine/VideoTexture/Exception.h b/source/gameengine/VideoTexture/Exception.h index c3c27abe019..c4de85ff34d 100644 --- a/source/gameengine/VideoTexture/Exception.h +++ b/source/gameengine/VideoTexture/Exception.h @@ -46,7 +46,7 @@ throw Exception (err, macroHRslt, __FILE__, __LINE__); \ } -#define THRWEXCP(err,hRslt) throw Exception (err, hRslt, __FILE__, __LINE__); +#define THRWEXCP(err,hRslt) throw Exception (err, hRslt, __FILE__, __LINE__) #if defined WIN32 @@ -209,9 +209,11 @@ extern ExpDesc MaterialNotAvailDesc; extern ExpDesc ImageSizesNotMatchDesc; extern ExpDesc ImageHasExportsDesc; extern ExpDesc InvalidColorChannelDesc; +extern ExpDesc InvalidImageModeDesc; extern ExpDesc SceneInvalidDesc; extern ExpDesc CameraInvalidDesc; extern ExpDesc ObserverInvalidDesc; +extern ExpDesc OffScreenInvalidDesc; extern ExpDesc MirrorInvalidDesc; extern ExpDesc MirrorSizeInvalidDesc; extern ExpDesc MirrorNormalInvalidDesc; @@ -219,7 +221,19 @@ extern ExpDesc MirrorHorizontalDesc; extern ExpDesc MirrorTooSmallDesc; extern ExpDesc SourceVideoEmptyDesc; extern ExpDesc SourceVideoCreationDesc; - +extern ExpDesc DeckLinkBadDisplayModeDesc; +extern ExpDesc DeckLinkBadPixelFormatDesc; +extern ExpDesc AutoDetectionNotAvailDesc; +extern ExpDesc DeckLinkOpenCardDesc; +extern ExpDesc DeckLinkBadFormatDesc; +extern ExpDesc DeckLinkInternalErrorDesc; +extern ExpDesc SourceVideoOnlyCaptureDesc; +extern ExpDesc VideoDeckLinkBadFormatDesc; +extern ExpDesc VideoDeckLinkOpenCardDesc; +extern ExpDesc VideoDeckLinkDvpInternalErrorDesc; +extern ExpDesc VideoDeckLinkPinMemoryErrorDesc; + +extern ExceptionID InvalidImageMode; void registerAllExceptions(void); #endif diff --git a/source/gameengine/VideoTexture/FilterBase.h b/source/gameengine/VideoTexture/FilterBase.h index 498917e2375..db688d551d0 100644 --- a/source/gameengine/VideoTexture/FilterBase.h +++ b/source/gameengine/VideoTexture/FilterBase.h @@ -44,6 +44,13 @@ #define VT_A(v) ((unsigned char*)&v)[3] #define VT_RGBA(v,r,g,b,a) VT_R(v)=(unsigned char)r, VT_G(v)=(unsigned char)g, VT_B(v)=(unsigned char)b, VT_A(v)=(unsigned char)a +#ifdef __BIG_ENDIAN__ +# define VT_SWAPBR(i) ((((i) >> 16) & 0xFF00) + (((i) & 0xFF00) << 16) + ((i) & 0xFF00FF)) +#else +# define VT_SWAPBR(i) ((((i) & 0xFF) << 16) + (((i) >> 16) & 0xFF) + ((i) & 0xFF00FF00)) +#endif + + // forward declaration class FilterBase; diff --git a/source/gameengine/VideoTexture/FilterSource.h b/source/gameengine/VideoTexture/FilterSource.h index bc80b2b36cc..820576dfff9 100644 --- a/source/gameengine/VideoTexture/FilterSource.h +++ b/source/gameengine/VideoTexture/FilterSource.h @@ -81,6 +81,30 @@ protected: } }; +/// class for BGRA32 conversion +class FilterBGRA32 : public FilterBase +{ +public: + /// constructor + FilterBGRA32 (void) {} + /// destructor + virtual ~FilterBGRA32 (void) {} + + /// get source pixel size + virtual unsigned int getPixelSize (void) { return 4; } + +protected: + /// filter pixel, source byte buffer + virtual unsigned int filter( + unsigned char *src, short x, short y, + short * size, unsigned int pixSize, unsigned int val) + { + VT_RGBA(val,src[2],src[1],src[0],src[3]); + return val; + } +}; + + /// class for BGR24 conversion class FilterBGR24 : public FilterBase { diff --git a/source/gameengine/VideoTexture/ImageBase.cpp b/source/gameengine/VideoTexture/ImageBase.cpp index 8be152c7b8e..0db1fa293da 100644 --- a/source/gameengine/VideoTexture/ImageBase.cpp +++ b/source/gameengine/VideoTexture/ImageBase.cpp @@ -32,7 +32,6 @@ extern "C" { #include "bgl.h" } -#include "glew-mx.h" #include #include @@ -50,6 +49,14 @@ extern "C" { // ImageBase class implementation +ExceptionID ImageHasExports; +ExceptionID InvalidColorChannel; +ExceptionID InvalidImageMode; + +ExpDesc ImageHasExportsDesc(ImageHasExports, "Image has exported buffers, cannot resize"); +ExpDesc InvalidColorChannelDesc(InvalidColorChannel, "Invalid or too many color channels specified. At most 4 values within R, G, B, A, 0, 1"); +ExpDesc InvalidImageModeDesc(InvalidImageMode, "Invalid image mode, only RGBA and BGRA are supported"); + // constructor ImageBase::ImageBase (bool staticSrc) : m_image(NULL), m_imgSize(0), m_avail(false), m_scale(false), m_scaleChange(false), m_flip(false), @@ -111,6 +118,28 @@ unsigned int * ImageBase::getImage (unsigned int texId, double ts) return m_avail ? m_image : NULL; } +bool ImageBase::loadImage(unsigned int *buffer, unsigned int size, unsigned int format, double ts) +{ + unsigned int *d, *s, v, len; + if (getImage(0, ts) != NULL && size >= getBuffSize()) { + switch (format) { + case GL_RGBA: + memcpy(buffer, m_image, getBuffSize()); + break; + case GL_BGRA: + len = (unsigned int)m_size[0] * m_size[1]; + for (s=m_image, d=buffer; len; len--) { + v = *s++; + *d++ = VT_SWAPBR(v); + } + break; + default: + THRWEXCP(InvalidImageMode,S_OK); + } + return true; + } + return false; +} // refresh image source void ImageBase::refresh (void) @@ -179,11 +208,18 @@ void ImageBase::setFilter (PyFilter * filt) m_pyfilter = filt; } -ExceptionID ImageHasExports; -ExceptionID InvalidColorChannel; +void ImageBase::swapImageBR() +{ + unsigned int size, v, *s; -ExpDesc ImageHasExportsDesc(ImageHasExports, "Image has exported buffers, cannot resize"); -ExpDesc InvalidColorChannelDesc(InvalidColorChannel, "Invalid or too many color channels specified. At most 4 values within R, G, B, A, 0, 1"); + if (m_avail) { + size = 1 * m_size[0] * m_size[1]; + for (s=m_image; size; size--) { + v = *s; + *s++ = VT_SWAPBR(v); + } + } +} // initialize image data void ImageBase::init (short width, short height) @@ -500,10 +536,57 @@ PyObject *Image_getSize (PyImage *self, void *closure) } // refresh image -PyObject *Image_refresh (PyImage *self) -{ +PyObject *Image_refresh (PyImage *self, PyObject *args) +{ + Py_buffer buffer; + bool done = true; + char *mode = NULL; + double ts = -1.0; + unsigned int format; + + memset(&buffer, 0, sizeof(buffer)); + if (PyArg_ParseTuple(args, "|s*sd:refresh", &buffer, &mode, &ts)) { + if (buffer.buf) { + // a target buffer is provided, verify its format + if (buffer.readonly) { + PyErr_SetString(PyExc_TypeError, "Buffers passed in argument must be writable"); + } + else if (!PyBuffer_IsContiguous(&buffer, 'C')) { + PyErr_SetString(PyExc_TypeError, "Buffers passed in argument must be contiguous in memory"); + } + else if (((intptr_t)buffer.buf & 3) != 0) { + PyErr_SetString(PyExc_TypeError, "Buffers passed in argument must be aligned to 4 bytes boundary"); + } + else { + // ready to get the image into our buffer + try { + if (mode == NULL || !strcmp(mode, "RGBA")) + format = GL_RGBA; + else if (!strcmp(mode, "BGRA")) + format = GL_BGRA; + else + THRWEXCP(InvalidImageMode,S_OK); + + done = self->m_image->loadImage((unsigned int *)buffer.buf, buffer.len, format, ts); + } + catch (Exception & exp) { + exp.report(); + } + } + PyBuffer_Release(&buffer); + if (PyErr_Occurred()) { + return NULL; + } + } + } + else { + return NULL; + } + self->m_image->refresh(); - Py_RETURN_NONE; + if (done) + Py_RETURN_TRUE; + Py_RETURN_FALSE; } // get scale diff --git a/source/gameengine/VideoTexture/ImageBase.h b/source/gameengine/VideoTexture/ImageBase.h index f646d145365..4c9fc5a58fb 100644 --- a/source/gameengine/VideoTexture/ImageBase.h +++ b/source/gameengine/VideoTexture/ImageBase.h @@ -40,6 +40,7 @@ #include "FilterBase.h" +#include "glew-mx.h" // forward declarations struct PyImage; @@ -104,6 +105,13 @@ public: /// calculate size(nearest power of 2) static short calcSize(short size); + /// calculate image from sources and send it to a target buffer instead of a texture + /// format is GL_RGBA or GL_BGRA + virtual bool loadImage(unsigned int *buffer, unsigned int size, unsigned int format, double ts); + + /// swap the B and R channel in-place in the image buffer + void swapImageBR(); + /// number of buffer pointing to m_image, public because not handled by this class int m_exports; @@ -348,7 +356,7 @@ PyObject *Image_getImage(PyImage *self, char *mode); // get image size PyObject *Image_getSize(PyImage *self, void *closure); // refresh image - invalidate current content -PyObject *Image_refresh(PyImage *self); +PyObject *Image_refresh(PyImage *self, PyObject *args); // get scale PyObject *Image_getScale(PyImage *self, void *closure); diff --git a/source/gameengine/VideoTexture/ImageMix.cpp b/source/gameengine/VideoTexture/ImageMix.cpp index 973be52e0fc..2de00f5ba05 100644 --- a/source/gameengine/VideoTexture/ImageMix.cpp +++ b/source/gameengine/VideoTexture/ImageMix.cpp @@ -156,7 +156,7 @@ static PyMethodDef imageMixMethods[] = { {"getWeight", (PyCFunction)getWeight, METH_VARARGS, "get image source weight"}, {"setWeight", (PyCFunction)setWeight, METH_VARARGS, "set image source weight"}, // methods from ImageBase class - {"refresh", (PyCFunction)Image_refresh, METH_NOARGS, "Refresh image - invalidate its current content"}, + {"refresh", (PyCFunction)Image_refresh, METH_VARARGS, "Refresh image - invalidate its current content"}, {NULL} }; // attributes structure diff --git a/source/gameengine/VideoTexture/ImageRender.cpp b/source/gameengine/VideoTexture/ImageRender.cpp index a374fbba2df..9991bf42a9f 100644 --- a/source/gameengine/VideoTexture/ImageRender.cpp +++ b/source/gameengine/VideoTexture/ImageRender.cpp @@ -43,6 +43,8 @@ #include "RAS_CameraData.h" #include "RAS_MeshObject.h" #include "RAS_Polygon.h" +#include "RAS_IOffScreen.h" +#include "RAS_ISync.h" #include "BLI_math.h" #include "ImageRender.h" @@ -51,11 +53,12 @@ #include "Exception.h" #include "Texture.h" -ExceptionID SceneInvalid, CameraInvalid, ObserverInvalid; +ExceptionID SceneInvalid, CameraInvalid, ObserverInvalid, OffScreenInvalid; ExceptionID MirrorInvalid, MirrorSizeInvalid, MirrorNormalInvalid, MirrorHorizontal, MirrorTooSmall; ExpDesc SceneInvalidDesc(SceneInvalid, "Scene object is invalid"); ExpDesc CameraInvalidDesc(CameraInvalid, "Camera object is invalid"); ExpDesc ObserverInvalidDesc(ObserverInvalid, "Observer object is invalid"); +ExpDesc OffScreenInvalidDesc(OffScreenInvalid, "Offscreen object is invalid"); ExpDesc MirrorInvalidDesc(MirrorInvalid, "Mirror object is invalid"); ExpDesc MirrorSizeInvalidDesc(MirrorSizeInvalid, "Mirror has no vertex or no size"); ExpDesc MirrorNormalInvalidDesc(MirrorNormalInvalid, "Cannot determine mirror plane"); @@ -63,12 +66,15 @@ ExpDesc MirrorHorizontalDesc(MirrorHorizontal, "Mirror is horizontal in local sp ExpDesc MirrorTooSmallDesc(MirrorTooSmall, "Mirror is too small"); // constructor -ImageRender::ImageRender (KX_Scene *scene, KX_Camera * camera) : - ImageViewport(), +ImageRender::ImageRender (KX_Scene *scene, KX_Camera * camera, PyRASOffScreen * offscreen) : + ImageViewport(offscreen), m_render(true), + m_done(false), m_scene(scene), m_camera(camera), m_owncamera(false), + m_offscreen(offscreen), + m_sync(NULL), m_observer(NULL), m_mirror(NULL), m_clip(100.f), @@ -81,6 +87,10 @@ ImageRender::ImageRender (KX_Scene *scene, KX_Camera * camera) : m_engine = KX_GetActiveEngine(); m_rasterizer = m_engine->GetRasterizer(); m_canvas = m_engine->GetCanvas(); + // keep a reference to the offscreen buffer + if (m_offscreen) { + Py_INCREF(m_offscreen); + } } // destructor @@ -88,6 +98,9 @@ ImageRender::~ImageRender (void) { if (m_owncamera) m_camera->Release(); + if (m_sync) + delete m_sync; + Py_XDECREF(m_offscreen); } // get background color @@ -121,30 +134,41 @@ void ImageRender::setBackgroundFromScene (KX_Scene *scene) // capture image from viewport -void ImageRender::calcImage (unsigned int texId, double ts) +void ImageRender::calcViewport (unsigned int texId, double ts, unsigned int format) { - if (m_rasterizer->GetDrawingMode() != RAS_IRasterizer::KX_TEXTURED || // no need for texture - m_camera->GetViewport() || // camera must be inactive - m_camera == m_scene->GetActiveCamera()) - { - // no need to compute texture in non texture rendering - m_avail = false; - return; - } // render the scene from the camera - Render(); - // get image from viewport - ImageViewport::calcImage(texId, ts); - // restore OpenGL state - m_canvas->EndFrame(); + if (!m_done) { + if (!Render()) { + return; + } + } + else if (m_offscreen) { + m_offscreen->ofs->Bind(RAS_IOffScreen::RAS_OFS_BIND_READ); + } + // wait until all render operations are completed + WaitSync(); + // get image from viewport (or FBO) + ImageViewport::calcViewport(texId, ts, format); + if (m_offscreen) { + m_offscreen->ofs->Unbind(); + } } -void ImageRender::Render() +bool ImageRender::Render() { RAS_FrameFrustum frustum; - if (!m_render) - return; + if (!m_render || + m_rasterizer->GetDrawingMode() != RAS_IRasterizer::KX_TEXTURED || // no need for texture + m_camera->GetViewport() || // camera must be inactive + m_camera == m_scene->GetActiveCamera()) + { + // no need to compute texture in non texture rendering + return false; + } + + if (!m_scene->IsShadowDone()) + m_engine->RenderShadowBuffers(m_scene); if (m_mirror) { @@ -164,7 +188,7 @@ void ImageRender::Render() MT_Scalar observerDistance = mirrorPlaneDTerm - observerWorldPos.dot(mirrorWorldZ); // if distance < 0.01 => observer is on wrong side of mirror, don't render if (observerDistance < 0.01) - return; + return false; // set camera world position = observerPos + normal * 2 * distance MT_Point3 cameraWorldPos = observerWorldPos + (MT_Scalar(2.0)*observerDistance)*mirrorWorldZ; m_camera->GetSGNode()->SetLocalPosition(cameraWorldPos); @@ -215,7 +239,15 @@ void ImageRender::Render() RAS_Rect area = m_canvas->GetWindowArea(); // The screen area that ImageViewport will copy is also the rendering zone - m_canvas->SetViewPort(m_position[0], m_position[1], m_position[0]+m_capSize[0]-1, m_position[1]+m_capSize[1]-1); + if (m_offscreen) { + // bind the fbo and set the viewport to full size + m_offscreen->ofs->Bind(RAS_IOffScreen::RAS_OFS_BIND_RENDER); + // this is needed to stop crashing in canvas check + m_canvas->UpdateViewPort(0, 0, m_offscreen->ofs->GetWidth(), m_offscreen->ofs->GetHeight()); + } + else { + m_canvas->SetViewPort(m_position[0], m_position[1], m_position[0]+m_capSize[0]-1, m_position[1]+m_capSize[1]-1); + } m_canvas->ClearColor(m_background[0], m_background[1], m_background[2], m_background[3]); m_canvas->ClearBuffer(RAS_ICanvas::COLOR_BUFFER|RAS_ICanvas::DEPTH_BUFFER); m_rasterizer->BeginFrame(m_engine->GetClockTime()); @@ -292,17 +324,18 @@ void ImageRender::Render() MT_Transform camtrans(m_camera->GetWorldToCamera()); MT_Matrix4x4 viewmat(camtrans); - m_rasterizer->SetViewMatrix(viewmat, m_camera->NodeGetWorldOrientation(), m_camera->NodeGetWorldPosition(), m_camera->GetCameraData()->m_perspective); + m_rasterizer->SetViewMatrix(viewmat, m_camera->NodeGetWorldOrientation(), m_camera->NodeGetWorldPosition(), m_camera->NodeGetLocalScaling(), m_camera->GetCameraData()->m_perspective); m_camera->SetModelviewMatrix(viewmat); // restore the stereo mode now that the matrix is computed m_rasterizer->SetStereoMode(stereomode); - if (stereomode == RAS_IRasterizer::RAS_STEREO_QUADBUFFERED) { - // In QUAD buffer stereo mode, the GE render pass ends with the right eye on the right buffer - // but we need to draw on the left buffer to capture the render - // TODO: implement an explicit function in rasterizer to restore the left buffer. - m_rasterizer->SetEye(RAS_IRasterizer::RAS_STEREO_LEFTEYE); - } + if (m_rasterizer->Stereo()) { + // stereo mode change render settings that disturb this render, cancel them all + // we don't need to restore them as they are set before each frame render. + glDrawBuffer(GL_BACK_LEFT); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glDisable(GL_POLYGON_STIPPLE); + } m_scene->CalculateVisibleMeshes(m_rasterizer,m_camera); @@ -314,8 +347,48 @@ void ImageRender::Render() // restore the canvas area now that the render is completed m_canvas->GetWindowArea() = area; + m_canvas->EndFrame(); + + // In case multisample is active, blit the FBO + if (m_offscreen) + m_offscreen->ofs->Blit(); + // end of all render operations, let's create a sync object just in case + if (m_sync) { + // a sync from a previous render, should not happen + delete m_sync; + m_sync = NULL; + } + m_sync = m_rasterizer->CreateSync(RAS_ISync::RAS_SYNC_TYPE_FENCE); + // remember that we have done render + m_done = true; + // the image is not available at this stage + m_avail = false; + return true; +} + +void ImageRender::Unbind() +{ + if (m_offscreen) + { + m_offscreen->ofs->Unbind(); + } } +void ImageRender::WaitSync() +{ + if (m_sync) { + m_sync->Wait(); + // done with it, deleted it + delete m_sync; + m_sync = NULL; + } + if (m_offscreen) { + // this is needed to finalize the image if the target is a texture + m_offscreen->ofs->MipMap(); + } + // all rendered operation done and complete, invalidate render for next time + m_done = false; +} // cast Image pointer to ImageRender inline ImageRender * getImageRender (PyImage *self) @@ -337,11 +410,13 @@ static int ImageRender_init(PyObject *pySelf, PyObject *args, PyObject *kwds) PyObject *scene; // camera object PyObject *camera; + // offscreen buffer object + PyRASOffScreen *offscreen = NULL; // parameter keywords - static const char *kwlist[] = {"sceneObj", "cameraObj", NULL}; + static const char *kwlist[] = {"sceneObj", "cameraObj", "ofsObj", NULL}; // get parameters - if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO", - const_cast(kwlist), &scene, &camera)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|O", + const_cast(kwlist), &scene, &camera, &offscreen)) return -1; try { @@ -357,11 +432,16 @@ static int ImageRender_init(PyObject *pySelf, PyObject *args, PyObject *kwds) // throw exception if camera is not available if (cameraPtr == NULL) THRWEXCP(CameraInvalid, S_OK); + if (offscreen) { + if (Py_TYPE(offscreen) != &PyRASOffScreen_Type) { + THRWEXCP(OffScreenInvalid, S_OK); + } + } // get pointer to image structure PyImage *self = reinterpret_cast(pySelf); // create source object if (self->m_image != NULL) delete self->m_image; - self->m_image = new ImageRender(scenePtr, cameraPtr); + self->m_image = new ImageRender(scenePtr, cameraPtr, offscreen); } catch (Exception & exp) { @@ -372,6 +452,55 @@ static int ImageRender_init(PyObject *pySelf, PyObject *args, PyObject *kwds) return 0; } +static PyObject *ImageRender_refresh(PyImage *self, PyObject *args) +{ + ImageRender *imageRender = getImageRender(self); + + if (!imageRender) { + PyErr_SetString(PyExc_TypeError, "Incomplete ImageRender() object"); + return NULL; + } + if (PyArg_ParseTuple(args, "")) { + // refresh called with no argument. + // For other image objects it simply invalidates the image buffer + // For ImageRender it triggers a render+sync + // Note that this only makes sense when doing offscreen render on texture + if (!imageRender->isDone()) { + if (!imageRender->Render()) { + Py_RETURN_FALSE; + } + // as we are not trying to read the pixels, just unbind + imageRender->Unbind(); + } + // wait until all render operations are completed + // this will also finalize the texture + imageRender->WaitSync(); + Py_RETURN_TRUE; + } + else { + // fallback on standard processing + PyErr_Clear(); + return Image_refresh(self, args); + } +} + +// refresh image +static PyObject *ImageRender_render(PyImage *self) +{ + ImageRender *imageRender = getImageRender(self); + + if (!imageRender) { + PyErr_SetString(PyExc_TypeError, "Incomplete ImageRender() object"); + return NULL; + } + if (!imageRender->Render()) { + Py_RETURN_FALSE; + } + // we are not reading the pixels now, unbind + imageRender->Unbind(); + Py_RETURN_TRUE; +} + // get background color static PyObject *getBackground (PyImage *self, void *closure) @@ -410,7 +539,8 @@ static int setBackground(PyImage *self, PyObject *value, void *closure) // methods structure static PyMethodDef imageRenderMethods[] = { // methods from ImageBase class - {"refresh", (PyCFunction)Image_refresh, METH_NOARGS, "Refresh image - invalidate its current content"}, + {"refresh", (PyCFunction)ImageRender_refresh, METH_VARARGS, "Refresh image - invalidate its current content after optionally transferring its content to a target buffer"}, + {"render", (PyCFunction)ImageRender_render, METH_NOARGS, "Render scene - run before refresh() to performs asynchronous render"}, {NULL} }; // attributes structure @@ -601,7 +731,9 @@ static PyGetSetDef imageMirrorGetSets[] = ImageRender::ImageRender (KX_Scene *scene, KX_GameObject *observer, KX_GameObject *mirror, RAS_IPolyMaterial *mat) : ImageViewport(), m_render(false), + m_done(false), m_scene(scene), + m_offscreen(NULL), m_observer(observer), m_mirror(mirror), m_clip(100.f) diff --git a/source/gameengine/VideoTexture/ImageRender.h b/source/gameengine/VideoTexture/ImageRender.h index ef55e4dea84..d062db44348 100644 --- a/source/gameengine/VideoTexture/ImageRender.h +++ b/source/gameengine/VideoTexture/ImageRender.h @@ -39,6 +39,8 @@ #include "DNA_screen_types.h" #include "RAS_ICanvas.h" #include "RAS_IRasterizer.h" +#include "RAS_IOffScreen.h" +#include "RAS_ISync.h" #include "ImageViewport.h" @@ -48,7 +50,7 @@ class ImageRender : public ImageViewport { public: /// constructor - ImageRender(KX_Scene *scene, KX_Camera *camera); + ImageRender(KX_Scene *scene, KX_Camera *camera, PyRASOffScreen *offscreen); ImageRender(KX_Scene *scene, KX_GameObject *observer, KX_GameObject *mirror, RAS_IPolyMaterial * mat); /// destructor @@ -63,16 +65,30 @@ public: float getClip (void) { return m_clip; } /// set whole buffer use void setClip (float clip) { m_clip = clip; } + /// render status + bool isDone() { return m_done; } + /// render frame (public so that it is accessible from python) + bool Render(); + /// in case fbo is used, method to unbind + void Unbind(); + /// wait for render to complete + void WaitSync(); protected: /// true if ready to render bool m_render; + /// is render done already? + bool m_done; /// rendered scene KX_Scene * m_scene; /// camera for render KX_Camera * m_camera; /// do we own the camera? bool m_owncamera; + /// if offscreen render + PyRASOffScreen *m_offscreen; + /// object to synchronize render even if no buffer transfer + RAS_ISync *m_sync; /// for mirror operation KX_GameObject * m_observer; KX_GameObject * m_mirror; @@ -91,15 +107,15 @@ protected: KX_KetsjiEngine* m_engine; /// background color - float m_background[4]; + float m_background[4]; /// render 3d scene to image - virtual void calcImage (unsigned int texId, double ts); + virtual void calcImage (unsigned int texId, double ts) { calcViewport(texId, ts, GL_RGBA); } + + /// render 3d scene to image + virtual void calcViewport (unsigned int texId, double ts, unsigned int format); - void Render(); - void SetupRenderFrame(KX_Scene *scene, KX_Camera* cam); - void RenderFrame(KX_Scene* scene, KX_Camera* cam); void setBackgroundFromScene(KX_Scene *scene); void SetWorldSettings(KX_WorldInfo* wi); }; diff --git a/source/gameengine/VideoTexture/ImageViewport.cpp b/source/gameengine/VideoTexture/ImageViewport.cpp index 820a019832e..8852c190053 100644 --- a/source/gameengine/VideoTexture/ImageViewport.cpp +++ b/source/gameengine/VideoTexture/ImageViewport.cpp @@ -45,14 +45,22 @@ // constructor -ImageViewport::ImageViewport (void) : m_alpha(false), m_texInit(false) +ImageViewport::ImageViewport (PyRASOffScreen *offscreen) : m_alpha(false), m_texInit(false) { // get viewport rectangle - RAS_Rect rect = KX_GetActiveEngine()->GetCanvas()->GetWindowArea(); - m_viewport[0] = rect.GetLeft(); - m_viewport[1] = rect.GetBottom(); - m_viewport[2] = rect.GetWidth(); - m_viewport[3] = rect.GetHeight(); + if (offscreen) { + m_viewport[0] = 0; + m_viewport[1] = 0; + m_viewport[2] = offscreen->ofs->GetWidth(); + m_viewport[3] = offscreen->ofs->GetHeight(); + } + else { + RAS_Rect rect = KX_GetActiveEngine()->GetCanvas()->GetWindowArea(); + m_viewport[0] = rect.GetLeft(); + m_viewport[1] = rect.GetBottom(); + m_viewport[2] = rect.GetWidth(); + m_viewport[3] = rect.GetHeight(); + } //glGetIntegerv(GL_VIEWPORT, m_viewport); // create buffer for viewport image @@ -60,7 +68,7 @@ ImageViewport::ImageViewport (void) : m_alpha(false), m_texInit(false) // float (1 float = 4 bytes per pixel) m_viewportImage = new BYTE [4 * getViewportSize()[0] * getViewportSize()[1]]; // set attributes - setWhole(false); + setWhole((offscreen) ? true : false); } // destructor @@ -126,25 +134,26 @@ void ImageViewport::setPosition (GLint pos[2]) // capture image from viewport -void ImageViewport::calcImage (unsigned int texId, double ts) +void ImageViewport::calcViewport (unsigned int texId, double ts, unsigned int format) { // if scale was changed if (m_scaleChange) // reset image init(m_capSize[0], m_capSize[1]); // if texture wasn't initialized - if (!m_texInit) { + if (!m_texInit && texId != 0) { // initialize it loadTexture(texId, m_image, m_size); m_texInit = true; } // if texture can be directly created - if (texId != 0 && m_pyfilter == NULL && m_capSize[0] == calcSize(m_capSize[0]) - && m_capSize[1] == calcSize(m_capSize[1]) && !m_flip && !m_zbuff && !m_depth) + if (texId != 0 && m_pyfilter == NULL && m_size[0] == m_capSize[0] && + m_size[1] == m_capSize[1] && !m_flip && !m_zbuff && !m_depth) { // just copy current viewport to texture glBindTexture(GL_TEXTURE_2D, texId); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_upLeft[0], m_upLeft[1], (GLsizei)m_capSize[0], (GLsizei)m_capSize[1]); + glBindTexture(GL_TEXTURE_2D, 0); // image is not available m_avail = false; } @@ -176,11 +185,33 @@ void ImageViewport::calcImage (unsigned int texId, double ts) // get frame buffer data if (m_alpha) { - glReadPixels(m_upLeft[0], m_upLeft[1], (GLsizei)m_capSize[0], (GLsizei)m_capSize[1], GL_RGBA, - GL_UNSIGNED_BYTE, m_viewportImage); - // filter loaded data - FilterRGBA32 filt; - filterImage(filt, m_viewportImage, m_capSize); + // as we are reading the pixel in the native format, we can read directly in the image buffer + // if we are sure that no processing is needed on the image + if (m_size[0] == m_capSize[0] && + m_size[1] == m_capSize[1] && + !m_flip && + !m_pyfilter) + { + glReadPixels(m_upLeft[0], m_upLeft[1], (GLsizei)m_capSize[0], (GLsizei)m_capSize[1], format, + GL_UNSIGNED_BYTE, m_image); + m_avail = true; + } + else if (!m_pyfilter) { + glReadPixels(m_upLeft[0], m_upLeft[1], (GLsizei)m_capSize[0], (GLsizei)m_capSize[1], format, + GL_UNSIGNED_BYTE, m_viewportImage); + FilterRGBA32 filt; + filterImage(filt, m_viewportImage, m_capSize); + } + else { + glReadPixels(m_upLeft[0], m_upLeft[1], (GLsizei)m_capSize[0], (GLsizei)m_capSize[1], GL_RGBA, + GL_UNSIGNED_BYTE, m_viewportImage); + FilterRGBA32 filt; + filterImage(filt, m_viewportImage, m_capSize); + if (format == GL_BGRA) { + // in place byte swapping + swapImageBR(); + } + } } else { glReadPixels(m_upLeft[0], m_upLeft[1], (GLsizei)m_capSize[0], (GLsizei)m_capSize[1], GL_RGB, @@ -188,12 +219,46 @@ void ImageViewport::calcImage (unsigned int texId, double ts) // filter loaded data FilterRGB24 filt; filterImage(filt, m_viewportImage, m_capSize); + if (format == GL_BGRA) { + // in place byte swapping + swapImageBR(); + } } } } } } +bool ImageViewport::loadImage(unsigned int *buffer, unsigned int size, unsigned int format, double ts) +{ + unsigned int *tmp_image; + bool ret; + + // if scale was changed + if (m_scaleChange) { + // reset image + init(m_capSize[0], m_capSize[1]); + } + + // size must be identical + if (size < getBuffSize()) + return false; + + if (m_avail) { + // just copy + return ImageBase::loadImage(buffer, size, format, ts); + } + else { + tmp_image = m_image; + m_image = buffer; + calcViewport(0, ts, format); + ret = m_avail; + m_image = tmp_image; + // since the image was not loaded to our buffer, it's not valid + m_avail = false; + } + return ret; +} // cast Image pointer to ImageViewport @@ -336,7 +401,7 @@ int ImageViewport_setCaptureSize(PyImage *self, PyObject *value, void *closure) // methods structure static PyMethodDef imageViewportMethods[] = { // methods from ImageBase class - {"refresh", (PyCFunction)Image_refresh, METH_NOARGS, "Refresh image - invalidate its current content"}, + {"refresh", (PyCFunction)Image_refresh, METH_VARARGS, "Refresh image - invalidate its current content"}, {NULL} }; // attributes structure diff --git a/source/gameengine/VideoTexture/ImageViewport.h b/source/gameengine/VideoTexture/ImageViewport.h index 10d894a9fb8..8a7e9cfd2ba 100644 --- a/source/gameengine/VideoTexture/ImageViewport.h +++ b/source/gameengine/VideoTexture/ImageViewport.h @@ -35,6 +35,7 @@ #include "Common.h" #include "ImageBase.h" +#include "RAS_IOffScreen.h" /// class for viewport access @@ -42,7 +43,7 @@ class ImageViewport : public ImageBase { public: /// constructor - ImageViewport (void); + ImageViewport (PyRASOffScreen *offscreen=NULL); /// destructor virtual ~ImageViewport (void); @@ -67,6 +68,9 @@ public: /// set position in viewport void setPosition (GLint pos[2] = NULL); + /// capture image from viewport to user buffer + virtual bool loadImage(unsigned int *buffer, unsigned int size, unsigned int format, double ts); + protected: /// frame buffer rectangle GLint m_viewport[4]; @@ -89,7 +93,10 @@ protected: bool m_texInit; /// capture image from viewport - virtual void calcImage (unsigned int texId, double ts); + virtual void calcImage (unsigned int texId, double ts) { calcViewport(texId, ts, GL_RGBA); } + + /// capture image from viewport + virtual void calcViewport (unsigned int texId, double ts, unsigned int format); /// get viewport size GLint * getViewportSize (void) { return m_viewport + 2; } diff --git a/source/gameengine/VideoTexture/Texture.cpp b/source/gameengine/VideoTexture/Texture.cpp index f1c7bc303ee..bb995747360 100644 --- a/source/gameengine/VideoTexture/Texture.cpp +++ b/source/gameengine/VideoTexture/Texture.cpp @@ -393,9 +393,10 @@ static PyObject *Texture_refresh(Texture *self, PyObject *args) } // load texture for rendering loadTexture(self->m_actTex, texture, size, self->m_mipmap); - - // refresh texture source, if required - if (refreshSource) self->m_source->m_image->refresh(); + } + // refresh texture source, if required + if (refreshSource) { + self->m_source->m_image->refresh(); } } } diff --git a/source/gameengine/VideoTexture/VideoBase.cpp b/source/gameengine/VideoTexture/VideoBase.cpp index 9c8df0ca8c4..d373055b5df 100644 --- a/source/gameengine/VideoTexture/VideoBase.cpp +++ b/source/gameengine/VideoTexture/VideoBase.cpp @@ -137,8 +137,53 @@ PyObject *Video_getStatus(PyImage *self, void *closure) } // refresh video -PyObject *Video_refresh(PyImage *self) +PyObject *Video_refresh(PyImage *self, PyObject *args) { + Py_buffer buffer; + char *mode = NULL; + unsigned int format; + double ts = -1.0; + + memset(&buffer, 0, sizeof(buffer)); + if (PyArg_ParseTuple(args, "|s*sd:refresh", &buffer, &mode, &ts)) { + if (buffer.buf) { + // a target buffer is provided, verify its format + if (buffer.readonly) { + PyErr_SetString(PyExc_TypeError, "Buffers passed in argument must be writable"); + } + else if (!PyBuffer_IsContiguous(&buffer, 'C')) { + PyErr_SetString(PyExc_TypeError, "Buffers passed in argument must be contiguous in memory"); + } + else if (((intptr_t)buffer.buf & 3) != 0) { + PyErr_SetString(PyExc_TypeError, "Buffers passed in argument must be aligned to 4 bytes boundary"); + } + else { + // ready to get the image into our buffer + try { + if (mode == NULL || !strcmp(mode, "RGBA")) + format = GL_RGBA; + else if (!strcmp(mode, "BGRA")) + format = GL_BGRA; + else + THRWEXCP(InvalidImageMode,S_OK); + + if (!self->m_image->loadImage((unsigned int *)buffer.buf, buffer.len, format, ts)) { + PyErr_SetString(PyExc_TypeError, "Could not load the buffer, perhaps size is not compatible"); + } + } + catch (Exception & exp) { + exp.report(); + } + } + PyBuffer_Release(&buffer); + if (PyErr_Occurred()) + return NULL; + } + } + else + { + return NULL; + } getVideo(self)->refresh(); return Video_getStatus(self, NULL); } diff --git a/source/gameengine/VideoTexture/VideoBase.h b/source/gameengine/VideoTexture/VideoBase.h index 6f35c474300..77f46fdccd8 100644 --- a/source/gameengine/VideoTexture/VideoBase.h +++ b/source/gameengine/VideoTexture/VideoBase.h @@ -190,7 +190,7 @@ void Video_open(VideoBase *self, char *file, short captureID); PyObject *Video_play(PyImage *self); PyObject *Video_pause(PyImage *self); PyObject *Video_stop(PyImage *self); -PyObject *Video_refresh(PyImage *self); +PyObject *Video_refresh(PyImage *self, PyObject *args); PyObject *Video_getStatus(PyImage *self, void *closure); PyObject *Video_getRange(PyImage *self, void *closure); int Video_setRange(PyImage *self, PyObject *value, void *closure); diff --git a/source/gameengine/VideoTexture/VideoFFmpeg.cpp b/source/gameengine/VideoTexture/VideoFFmpeg.cpp index 5fed1211d6c..083e9e28502 100644 --- a/source/gameengine/VideoTexture/VideoFFmpeg.cpp +++ b/source/gameengine/VideoTexture/VideoFFmpeg.cpp @@ -1203,7 +1203,7 @@ static PyMethodDef videoMethods[] = {"play", (PyCFunction)Video_play, METH_NOARGS, "Play (restart) video"}, {"pause", (PyCFunction)Video_pause, METH_NOARGS, "pause video"}, {"stop", (PyCFunction)Video_stop, METH_NOARGS, "stop video (play will replay it from start)"}, - {"refresh", (PyCFunction)Video_refresh, METH_NOARGS, "Refresh video - get its status"}, + {"refresh", (PyCFunction)Video_refresh, METH_VARARGS, "Refresh video - get its status"}, {NULL} }; // attributes structure @@ -1326,7 +1326,7 @@ static PyObject *Image_reload(PyImage *self, PyObject *args) // methods structure static PyMethodDef imageMethods[] = { // methods from VideoBase class - {"refresh", (PyCFunction)Video_refresh, METH_NOARGS, "Refresh image, i.e. load it"}, + {"refresh", (PyCFunction)Video_refresh, METH_VARARGS, "Refresh image, i.e. load it"}, {"reload", (PyCFunction)Image_reload, METH_VARARGS, "Reload image, i.e. reopen it"}, {NULL} }; -- cgit v1.2.3 From c0bf881ebfa93784946ee3d03ccf15122c1b74c3 Mon Sep 17 00:00:00 2001 From: Benoit Bolsee Date: Fri, 10 Jun 2016 00:28:19 +0200 Subject: BL_Shader.setUniformEyef(name) defines a uniform that reflects the eye being rendered in stereo mode: 0.0 for the left eye, 0.5 for the right eye. In non stereo mode, the value of the uniform is fixed to 0.0. The typical use of this uniform is in stereo mode to sample stereo textures containing the left and right eye images in a top-bottom order. python: shader = obj.meshes[0].materials[mat].getShader() shader.setUniformEyef("eye") shader: uniform float eye; uniform sampler2D tex; void main(void) { vec4 color; float ty, tx; tx = gl_TexCoord[0].x; ty = eye+gl_TexCoord[0].y*0.5; // ty will be between 0 and 0.5 for the left eye render // and 0.5 and 1.0 for the right eye render. color = texture(tex, vec2(tx, ty)); ... } --- source/gameengine/Ketsji/BL_Shader.cpp | 156 +++++++++++++++++++++------------ source/gameengine/Ketsji/BL_Shader.h | 4 +- 2 files changed, 102 insertions(+), 58 deletions(-) (limited to 'source/gameengine') diff --git a/source/gameengine/Ketsji/BL_Shader.cpp b/source/gameengine/Ketsji/BL_Shader.cpp index 6613780a0f8..72815cadc70 100644 --- a/source/gameengine/Ketsji/BL_Shader.cpp +++ b/source/gameengine/Ketsji/BL_Shader.cpp @@ -32,6 +32,7 @@ #include "MT_Matrix4x4.h" #include "MT_Matrix3x3.h" #include "KX_PyMath.h" +#include "KX_PythonInit.h" #include "MEM_guardedalloc.h" #include "RAS_MeshObject.h" @@ -67,15 +68,16 @@ BL_Uniform::~BL_Uniform() #endif } -void BL_Uniform::Apply(class BL_Shader *shader) +bool BL_Uniform::Apply(class BL_Shader *shader) { #ifdef SORT_UNIFORMS + RAS_IRasterizer *ras; MT_assert(mType > UNI_NONE && mType < UNI_MAX && mData); - if (!mDirty) { - return; - } + if (!mDirty) + return false; + mDirty = false; switch (mType) { case UNI_FLOAT: { @@ -83,6 +85,15 @@ void BL_Uniform::Apply(class BL_Shader *shader) glUniform1fARB(mLoc, (GLfloat)*f); break; } + case UNI_FLOAT_EYE: + { + float *f = (float*)mData; + ras = KX_GetActiveEngine()->GetRasterizer(); + *f = (ras->GetEye() == RAS_IRasterizer::RAS_STEREO_LEFTEYE) ? 0.0f : 0.5f; + glUniform1fARB(mLoc, (GLfloat)*f); + mDirty = (ras->Stereo()) ? true : false; + break; + } case UNI_INT: { int *f = (int *)mData; @@ -138,7 +149,7 @@ void BL_Uniform::Apply(class BL_Shader *shader) break; } } - mDirty = false; + return mDirty; #endif } @@ -274,11 +285,10 @@ void BL_Shader::ApplyShader() return; } - for (unsigned int i = 0; i < mUniforms.size(); i++) { - mUniforms[i]->Apply(this); - } - mDirty = false; + for (unsigned int i=0; iApply(this); + } #endif } @@ -314,64 +324,70 @@ bool BL_Shader::LinkProgram() return false; } - // -- vertex shader ------------------ - tmpVert = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB); - glShaderSourceARB(tmpVert, 1, (const char **)&vertProg, 0); - glCompileShaderARB(tmpVert); - glGetObjectParameterivARB(tmpVert, GL_OBJECT_INFO_LOG_LENGTH_ARB, (GLint *)&vertlen); - - // print info if any - if (vertlen > 0 && vertlen < MAX_LOG_LEN) { - logInf = (char *)MEM_mallocN(vertlen, "vert-log"); - glGetInfoLogARB(tmpVert, vertlen, (GLsizei *)&char_len, logInf); - - if (char_len > 0) { - spit("---- Vertex Shader Error ----"); - spit(logInf); + if (vertProg[0] != 0) { + // -- vertex shader ------------------ + tmpVert = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB); + glShaderSourceARB(tmpVert, 1, (const char**)&vertProg, 0); + glCompileShaderARB(tmpVert); + glGetObjectParameterivARB(tmpVert, GL_OBJECT_INFO_LOG_LENGTH_ARB, (GLint*)&vertlen); + + // print info if any + if (vertlen > 0 && vertlen < MAX_LOG_LEN) { + logInf = (char*)MEM_mallocN(vertlen, "vert-log"); + glGetInfoLogARB(tmpVert, vertlen, (GLsizei*)&char_len, logInf); + if (char_len > 0) { + spit("---- Vertex Shader Error ----"); + spit(logInf); + } + MEM_freeN(logInf); + logInf = 0; + } + // check for compile errors + glGetObjectParameterivARB(tmpVert, GL_OBJECT_COMPILE_STATUS_ARB, (GLint*)&vertstatus); + if (!vertstatus) { + spit("---- Vertex shader failed to compile ----"); + goto programError; } - - MEM_freeN(logInf); - logInf = 0; - } - - // check for compile errors - glGetObjectParameterivARB(tmpVert, GL_OBJECT_COMPILE_STATUS_ARB, (GLint *)&vertstatus); - if (!vertstatus) { - spit("---- Vertex shader failed to compile ----"); - goto programError; } - // -- fragment shader ---------------- - tmpFrag = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB); - glShaderSourceARB(tmpFrag, 1, (const char **)&fragProg, 0); - glCompileShaderARB(tmpFrag); - glGetObjectParameterivARB(tmpFrag, GL_OBJECT_INFO_LOG_LENGTH_ARB, (GLint *)&fraglen); - - if (fraglen > 0 && fraglen < MAX_LOG_LEN) { - logInf = (char *)MEM_mallocN(fraglen, "frag-log"); - glGetInfoLogARB(tmpFrag, fraglen, (GLsizei *)&char_len, logInf); - - if (char_len > 0) { - spit("---- Fragment Shader Error ----"); - spit(logInf); + if (fragProg[0] != 0) { + // -- fragment shader ---------------- + tmpFrag = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB); + glShaderSourceARB(tmpFrag, 1, (const char**)&fragProg, 0); + glCompileShaderARB(tmpFrag); + glGetObjectParameterivARB(tmpFrag, GL_OBJECT_INFO_LOG_LENGTH_ARB, (GLint*)&fraglen); + if (fraglen > 0 && fraglen < MAX_LOG_LEN) { + logInf = (char*)MEM_mallocN(fraglen, "frag-log"); + glGetInfoLogARB(tmpFrag, fraglen, (GLsizei*)&char_len, logInf); + if (char_len > 0) { + spit("---- Fragment Shader Error ----"); + spit(logInf); + } + MEM_freeN(logInf); + logInf = 0; } - MEM_freeN(logInf); - logInf = 0; + glGetObjectParameterivARB(tmpFrag, GL_OBJECT_COMPILE_STATUS_ARB, (GLint*)&fragstatus); + if (!fragstatus) { + spit("---- Fragment shader failed to compile ----"); + goto programError; + } } - - glGetObjectParameterivARB(tmpFrag, GL_OBJECT_COMPILE_STATUS_ARB, (GLint *)&fragstatus); - - if (!fragstatus) { - spit("---- Fragment shader failed to compile ----"); + + if (!tmpFrag && !tmpVert) { + spit("---- No shader given ----"); goto programError; } // -- program ------------------------ // set compiled vert/frag shader & link tmpProg = glCreateProgramObjectARB(); - glAttachObjectARB(tmpProg, tmpVert); - glAttachObjectARB(tmpProg, tmpFrag); + if (tmpVert) { + glAttachObjectARB(tmpProg, tmpVert); + } + if (tmpFrag) { + glAttachObjectARB(tmpProg, tmpFrag); + } glLinkProgramARB(tmpProg); glGetObjectParameterivARB(tmpProg, GL_OBJECT_INFO_LOG_LENGTH_ARB, (GLint *)&proglen); glGetObjectParameterivARB(tmpProg, GL_OBJECT_LINK_STATUS_ARB, (GLint *)&progstatus); @@ -396,8 +412,12 @@ bool BL_Shader::LinkProgram() // set mShader = tmpProg; - glDeleteObjectARB(tmpVert); - glDeleteObjectARB(tmpFrag); + if (tmpVert) { + glDeleteObjectARB(tmpVert); + } + if (tmpFrag) { + glDeleteObjectARB(tmpFrag); + } mOk = 1; mError = 0; return true; @@ -748,6 +768,7 @@ PyMethodDef BL_Shader::Methods[] = { KX_PYMETHODTABLE(BL_Shader, validate), // access functions KX_PYMETHODTABLE(BL_Shader, isValid), + KX_PYMETHODTABLE(BL_Shader, setUniformEyef), KX_PYMETHODTABLE(BL_Shader, setUniform1f), KX_PYMETHODTABLE(BL_Shader, setUniform2f), KX_PYMETHODTABLE(BL_Shader, setUniform3f), @@ -1019,6 +1040,27 @@ KX_PYMETHODDEF_DOC(BL_Shader, setUniform4f, "setUniform4f(name, fx,fy,fz, fw) ") return NULL; } +KX_PYMETHODDEF_DOC(BL_Shader, setUniformEyef, "setUniformEyef(name)") +{ + if (mError) { + Py_RETURN_NONE; + } + const char *uniform; + float value = 0.0f; + if (PyArg_ParseTuple(args, "s:setUniformEyef", &uniform)) { + int loc = GetUniformLocation(uniform); + if (loc != -1) { +#ifdef SORT_UNIFORMS + SetUniformfv(loc, BL_Uniform::UNI_FLOAT_EYE, &value, sizeof(float)); +#else + SetUniform(loc, (int)value); +#endif + } + Py_RETURN_NONE; + } + return NULL; +} + KX_PYMETHODDEF_DOC(BL_Shader, setUniform1i, "setUniform1i(name, ix)") { if (mError) { diff --git a/source/gameengine/Ketsji/BL_Shader.h b/source/gameengine/Ketsji/BL_Shader.h index aef4b42a85a..5de715d67d4 100644 --- a/source/gameengine/Ketsji/BL_Shader.h +++ b/source/gameengine/Ketsji/BL_Shader.h @@ -64,10 +64,11 @@ public: UNI_FLOAT4, UNI_MAT3, UNI_MAT4, + UNI_FLOAT_EYE, UNI_MAX }; - void Apply(class BL_Shader *shader); + bool Apply(class BL_Shader *shader); void SetData(int location, int type, bool transpose = false); int GetLocation() { return mLoc; } void *getData() { return mData; } @@ -226,6 +227,7 @@ public: KX_PYMETHOD_DOC(BL_Shader, setUniform3i); KX_PYMETHOD_DOC(BL_Shader, setUniform2i); KX_PYMETHOD_DOC(BL_Shader, setUniform1i); + KX_PYMETHOD_DOC(BL_Shader, setUniformEyef); KX_PYMETHOD_DOC(BL_Shader, setUniformfv); KX_PYMETHOD_DOC(BL_Shader, setUniformiv); KX_PYMETHOD_DOC(BL_Shader, setUniformMatrix4); -- cgit v1.2.3 From eea89417f4a3e2221e25c9c3ae6a601a61a05c22 Mon Sep 17 00:00:00 2001 From: Benoit Bolsee Date: Fri, 10 Jun 2016 10:09:26 +0200 Subject: BGE: DeckLink card support for video capture and streaming. You can capture and stream video in the BGE using the DeckLink video cards from Black Magic Design. You need a card and Desktop Video software version 10.4 or above to use these features in the BGE. Many thanks to Nuno Estanquiero who tested the patch extensively on a variety of Decklink products, it wouldn't have been possible without his help. You can find a brief summary of the decklink features here: https://wiki.blender.org/index.php/Dev:Source/GameEngine/Decklink The full API details and samples are in the Python API documentation. bge.texture.VideoDeckLink(format, capture=0): Use this object to capture a video stream. the format argument describes the video and pixel formats and the capture argument the card number. This object can be used as a source for bge.texture.Texture so that the frame is sent to the GPU, or by itself using the new refresh method to get the video frame in a buffer. The frames are usually not in RGB but in YUV format (8bit or 10bit); they require a shader to extract the RGB components in the GPU. Details and sample shaders in the documentation. 3D video capture is supported: the frames are double height with left and right eyes in top-bottom order. The 'eye' uniform (see setUniformEyef) can be used to sample the 3D frame when the BGE is also in stereo mode. This allows to composite a 3D video stream with a 3D scene and render it in stereo. In Windows, and if you have a nVidia Quadro GPU, you can benefit of an additional performance boost by using 'GPUDirect': a method to send a video frame to the GPU without going through the OGL driver. The 'pinned memory' OGL extension is also supported (only on high-end AMD GPU) with the same effect. bge.texture.DeckLink(cardIdx=0, format=""): Use this object to send video frame to a DeckLink card. Only the immediate mode is supported, the scheduled mode is not implemented. This object is similar to bge.texture.Texture: you need to attach a image source and call refresh() to compute and send the frame to the card. This object is best suited for video keying: a video stream (not captured) flows through the card and the frame you send to the card are displayed above it (the card does the compositing automatically based on the alpha channel). At the time of this commit, 3D video keying is supported in the BGE but not in the DeckLink card due to a color space issue. --- source/gameengine/VideoTexture/CMakeLists.txt | 13 + source/gameengine/VideoTexture/Common.h | 3 +- source/gameengine/VideoTexture/DeckLink.cpp | 813 ++++++++++++++ source/gameengine/VideoTexture/DeckLink.h | 86 ++ source/gameengine/VideoTexture/Exception.cpp | 2 +- source/gameengine/VideoTexture/VideoDeckLink.cpp | 1228 ++++++++++++++++++++++ source/gameengine/VideoTexture/VideoDeckLink.h | 256 +++++ source/gameengine/VideoTexture/blendVideoTex.cpp | 15 + 8 files changed, 2414 insertions(+), 2 deletions(-) create mode 100644 source/gameengine/VideoTexture/DeckLink.cpp create mode 100644 source/gameengine/VideoTexture/DeckLink.h create mode 100644 source/gameengine/VideoTexture/VideoDeckLink.cpp create mode 100644 source/gameengine/VideoTexture/VideoDeckLink.h (limited to 'source/gameengine') diff --git a/source/gameengine/VideoTexture/CMakeLists.txt b/source/gameengine/VideoTexture/CMakeLists.txt index 4be9a9abe5c..1eb09b02e05 100644 --- a/source/gameengine/VideoTexture/CMakeLists.txt +++ b/source/gameengine/VideoTexture/CMakeLists.txt @@ -45,6 +45,9 @@ set(INC ../../../intern/glew-mx ../../../intern/guardedalloc ../../../intern/string + ../../../intern/decklink + ../../../intern/gpudirect + ../../../intern/atomic ) set(INC_SYS @@ -68,8 +71,10 @@ set(SRC ImageViewport.cpp PyTypeList.cpp Texture.cpp + DeckLink.cpp VideoBase.cpp VideoFFmpeg.cpp + VideoDeckLink.cpp blendVideoTex.cpp BlendType.h @@ -87,8 +92,10 @@ set(SRC ImageViewport.h PyTypeList.h Texture.h + DeckLink.h VideoBase.h VideoFFmpeg.h + VideoDeckLink.h ) if(WITH_CODEC_FFMPEG) @@ -100,7 +107,13 @@ if(WITH_CODEC_FFMPEG) remove_strict_flags_file( VideoFFmpeg.cpp + VideoDeckLink + DeckLink ) endif() +if(WITH_GAMEENGINE_DECKLINK) + add_definitions(-DWITH_GAMEENGINE_DECKLINK) +endif() + blender_add_lib(ge_videotex "${SRC}" "${INC}" "${INC_SYS}") diff --git a/source/gameengine/VideoTexture/Common.h b/source/gameengine/VideoTexture/Common.h index 90f7e66452a..22ea177addc 100644 --- a/source/gameengine/VideoTexture/Common.h +++ b/source/gameengine/VideoTexture/Common.h @@ -36,7 +36,8 @@ #define NULL 0 #endif -#ifndef HRESULT +#ifndef _HRESULT_DEFINED +#define _HRESULT_DEFINED #define HRESULT long #endif diff --git a/source/gameengine/VideoTexture/DeckLink.cpp b/source/gameengine/VideoTexture/DeckLink.cpp new file mode 100644 index 00000000000..0506756ef2d --- /dev/null +++ b/source/gameengine/VideoTexture/DeckLink.cpp @@ -0,0 +1,813 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program 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 Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * +* The Original Code is Copyright (C) 2015, Blender Foundation +* All rights reserved. +* +* The Original Code is: all of this file. +* +* Contributor(s): Blender Foundation. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file gameengine/VideoTexture/Texture.cpp + * \ingroup bgevideotex + */ + +#ifdef WITH_GAMEENGINE_DECKLINK + +// implementation + +// FFmpeg defines its own version of stdint.h on Windows. +// Decklink needs FFmpeg, so it uses its version of stdint.h +// this is necessary for INT64_C macro +#ifndef __STDC_CONSTANT_MACROS +#define __STDC_CONSTANT_MACROS +#endif +// this is necessary for UINTPTR_MAX (used by atomic-ops) +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS +#endif + +#include "atomic_ops.h" + +#include "EXP_PyObjectPlus.h" +#include "KX_KetsjiEngine.h" +#include "KX_PythonInit.h" +#include "DeckLink.h" + +#include + +// macro for exception handling and logging +#define CATCH_EXCP catch (Exception & exp) \ +{ exp.report(); return NULL; } + +static struct +{ + const char *name; + BMDDisplayMode mode; +} sModeStringTab[] = { + { "NTSC", bmdModeNTSC }, + { "NTSC2398", bmdModeNTSC2398 }, + { "PAL", bmdModePAL }, + { "NTSCp", bmdModeNTSCp }, + { "PALp", bmdModePALp }, + + /* HD 1080 Modes */ + + { "HD1080p2398", bmdModeHD1080p2398 }, + { "HD1080p24", bmdModeHD1080p24 }, + { "HD1080p25", bmdModeHD1080p25 }, + { "HD1080p2997", bmdModeHD1080p2997 }, + { "HD1080p30", bmdModeHD1080p30 }, + { "HD1080i50", bmdModeHD1080i50 }, + { "HD1080i5994", bmdModeHD1080i5994 }, + { "HD1080i6000", bmdModeHD1080i6000 }, + { "HD1080p50", bmdModeHD1080p50 }, + { "HD1080p5994", bmdModeHD1080p5994 }, + { "HD1080p6000", bmdModeHD1080p6000 }, + + /* HD 720 Modes */ + + { "HD720p50", bmdModeHD720p50 }, + { "HD720p5994", bmdModeHD720p5994 }, + { "HD720p60", bmdModeHD720p60 }, + + /* 2k Modes */ + + { "2k2398", bmdMode2k2398 }, + { "2k24", bmdMode2k24 }, + { "2k25", bmdMode2k25 }, + + /* DCI Modes (output only) */ + + { "2kDCI2398", bmdMode2kDCI2398 }, + { "2kDCI24", bmdMode2kDCI24 }, + { "2kDCI25", bmdMode2kDCI25 }, + + /* 4k Modes */ + + { "4K2160p2398", bmdMode4K2160p2398 }, + { "4K2160p24", bmdMode4K2160p24 }, + { "4K2160p25", bmdMode4K2160p25 }, + { "4K2160p2997", bmdMode4K2160p2997 }, + { "4K2160p30", bmdMode4K2160p30 }, + { "4K2160p50", bmdMode4K2160p50 }, + { "4K2160p5994", bmdMode4K2160p5994 }, + { "4K2160p60", bmdMode4K2160p60 }, + // sentinel + { NULL } +}; + +static struct +{ + const char *name; + BMDPixelFormat format; +} sFormatStringTab[] = { + { "8BitYUV", bmdFormat8BitYUV }, + { "10BitYUV", bmdFormat10BitYUV }, + { "8BitARGB", bmdFormat8BitARGB }, + { "8BitBGRA", bmdFormat8BitBGRA }, + { "10BitRGB", bmdFormat10BitRGB }, + { "12BitRGB", bmdFormat12BitRGB }, + { "12BitRGBLE", bmdFormat12BitRGBLE }, + { "10BitRGBXLE", bmdFormat10BitRGBXLE }, + { "10BitRGBX", bmdFormat10BitRGBX }, + // sentinel + { NULL } +}; + +ExceptionID DeckLinkBadDisplayMode, DeckLinkBadPixelFormat; +ExpDesc DeckLinkBadDisplayModeDesc(DeckLinkBadDisplayMode, "Invalid or unsupported display mode"); +ExpDesc DeckLinkBadPixelFormatDesc(DeckLinkBadPixelFormat, "Invalid or unsupported pixel format"); + +HRESULT decklink_ReadDisplayMode(const char *format, size_t len, BMDDisplayMode *displayMode) +{ + int i; + + if (len == 0) + len = strlen(format); + for (i = 0; sModeStringTab[i].name != NULL; i++) { + if (strlen(sModeStringTab[i].name) == len && + !strncmp(sModeStringTab[i].name, format, len)) + { + *displayMode = sModeStringTab[i].mode; + return S_OK; + } + } + if (len != 4) + THRWEXCP(DeckLinkBadDisplayMode, S_OK); + // assume the user entered directly the mode value as a 4 char string + *displayMode = (BMDDisplayMode)((((uint32_t)format[0]) << 24) + (((uint32_t)format[1]) << 16) + (((uint32_t)format[2]) << 8) + ((uint32_t)format[3])); + return S_OK; +} + +HRESULT decklink_ReadPixelFormat(const char *format, size_t len, BMDPixelFormat *pixelFormat) +{ + int i; + + if (!len) + len = strlen(format); + for (i = 0; sFormatStringTab[i].name != NULL; i++) { + if (strlen(sFormatStringTab[i].name) == len && + !strncmp(sFormatStringTab[i].name, format, len)) + { + *pixelFormat = sFormatStringTab[i].format; + return S_OK; + } + } + if (len != 4) + THRWEXCP(DeckLinkBadPixelFormat, S_OK); + // assume the user entered directly the mode value as a 4 char string + *pixelFormat = (BMDPixelFormat)((((uint32_t)format[0]) << 24) + (((uint32_t)format[1]) << 16) + (((uint32_t)format[2]) << 8) + ((uint32_t)format[3])); + return S_OK; +} + +class DeckLink3DFrameWrapper : public IDeckLinkVideoFrame, IDeckLinkVideoFrame3DExtensions +{ +public: + // IUnknown + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID *ppv) + { + if (!memcmp(&iid, &IID_IDeckLinkVideoFrame3DExtensions, sizeof(iid))) { + if (mpRightEye) { + *ppv = (IDeckLinkVideoFrame3DExtensions*)this; + return S_OK; + } + } + return E_NOTIMPL; + } + virtual ULONG STDMETHODCALLTYPE AddRef(void) { return 1U; } + virtual ULONG STDMETHODCALLTYPE Release(void) { return 1U; } + // IDeckLinkVideoFrame + virtual long STDMETHODCALLTYPE GetWidth(void) { return mpLeftEye->GetWidth(); } + virtual long STDMETHODCALLTYPE GetHeight(void) { return mpLeftEye->GetHeight(); } + virtual long STDMETHODCALLTYPE GetRowBytes(void) { return mpLeftEye->GetRowBytes(); } + virtual BMDPixelFormat STDMETHODCALLTYPE GetPixelFormat(void) { return mpLeftEye->GetPixelFormat(); } + virtual BMDFrameFlags STDMETHODCALLTYPE GetFlags(void) { return mpLeftEye->GetFlags(); } + virtual HRESULT STDMETHODCALLTYPE GetBytes(void **buffer) { return mpLeftEye->GetBytes(buffer); } + virtual HRESULT STDMETHODCALLTYPE GetTimecode(BMDTimecodeFormat format,IDeckLinkTimecode **timecode) + { return mpLeftEye->GetTimecode(format, timecode); } + virtual HRESULT STDMETHODCALLTYPE GetAncillaryData(IDeckLinkVideoFrameAncillary **ancillary) + { return mpLeftEye->GetAncillaryData(ancillary); } + // IDeckLinkVideoFrame3DExtensions + virtual BMDVideo3DPackingFormat STDMETHODCALLTYPE Get3DPackingFormat(void) + { + return bmdVideo3DPackingLeftOnly; + } + virtual HRESULT STDMETHODCALLTYPE GetFrameForRightEye( + /* [out] */ IDeckLinkVideoFrame **rightEyeFrame) + { + mpRightEye->AddRef(); + *rightEyeFrame = mpRightEye; + return S_OK; + } + // Constructor + DeckLink3DFrameWrapper(IDeckLinkVideoFrame *leftEye, IDeckLinkVideoFrame *rightEye) + { + mpLeftEye = leftEye; + mpRightEye = rightEye; + } + // no need for a destructor, it's just a wrapper +private: + IDeckLinkVideoFrame *mpLeftEye; + IDeckLinkVideoFrame *mpRightEye; +}; + +static void decklink_Reset(DeckLink *self) +{ + self->m_lastClock = 0.0; + self->mDLOutput = NULL; + self->mUse3D = false; + self->mDisplayMode = bmdModeUnknown; + self->mKeyingSupported = false; + self->mHDKeyingSupported = false; + self->mSize[0] = 0; + self->mSize[1] = 0; + self->mFrameSize = 0; + self->mLeftFrame = NULL; + self->mRightFrame = NULL; + self->mKeyer = NULL; + self->mUseKeying = false; + self->mKeyingLevel = 255; + self->mUseExtend = false; +} + +#ifdef __BIG_ENDIAN__ +#define CONV_PIXEL(i) ((((i)>>16)&0xFF00)+(((i)&0xFF00)<<16)+((i)&0xFF00FF)) +#else +#define CONV_PIXEL(i) ((((i)&0xFF)<<16)+(((i)>>16)&0xFF)+((i)&0xFF00FF00)) +#endif + +// adapt the pixel format and picture size from VideoTexture (RGBA) to DeckLink (BGRA) +static void decklink_ConvImage(uint32_t *dest, const short *destSize, const uint32_t *source, const short *srcSize, bool extend) +{ + short w, h, x, y; + const uint32_t *s; + uint32_t *d, p; + bool sameSize = (destSize[0] == srcSize[0] && destSize[1] == srcSize[1]); + + if (sameSize || !extend) { + // here we convert pixel by pixel + w = (destSize[0] < srcSize[0]) ? destSize[0] : srcSize[0]; + h = (destSize[1] < srcSize[1]) ? destSize[1] : srcSize[1]; + for (y = 0; y < h; ++y) { + s = source + y*srcSize[0]; + d = dest + y*destSize[0]; + for (x = 0; x < w; ++x, ++s, ++d) { + *d = CONV_PIXEL(*s); + } + } + } + else { + // here we scale + // interpolation accumulator + int accHeight = srcSize[1] >> 1; + d = dest; + s = source; + // process image rows + for (y = 0; y < srcSize[1]; ++y) { + // increase height accum + accHeight += destSize[1]; + // if pixel row has to be drawn + if (accHeight >= srcSize[1]) { + // decrease accum + accHeight -= srcSize[1]; + // width accum + int accWidth = srcSize[0] >> 1; + // process row + for (x = 0; x < srcSize[0]; ++x, ++s) { + // increase width accum + accWidth += destSize[0]; + // convert pixel + p = CONV_PIXEL(*s); + // if pixel has to be drown one or more times + while (accWidth >= srcSize[0]) { + // decrease accum + accWidth -= srcSize[0]; + *d++ = p; + } + } + // if there should be more identical lines + while (accHeight >= srcSize[1]) { + accHeight -= srcSize[1]; + // copy previous line + memcpy(d, d - destSize[0], 4 * destSize[0]); + d += destSize[0]; + } + } + else { + // if we skip a source line + s += srcSize[0]; + } + } + } +} + +// DeckLink object allocation +static PyObject *DeckLink_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + // allocate object + DeckLink * self = reinterpret_cast(type->tp_alloc(type, 0)); + // initialize object structure + decklink_Reset(self); + // m_leftEye is a python object, it's handled by python + self->m_leftEye = NULL; + self->m_rightEye = NULL; + // return allocated object + return reinterpret_cast(self); +} + + +// forward declaration +PyObject *DeckLink_close(DeckLink *self); +int DeckLink_setSource(DeckLink *self, PyObject *value, void *closure); + + +// DeckLink object deallocation +static void DeckLink_dealloc(DeckLink *self) +{ + // release renderer + Py_XDECREF(self->m_leftEye); + // close decklink + PyObject *ret = DeckLink_close(self); + Py_DECREF(ret); + // release object + Py_TYPE((PyObject *)self)->tp_free((PyObject *)self); +} + + +ExceptionID AutoDetectionNotAvail, DeckLinkOpenCard, DeckLinkBadFormat, DeckLinkInternalError; +ExpDesc AutoDetectionNotAvailDesc(AutoDetectionNotAvail, "Auto detection not yet available"); +ExpDesc DeckLinkOpenCardDesc(DeckLinkOpenCard, "Cannot open card for output"); +ExpDesc DeckLinkBadFormatDesc(DeckLinkBadFormat, "Invalid or unsupported output format, use [/3D]"); +ExpDesc DeckLinkInternalErrorDesc(DeckLinkInternalError, "DeckLink API internal error, please report"); + +// DeckLink object initialization +static int DeckLink_init(DeckLink *self, PyObject *args, PyObject *kwds) +{ + IDeckLinkIterator* pIterator; + IDeckLinkAttributes* pAttributes; + IDeckLinkDisplayModeIterator* pDisplayModeIterator; + IDeckLinkDisplayMode* pDisplayMode; + IDeckLink* pDL; + char* p3D; + BOOL flag; + size_t len; + int i; + uint32_t displayFlags; + BMDVideoOutputFlags outputFlags; + BMDDisplayModeSupport support; + uint32_t* bytes; + + + // material ID + short cardIdx = 0; + // texture ID + char *format = NULL; + + static const char *kwlist[] = {"cardIdx", "format", NULL}; + // get parameters + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|hs", + const_cast(kwlist), &cardIdx, &format)) + return -1; + + try { + if (format == NULL) { + THRWEXCP(AutoDetectionNotAvail, S_OK); + } + + if ((p3D = strchr(format, '/')) != NULL && strcmp(p3D, "/3D")) + THRWEXCP(DeckLinkBadFormat, S_OK); + self->mUse3D = (p3D) ? true : false; + // read the mode + len = (p3D) ? (size_t)(p3D - format) : strlen(format); + // throws if bad mode + decklink_ReadDisplayMode(format, len, &self->mDisplayMode); + + pIterator = BMD_CreateDeckLinkIterator(); + pDL = NULL; + if (pIterator) { + i = 0; + while (pIterator->Next(&pDL) == S_OK) { + if (i == cardIdx) { + break; + } + i++; + pDL->Release(); + pDL = NULL; + } + pIterator->Release(); + } + + if (!pDL) { + THRWEXCP(DeckLinkOpenCard, S_OK); + } + // detect the capabilities + if (pDL->QueryInterface(IID_IDeckLinkAttributes, (void**)&pAttributes) == S_OK) { + if (pAttributes->GetFlag(BMDDeckLinkSupportsInternalKeying, &flag) == S_OK && flag) { + self->mKeyingSupported = true; + if (pAttributes->GetFlag(BMDDeckLinkSupportsHDKeying, &flag) == S_OK && flag) { + self->mHDKeyingSupported = true; + } + } + pAttributes->Release(); + } + + if (pDL->QueryInterface(IID_IDeckLinkOutput, (void**)&self->mDLOutput) != S_OK) { + self->mDLOutput = NULL; + } + if (self->mKeyingSupported) { + pDL->QueryInterface(IID_IDeckLinkKeyer, (void **)&self->mKeyer); + } + // we don't need the device anymore, release to avoid leaking + pDL->Release(); + + if (!self->mDLOutput) + THRWEXCP(DeckLinkOpenCard, S_OK); + + if (self->mDLOutput->GetDisplayModeIterator(&pDisplayModeIterator) != S_OK) + THRWEXCP(DeckLinkInternalError, S_OK); + + displayFlags = (self->mUse3D) ? bmdDisplayModeSupports3D : 0; + outputFlags = (self->mUse3D) ? bmdVideoOutputDualStream3D : bmdVideoOutputFlagDefault; + pDisplayMode = NULL; + i = 0; + while (pDisplayModeIterator->Next(&pDisplayMode) == S_OK) { + if (pDisplayMode->GetDisplayMode() == self->mDisplayMode + && (pDisplayMode->GetFlags() & displayFlags) == displayFlags) { + if (self->mDLOutput->DoesSupportVideoMode(self->mDisplayMode, bmdFormat8BitBGRA, outputFlags, &support, NULL) != S_OK || + support == bmdDisplayModeNotSupported) + { + printf("Warning: DeckLink card %d reports no BGRA support, proceed anyway\n", cardIdx); + } + break; + } + pDisplayMode->Release(); + pDisplayMode = NULL; + i++; + } + pDisplayModeIterator->Release(); + + if (!pDisplayMode) + THRWEXCP(DeckLinkBadFormat, S_OK); + self->mSize[0] = pDisplayMode->GetWidth(); + self->mSize[1] = pDisplayMode->GetHeight(); + self->mFrameSize = 4*self->mSize[0]*self->mSize[1]; + pDisplayMode->Release(); + if (self->mDLOutput->EnableVideoOutput(self->mDisplayMode, outputFlags) != S_OK) + // this shouldn't fail + THRWEXCP(DeckLinkOpenCard, S_OK); + + if (self->mDLOutput->CreateVideoFrame(self->mSize[0], self->mSize[1], self->mSize[0] * 4, bmdFormat8BitBGRA, bmdFrameFlagFlipVertical, &self->mLeftFrame) != S_OK) + THRWEXCP(DeckLinkInternalError, S_OK); + // clear alpha channel in the frame buffer + self->mLeftFrame->GetBytes((void **)&bytes); + memset(bytes, 0, self->mFrameSize); + if (self->mUse3D) { + if (self->mDLOutput->CreateVideoFrame(self->mSize[0], self->mSize[1], self->mSize[0] * 4, bmdFormat8BitBGRA, bmdFrameFlagFlipVertical, &self->mRightFrame) != S_OK) + THRWEXCP(DeckLinkInternalError, S_OK); + // clear alpha channel in the frame buffer + self->mRightFrame->GetBytes((void **)&bytes); + memset(bytes, 0, self->mFrameSize); + } + } + catch (Exception & exp) + { + printf("DeckLink: exception when opening card %d: %s\n", cardIdx, exp.what()); + exp.report(); + // normally, the object should be deallocated + return -1; + } + // initialization succeeded + return 0; +} + + +// close added decklink +PyObject *DeckLink_close(DeckLink * self) +{ + if (self->mLeftFrame) + self->mLeftFrame->Release(); + if (self->mRightFrame) + self->mRightFrame->Release(); + if (self->mKeyer) + self->mKeyer->Release(); + if (self->mDLOutput) + self->mDLOutput->Release(); + decklink_Reset(self); + Py_RETURN_NONE; +} + + +// refresh decklink key frame +static PyObject *DeckLink_refresh(DeckLink *self, PyObject *args) +{ + // get parameter - refresh source + PyObject *param; + double ts = -1.0; + + if (!PyArg_ParseTuple(args, "O|d:refresh", ¶m, &ts) || !PyBool_Check(param)) { + // report error + PyErr_SetString(PyExc_TypeError, "The value must be a bool"); + return NULL; + } + // some trick here: we are in the business of loading a key frame in decklink, + // no use to do it if we are still in the same rendering frame. + // We find this out by looking at the engine current clock time + KX_KetsjiEngine* engine = KX_GetActiveEngine(); + if (engine->GetClockTime() != self->m_lastClock) + { + self->m_lastClock = engine->GetClockTime(); + // set source refresh + bool refreshSource = (param == Py_True); + uint32_t *leftEye = NULL; + uint32_t *rightEye = NULL; + // try to process key frame from source + try { + // check if optimization is possible + if (self->m_leftEye != NULL) { + ImageBase *leftImage = self->m_leftEye->m_image; + short * srcSize = leftImage->getSize(); + self->mLeftFrame->GetBytes((void **)&leftEye); + if (srcSize[0] == self->mSize[0] && srcSize[1] == self->mSize[1]) + { + // buffer has same size, can load directly + if (!leftImage->loadImage(leftEye, self->mFrameSize, GL_BGRA, ts)) + leftEye = NULL; + } + else { + // scaling is required, go the hard way + unsigned int *src = leftImage->getImage(0, ts); + if (src != NULL) + decklink_ConvImage(leftEye, self->mSize, src, srcSize, self->mUseExtend); + else + leftEye = NULL; + } + } + if (leftEye) { + if (self->mUse3D && self->m_rightEye != NULL) { + ImageBase *rightImage = self->m_rightEye->m_image; + short * srcSize = rightImage->getSize(); + self->mRightFrame->GetBytes((void **)&rightEye); + if (srcSize[0] == self->mSize[0] && srcSize[1] == self->mSize[1]) + { + // buffer has same size, can load directly + rightImage->loadImage(rightEye, self->mFrameSize, GL_BGRA, ts); + } + else { + // scaling is required, go the hard way + unsigned int *src = rightImage->getImage(0, ts); + if (src != NULL) + decklink_ConvImage(rightEye, self->mSize, src, srcSize, self->mUseExtend); + } + } + if (self->mUse3D) { + DeckLink3DFrameWrapper frame3D( + (IDeckLinkVideoFrame*)self->mLeftFrame, + (IDeckLinkVideoFrame*)self->mRightFrame); + self->mDLOutput->DisplayVideoFrameSync(&frame3D); + } + else { + self->mDLOutput->DisplayVideoFrameSync((IDeckLinkVideoFrame*)self->mLeftFrame); + } + } + // refresh texture source, if required + if (refreshSource) { + if (self->m_leftEye) + self->m_leftEye->m_image->refresh(); + if (self->m_rightEye) + self->m_rightEye->m_image->refresh(); + } + } + CATCH_EXCP; + } + Py_RETURN_NONE; +} + +// get source object +static PyObject *DeckLink_getSource(DeckLink *self, PyObject *value, void *closure) +{ + // if source exists + if (self->m_leftEye != NULL) { + Py_INCREF(self->m_leftEye); + return reinterpret_cast(self->m_leftEye); + } + // otherwise return None + Py_RETURN_NONE; +} + + +// set source object +int DeckLink_setSource(DeckLink *self, PyObject *value, void *closure) +{ + // check new value + if (value == NULL || !pyImageTypes.in(Py_TYPE(value))) { + // report value error + PyErr_SetString(PyExc_TypeError, "Invalid type of value"); + return -1; + } + // increase ref count for new value + Py_INCREF(value); + // release previous + Py_XDECREF(self->m_leftEye); + // set new value + self->m_leftEye = reinterpret_cast(value); + // return success + return 0; +} + +// get source object +static PyObject *DeckLink_getRight(DeckLink *self, PyObject *value, void *closure) +{ + // if source exists + if (self->m_rightEye != NULL) + { + Py_INCREF(self->m_rightEye); + return reinterpret_cast(self->m_rightEye); + } + // otherwise return None + Py_RETURN_NONE; +} + + +// set source object +static int DeckLink_setRight(DeckLink *self, PyObject *value, void *closure) +{ + // check new value + if (value == NULL || !pyImageTypes.in(Py_TYPE(value))) + { + // report value error + PyErr_SetString(PyExc_TypeError, "Invalid type of value"); + return -1; + } + // increase ref count for new value + Py_INCREF(value); + // release previous + Py_XDECREF(self->m_rightEye); + // set new value + self->m_rightEye = reinterpret_cast(value); + // return success + return 0; +} + + +static PyObject *DeckLink_getKeying(DeckLink *self, PyObject *value, void *closure) +{ + if (self->mUseKeying) Py_RETURN_TRUE; + else Py_RETURN_FALSE; +} + +static int DeckLink_setKeying(DeckLink *self, PyObject *value, void *closure) +{ + if (value == NULL || !PyBool_Check(value)) + { + PyErr_SetString(PyExc_TypeError, "The value must be a bool"); + return -1; + } + if (self->mKeyer != NULL) + { + if (value == Py_True) + { + if (self->mKeyer->Enable(false) != S_OK) + { + PyErr_SetString(PyExc_RuntimeError, "Error enabling keyer"); + return -1; + } + self->mUseKeying = true; + self->mKeyer->SetLevel(self->mKeyingLevel); + } + else + { + self->mKeyer->Disable(); + self->mUseKeying = false; + } + } + // success + return 0; +} + +static PyObject *DeckLink_getLevel(DeckLink *self, PyObject *value, void *closure) +{ + return Py_BuildValue("h", self->mKeyingLevel); +} + +static int DeckLink_setLevel(DeckLink *self, PyObject *value, void *closure) +{ + long level; + if (value == NULL || !PyLong_Check(value)) { + PyErr_SetString(PyExc_TypeError, "The value must be an integer from 0 to 255"); + return -1; + } + level = PyLong_AsLong(value); + if (level > 255) + level = 255; + else if (level < 0) + level = 0; + self->mKeyingLevel = (uint8_t)level; + if (self->mUseKeying) { + if (self->mKeyer->SetLevel(self->mKeyingLevel) != S_OK) { + PyErr_SetString(PyExc_RuntimeError, "Error changin level of keyer"); + return -1; + } + } + // success + return 0; +} + +static PyObject *DeckLink_getExtend(DeckLink *self, PyObject *value, void *closure) +{ + if (self->mUseExtend) Py_RETURN_TRUE; + else Py_RETURN_FALSE; +} + +static int DeckLink_setExtend(DeckLink *self, PyObject *value, void *closure) +{ + if (value == NULL || !PyBool_Check(value)) + { + PyErr_SetString(PyExc_TypeError, "The value must be a bool"); + return -1; + } + self->mUseExtend = (value == Py_True); + return 0; +} + +// class DeckLink methods +static PyMethodDef decklinkMethods[] = +{ + { "close", (PyCFunction)DeckLink_close, METH_NOARGS, "Close dynamic decklink and restore original"}, + { "refresh", (PyCFunction)DeckLink_refresh, METH_VARARGS, "Refresh decklink from source"}, + {NULL} /* Sentinel */ +}; + +// class DeckLink attributes +static PyGetSetDef decklinkGetSets[] = +{ + { (char*)"source", (getter)DeckLink_getSource, (setter)DeckLink_setSource, (char*)"source of decklink (left eye)", NULL}, + { (char*)"right", (getter)DeckLink_getRight, (setter)DeckLink_setRight, (char*)"source of decklink (right eye)", NULL }, + { (char*)"keying", (getter)DeckLink_getKeying, (setter)DeckLink_setKeying, (char*)"whether keying is enabled (frame is alpha-composited with passthrough output)", NULL }, + { (char*)"level", (getter)DeckLink_getLevel, (setter)DeckLink_setLevel, (char*)"change the level of keying (overall alpha level of key frame, 0 to 255)", NULL }, + { (char*)"extend", (getter)DeckLink_getExtend, (setter)DeckLink_setExtend, (char*)"whether image should stretched to fit frame", NULL }, + { NULL } +}; + + +// class DeckLink declaration +PyTypeObject DeckLinkType = +{ + PyVarObject_HEAD_INIT(NULL, 0) + "VideoTexture.DeckLink", /*tp_name*/ + sizeof(DeckLink), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)DeckLink_dealloc,/*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + &imageBufferProcs, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + "DeckLink objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + decklinkMethods, /* tp_methods */ + 0, /* tp_members */ + decklinkGetSets, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)DeckLink_init, /* tp_init */ + 0, /* tp_alloc */ + DeckLink_new, /* tp_new */ +}; + +#endif /* WITH_GAMEENGINE_DECKLINK */ diff --git a/source/gameengine/VideoTexture/DeckLink.h b/source/gameengine/VideoTexture/DeckLink.h new file mode 100644 index 00000000000..1c96af7b4bc --- /dev/null +++ b/source/gameengine/VideoTexture/DeckLink.h @@ -0,0 +1,86 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program 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 Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * +* The Original Code is Copyright (C) 2015, Blender Foundation +* All rights reserved. +* +* The Original Code is: all of this file. +* +* Contributor(s): Blender Foundation. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file VideoTexture/DeckLink.h + * \ingroup bgevideotex + */ + +#ifndef __DECKLINK_H__ +#define __DECKLINK_H__ + +#ifdef WITH_GAMEENGINE_DECKLINK + +#include "EXP_PyObjectPlus.h" +#include + +#include "DNA_image_types.h" + +#include "DeckLinkAPI.h" + +#include "ImageBase.h" +#include "BlendType.h" +#include "Exception.h" + + +// type DeckLink declaration +struct DeckLink +{ + PyObject_HEAD + + // last refresh + double m_lastClock; + // decklink card to which we output + IDeckLinkOutput * mDLOutput; + IDeckLinkKeyer * mKeyer; + IDeckLinkMutableVideoFrame *mLeftFrame; + IDeckLinkMutableVideoFrame *mRightFrame; + bool mUse3D; + bool mUseKeying; + bool mUseExtend; + bool mKeyingSupported; + bool mHDKeyingSupported; + uint8_t mKeyingLevel; + BMDDisplayMode mDisplayMode; + short mSize[2]; + uint32_t mFrameSize; + + // image source + PyImage * m_leftEye; + PyImage * m_rightEye; +}; + + +// DeckLink type description +extern PyTypeObject DeckLinkType; + +// helper function +HRESULT decklink_ReadDisplayMode(const char *format, size_t len, BMDDisplayMode *displayMode); +HRESULT decklink_ReadPixelFormat(const char *format, size_t len, BMDPixelFormat *displayMode); + +#endif /* WITH_GAMEENGINE_DECKLINK */ + +#endif /* __DECKLINK_H__ */ diff --git a/source/gameengine/VideoTexture/Exception.cpp b/source/gameengine/VideoTexture/Exception.cpp index 322b7f9aef3..9f82987ea62 100644 --- a/source/gameengine/VideoTexture/Exception.cpp +++ b/source/gameengine/VideoTexture/Exception.cpp @@ -225,7 +225,7 @@ void registerAllExceptions(void) SourceVideoEmptyDesc.registerDesc(); SourceVideoCreationDesc.registerDesc(); OffScreenInvalidDesc.registerDesc(); -#ifdef WITH_DECKLINK +#ifdef WITH_GAMEENGINE_DECKLINK AutoDetectionNotAvailDesc.registerDesc(); DeckLinkBadDisplayModeDesc.registerDesc(); DeckLinkBadPixelFormatDesc.registerDesc(); diff --git a/source/gameengine/VideoTexture/VideoDeckLink.cpp b/source/gameengine/VideoTexture/VideoDeckLink.cpp new file mode 100644 index 00000000000..c8d3c28c551 --- /dev/null +++ b/source/gameengine/VideoTexture/VideoDeckLink.cpp @@ -0,0 +1,1228 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program 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 Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * +* The Original Code is Copyright (C) 2015, Blender Foundation +* All rights reserved. +* +* The Original Code is: all of this file. +* +* Contributor(s): Blender Foundation. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file gameengine/VideoTexture/VideoDeckLink.cpp + * \ingroup bgevideotex + */ + +#ifdef WITH_GAMEENGINE_DECKLINK + +// FFmpeg defines its own version of stdint.h on Windows. +// Decklink needs FFmpeg, so it uses its version of stdint.h +// this is necessary for INT64_C macro +#ifndef __STDC_CONSTANT_MACROS +#define __STDC_CONSTANT_MACROS +#endif +// this is necessary for UINTPTR_MAX (used by atomic-ops) +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS +#ifdef __STDC_LIMIT_MACROS /* else it may be unused */ +#endif +#endif +#include +#include +#ifndef WIN32 +#include +#include +#include +#endif + +#include "atomic_ops.h" + +#include "MEM_guardedalloc.h" +#include "PIL_time.h" +#include "VideoDeckLink.h" +#include "DeckLink.h" +#include "Exception.h" +#include "KX_KetsjiEngine.h" +#include "KX_PythonInit.h" + +extern ExceptionID DeckLinkInternalError; +ExceptionID SourceVideoOnlyCapture, VideoDeckLinkBadFormat, VideoDeckLinkOpenCard, VideoDeckLinkDvpInternalError, VideoDeckLinkPinMemoryError; +ExpDesc SourceVideoOnlyCaptureDesc(SourceVideoOnlyCapture, "This video source only allows live capture"); +ExpDesc VideoDeckLinkBadFormatDesc(VideoDeckLinkBadFormat, "Invalid or unsupported capture format, should be /[/3D]"); +ExpDesc VideoDeckLinkOpenCardDesc(VideoDeckLinkOpenCard, "Cannot open capture card, check if driver installed"); +ExpDesc VideoDeckLinkDvpInternalErrorDesc(VideoDeckLinkDvpInternalError, "DVP API internal error, please report"); +ExpDesc VideoDeckLinkPinMemoryErrorDesc(VideoDeckLinkPinMemoryError, "Error pinning memory"); + + +#ifdef WIN32 +//////////////////////////////////////////// +// SynInfo +// +// Sets up a semaphore which is shared between the GPU and CPU and used to +// synchronise access to DVP buffers. +#define DVP_CHECK(cmd) if ((cmd) != DVP_STATUS_OK) THRWEXCP(VideoDeckLinkDvpInternalError, S_OK) + +struct SyncInfo +{ + SyncInfo(uint32_t semaphoreAllocSize, uint32_t semaphoreAddrAlignment) + { + mSemUnaligned = (uint32_t*)malloc(semaphoreAllocSize + semaphoreAddrAlignment - 1); + + // Apply alignment constraints + uint64_t val = (uint64_t)mSemUnaligned; + val += semaphoreAddrAlignment - 1; + val &= ~((uint64_t)semaphoreAddrAlignment - 1); + mSem = (uint32_t*)val; + + // Initialise + mSem[0] = 0; + mReleaseValue = 0; + mAcquireValue = 0; + + // Setup DVP sync object and import it + DVPSyncObjectDesc syncObjectDesc; + syncObjectDesc.externalClientWaitFunc = NULL; + syncObjectDesc.sem = (uint32_t*)mSem; + + DVP_CHECK(dvpImportSyncObject(&syncObjectDesc, &mDvpSync)); + + } + ~SyncInfo() + { + dvpFreeSyncObject(mDvpSync); + free((void*)mSemUnaligned); + } + + volatile uint32_t* mSem; + volatile uint32_t* mSemUnaligned; + volatile uint32_t mReleaseValue; + volatile uint32_t mAcquireValue; + DVPSyncObjectHandle mDvpSync; +}; + +//////////////////////////////////////////// +// TextureTransferDvp: transfer with GPUDirect +//////////////////////////////////////////// + +class TextureTransferDvp : public TextureTransfer +{ +public: + TextureTransferDvp(DVPBufferHandle dvpTextureHandle, TextureDesc *pDesc, void *address, uint32_t allocatedSize) + { + DVPSysmemBufferDesc sysMemBuffersDesc; + + mExtSync = NULL; + mGpuSync = NULL; + mDvpSysMemHandle = 0; + mDvpTextureHandle = 0; + mTextureHeight = 0; + mAllocatedSize = 0; + mBuffer = NULL; + + if (!_PinBuffer(address, allocatedSize)) + THRWEXCP(VideoDeckLinkPinMemoryError, S_OK); + mAllocatedSize = allocatedSize; + mBuffer = address; + + try { + if (!mBufferAddrAlignment) { + DVP_CHECK(dvpGetRequiredConstantsGLCtx(&mBufferAddrAlignment, &mBufferGpuStrideAlignment, + &mSemaphoreAddrAlignment, &mSemaphoreAllocSize, + &mSemaphorePayloadOffset, &mSemaphorePayloadSize)); + } + mExtSync = new SyncInfo(mSemaphoreAllocSize, mSemaphoreAddrAlignment); + mGpuSync = new SyncInfo(mSemaphoreAllocSize, mSemaphoreAddrAlignment); + sysMemBuffersDesc.width = pDesc->width; + sysMemBuffersDesc.height = pDesc->height; + sysMemBuffersDesc.stride = pDesc->stride; + switch (pDesc->format) { + case GL_RED_INTEGER: + sysMemBuffersDesc.format = DVP_RED_INTEGER; + break; + default: + sysMemBuffersDesc.format = DVP_BGRA; + break; + } + switch (pDesc->type) { + case GL_UNSIGNED_BYTE: + sysMemBuffersDesc.type = DVP_UNSIGNED_BYTE; + break; + case GL_UNSIGNED_INT_2_10_10_10_REV: + sysMemBuffersDesc.type = DVP_UNSIGNED_INT_2_10_10_10_REV; + break; + case GL_UNSIGNED_INT_8_8_8_8: + sysMemBuffersDesc.type = DVP_UNSIGNED_INT_8_8_8_8; + break; + case GL_UNSIGNED_INT_10_10_10_2: + sysMemBuffersDesc.type = DVP_UNSIGNED_INT_10_10_10_2; + break; + default: + sysMemBuffersDesc.type = DVP_UNSIGNED_INT; + break; + } + sysMemBuffersDesc.size = pDesc->width * pDesc->height * 4; + sysMemBuffersDesc.bufAddr = mBuffer; + DVP_CHECK(dvpCreateBuffer(&sysMemBuffersDesc, &mDvpSysMemHandle)); + DVP_CHECK(dvpBindToGLCtx(mDvpSysMemHandle)); + mDvpTextureHandle = dvpTextureHandle; + mTextureHeight = pDesc->height; + } + catch (Exception &) { + clean(); + throw; + } + } + ~TextureTransferDvp() + { + clean(); + } + + virtual void PerformTransfer() + { + // perform the transfer + // tell DVP that the old texture buffer will no longer be used + dvpMapBufferEndAPI(mDvpTextureHandle); + // do we need this? + mGpuSync->mReleaseValue++; + dvpBegin(); + // Copy from system memory to GPU texture + dvpMapBufferWaitDVP(mDvpTextureHandle); + dvpMemcpyLined(mDvpSysMemHandle, mExtSync->mDvpSync, mExtSync->mAcquireValue, DVP_TIMEOUT_IGNORED, + mDvpTextureHandle, mGpuSync->mDvpSync, mGpuSync->mReleaseValue, 0, mTextureHeight); + dvpMapBufferEndDVP(mDvpTextureHandle); + dvpEnd(); + dvpMapBufferWaitAPI(mDvpTextureHandle); + // the transfer is now complete and the texture is ready for use + } + +private: + static uint32_t mBufferAddrAlignment; + static uint32_t mBufferGpuStrideAlignment; + static uint32_t mSemaphoreAddrAlignment; + static uint32_t mSemaphoreAllocSize; + static uint32_t mSemaphorePayloadOffset; + static uint32_t mSemaphorePayloadSize; + + void clean() + { + if (mDvpSysMemHandle) { + dvpUnbindFromGLCtx(mDvpSysMemHandle); + dvpDestroyBuffer(mDvpSysMemHandle); + } + if (mExtSync) + delete mExtSync; + if (mGpuSync) + delete mGpuSync; + if (mBuffer) + _UnpinBuffer(mBuffer, mAllocatedSize); + } + SyncInfo* mExtSync; + SyncInfo* mGpuSync; + DVPBufferHandle mDvpSysMemHandle; + DVPBufferHandle mDvpTextureHandle; + uint32_t mTextureHeight; + uint32_t mAllocatedSize; + void* mBuffer; +}; + +uint32_t TextureTransferDvp::mBufferAddrAlignment; +uint32_t TextureTransferDvp::mBufferGpuStrideAlignment; +uint32_t TextureTransferDvp::mSemaphoreAddrAlignment; +uint32_t TextureTransferDvp::mSemaphoreAllocSize; +uint32_t TextureTransferDvp::mSemaphorePayloadOffset; +uint32_t TextureTransferDvp::mSemaphorePayloadSize; + +#endif + +//////////////////////////////////////////// +// TextureTransferOGL: transfer using standard OGL buffers +//////////////////////////////////////////// + +class TextureTransferOGL : public TextureTransfer +{ +public: + TextureTransferOGL(GLuint texId, TextureDesc *pDesc, void *address) + { + memcpy(&mDesc, pDesc, sizeof(mDesc)); + mTexId = texId; + mBuffer = address; + + // as we cache transfer object, we will create one texture to hold the buffer + glGenBuffers(1, &mUnpinnedTextureBuffer); + // create a storage for it + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mUnpinnedTextureBuffer); + glBufferData(GL_PIXEL_UNPACK_BUFFER, pDesc->size, NULL, GL_DYNAMIC_DRAW); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + } + ~TextureTransferOGL() + { + glDeleteBuffers(1, &mUnpinnedTextureBuffer); + } + + virtual void PerformTransfer() + { + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mUnpinnedTextureBuffer); + glBufferSubData(GL_PIXEL_UNPACK_BUFFER, 0, mDesc.size, mBuffer); + glBindTexture(GL_TEXTURE_2D, mTexId); + // NULL for last arg indicates use current GL_PIXEL_UNPACK_BUFFER target as texture data + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, mDesc.width, mDesc.height, mDesc.format, mDesc.type, NULL); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + } +private: + // intermediate texture to receive the buffer + GLuint mUnpinnedTextureBuffer; + // target texture to receive the image + GLuint mTexId; + // buffer + void *mBuffer; + // characteristic of the image + TextureDesc mDesc; +}; + +//////////////////////////////////////////// +// TextureTransferPMB: transfer using pinned memory buffer +//////////////////////////////////////////// + +class TextureTransferPMD : public TextureTransfer +{ +public: + TextureTransferPMD(GLuint texId, TextureDesc *pDesc, void *address, uint32_t allocatedSize) + { + memcpy(&mDesc, pDesc, sizeof(mDesc)); + mTexId = texId; + mBuffer = address; + mAllocatedSize = allocatedSize; + + _PinBuffer(address, allocatedSize); + + // as we cache transfer object, we will create one texture to hold the buffer + glGenBuffers(1, &mPinnedTextureBuffer); + // create a storage for it + glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, mPinnedTextureBuffer); + glBufferData(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, pDesc->size, address, GL_STREAM_DRAW); + glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, 0); + } + ~TextureTransferPMD() + { + glDeleteBuffers(1, &mPinnedTextureBuffer); + if (mBuffer) + _UnpinBuffer(mBuffer, mAllocatedSize); + } + + virtual void PerformTransfer() + { + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mPinnedTextureBuffer); + glBindTexture(GL_TEXTURE_2D, mTexId); + // NULL for last arg indicates use current GL_PIXEL_UNPACK_BUFFER target as texture data + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, mDesc.width, mDesc.height, mDesc.format, mDesc.type, NULL); + // wait for the trasnfer to complete + GLsync fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glClientWaitSync(fence, GL_SYNC_FLUSH_COMMANDS_BIT, 40 * 1000 * 1000); // timeout in nanosec + glDeleteSync(fence); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + } +private: + // intermediate texture to receive the buffer + GLuint mPinnedTextureBuffer; + // target texture to receive the image + GLuint mTexId; + // buffer + void *mBuffer; + // the allocated size + uint32_t mAllocatedSize; + // characteristic of the image + TextureDesc mDesc; +}; + +bool TextureTransfer::_PinBuffer(void *address, uint32_t size) +{ +#ifdef WIN32 + return VirtualLock(address, size); +#elif defined(_POSIX_MEMLOCK_RANGE) + return !mlock(address, size); +#endif +} + +void TextureTransfer::_UnpinBuffer(void* address, uint32_t size) +{ +#ifdef WIN32 + VirtualUnlock(address, size); +#elif defined(_POSIX_MEMLOCK_RANGE) + munlock(address, size); +#endif +} + + + +//////////////////////////////////////////// +// PinnedMemoryAllocator +//////////////////////////////////////////// + + +// static members +bool PinnedMemoryAllocator::mGPUDirectInitialized = false; +bool PinnedMemoryAllocator::mHasDvp = false; +bool PinnedMemoryAllocator::mHasAMDPinnedMemory = false; +size_t PinnedMemoryAllocator::mReservedProcessMemory = 0; + +bool PinnedMemoryAllocator::ReserveMemory(size_t size) +{ +#ifdef WIN32 + // Increase the process working set size to allow pinning of memory. + if (size <= mReservedProcessMemory) + return true; + SIZE_T dwMin = 0, dwMax = 0; + HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_SET_QUOTA, FALSE, GetCurrentProcessId()); + if (!hProcess) + return false; + + // Retrieve the working set size of the process. + if (!dwMin && !GetProcessWorkingSetSize(hProcess, &dwMin, &dwMax)) + return false; + + BOOL res = SetProcessWorkingSetSize(hProcess, (size - mReservedProcessMemory) + dwMin, (size - mReservedProcessMemory) + dwMax); + if (!res) + return false; + mReservedProcessMemory = size; + CloseHandle(hProcess); + return true; +#else + struct rlimit rlim; + if (getrlimit(RLIMIT_MEMLOCK, &rlim) == 0) { + if (rlim.rlim_cur < size) { + if (rlim.rlim_max < size) + rlim.rlim_max = size; + rlim.rlim_cur = size; + return !setrlimit(RLIMIT_MEMLOCK, &rlim); + } + } + return false; +#endif +} + +PinnedMemoryAllocator::PinnedMemoryAllocator(unsigned cacheSize, size_t memSize) : +mRefCount(1U), +#ifdef WIN32 +mDvpCaptureTextureHandle(0), +#endif +mTexId(0), +mBufferCacheSize(cacheSize) +{ + pthread_mutex_init(&mMutex, NULL); + // do it once + if (!mGPUDirectInitialized) { +#ifdef WIN32 + // In windows, AMD_pinned_memory option is not available, + // we must use special DVP API only available for Quadro cards + const char* renderer = (const char *)glGetString(GL_RENDERER); + mHasDvp = (strstr(renderer, "Quadro") != NULL); + + if (mHasDvp) { + // In case the DLL is not in place, don't fail, just fallback on OpenGL + if (dvpInitGLContext(DVP_DEVICE_FLAGS_SHARE_APP_CONTEXT) != DVP_STATUS_OK) { + printf("Warning: Could not initialize DVP context, fallback on OpenGL transfer.\nInstall dvp.dll to take advantage of nVidia GPUDirect.\n"); + mHasDvp = false; + } + } +#endif + if (GLEW_AMD_pinned_memory) + mHasAMDPinnedMemory = true; + + mGPUDirectInitialized = true; + } + if (mHasDvp || mHasAMDPinnedMemory) { + ReserveMemory(memSize); + } +} + +PinnedMemoryAllocator::~PinnedMemoryAllocator() +{ + void *address; + // first clean the cache if not already done + while (!mBufferCache.empty()) { + address = mBufferCache.back(); + mBufferCache.pop_back(); + _ReleaseBuffer(address); + } + // clean preallocated buffers + while (!mAllocatedSize.empty()) { + address = mAllocatedSize.begin()->first; + _ReleaseBuffer(address); + } + +#ifdef WIN32 + if (mDvpCaptureTextureHandle) + dvpDestroyBuffer(mDvpCaptureTextureHandle); +#endif +} + +void PinnedMemoryAllocator::TransferBuffer(void* address, TextureDesc* texDesc, GLuint texId) +{ + uint32_t allocatedSize = 0; + TextureTransfer *pTransfer = NULL; + + Lock(); + if (mAllocatedSize.count(address) > 0) + allocatedSize = mAllocatedSize[address]; + Unlock(); + if (!allocatedSize) + // internal error!! + return; + if (mTexId != texId) + { + // first time we try to send data to the GPU, allocate a buffer for the texture + glBindTexture(GL_TEXTURE_2D, texId); + 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); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexImage2D(GL_TEXTURE_2D, 0, texDesc->internalFormat, texDesc->width, texDesc->height, 0, texDesc->format, texDesc->type, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + mTexId = texId; + } +#ifdef WIN32 + if (mHasDvp) + { + if (!mDvpCaptureTextureHandle) + { + // bind DVP to the OGL texture + DVP_CHECK(dvpCreateGPUTextureGL(texId, &mDvpCaptureTextureHandle)); + } + } +#endif + Lock(); + if (mPinnedBuffer.count(address) > 0) + { + pTransfer = mPinnedBuffer[address]; + } + Unlock(); + if (!pTransfer) + { +#ifdef WIN32 + if (mHasDvp) + pTransfer = new TextureTransferDvp(mDvpCaptureTextureHandle, texDesc, address, allocatedSize); + else +#endif + if (mHasAMDPinnedMemory) { + pTransfer = new TextureTransferPMD(texId, texDesc, address, allocatedSize); + } + else { + pTransfer = new TextureTransferOGL(texId, texDesc, address); + } + if (pTransfer) + { + Lock(); + mPinnedBuffer[address] = pTransfer; + Unlock(); + } + } + if (pTransfer) + pTransfer->PerformTransfer(); +} + +// IUnknown methods +HRESULT STDMETHODCALLTYPE PinnedMemoryAllocator::QueryInterface(REFIID /*iid*/, LPVOID* /*ppv*/) +{ + return E_NOTIMPL; +} + +ULONG STDMETHODCALLTYPE PinnedMemoryAllocator::AddRef(void) +{ + return atomic_add_uint32(&mRefCount, 1U); +} + +ULONG STDMETHODCALLTYPE PinnedMemoryAllocator::Release(void) +{ + uint32_t newCount = atomic_sub_uint32(&mRefCount, 1U); + if (newCount == 0) + delete this; + return (ULONG)newCount; +} + +// IDeckLinkMemoryAllocator methods +HRESULT STDMETHODCALLTYPE PinnedMemoryAllocator::AllocateBuffer(dl_size_t bufferSize, void* *allocatedBuffer) +{ + Lock(); + if (mBufferCache.empty()) + { + // Allocate memory on a page boundary + // Note: aligned alloc exist in Blender but only for small alignment, use direct allocation then. + // Note: the DeckLink API tries to allocate up to 65 buffer in advance, we will limit this to 3 + // because we don't need any caching + if (mAllocatedSize.size() >= mBufferCacheSize) + *allocatedBuffer = NULL; + else { +#ifdef WIN32 + *allocatedBuffer = VirtualAlloc(NULL, bufferSize, MEM_COMMIT | MEM_RESERVE | MEM_WRITE_WATCH, PAGE_READWRITE); +#else + if (posix_memalign(allocatedBuffer, 4096, bufferSize) != 0) + *allocatedBuffer = NULL; +#endif + mAllocatedSize[*allocatedBuffer] = bufferSize; + } + } + else { + // Re-use most recently ReleaseBuffer'd address + *allocatedBuffer = mBufferCache.back(); + mBufferCache.pop_back(); + } + Unlock(); + return (*allocatedBuffer) ? S_OK : E_OUTOFMEMORY; +} + +HRESULT STDMETHODCALLTYPE PinnedMemoryAllocator::ReleaseBuffer(void* buffer) +{ + HRESULT result = S_OK; + Lock(); + if (mBufferCache.size() < mBufferCacheSize) { + mBufferCache.push_back(buffer); + } + else { + result = _ReleaseBuffer(buffer); + } + Unlock(); + return result; +} + + +HRESULT PinnedMemoryAllocator::_ReleaseBuffer(void* buffer) +{ + TextureTransfer *pTransfer; + if (mAllocatedSize.count(buffer) == 0) { + // Internal error!! + return S_OK; + } + else { + // No room left in cache, so un-pin (if it was pinned) and free this buffer + if (mPinnedBuffer.count(buffer) > 0) { + pTransfer = mPinnedBuffer[buffer]; + mPinnedBuffer.erase(buffer); + delete pTransfer; + } +#ifdef WIN32 + VirtualFree(buffer, 0, MEM_RELEASE); +#else + free(buffer); +#endif + mAllocatedSize.erase(buffer); + } + return S_OK; +} + +HRESULT STDMETHODCALLTYPE PinnedMemoryAllocator::Commit() +{ + return S_OK; +} + +HRESULT STDMETHODCALLTYPE PinnedMemoryAllocator::Decommit() +{ + void *buffer; + Lock(); + while (!mBufferCache.empty()) { + // Cleanup any frames allocated and pinned in AllocateBuffer() but not freed in ReleaseBuffer() + buffer = mBufferCache.back(); + mBufferCache.pop_back(); + _ReleaseBuffer(buffer); + } + Unlock(); + return S_OK; +} + + +//////////////////////////////////////////// +// Capture Delegate Class +//////////////////////////////////////////// + +CaptureDelegate::CaptureDelegate(VideoDeckLink* pOwner) : mpOwner(pOwner) +{ +} + +HRESULT CaptureDelegate::VideoInputFrameArrived(IDeckLinkVideoInputFrame* inputFrame, IDeckLinkAudioInputPacket* /*audioPacket*/) +{ + if (!inputFrame) { + // It's possible to receive a NULL inputFrame, but a valid audioPacket. Ignore audio-only frame. + return S_OK; + } + if ((inputFrame->GetFlags() & bmdFrameHasNoInputSource) == bmdFrameHasNoInputSource) { + // let's not bother transferring frames if there is no source + return S_OK; + } + mpOwner->VideoFrameArrived(inputFrame); + return S_OK; +} + +HRESULT CaptureDelegate::VideoInputFormatChanged(BMDVideoInputFormatChangedEvents notificationEvents, IDeckLinkDisplayMode *newDisplayMode, BMDDetectedVideoInputFormatFlags detectedSignalFlags) +{ + return S_OK; +} + + + + +// macro for exception handling and logging +#define CATCH_EXCP catch (Exception & exp) \ +{ exp.report(); m_status = SourceError; } + +// class VideoDeckLink + + +// constructor +VideoDeckLink::VideoDeckLink (HRESULT * hRslt) : VideoBase(), +mDLInput(NULL), +mUse3D(false), +mFrameWidth(0), +mFrameHeight(0), +mpAllocator(NULL), +mpCaptureDelegate(NULL), +mpCacheFrame(NULL), +mClosing(false) +{ + mDisplayMode = (BMDDisplayMode)0; + mPixelFormat = (BMDPixelFormat)0; + pthread_mutex_init(&mCacheMutex, NULL); +} + +// destructor +VideoDeckLink::~VideoDeckLink () +{ + LockCache(); + mClosing = true; + if (mpCacheFrame) + { + mpCacheFrame->Release(); + mpCacheFrame = NULL; + } + UnlockCache(); + if (mDLInput != NULL) + { + // Cleanup for Capture + mDLInput->StopStreams(); + mDLInput->SetCallback(NULL); + mDLInput->DisableVideoInput(); + mDLInput->DisableAudioInput(); + mDLInput->FlushStreams(); + if (mDLInput->Release() != 0) { + printf("Reference count not NULL on DeckLink device when closing it, please report!\n"); + } + mDLInput = NULL; + } + + if (mpAllocator) + { + // if the device was properly cleared, this should be 0 + if (mpAllocator->Release() != 0) { + printf("Reference count not NULL on Allocator when closing it, please report!\n"); + } + mpAllocator = NULL; + } + if (mpCaptureDelegate) + { + delete mpCaptureDelegate; + mpCaptureDelegate = NULL; + } +} + +void VideoDeckLink::refresh(void) +{ + m_avail = false; +} + +// release components +bool VideoDeckLink::release() +{ + // release + return true; +} + +// open video file +void VideoDeckLink::openFile (char *filename) +{ + // only live capture on this device + THRWEXCP(SourceVideoOnlyCapture, S_OK); +} + + +// open video capture device +void VideoDeckLink::openCam (char *format, short camIdx) +{ + IDeckLinkDisplayModeIterator* pDLDisplayModeIterator; + BMDDisplayModeSupport modeSupport; + IDeckLinkDisplayMode* pDLDisplayMode; + IDeckLinkIterator* pIterator; + BMDTimeValue frameDuration; + BMDTimeScale frameTimescale; + IDeckLink* pDL; + uint32_t displayFlags, inputFlags; + char *pPixel, *p3D, *pEnd, *pSize; + size_t len; + int i, modeIdx, cacheSize; + + // format is constructed as /[/3D][:] + // takes the form of BMDDisplayMode identifier minus the 'bmdMode' prefix. + // This implementation understands all the modes defined in SDK 10.3.1 but you can alternatively + // use the 4 characters internal representation of the mode (e.g. 'HD1080p24' == '24ps') + // takes the form of BMDPixelFormat identifier minus the 'bmdFormat' prefix. + // This implementation understand all the formats defined in SDK 10.32.1 but you can alternatively + // use the 4 characters internal representation of the format (e.g. '10BitRGB' == 'r210') + // Not all combinations of mode and pixel format are possible and it also depends on the card! + // Use /3D postfix if you are capturing a 3D stream with frame packing + // Example: To capture FullHD 1920x1080@24Hz with 3D packing and 4:4:4 10 bits RGB pixel format, use + // "HD1080p24/10BitRGB/3D" (same as "24ps/r210/3D") + // (this will be the normal capture format for FullHD on the DeckLink 4k extreme) + + if ((pSize = strchr(format, ':')) != NULL) { + cacheSize = strtol(pSize+1, &pEnd, 10); + } + else { + cacheSize = 8; + pSize = format + strlen(format); + } + if ((pPixel = strchr(format, '/')) == NULL || + ((p3D = strchr(pPixel + 1, '/')) != NULL && strncmp(p3D, "/3D", pSize-p3D))) + THRWEXCP(VideoDeckLinkBadFormat, S_OK); + mUse3D = (p3D) ? true : false; + // to simplify pixel format parsing + if (!p3D) + p3D = pSize; + + // read the mode + len = (size_t)(pPixel - format); + // accept integer display mode + + try { + // throws if bad mode + decklink_ReadDisplayMode(format, len, &mDisplayMode); + // found a valid mode, remember that we do not look for an index + modeIdx = -1; + } + catch (Exception &) { + // accept also purely numerical mode as a mode index + modeIdx = strtol(format, &pEnd, 10); + if (pEnd != pPixel || modeIdx < 0) + // not a pure number, give up + throw; + } + + // skip / + pPixel++; + len = (size_t)(p3D - pPixel); + // throws if bad format + decklink_ReadPixelFormat(pPixel, len, &mPixelFormat); + + // Caution: DeckLink API used from this point, make sure entity are released before throwing + // open the card + pIterator = BMD_CreateDeckLinkIterator(); + if (pIterator) { + i = 0; + while (pIterator->Next(&pDL) == S_OK) { + if (i == camIdx) { + if (pDL->QueryInterface(IID_IDeckLinkInput, (void**)&mDLInput) != S_OK) + mDLInput = NULL; + pDL->Release(); + break; + } + i++; + pDL->Release(); + } + pIterator->Release(); + } + if (!mDLInput) + THRWEXCP(VideoDeckLinkOpenCard, S_OK); + + + // check if display mode and pixel format are supported + if (mDLInput->GetDisplayModeIterator(&pDLDisplayModeIterator) != S_OK) + THRWEXCP(DeckLinkInternalError, S_OK); + + pDLDisplayMode = NULL; + displayFlags = (mUse3D) ? bmdDisplayModeSupports3D : 0; + inputFlags = (mUse3D) ? bmdVideoInputDualStream3D : bmdVideoInputFlagDefault; + while (pDLDisplayModeIterator->Next(&pDLDisplayMode) == S_OK) + { + if (modeIdx == 0 || pDLDisplayMode->GetDisplayMode() == mDisplayMode) { + // in case we get here because of modeIdx, make sure we have mDisplayMode set + mDisplayMode = pDLDisplayMode->GetDisplayMode(); + if ((pDLDisplayMode->GetFlags() & displayFlags) == displayFlags && + mDLInput->DoesSupportVideoMode(mDisplayMode, mPixelFormat, inputFlags, &modeSupport, NULL) == S_OK && + modeSupport == bmdDisplayModeSupported) + { + break; + } + } + pDLDisplayMode->Release(); + pDLDisplayMode = NULL; + if (modeIdx-- == 0) { + // reached the correct mode index but it does not meet the pixel format, give up + break; + } + } + pDLDisplayModeIterator->Release(); + + if (pDLDisplayMode == NULL) + THRWEXCP(VideoDeckLinkBadFormat, S_OK); + + mFrameWidth = pDLDisplayMode->GetWidth(); + mFrameHeight = pDLDisplayMode->GetHeight(); + mTextureDesc.height = (mUse3D) ? 2 * mFrameHeight : mFrameHeight; + pDLDisplayMode->GetFrameRate(&frameDuration, &frameTimescale); + pDLDisplayMode->Release(); + // for information, in case the application wants to know + m_size[0] = mFrameWidth; + m_size[1] = mTextureDesc.height; + m_frameRate = (float)frameTimescale / (float)frameDuration; + + switch (mPixelFormat) + { + case bmdFormat8BitYUV: + // 2 pixels per word + mTextureDesc.stride = mFrameWidth * 2; + mTextureDesc.width = mFrameWidth / 2; + mTextureDesc.internalFormat = GL_RGBA; + mTextureDesc.format = GL_BGRA; + mTextureDesc.type = GL_UNSIGNED_BYTE; + break; + case bmdFormat10BitYUV: + // 6 pixels in 4 words, rounded to 48 pixels + mTextureDesc.stride = ((mFrameWidth + 47) / 48) * 128; + mTextureDesc.width = mTextureDesc.stride/4; + mTextureDesc.internalFormat = GL_RGB10_A2; + mTextureDesc.format = GL_BGRA; + mTextureDesc.type = GL_UNSIGNED_INT_2_10_10_10_REV; + break; + case bmdFormat8BitARGB: + mTextureDesc.stride = mFrameWidth * 4; + mTextureDesc.width = mFrameWidth; + mTextureDesc.internalFormat = GL_RGBA; + mTextureDesc.format = GL_BGRA; + mTextureDesc.type = GL_UNSIGNED_INT_8_8_8_8; + break; + case bmdFormat8BitBGRA: + mTextureDesc.stride = mFrameWidth * 4; + mTextureDesc.width = mFrameWidth; + mTextureDesc.internalFormat = GL_RGBA; + mTextureDesc.format = GL_BGRA; + mTextureDesc.type = GL_UNSIGNED_BYTE; + break; + case bmdFormat10BitRGBXLE: + // 1 pixel per word, rounded to 64 pixels + mTextureDesc.stride = ((mFrameWidth + 63) / 64) * 256; + mTextureDesc.width = mTextureDesc.stride/4; + mTextureDesc.internalFormat = GL_RGB10_A2; + mTextureDesc.format = GL_RGBA; + mTextureDesc.type = GL_UNSIGNED_INT_10_10_10_2; + break; + case bmdFormat10BitRGBX: + case bmdFormat10BitRGB: + // 1 pixel per word, rounded to 64 pixels + mTextureDesc.stride = ((mFrameWidth + 63) / 64) * 256; + mTextureDesc.width = mTextureDesc.stride/4; + mTextureDesc.internalFormat = GL_R32UI; + mTextureDesc.format = GL_RED_INTEGER; + mTextureDesc.type = GL_UNSIGNED_INT; + break; + case bmdFormat12BitRGB: + case bmdFormat12BitRGBLE: + // 8 pixels in 9 word + mTextureDesc.stride = (mFrameWidth * 36) / 8; + mTextureDesc.width = mTextureDesc.stride/4; + mTextureDesc.internalFormat = GL_R32UI; + mTextureDesc.format = GL_RED_INTEGER; + mTextureDesc.type = GL_UNSIGNED_INT; + break; + default: + // for unknown pixel format, this will be resolved when a frame arrives + mTextureDesc.format = GL_RED_INTEGER; + mTextureDesc.type = GL_UNSIGNED_INT; + break; + } + // reserve memory for cache frame + 1 to accomodate for pixel format that we don't know yet + // note: we can't use stride as it is not yet known if the pixel format is unknown + // use instead the frame width as in worst case it's not much different (e.g. HD720/10BITYUV: 1296 pixels versus 1280) + // note: some pixel format take more than 4 bytes take that into account (9/8 versus 1) + mpAllocator = new PinnedMemoryAllocator(cacheSize, mFrameWidth*mTextureDesc.height * 4 * (1+cacheSize*9/8)); + + if (mDLInput->SetVideoInputFrameMemoryAllocator(mpAllocator) != S_OK) + THRWEXCP(DeckLinkInternalError, S_OK); + + mpCaptureDelegate = new CaptureDelegate(this); + if (mDLInput->SetCallback(mpCaptureDelegate) != S_OK) + THRWEXCP(DeckLinkInternalError, S_OK); + + if (mDLInput->EnableVideoInput(mDisplayMode, mPixelFormat, ((mUse3D) ? bmdVideoInputDualStream3D : bmdVideoInputFlagDefault)) != S_OK) + // this shouldn't failed, we tested above + THRWEXCP(DeckLinkInternalError, S_OK); + + // just in case it is needed to capture from certain cards, we don't check error because we don't need audio + mDLInput->EnableAudioInput(bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, 2); + + // open base class + VideoBase::openCam(format, camIdx); + + // ready to capture, will start when application calls play() +} + +// play video +bool VideoDeckLink::play (void) +{ + try + { + // if object is able to play + if (VideoBase::play()) + { + mDLInput->FlushStreams(); + return (mDLInput->StartStreams() == S_OK); + } + } + CATCH_EXCP; + return false; +} + + +// pause video +bool VideoDeckLink::pause (void) +{ + try + { + if (VideoBase::pause()) + { + mDLInput->PauseStreams(); + return true; + } + } + CATCH_EXCP; + return false; +} + +// stop video +bool VideoDeckLink::stop (void) +{ + try + { + VideoBase::stop(); + mDLInput->StopStreams(); + return true; + } + CATCH_EXCP; + return false; +} + + +// set video range +void VideoDeckLink::setRange (double start, double stop) +{ +} + +// set framerate +void VideoDeckLink::setFrameRate (float rate) +{ +} + + +// image calculation +// send cache frame directly to GPU +void VideoDeckLink::calcImage (unsigned int texId, double ts) +{ + IDeckLinkVideoInputFrame* pFrame; + LockCache(); + pFrame = mpCacheFrame; + mpCacheFrame = NULL; + UnlockCache(); + if (pFrame) { + // BUG: the dvpBindToGLCtx function fails the first time it is used, don't know why. + // This causes an exception to be thrown. + // This should be fixed but in the meantime we will catch the exception because + // it is crucial that we release the frame to keep the reference count right on the DeckLink device + try { + uint32_t rowSize = pFrame->GetRowBytes(); + uint32_t textureSize = rowSize * pFrame->GetHeight(); + void* videoPixels = NULL; + void* rightEyePixels = NULL; + if (!mTextureDesc.stride) { + // we could not compute the texture size earlier (unknown pixel size) + // let's do it now + mTextureDesc.stride = rowSize; + mTextureDesc.width = mTextureDesc.stride / 4; + } + if (mTextureDesc.stride != rowSize) { + // unexpected frame size, ignore + // TBD: print a warning + } + else { + pFrame->GetBytes(&videoPixels); + if (mUse3D) { + IDeckLinkVideoFrame3DExtensions *if3DExtensions = NULL; + IDeckLinkVideoFrame *rightEyeFrame = NULL; + if (pFrame->QueryInterface(IID_IDeckLinkVideoFrame3DExtensions, (void **)&if3DExtensions) == S_OK && + if3DExtensions->GetFrameForRightEye(&rightEyeFrame) == S_OK) { + rightEyeFrame->GetBytes(&rightEyePixels); + textureSize += ((uint64_t)rightEyePixels - (uint64_t)videoPixels); + } + if (rightEyeFrame) + rightEyeFrame->Release(); + if (if3DExtensions) + if3DExtensions->Release(); + } + mTextureDesc.size = mTextureDesc.width * mTextureDesc.height * 4; + if (mTextureDesc.size == textureSize) { + // this means that both left and right frame are contiguous and that there is no padding + // do the transfer + mpAllocator->TransferBuffer(videoPixels, &mTextureDesc, texId); + } + } + } + catch (Exception &) { + pFrame->Release(); + throw; + } + // this will trigger PinnedMemoryAllocator::RealaseBuffer + pFrame->Release(); + } + // currently we don't pass the image to the application + m_avail = false; +} + +// A frame is available from the board +// Called from an internal thread, just pass the frame to the main thread +void VideoDeckLink::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame) +{ + IDeckLinkVideoInputFrame* pOldFrame = NULL; + LockCache(); + if (!mClosing) + { + pOldFrame = mpCacheFrame; + mpCacheFrame = inputFrame; + inputFrame->AddRef(); + } + UnlockCache(); + // old frame no longer needed, just release it + if (pOldFrame) + pOldFrame->Release(); +} + +// python methods + +// object initialization +static int VideoDeckLink_init(PyObject *pySelf, PyObject *args, PyObject *kwds) +{ + static const char *kwlist[] = { "format", "capture", NULL }; + PyImage *self = reinterpret_cast(pySelf); + // see openCam for a description of format + char * format = NULL; + // capture device number, i.e. DeckLink card number, default first one + short capt = 0; + + if (!GLEW_VERSION_1_5) { + PyErr_SetString(PyExc_RuntimeError, "VideoDeckLink requires at least OpenGL 1.5"); + return -1; + } + // get parameters + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|h", + const_cast(kwlist), &format, &capt)) + return -1; + + try { + // create video object + Video_init(self); + + // open video source, control comes back to VideoDeckLink::openCam + Video_open(getVideo(self), format, capt); + } + catch (Exception & exp) { + exp.report(); + return -1; + } + // initialization succeded + return 0; +} + +// methods structure +static PyMethodDef videoMethods[] = +{ // methods from VideoBase class + {"play", (PyCFunction)Video_play, METH_NOARGS, "Play (restart) video"}, + {"pause", (PyCFunction)Video_pause, METH_NOARGS, "pause video"}, + {"stop", (PyCFunction)Video_stop, METH_NOARGS, "stop video (play will replay it from start)"}, + {"refresh", (PyCFunction)Video_refresh, METH_VARARGS, "Refresh video - get its status"}, + {NULL} +}; +// attributes structure +static PyGetSetDef videoGetSets[] = +{ // methods from VideoBase class + {(char*)"status", (getter)Video_getStatus, NULL, (char*)"video status", NULL}, + {(char*)"framerate", (getter)Video_getFrameRate, NULL, (char*)"frame rate", NULL}, + // attributes from ImageBase class + {(char*)"valid", (getter)Image_valid, NULL, (char*)"bool to tell if an image is available", NULL}, + {(char*)"image", (getter)Image_getImage, NULL, (char*)"image data", NULL}, + {(char*)"size", (getter)Image_getSize, NULL, (char*)"image size", NULL}, + {(char*)"scale", (getter)Image_getScale, (setter)Image_setScale, (char*)"fast scale of image (near neighbor)", NULL}, + {(char*)"flip", (getter)Image_getFlip, (setter)Image_setFlip, (char*)"flip image vertically", NULL}, + {(char*)"filter", (getter)Image_getFilter, (setter)Image_setFilter, (char*)"pixel filter", NULL}, + {NULL} +}; + +// python type declaration +PyTypeObject VideoDeckLinkType = +{ + PyVarObject_HEAD_INIT(NULL, 0) + "VideoTexture.VideoDeckLink", /*tp_name*/ + sizeof(PyImage), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)Image_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + &imageBufferProcs, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + "DeckLink video source", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + videoMethods, /* tp_methods */ + 0, /* tp_members */ + videoGetSets, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)VideoDeckLink_init, /* tp_init */ + 0, /* tp_alloc */ + Image_allocNew, /* tp_new */ +}; + + + +//////////////////////////////////////////// +// DeckLink Capture Delegate Class +//////////////////////////////////////////// + +#endif // WITH_GAMEENGINE_DECKLINK + diff --git a/source/gameengine/VideoTexture/VideoDeckLink.h b/source/gameengine/VideoTexture/VideoDeckLink.h new file mode 100644 index 00000000000..be81f63d93c --- /dev/null +++ b/source/gameengine/VideoTexture/VideoDeckLink.h @@ -0,0 +1,256 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program 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 Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * +* The Original Code is Copyright (C) 2015, Blender Foundation +* All rights reserved. +* +* The Original Code is: all of this file. +* +* Contributor(s): Blender Foundation. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file VideoDeckLink.h + * \ingroup bgevideotex + */ + +#ifndef __VIDEODECKLINK_H__ +#define __VIDEODECKLINK_H__ + +#ifdef WITH_GAMEENGINE_DECKLINK + +/* this needs to be parsed with __cplusplus defined before included through DeckLink_compat.h */ +#if defined(__FreeBSD__) +# include +#endif +#include +#include + +extern "C" { +#include +#include "DNA_listBase.h" +#include "BLI_threads.h" +#include "BLI_blenlib.h" +} +#include "GL/glew.h" +#ifdef WIN32 +#include "dvpapi.h" +#endif +#include "DeckLinkAPI.h" +#include "VideoBase.h" + +class PinnedMemoryAllocator; + +struct TextureDesc +{ + uint32_t width; + uint32_t height; + uint32_t stride; + uint32_t size; + GLenum internalFormat; + GLenum format; + GLenum type; + TextureDesc() + { + width = 0; + height = 0; + stride = 0; + size = 0; + internalFormat = 0; + format = 0; + type = 0; + } +}; + +class CaptureDelegate; + +// type VideoDeckLink declaration +class VideoDeckLink : public VideoBase +{ + friend class CaptureDelegate; +public: + /// constructor + VideoDeckLink (HRESULT * hRslt); + /// destructor + virtual ~VideoDeckLink (); + + /// open video/image file + virtual void openFile(char *file); + /// open video capture device + virtual void openCam(char *driver, short camIdx); + + /// release video source + virtual bool release (void); + /// overwrite base refresh to handle fixed image + virtual void refresh(void); + /// play video + virtual bool play (void); + /// pause video + virtual bool pause (void); + /// stop video + virtual bool stop (void); + /// set play range + virtual void setRange (double start, double stop); + /// set frame rate + virtual void setFrameRate (float rate); + +protected: + // format and codec information + /// image calculation + virtual void calcImage (unsigned int texId, double ts); + +private: + void VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame); + void LockCache() + { + pthread_mutex_lock(&mCacheMutex); + } + void UnlockCache() + { + pthread_mutex_unlock(&mCacheMutex); + } + + IDeckLinkInput* mDLInput; + BMDDisplayMode mDisplayMode; + BMDPixelFormat mPixelFormat; + bool mUse3D; + uint32_t mFrameWidth; + uint32_t mFrameHeight; + TextureDesc mTextureDesc; + PinnedMemoryAllocator* mpAllocator; + CaptureDelegate* mpCaptureDelegate; + + // cache frame in transit between the callback thread and the main BGE thread + // keep only one frame in cache because we just want to keep up with real time + pthread_mutex_t mCacheMutex; + IDeckLinkVideoInputFrame* mpCacheFrame; + bool mClosing; + +}; + +inline VideoDeckLink *getDeckLink(PyImage *self) +{ + return static_cast(self->m_image); +} + +//////////////////////////////////////////// +// TextureTransfer : Abstract class to perform a transfer to GPU memory using fast transfer if available +//////////////////////////////////////////// +class TextureTransfer +{ +public: + TextureTransfer() {} + virtual ~TextureTransfer() { } + + virtual void PerformTransfer() = 0; +protected: + static bool _PinBuffer(void *address, uint32_t size); + static void _UnpinBuffer(void* address, uint32_t size); +}; + +//////////////////////////////////////////// +// PinnedMemoryAllocator +//////////////////////////////////////////// + +// PinnedMemoryAllocator implements the IDeckLinkMemoryAllocator interface and can be used instead of the +// built-in frame allocator, by setting with SetVideoInputFrameMemoryAllocator() or SetVideoOutputFrameMemoryAllocator(). +// +// For this sample application a custom frame memory allocator is used to ensure each address +// of frame memory is aligned on a 4kB boundary required by the OpenGL pinned memory extension. +// If the pinned memory extension is not available, this allocator will still be used and +// demonstrates how to cache frame allocations for efficiency. +// +// The frame cache delays the releasing of buffers until the cache fills up, thereby avoiding an +// allocate plus pin operation for every frame, followed by an unpin and deallocate on every frame. + + +class PinnedMemoryAllocator : public IDeckLinkMemoryAllocator +{ +public: + PinnedMemoryAllocator(unsigned cacheSize, size_t memSize); + virtual ~PinnedMemoryAllocator(); + + void TransferBuffer(void* address, TextureDesc* texDesc, GLuint texId); + + // IUnknown methods + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID *ppv); + virtual ULONG STDMETHODCALLTYPE AddRef(void); + virtual ULONG STDMETHODCALLTYPE Release(void); + + // IDeckLinkMemoryAllocator methods + virtual HRESULT STDMETHODCALLTYPE AllocateBuffer(dl_size_t bufferSize, void* *allocatedBuffer); + virtual HRESULT STDMETHODCALLTYPE ReleaseBuffer(void* buffer); + virtual HRESULT STDMETHODCALLTYPE Commit(); + virtual HRESULT STDMETHODCALLTYPE Decommit(); + +private: + static bool mGPUDirectInitialized; + static bool mHasDvp; + static bool mHasAMDPinnedMemory; + static size_t mReservedProcessMemory; + static bool ReserveMemory(size_t size); + + void Lock() + { + pthread_mutex_lock(&mMutex); + } + void Unlock() + { + pthread_mutex_unlock(&mMutex); + } + HRESULT _ReleaseBuffer(void* buffer); + + uint32_t mRefCount; + // protect the cache and the allocated map, + // not the pinnedBuffer map as it is only used from main thread + pthread_mutex_t mMutex; + std::map mAllocatedSize; + std::vector mBufferCache; + std::map mPinnedBuffer; +#ifdef WIN32 + DVPBufferHandle mDvpCaptureTextureHandle; +#endif + // target texture in GPU + GLuint mTexId; + uint32_t mBufferCacheSize; +}; + +//////////////////////////////////////////// +// Capture Delegate Class +//////////////////////////////////////////// + +class CaptureDelegate : public IDeckLinkInputCallback +{ + VideoDeckLink* mpOwner; + +public: + CaptureDelegate(VideoDeckLink* pOwner); + + // IUnknown needs only a dummy implementation + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID *ppv) { return E_NOINTERFACE; } + virtual ULONG STDMETHODCALLTYPE AddRef() { return 1; } + virtual ULONG STDMETHODCALLTYPE Release() { return 1; } + + virtual HRESULT STDMETHODCALLTYPE VideoInputFrameArrived(IDeckLinkVideoInputFrame *videoFrame, IDeckLinkAudioInputPacket *audioPacket); + virtual HRESULT STDMETHODCALLTYPE VideoInputFormatChanged(BMDVideoInputFormatChangedEvents notificationEvents, IDeckLinkDisplayMode *newDisplayMode, BMDDetectedVideoInputFormatFlags detectedSignalFlags); +}; + + +#endif /* WITH_GAMEENGINE_DECKLINK */ + +#endif /* __VIDEODECKLINK_H__ */ diff --git a/source/gameengine/VideoTexture/blendVideoTex.cpp b/source/gameengine/VideoTexture/blendVideoTex.cpp index a62ffee3137..9b046d46412 100644 --- a/source/gameengine/VideoTexture/blendVideoTex.cpp +++ b/source/gameengine/VideoTexture/blendVideoTex.cpp @@ -128,6 +128,10 @@ static PyMethodDef moduleMethods[] = extern PyTypeObject VideoFFmpegType; extern PyTypeObject ImageFFmpegType; #endif +#ifdef WITH_GAMEENGINE_DECKLINK +extern PyTypeObject VideoDeckLinkType; +extern PyTypeObject DeckLinkType; +#endif extern PyTypeObject FilterBlueScreenType; extern PyTypeObject FilterGrayType; extern PyTypeObject FilterColorType; @@ -144,6 +148,9 @@ static void registerAllTypes(void) #ifdef WITH_FFMPEG pyImageTypes.add(&VideoFFmpegType, "VideoFFmpeg"); pyImageTypes.add(&ImageFFmpegType, "ImageFFmpeg"); +#endif +#ifdef WITH_GAMEENGINE_DECKLINK + pyImageTypes.add(&VideoDeckLinkType, "VideoDeckLink"); #endif pyImageTypes.add(&ImageBuffType, "ImageBuff"); pyImageTypes.add(&ImageMixType, "ImageMix"); @@ -194,6 +201,10 @@ PyMODINIT_FUNC initVideoTexturePythonBinding(void) return NULL; if (PyType_Ready(&TextureType) < 0) return NULL; +#ifdef WITH_GAMEENGINE_DECKLINK + if (PyType_Ready(&DeckLinkType) < 0) + return NULL; +#endif m = PyModule_Create(&VideoTexture_module_def); PyDict_SetItemString(PySys_GetObject("modules"), VideoTexture_module_def.m_name, m); @@ -207,6 +218,10 @@ PyMODINIT_FUNC initVideoTexturePythonBinding(void) Py_INCREF(&TextureType); PyModule_AddObject(m, "Texture", (PyObject *)&TextureType); +#ifdef WITH_GAMEENGINE_DECKLINK + Py_INCREF(&DeckLinkType); + PyModule_AddObject(m, "DeckLink", (PyObject *)&DeckLinkType); +#endif PyModule_AddIntConstant(m, "SOURCE_ERROR", SourceError); PyModule_AddIntConstant(m, "SOURCE_EMPTY", SourceEmpty); PyModule_AddIntConstant(m, "SOURCE_READY", SourceReady); -- cgit v1.2.3