Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenoit Bolsee <benoit.bolsee@online.be>2016-06-10 00:56:45 +0300
committerBenoit Bolsee <benoit.bolsee@online.be>2016-06-11 23:05:20 +0300
commit40f1c4f34337d7dfb3fa5bcbd2daa2f602e12011 (patch)
tree9487b558d82438484577598ac86d2c9a67185d92 /source/gameengine/VideoTexture
parent5b061ddf1e3fc0a80a2d0b9fe478b6882a5898bd (diff)
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.<imagetype>.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)
Diffstat (limited to 'source/gameengine/VideoTexture')
-rw-r--r--source/gameengine/VideoTexture/Exception.cpp15
-rw-r--r--source/gameengine/VideoTexture/Exception.h18
-rw-r--r--source/gameengine/VideoTexture/FilterBase.h7
-rw-r--r--source/gameengine/VideoTexture/FilterSource.h24
-rw-r--r--source/gameengine/VideoTexture/ImageBase.cpp99
-rw-r--r--source/gameengine/VideoTexture/ImageBase.h10
-rw-r--r--source/gameengine/VideoTexture/ImageMix.cpp2
-rw-r--r--source/gameengine/VideoTexture/ImageRender.cpp200
-rw-r--r--source/gameengine/VideoTexture/ImageRender.h28
-rw-r--r--source/gameengine/VideoTexture/ImageViewport.cpp99
-rw-r--r--source/gameengine/VideoTexture/ImageViewport.h11
-rw-r--r--source/gameengine/VideoTexture/Texture.cpp7
-rw-r--r--source/gameengine/VideoTexture/VideoBase.cpp47
-rw-r--r--source/gameengine/VideoTexture/VideoBase.h2
-rw-r--r--source/gameengine/VideoTexture/VideoFFmpeg.cpp4
15 files changed, 495 insertions, 78 deletions
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 <vector>
#include <string.h>
@@ -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<char**>(kwlist), &scene, &camera))
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|O",
+ const_cast<char**>(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<PyImage*>(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}
};