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
path: root/source
diff options
context:
space:
mode:
authorCampbell Barton <ideasman42@gmail.com>2016-06-13 12:26:56 +0300
committerCampbell Barton <ideasman42@gmail.com>2016-06-13 12:26:56 +0300
commit5864269b2c60b150e8ad4244ae6dfccc04f0b44a (patch)
treefb11460098269de11de2843ca29e9a61c8fa7bda /source
parent46e1d85e61aac190e382359d722fafbd533d35ee (diff)
parent617c4d6adbfe64b3a72b5c48f918f39d30aa18dc (diff)
Merge branch 'master' into blender2.8
Diffstat (limited to 'source')
-rw-r--r--source/blender/blenkernel/intern/editderivedmesh.c4
-rw-r--r--source/blender/blenlib/BLI_math_vector.h18
-rw-r--r--source/blender/blenlib/BLI_rand.h1
-rw-r--r--source/blender/blenlib/intern/math_vector.c117
-rw-r--r--source/blender/blenlib/intern/rand.c40
-rw-r--r--source/blender/bmesh/intern/bmesh_marking.c2
-rw-r--r--source/blender/bmesh/intern/bmesh_polygon.c242
-rw-r--r--source/blender/bmesh/intern/bmesh_polygon.h9
-rw-r--r--source/blender/bmesh/operators/bmo_removedoubles.c21
-rw-r--r--source/blender/editors/interface/interface_draw.c12
-rw-r--r--source/blender/editors/mask/mask_draw.c18
-rw-r--r--source/blender/editors/sculpt_paint/paint_stroke.c12
-rw-r--r--source/blender/editors/space_clip/clip_draw.c52
-rw-r--r--source/blender/editors/space_sequencer/sequencer_draw.c3
-rw-r--r--source/blender/editors/space_sequencer/sequencer_select.c108
-rw-r--r--source/blender/editors/space_view3d/drawmesh.c6
-rw-r--r--source/blender/editors/space_view3d/view3d_draw.c4
-rw-r--r--source/blender/editors/transform/transform_conversions.c7
-rw-r--r--source/blender/editors/transform/transform_orientations.c4
-rw-r--r--source/blender/gpu/GPU_basic_shader.h3
-rw-r--r--source/blender/gpu/intern/gpu_basic_shader.c23
-rw-r--r--source/blender/gpu/intern/gpu_buffers.c23
-rw-r--r--source/blender/python/bmesh/bmesh_py_types.c80
-rw-r--r--source/blenderplayer/CMakeLists.txt8
-rw-r--r--source/gameengine/BlenderRoutines/BL_KetsjiEmbedStart.cpp1
-rw-r--r--source/gameengine/GamePlayer/ghost/GPG_Application.cpp12
-rw-r--r--source/gameengine/GamePlayer/ghost/GPG_Application.h6
-rw-r--r--source/gameengine/GamePlayer/ghost/GPG_ghost.cpp11
-rw-r--r--source/gameengine/Ketsji/BL_Shader.cpp156
-rw-r--r--source/gameengine/Ketsji/BL_Shader.h4
-rw-r--r--source/gameengine/Ketsji/KX_Dome.cpp4
-rw-r--r--source/gameengine/Ketsji/KX_KetsjiEngine.cpp32
-rw-r--r--source/gameengine/Ketsji/KX_KetsjiEngine.h20
-rw-r--r--source/gameengine/Ketsji/KX_PythonInit.cpp178
-rw-r--r--source/gameengine/Ketsji/KX_Scene.cpp1
-rw-r--r--source/gameengine/Ketsji/KX_Scene.h7
-rw-r--r--source/gameengine/Rasterizer/CMakeLists.txt2
-rw-r--r--source/gameengine/Rasterizer/RAS_IOffScreen.h84
-rw-r--r--source/gameengine/Rasterizer/RAS_IRasterizer.h16
-rw-r--r--source/gameengine/Rasterizer/RAS_ISync.h48
-rw-r--r--source/gameengine/Rasterizer/RAS_OpenGLRasterizer/CMakeLists.txt4
-rw-r--r--source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLLight.cpp2
-rw-r--r--source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLOffScreen.cpp347
-rw-r--r--source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLOffScreen.h65
-rw-r--r--source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLRasterizer.cpp41
-rw-r--r--source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLRasterizer.h11
-rw-r--r--source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLSync.cpp82
-rw-r--r--source/gameengine/Rasterizer/RAS_OpenGLRasterizer/RAS_OpenGLSync.h50
-rw-r--r--source/gameengine/VideoTexture/CMakeLists.txt13
-rw-r--r--source/gameengine/VideoTexture/Common.h3
-rw-r--r--source/gameengine/VideoTexture/DeckLink.cpp813
-rw-r--r--source/gameengine/VideoTexture/DeckLink.h86
-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/VideoDeckLink.cpp1228
-rw-r--r--source/gameengine/VideoTexture/VideoDeckLink.h256
-rw-r--r--source/gameengine/VideoTexture/VideoFFmpeg.cpp4
-rw-r--r--source/gameengine/VideoTexture/blendVideoTex.cpp15
70 files changed, 4625 insertions, 363 deletions
diff --git a/source/blender/blenkernel/intern/editderivedmesh.c b/source/blender/blenkernel/intern/editderivedmesh.c
index 6b81c47765b..c1013342bd9 100644
--- a/source/blender/blenkernel/intern/editderivedmesh.c
+++ b/source/blender/blenkernel/intern/editderivedmesh.c
@@ -1185,6 +1185,10 @@ static void emDM_drawMappedFaces(
if (draw_option_prev == DM_DRAW_OPTION_STIPPLE) {
GPU_basic_shader_bind(GPU_SHADER_USE_COLOR);
}
+
+ if (shade_prev == GL_FLAT) {
+ glShadeModel(GL_SMOOTH);
+ }
}
static void bmdm_get_tri_uv(BMLoop *ltri[3], MLoopUV *luv[3], const int cd_loop_uv_offset)
diff --git a/source/blender/blenlib/BLI_math_vector.h b/source/blender/blenlib/BLI_math_vector.h
index 5f76b79b298..25f485a25aa 100644
--- a/source/blender/blenlib/BLI_math_vector.h
+++ b/source/blender/blenlib/BLI_math_vector.h
@@ -282,18 +282,18 @@ void angle_poly_v3(float *angles, const float *verts[3], int len);
/********************************* Geometry **********************************/
-void project_v2_v2v2(float c[2], const float v1[2], const float v2[2]);
-void project_v3_v3v3(float r[3], const float p[3], const float n[3]);
-void project_plane_v3_v3v3(float c[3], const float v[3], const float v_plane[3]);
-void project_plane_v2_v2v2(float c[2], const float v[2], const float v_plane[2]);
-void project_v3_plane(float v[3], const float n[3], const float p[3]);
-void reflect_v3_v3v3(float r[3], const float v[3], const float n[3]);
+void project_v2_v2v2(float out[2], const float p[2], const float v_proj[2]);
+void project_v3_v3v3(float out[3], const float p[3], const float v_proj[3]);
+void project_plane_v3_v3v3(float out[3], const float p[3], const float v_plane[3]);
+void project_plane_v2_v2v2(float out[2], const float p[2], const float v_plane[2]);
+void project_v3_plane(float out[3], const float plane_no[3], const float plane_co[3]);
+void reflect_v3_v3v3(float out[3], const float vec[3], const float normal[3]);
void ortho_basis_v3v3_v3(float r_n1[3], float r_n2[3], const float n[3]);
-void ortho_v3_v3(float p[3], const float v[3]);
-void ortho_v2_v2(float p[2], const float v[2]);
+void ortho_v3_v3(float out[3], const float v[3]);
+void ortho_v2_v2(float out[2], const float v[2]);
void bisect_v3_v3v3v3(float r[3], const float a[3], const float b[3], const float c[3]);
void rotate_v3_v3v3fl(float v[3], const float p[3], const float axis[3], const float angle);
-void rotate_normalized_v3_v3v3fl(float v[3], const float p[3], const float axis[3], const float angle);
+void rotate_normalized_v3_v3v3fl(float out[3], const float p[3], const float axis[3], const float angle);
/*********************************** Other ***********************************/
diff --git a/source/blender/blenlib/BLI_rand.h b/source/blender/blenlib/BLI_rand.h
index 7b84dddcb68..f36d2faa1b8 100644
--- a/source/blender/blenlib/BLI_rand.h
+++ b/source/blender/blenlib/BLI_rand.h
@@ -49,6 +49,7 @@ void BLI_rng_free(struct RNG *rng) ATTR_NONNULL(1);
void BLI_rng_seed(struct RNG *rng, unsigned int seed) ATTR_NONNULL(1);
void BLI_rng_srandom(struct RNG *rng, unsigned int seed) ATTR_NONNULL(1);
+void BLI_rng_get_char_n(RNG *rng, char *bytes, size_t bytes_len) ATTR_NONNULL(1, 2);
int BLI_rng_get_int(struct RNG *rng) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1);
unsigned int BLI_rng_get_uint(struct RNG *rng) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1);
double BLI_rng_get_double(struct RNG *rng) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1);
diff --git a/source/blender/blenlib/intern/math_vector.c b/source/blender/blenlib/intern/math_vector.c
index 7f2db3743df..988034349e0 100644
--- a/source/blender/blenlib/intern/math_vector.c
+++ b/source/blender/blenlib/intern/math_vector.c
@@ -585,23 +585,27 @@ void angle_poly_v3(float *angles, const float *verts[3], int len)
/********************************* Geometry **********************************/
-/* Project v1 on v2 */
-void project_v2_v2v2(float c[2], const float v1[2], const float v2[2])
+/**
+ * Project \a p onto \a v_proj
+ */
+void project_v2_v2v2(float out[2], const float p[2], const float v_proj[2])
{
- const float mul = dot_v2v2(v1, v2) / dot_v2v2(v2, v2);
+ const float mul = dot_v2v2(p, v_proj) / dot_v2v2(v_proj, v_proj);
- c[0] = mul * v2[0];
- c[1] = mul * v2[1];
+ out[0] = mul * v_proj[0];
+ out[1] = mul * v_proj[1];
}
-/* Project v1 on v2 */
-void project_v3_v3v3(float c[3], const float v1[3], const float v2[3])
+/**
+ * Project \a p onto \a v_proj
+ */
+void project_v3_v3v3(float out[3], const float p[3], const float v_proj[3])
{
- const float mul = dot_v3v3(v1, v2) / dot_v3v3(v2, v2);
+ const float mul = dot_v3v3(p, v_proj) / dot_v3v3(v_proj, v_proj);
- c[0] = mul * v2[0];
- c[1] = mul * v2[1];
- c[2] = mul * v2[2];
+ out[0] = mul * v_proj[0];
+ out[1] = mul * v_proj[1];
+ out[2] = mul * v_proj[2];
}
/**
@@ -617,35 +621,35 @@ void project_v3_v3v3(float c[3], const float v1[3], const float v2[3])
* sub_v3_v3v3(c, v, c);
* \endcode
*/
-void project_plane_v3_v3v3(float c[3], const float v[3], const float v_plane[3])
+void project_plane_v3_v3v3(float out[3], const float p[3], const float v_plane[3])
{
- const float mul = dot_v3v3(v, v_plane) / dot_v3v3(v_plane, v_plane);
+ const float mul = dot_v3v3(p, v_plane) / dot_v3v3(v_plane, v_plane);
- c[0] = v[0] - (mul * v_plane[0]);
- c[1] = v[1] - (mul * v_plane[1]);
- c[2] = v[2] - (mul * v_plane[2]);
+ out[0] = p[0] - (mul * v_plane[0]);
+ out[1] = p[1] - (mul * v_plane[1]);
+ out[2] = p[2] - (mul * v_plane[2]);
}
-void project_plane_v2_v2v2(float c[2], const float v[2], const float v_plane[2])
+void project_plane_v2_v2v2(float out[2], const float p[2], const float v_plane[2])
{
- const float mul = dot_v2v2(v, v_plane) / dot_v2v2(v_plane, v_plane);
+ const float mul = dot_v2v2(p, v_plane) / dot_v2v2(v_plane, v_plane);
- c[0] = v[0] - (mul * v_plane[0]);
- c[1] = v[1] - (mul * v_plane[1]);
+ out[0] = p[0] - (mul * v_plane[0]);
+ out[1] = p[1] - (mul * v_plane[1]);
}
/* project a vector on a plane defined by normal and a plane point p */
-void project_v3_plane(float v[3], const float n[3], const float p[3])
+void project_v3_plane(float out[3], const float plane_no[3], const float plane_co[3])
{
float vector[3];
float mul;
- sub_v3_v3v3(vector, v, p);
- mul = dot_v3v3(vector, n) / len_squared_v3(n);
+ sub_v3_v3v3(vector, out, plane_co);
+ mul = dot_v3v3(vector, plane_no) / len_squared_v3(plane_no);
- mul_v3_v3fl(vector, n, mul);
+ mul_v3_v3fl(vector, plane_no, mul);
- sub_v3_v3(v, vector);
+ sub_v3_v3(out, vector);
}
/* Returns a vector bisecting the angle at v2 formed by v1, v2 and v3 */
@@ -664,15 +668,15 @@ void bisect_v3_v3v3v3(float out[3], const float v1[3], const float v2[3], const
* Returns a reflection vector from a vector and a normal vector
* reflect = vec - ((2 * DotVecs(vec, mirror)) * mirror)
*/
-void reflect_v3_v3v3(float out[3], const float vec[3], const float normal[3])
+void reflect_v3_v3v3(float out[3], const float v[3], const float normal[3])
{
- const float dot2 = 2.0f * dot_v3v3(vec, normal);
+ const float dot2 = 2.0f * dot_v3v3(v, normal);
BLI_ASSERT_UNIT_V3(normal);
- out[0] = vec[0] - (dot2 * normal[0]);
- out[1] = vec[1] - (dot2 * normal[1]);
- out[2] = vec[2] - (dot2 * normal[2]);
+ out[0] = v[0] - (dot2 * normal[0]);
+ out[1] = v[1] - (dot2 * normal[1]);
+ out[2] = v[2] - (dot2 * normal[2]);
}
/**
@@ -710,27 +714,27 @@ void ortho_basis_v3v3_v3(float r_n1[3], float r_n2[3], const float n[3])
*
* \note return vector won't maintain same length.
*/
-void ortho_v3_v3(float p[3], const float v[3])
+void ortho_v3_v3(float out[3], const float v[3])
{
const int axis = axis_dominant_v3_single(v);
- BLI_assert(p != v);
+ BLI_assert(out != v);
switch (axis) {
case 0:
- p[0] = -v[1] - v[2];
- p[1] = v[0];
- p[2] = v[0];
+ out[0] = -v[1] - v[2];
+ out[1] = v[0];
+ out[2] = v[0];
break;
case 1:
- p[0] = v[1];
- p[1] = -v[0] - v[2];
- p[2] = v[1];
+ out[0] = v[1];
+ out[1] = -v[0] - v[2];
+ out[2] = v[1];
break;
case 2:
- p[0] = v[2];
- p[1] = v[2];
- p[2] = -v[0] - v[1];
+ out[0] = v[2];
+ out[1] = v[2];
+ out[2] = -v[0] - v[1];
break;
}
}
@@ -738,18 +742,19 @@ void ortho_v3_v3(float p[3], const float v[3])
/**
* no brainer compared to v3, just have for consistency.
*/
-void ortho_v2_v2(float p[2], const float v[2])
+void ortho_v2_v2(float out[2], const float v[2])
{
- BLI_assert(p != v);
+ BLI_assert(out != v);
- p[0] = -v[1];
- p[1] = v[0];
+ out[0] = -v[1];
+ out[1] = v[0];
}
-/* Rotate a point p by angle theta around an arbitrary axis r
+/**
+ * Rotate a point \a p by \a angle around an arbitrary unit length \a axis.
* http://local.wasp.uwa.edu.au/~pbourke/geometry/
*/
-void rotate_normalized_v3_v3v3fl(float r[3], const float p[3], const float axis[3], const float angle)
+void rotate_normalized_v3_v3v3fl(float out[3], const float p[3], const float axis[3], const float angle)
{
const float costheta = cosf(angle);
const float sintheta = sinf(angle);
@@ -757,17 +762,17 @@ void rotate_normalized_v3_v3v3fl(float r[3], const float p[3], const float axis[
/* double check they are normalized */
BLI_ASSERT_UNIT_V3(axis);
- r[0] = ((costheta + (1 - costheta) * axis[0] * axis[0]) * p[0]) +
- (((1 - costheta) * axis[0] * axis[1] - axis[2] * sintheta) * p[1]) +
- (((1 - costheta) * axis[0] * axis[2] + axis[1] * sintheta) * p[2]);
+ out[0] = ((costheta + (1 - costheta) * axis[0] * axis[0]) * p[0]) +
+ (((1 - costheta) * axis[0] * axis[1] - axis[2] * sintheta) * p[1]) +
+ (((1 - costheta) * axis[0] * axis[2] + axis[1] * sintheta) * p[2]);
- r[1] = (((1 - costheta) * axis[0] * axis[1] + axis[2] * sintheta) * p[0]) +
- ((costheta + (1 - costheta) * axis[1] * axis[1]) * p[1]) +
- (((1 - costheta) * axis[1] * axis[2] - axis[0] * sintheta) * p[2]);
+ out[1] = (((1 - costheta) * axis[0] * axis[1] + axis[2] * sintheta) * p[0]) +
+ ((costheta + (1 - costheta) * axis[1] * axis[1]) * p[1]) +
+ (((1 - costheta) * axis[1] * axis[2] - axis[0] * sintheta) * p[2]);
- r[2] = (((1 - costheta) * axis[0] * axis[2] - axis[1] * sintheta) * p[0]) +
- (((1 - costheta) * axis[1] * axis[2] + axis[0] * sintheta) * p[1]) +
- ((costheta + (1 - costheta) * axis[2] * axis[2]) * p[2]);
+ out[2] = (((1 - costheta) * axis[0] * axis[2] - axis[1] * sintheta) * p[0]) +
+ (((1 - costheta) * axis[1] * axis[2] + axis[0] * sintheta) * p[1]) +
+ ((costheta + (1 - costheta) * axis[2] * axis[2]) * p[2]);
}
void rotate_v3_v3v3fl(float r[3], const float p[3], const float axis[3], const float angle)
diff --git a/source/blender/blenlib/intern/rand.c b/source/blender/blenlib/intern/rand.c
index 66c568a7ff3..40d9a3da3d9 100644
--- a/source/blender/blenlib/intern/rand.c
+++ b/source/blender/blenlib/intern/rand.c
@@ -46,6 +46,7 @@
#define MULTIPLIER 0x5DEECE66Dll
#define MASK 0x0000FFFFFFFFFFFFll
+#define MASK_BYTES 2
#define ADDEND 0xB
#define LOWSEED 0x330E
@@ -107,6 +108,45 @@ BLI_INLINE void rng_step(RNG *rng)
rng->X = (MULTIPLIER * rng->X + ADDEND) & MASK;
}
+void BLI_rng_get_char_n(RNG *rng, char *bytes, size_t bytes_len)
+{
+ size_t last_len = 0;
+ size_t trim_len = bytes_len;
+
+#define RAND_STRIDE (sizeof(rng->X) - MASK_BYTES)
+
+ if (trim_len > RAND_STRIDE) {
+ last_len = trim_len % RAND_STRIDE;
+ trim_len = trim_len - last_len;
+ }
+ else {
+ trim_len = 0;
+ last_len = bytes_len;
+ }
+
+ const char *data_src = (void *)&(rng->X);
+ size_t i = 0;
+ while (i != trim_len) {
+ BLI_assert(i < trim_len);
+#ifdef __BIG_ENDIAN__
+ for (size_t j = (RAND_STRIDE + MASK_BYTES) - 1; j != MASK_BYTES - 1; j--)
+#else
+ for (size_t j = 0; j != RAND_STRIDE; j++)
+#endif
+ {
+ bytes[i++] = data_src[j];
+ }
+ rng_step(rng);
+ }
+ if (last_len) {
+ for (size_t j = 0; j != last_len; j++) {
+ bytes[i++] = data_src[j];
+ }
+ }
+
+#undef RAND_STRIDE
+}
+
int BLI_rng_get_int(RNG *rng)
{
rng_step(rng);
diff --git a/source/blender/bmesh/intern/bmesh_marking.c b/source/blender/bmesh/intern/bmesh_marking.c
index 3fe888736f0..d6ca7239e39 100644
--- a/source/blender/bmesh/intern/bmesh_marking.c
+++ b/source/blender/bmesh/intern/bmesh_marking.c
@@ -904,7 +904,7 @@ void BM_editselection_plane(BMEditSelection *ese, float r_plane[3])
}
else if (ese->htype == BM_FACE) {
BMFace *efa = (BMFace *)ese->ele;
- BM_face_calc_plane(efa, r_plane);
+ BM_face_calc_tangent_auto(efa, r_plane);
}
}
diff --git a/source/blender/bmesh/intern/bmesh_polygon.c b/source/blender/bmesh/intern/bmesh_polygon.c
index 62b29e61d08..79051a2490f 100644
--- a/source/blender/bmesh/intern/bmesh_polygon.c
+++ b/source/blender/bmesh/intern/bmesh_polygon.c
@@ -286,64 +286,258 @@ float BM_face_calc_perimeter(const BMFace *f)
return perimeter;
}
-void BM_vert_tri_calc_plane(BMVert *verts[3], float r_plane[3])
+/**
+ * Utility function to calculate the edge which is most different from the other two.
+ *
+ * \return The first edge index, where the second vertex is ``(index + 1) % 3``.
+ */
+static int bm_vert_tri_find_unique_edge(BMVert *verts[3])
{
- float lens[3];
+ /* find the most 'unique' loop, (greatest difference to others) */
+#if 1
+ /* optimized version that avoids sqrt */
float difs[3];
- int order[3] = {0, 1, 2};
+ for (int i_prev = 1, i_curr = 2, i_next = 0;
+ i_next < 3;
+ i_prev = i_curr, i_curr = i_next++)
+ {
+ const float *co = verts[i_curr]->co;
+ const float *co_other[2] = {verts[i_prev]->co, verts[i_next]->co};
+ float proj_dir[3];
+ mid_v3_v3v3(proj_dir, co_other[0], co_other[1]);
+ sub_v3_v3(proj_dir, co);
+
+ float proj_pair[2][3];
+ project_v3_v3v3(proj_pair[0], co_other[0], proj_dir);
+ project_v3_v3v3(proj_pair[1], co_other[1], proj_dir);
+ difs[i_next] = len_squared_v3v3(proj_pair[0], proj_pair[1]);
+ }
+#else
+ const float lens[3] = {
+ len_v3v3(verts[0]->co, verts[1]->co),
+ len_v3v3(verts[1]->co, verts[2]->co),
+ len_v3v3(verts[2]->co, verts[0]->co),
+ };
+ const float difs[3] = {
+ fabsf(lens[1] - lens[2]),
+ fabsf(lens[2] - lens[0]),
+ fabsf(lens[0] - lens[1]),
+ };
+#endif
- lens[0] = len_v3v3(verts[0]->co, verts[1]->co);
- lens[1] = len_v3v3(verts[1]->co, verts[2]->co);
- lens[2] = len_v3v3(verts[2]->co, verts[0]->co);
+ int order[3] = {0, 1, 2};
+ axis_sort_v3(difs, order);
- /* find the shortest or the longest loop */
- difs[0] = fabsf(lens[1] - lens[2]);
- difs[1] = fabsf(lens[2] - lens[0]);
- difs[2] = fabsf(lens[0] - lens[1]);
+ return order[0];
+}
- axis_sort_v3(difs, order);
- sub_v3_v3v3(r_plane, verts[order[0]]->co, verts[(order[0] + 1) % 3]->co);
+/**
+ * Calculate a tangent from any 3 vertices.
+ *
+ * The tangent aligns to the most *unique* edge
+ * (the edge most unlike the other two).
+ *
+ * \param r_tangent: Calculated unit length tangent (return value).
+ */
+void BM_vert_tri_calc_tangent_edge(BMVert *verts[3], float r_tangent[3])
+{
+ const int index = bm_vert_tri_find_unique_edge(verts);
+
+ sub_v3_v3v3(r_tangent, verts[index]->co, verts[(index + 1) % 3]->co);
+
+ normalize_v3(r_tangent);
}
/**
- * Compute a meaningful direction along the face (use for manipulator axis).
- * \note result isnt normalized.
+ * Calculate a tangent from any 3 vertices,
+ *
+ * The tangent follows the center-line formed by the most unique edges center
+ * and the opposite vertex.
+ *
+ * \param r_tangent: Calculated unit length tangent (return value).
*/
-void BM_face_calc_plane(const BMFace *f, float r_plane[3])
+void BM_vert_tri_calc_tangent_edge_pair(BMVert *verts[3], float r_tangent[3])
+{
+ const int index = bm_vert_tri_find_unique_edge(verts);
+
+ const float *v_a = verts[index]->co;
+ const float *v_b = verts[(index + 1) % 3]->co;
+ const float *v_other = verts[(index + 2) % 3]->co;
+
+ mid_v3_v3v3(r_tangent, v_a, v_b);
+ sub_v3_v3v3(r_tangent, v_other, r_tangent);
+
+ normalize_v3(r_tangent);
+}
+
+/**
+ * Compute the tanget of the face, using the longest edge.
+ */
+void BM_face_calc_tangent_edge(const BMFace *f, float r_tangent[3])
+{
+ const BMLoop *l_long = BM_face_find_longest_loop((BMFace *)f);
+
+ sub_v3_v3v3(r_tangent, l_long->v->co, l_long->next->v->co);
+
+ normalize_v3(r_tangent);
+
+}
+
+/**
+ * Compute the tanget of the face, using the two longest disconected edges.
+ *
+ * \param r_tangent: Calculated unit length tangent (return value).
+ */
+void BM_face_calc_tangent_edge_pair(const BMFace *f, float r_tangent[3])
{
if (f->len == 3) {
BMVert *verts[3];
BM_face_as_array_vert_tri((BMFace *)f, verts);
- BM_vert_tri_calc_plane(verts, r_plane);
+ BM_vert_tri_calc_tangent_edge_pair(verts, r_tangent);
}
else if (f->len == 4) {
+ /* Use longest edge pair */
BMVert *verts[4];
float vec[3], vec_a[3], vec_b[3];
- // BM_iter_as_array(NULL, BM_VERTS_OF_FACE, efa, (void **)verts, 4);
BM_face_as_array_vert_quad((BMFace *)f, verts);
sub_v3_v3v3(vec_a, verts[3]->co, verts[2]->co);
sub_v3_v3v3(vec_b, verts[0]->co, verts[1]->co);
- add_v3_v3v3(r_plane, vec_a, vec_b);
+ add_v3_v3v3(r_tangent, vec_a, vec_b);
sub_v3_v3v3(vec_a, verts[0]->co, verts[3]->co);
sub_v3_v3v3(vec_b, verts[1]->co, verts[2]->co);
add_v3_v3v3(vec, vec_a, vec_b);
- /* use the biggest edge length */
- if (len_squared_v3(r_plane) < len_squared_v3(vec)) {
- copy_v3_v3(r_plane, vec);
+ /* use the longest edge length */
+ if (len_squared_v3(r_tangent) < len_squared_v3(vec)) {
+ copy_v3_v3(r_tangent, vec);
}
}
else {
- const BMLoop *l_long = BM_face_find_longest_loop((BMFace *)f);
+ /* For ngons use two longest disconnected edges */
+ BMLoop *l_long = BM_face_find_longest_loop((BMFace *)f);
+ BMLoop *l_long_other = NULL;
- sub_v3_v3v3(r_plane, l_long->v->co, l_long->next->v->co);
+ float len_max_sq = 0.0f;
+ float vec_a[3], vec_b[3];
+
+ BMLoop *l_iter = l_long->prev->prev;
+ BMLoop *l_last = l_long->next;
+
+ do {
+ const float len_sq = len_squared_v3v3(l_iter->v->co, l_iter->next->v->co);
+ if (len_sq >= len_max_sq) {
+ l_long_other = l_iter;
+ len_max_sq = len_sq;
+ }
+ } while ((l_iter = l_iter->prev) != l_last);
+
+ sub_v3_v3v3(vec_a, l_long->next->v->co, l_long->v->co);
+ sub_v3_v3v3(vec_b, l_long_other->v->co, l_long_other->next->v->co);
+ add_v3_v3v3(r_tangent, vec_a, vec_b);
+
+ /* Edges may not be opposite side of the ngon,
+ * this could cause problems for ngons with multiple-aligned edges of the same length.
+ * Fallback to longest edge. */
+ if (UNLIKELY(normalize_v3(r_tangent) == 0.0f)) {
+ normalize_v3_v3(r_tangent, vec_a);
+ }
}
+}
- normalize_v3(r_plane);
+/**
+ * Compute the tanget of the face, using the edge farthest away from any vertex in the face.
+ *
+ * \param r_tangent: Calculated unit length tangent (return value).
+ */
+void BM_face_calc_tangent_edge_diagonal(const BMFace *f, float r_tangent[3])
+{
+ BMLoop *l_iter, *l_first;
+
+ l_iter = l_first = BM_FACE_FIRST_LOOP(f);
+
+ /* incase of degenerate faces */
+ zero_v3(r_tangent);
+
+ /* warning: O(n^2) loop here, take care! */
+ float dist_max_sq = 0.0f;
+ do {
+ BMLoop *l_iter_other = l_iter->next;
+ BMLoop *l_iter_last = l_iter->prev;
+ do {
+ BLI_assert(!ELEM(l_iter->v->co, l_iter_other->v->co, l_iter_other->next->v->co));
+ float co_other[3], vec[3];
+ closest_to_line_segment_v3(co_other, l_iter->v->co, l_iter_other->v->co, l_iter_other->next->v->co);
+ sub_v3_v3v3(vec, l_iter->v->co, co_other);
+
+ const float dist_sq = len_squared_v3(vec);
+ if (dist_sq > dist_max_sq) {
+ dist_max_sq = dist_sq;
+ copy_v3_v3(r_tangent, vec);
+ }
+ } while ((l_iter_other = l_iter_other->next) != l_iter_last);
+ } while ((l_iter = l_iter->next) != l_first);
+
+ normalize_v3(r_tangent);
+}
+
+/**
+ * Compute the tanget of the face, using longest distance between vertices on the face.
+ *
+ * \note The logic is almost identical to #BM_face_calc_tangent_edge_diagonal
+ */
+void BM_face_calc_tangent_vert_diagonal(const BMFace *f, float r_tangent[3])
+{
+ BMLoop *l_iter, *l_first;
+
+ l_iter = l_first = BM_FACE_FIRST_LOOP(f);
+
+ /* incase of degenerate faces */
+ zero_v3(r_tangent);
+
+ /* warning: O(n^2) loop here, take care! */
+ float dist_max_sq = 0.0f;
+ do {
+ BMLoop *l_iter_other = l_iter->next;
+ do {
+ float vec[3];
+ sub_v3_v3v3(vec, l_iter->v->co, l_iter_other->v->co);
+
+ const float dist_sq = len_squared_v3(vec);
+ if (dist_sq > dist_max_sq) {
+ dist_max_sq = dist_sq;
+ copy_v3_v3(r_tangent, vec);
+ }
+ } while ((l_iter_other = l_iter_other->next) != l_iter);
+ } while ((l_iter = l_iter->next) != l_first);
+
+ normalize_v3(r_tangent);
+}
+
+/**
+ * Compute a meaningful direction along the face (use for manipulator axis).
+ *
+ * \note Callers shouldn't depend on the *exact* method used here.
+ */
+void BM_face_calc_tangent_auto(const BMFace *f, float r_tangent[3])
+{
+ if (f->len == 3) {
+ /* most 'unique' edge of a triangle */
+ BMVert *verts[3];
+ BM_face_as_array_vert_tri((BMFace *)f, verts);
+ BM_vert_tri_calc_tangent_edge(verts, r_tangent);
+ }
+ else if (f->len == 4) {
+ /* longest edge pair of a quad */
+ BM_face_calc_tangent_edge_pair((BMFace *)f, r_tangent);
+ }
+ else {
+ /* longest edge of an ngon */
+ BM_face_calc_tangent_edge((BMFace *)f, r_tangent);
+ }
}
/**
diff --git a/source/blender/bmesh/intern/bmesh_polygon.h b/source/blender/bmesh/intern/bmesh_polygon.h
index 8f0df81af73..1e50a504875 100644
--- a/source/blender/bmesh/intern/bmesh_polygon.h
+++ b/source/blender/bmesh/intern/bmesh_polygon.h
@@ -45,7 +45,11 @@ float BM_face_calc_normal_vcos(
float BM_face_calc_normal_subset(const BMLoop *l_first, const BMLoop *l_last, float r_no[3]) ATTR_NONNULL();
float BM_face_calc_area(const BMFace *f) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
float BM_face_calc_perimeter(const BMFace *f) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
-void BM_face_calc_plane(const BMFace *f, float r_plane[3]) ATTR_NONNULL();
+void BM_face_calc_tangent_edge(const BMFace *f, float r_plane[3]) ATTR_NONNULL();
+void BM_face_calc_tangent_edge_pair(const BMFace *f, float r_plane[3]) ATTR_NONNULL();
+void BM_face_calc_tangent_edge_diagonal(const BMFace *f, float r_plane[3]) ATTR_NONNULL();
+void BM_face_calc_tangent_vert_diagonal(const BMFace *f, float r_plane[3]) ATTR_NONNULL();
+void BM_face_calc_tangent_auto(const BMFace *f, float r_plane[3]) ATTR_NONNULL();
void BM_face_calc_center_bounds(const BMFace *f, float center[3]) ATTR_NONNULL();
void BM_face_calc_center_mean(const BMFace *f, float center[3]) ATTR_NONNULL();
void BM_face_calc_center_mean_vcos(
@@ -90,6 +94,7 @@ void BM_face_as_array_vert_quad(BMFace *f, BMVert *r_verts[4]) ATTR_NONNULL();
void BM_face_as_array_loop_tri(BMFace *f, BMLoop *r_loops[3]) ATTR_NONNULL();
void BM_face_as_array_loop_quad(BMFace *f, BMLoop *r_loops[4]) ATTR_NONNULL();
-void BM_vert_tri_calc_plane(BMVert *verts[3], float r_plane[3]);
+void BM_vert_tri_calc_tangent_edge(BMVert *verts[3], float r_tangent[3]);
+void BM_vert_tri_calc_tangent_edge_pair(BMVert *verts[3], float r_tangent[3]);
#endif /* __BMESH_POLYGON_H__ */
diff --git a/source/blender/bmesh/operators/bmo_removedoubles.c b/source/blender/bmesh/operators/bmo_removedoubles.c
index a1f40b31fc7..c58b4814726 100644
--- a/source/blender/bmesh/operators/bmo_removedoubles.c
+++ b/source/blender/bmesh/operators/bmo_removedoubles.c
@@ -224,22 +224,13 @@ void bmo_weld_verts_exec(BMesh *bm, BMOperator *op)
if (v1 == v2) {
BMO_elem_flag_enable(bm, e, EDGE_COL);
}
- else if (!BM_edge_exists(v1, v2)) {
- BMEdge *e_new = BM_edge_create(bm, v1, v2, e, BM_CREATE_NOP);
-
- /* low level selection, not essential but means we can keep
- * edge selection valid on auto-merge for example. */
- if ((BM_elem_flag_test(e, BM_ELEM_SELECT) == true) &&
- (BM_elem_flag_test(e_new, BM_ELEM_SELECT) == false))
- {
- BM_elem_flag_disable(e, BM_ELEM_SELECT);
- BM_elem_flag_merge_into(e_new, e_new, e);
- BM_elem_flag_enable(e_new, BM_ELEM_SELECT);
- /* bm->totedgesel remains valid */
- }
- else {
- BM_elem_flag_merge_into(e_new, e_new, e);
+ else {
+ /* always merge flags, even for edges we already created */
+ BMEdge *e_new = BM_edge_exists(v1, v2);
+ if (e_new == NULL) {
+ e_new = BM_edge_create(bm, v1, v2, e, BM_CREATE_NOP);
}
+ BM_elem_flag_merge(e_new, e);
}
BMO_elem_flag_enable(bm, e, ELE_DEL);
diff --git a/source/blender/editors/interface/interface_draw.c b/source/blender/editors/interface/interface_draw.c
index d78b41820b4..72a6a04feec 100644
--- a/source/blender/editors/interface/interface_draw.c
+++ b/source/blender/editors/interface/interface_draw.c
@@ -1205,8 +1205,7 @@ void ui_draw_but_UNITVEC(uiBut *but, uiWidgetColors *wcol, const rcti *rect)
qobj = gluNewQuadric();
gluQuadricDrawStyle(qobj, GLU_FILL);
- int bound_options = GPU_basic_shader_bound_options();
- GPU_basic_shader_bind(bound_options);
+ GPU_basic_shader_bind(GPU_basic_shader_bound_options());
gluSphere(qobj, 100.0, 32, 24);
gluDeleteQuadric(qobj);
@@ -1538,10 +1537,12 @@ void ui_draw_but_TRACKPREVIEW(ARegion *ar, uiBut *but, uiWidgetColors *UNUSED(wc
BLI_rctf_size_x(&rect),
BLI_rctf_size_y(&rect));
+ GPU_basic_shader_bind_enable(GPU_SHADER_LINE);
+
for (int a = 0; a < 2; a++) {
if (a == 1) {
- glLineStipple(3, 0xaaaa);
- glEnable(GL_LINE_STIPPLE);
+ GPU_basic_shader_bind_enable(GPU_SHADER_STIPPLE);
+ GPU_basic_shader_line_stipple(3, 0xAAAA);
UI_ThemeColor(TH_SEL_MARKER);
}
else {
@@ -1555,9 +1556,10 @@ void ui_draw_but_TRACKPREVIEW(ARegion *ar, uiBut *but, uiWidgetColors *UNUSED(wc
glVertex2f(0.0f, 10.0f);
glEnd();
}
+
+ GPU_basic_shader_bind_disable(GPU_SHADER_LINE | GPU_SHADER_STIPPLE);
}
- glDisable(GL_LINE_STIPPLE);
glPopMatrix();
ok = true;
diff --git a/source/blender/editors/mask/mask_draw.c b/source/blender/editors/mask/mask_draw.c
index 9a8382b2136..2b4f94a37ef 100644
--- a/source/blender/editors/mask/mask_draw.c
+++ b/source/blender/editors/mask/mask_draw.c
@@ -51,6 +51,8 @@
#include "BIF_gl.h"
#include "BIF_glutil.h"
+#include "GPU_basic_shader.h"
+
#include "UI_resources.h"
#include "UI_view2d.h"
@@ -100,10 +102,10 @@ static void draw_spline_parents(MaskLayer *UNUSED(masklay), MaskSpline *spline)
if (!spline->tot_point)
return;
- glColor3ub(0, 0, 0);
- glEnable(GL_LINE_STIPPLE);
- glLineStipple(1, 0xAAAA);
+ GPU_basic_shader_bind_enable(GPU_SHADER_LINE | GPU_SHADER_STIPPLE);
+ GPU_basic_shader_line_stipple(1, 0xAAAA);
+ glColor3ub(0, 0, 0);
glBegin(GL_LINES);
for (i = 0; i < spline->tot_point; i++) {
@@ -121,7 +123,7 @@ static void draw_spline_parents(MaskLayer *UNUSED(masklay), MaskSpline *spline)
glEnd();
- glDisable(GL_LINE_STIPPLE);
+ GPU_basic_shader_bind_disable(GPU_SHADER_LINE | GPU_SHADER_STIPPLE);
}
#endif
@@ -455,7 +457,8 @@ static void mask_draw_curve_type(const bContext *C, MaskSpline *spline, float (*
case MASK_DT_DASH:
default:
- glEnable(GL_LINE_STIPPLE);
+ GPU_basic_shader_bind_enable(GPU_SHADER_LINE | GPU_SHADER_STIPPLE);
+ GPU_basic_shader_line_stipple(3, 0xAAAA);
#ifdef USE_XOR
glEnable(GL_COLOR_LOGIC_OP);
@@ -463,7 +466,6 @@ static void mask_draw_curve_type(const bContext *C, MaskSpline *spline, float (*
#endif
mask_color_active_tint(rgb_tmp, rgb_spline, is_active);
glColor4ubv(rgb_tmp);
- glLineStipple(3, 0xaaaa);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2, GL_FLOAT, 0, points);
glDrawArrays(draw_method, 0, tot_point);
@@ -473,10 +475,10 @@ static void mask_draw_curve_type(const bContext *C, MaskSpline *spline, float (*
#endif
mask_color_active_tint(rgb_tmp, rgb_black, is_active);
glColor4ubv(rgb_tmp);
- glLineStipple(3, 0x5555);
+ GPU_basic_shader_line_stipple(3, 0x5555);
glDrawArrays(draw_method, 0, tot_point);
- glDisable(GL_LINE_STIPPLE);
+ GPU_basic_shader_bind_disable(GPU_SHADER_LINE | GPU_SHADER_STIPPLE);
break;
diff --git a/source/blender/editors/sculpt_paint/paint_stroke.c b/source/blender/editors/sculpt_paint/paint_stroke.c
index 65857cccb15..67fbd000b64 100644
--- a/source/blender/editors/sculpt_paint/paint_stroke.c
+++ b/source/blender/editors/sculpt_paint/paint_stroke.c
@@ -58,6 +58,8 @@
#include "BIF_gl.h"
#include "BIF_glutil.h"
+#include "GPU_basic_shader.h"
+
#include "ED_screen.h"
#include "ED_view3d.h"
@@ -160,11 +162,11 @@ static void paint_draw_line_cursor(bContext *C, int x, int y, void *customdata)
glEnable(GL_LINE_SMOOTH);
glEnable(GL_BLEND);
- glEnable(GL_LINE_STIPPLE);
- glLineStipple(3, 0xAAAA);
+ GPU_basic_shader_bind_enable(GPU_SHADER_LINE | GPU_SHADER_STIPPLE);
+ GPU_basic_shader_line_stipple(3, 0xAAAA);
+ GPU_basic_shader_line_width(3.0);
glColor4ub(0, 0, 0, paint->paint_cursor_col[3]);
- glLineWidth(3.0);
if (stroke->constrain_line) {
sdrawline((int)stroke->last_mouse_position[0], (int)stroke->last_mouse_position[1],
stroke->constrained_pos[0], stroke->constrained_pos[1]);
@@ -175,7 +177,7 @@ static void paint_draw_line_cursor(bContext *C, int x, int y, void *customdata)
}
glColor4ub(255, 255, 255, paint->paint_cursor_col[3]);
- glLineWidth(1.0);
+ GPU_basic_shader_line_width(1.0);
if (stroke->constrain_line) {
sdrawline((int)stroke->last_mouse_position[0], (int)stroke->last_mouse_position[1],
stroke->constrained_pos[0], stroke->constrained_pos[1]);
@@ -185,7 +187,7 @@ static void paint_draw_line_cursor(bContext *C, int x, int y, void *customdata)
x, y);
}
- glDisable(GL_LINE_STIPPLE);
+ GPU_basic_shader_bind_disable(GPU_SHADER_LINE | GPU_SHADER_STIPPLE);
glDisable(GL_BLEND);
glDisable(GL_LINE_SMOOTH);
diff --git a/source/blender/editors/space_clip/clip_draw.c b/source/blender/editors/space_clip/clip_draw.c
index 1c5be3d1fb5..695d04d3850 100644
--- a/source/blender/editors/space_clip/clip_draw.c
+++ b/source/blender/editors/space_clip/clip_draw.c
@@ -336,8 +336,10 @@ static void draw_stabilization_border(SpaceClip *sc, ARegion *ar, int width, int
/* draw boundary border for frame if stabilization is enabled */
if (sc->flag & SC_SHOW_STABLE && clip->tracking.stabilization.flag & TRACKING_2D_STABILIZATION) {
glColor3f(0.0f, 0.0f, 0.0f);
- glLineStipple(3, 0xaaaa);
- glEnable(GL_LINE_STIPPLE);
+
+ GPU_basic_shader_bind_enable(GPU_SHADER_LINE | GPU_SHADER_STIPPLE);
+ GPU_basic_shader_line_stipple(3, 0xAAAA);
+
glEnable(GL_COLOR_LOGIC_OP);
glLogicOp(GL_NOR);
@@ -357,7 +359,7 @@ static void draw_stabilization_border(SpaceClip *sc, ARegion *ar, int width, int
glPopMatrix();
glDisable(GL_COLOR_LOGIC_OP);
- glDisable(GL_LINE_STIPPLE);
+ GPU_basic_shader_bind_disable(GPU_SHADER_LINE | GPU_SHADER_STIPPLE);
}
}
@@ -627,8 +629,8 @@ static void draw_marker_areas(SpaceClip *sc, MovieTrackingTrack *track, MovieTra
glEnd();
glColor3f(0.0f, 0.0f, 0.0f);
- glLineStipple(3, 0xaaaa);
- glEnable(GL_LINE_STIPPLE);
+ GPU_basic_shader_bind_enable(GPU_SHADER_LINE | GPU_SHADER_STIPPLE);
+ GPU_basic_shader_line_stipple(3, 0xAAAA);
glEnable(GL_COLOR_LOGIC_OP);
glLogicOp(GL_NOR);
@@ -638,7 +640,7 @@ static void draw_marker_areas(SpaceClip *sc, MovieTrackingTrack *track, MovieTra
glEnd();
glDisable(GL_COLOR_LOGIC_OP);
- glDisable(GL_LINE_STIPPLE);
+ GPU_basic_shader_bind_disable(GPU_SHADER_LINE | GPU_SHADER_STIPPLE);
}
}
@@ -647,8 +649,11 @@ static void draw_marker_areas(SpaceClip *sc, MovieTrackingTrack *track, MovieTra
glTranslate2fv(marker_pos);
if (tiny) {
- glLineStipple(3, 0xaaaa);
- glEnable(GL_LINE_STIPPLE);
+ GPU_basic_shader_bind_enable(GPU_SHADER_LINE | GPU_SHADER_STIPPLE);
+ GPU_basic_shader_line_stipple(3, 0xAAAA);
+ }
+ else {
+ GPU_basic_shader_bind_enable(GPU_SHADER_LINE);
}
if ((track->pat_flag & SELECT) == sel && (sc->flag & SC_SHOW_MARKER_PATTERN)) {
@@ -713,8 +718,12 @@ static void draw_marker_areas(SpaceClip *sc, MovieTrackingTrack *track, MovieTra
glEnd();
}
- if (tiny)
- glDisable(GL_LINE_STIPPLE);
+ if (tiny) {
+ GPU_basic_shader_bind_disable(GPU_SHADER_LINE | GPU_SHADER_STIPPLE);
+ }
+ else {
+ GPU_basic_shader_bind_disable(GPU_SHADER_LINE);
+ }
glPopMatrix();
}
@@ -852,16 +861,12 @@ static void draw_marker_slide_zones(SpaceClip *sc, MovieTrackingTrack *track, Mo
glLineWidth(outline ? 3.0f : 1.0f);
- glEnable(GL_LINE_STIPPLE);
- glLineStipple(3, 0xaaaa);
-
glBegin(GL_LINES);
glVertex2f(0.0f, 0.0f);
glVertex2fv(tilt_ctrl);
glEnd();
- glDisable(GL_LINE_STIPPLE);
-
+ GPU_basic_shader_bind_disable(GPU_SHADER_LINE | GPU_SHADER_STIPPLE);
/* slider to control pattern tilt */
draw_marker_slide_square(tilt_ctrl[0], tilt_ctrl[1], patdx, patdy, outline, px);
@@ -1133,11 +1138,14 @@ static void draw_plane_marker_ex(SpaceClip *sc, Scene *scene, MovieTrackingPlane
const bool thick = draw_outline && !tiny;
if (stipple) {
- glLineStipple(3, 0xaaaa);
- glEnable(GL_LINE_STIPPLE);
+ GPU_basic_shader_bind_enable(GPU_SHADER_LINE | GPU_SHADER_STIPPLE);
+ GPU_basic_shader_line_stipple(3, 0xAAAA);
+ }
+ else {
+ GPU_basic_shader_bind_enable(GPU_SHADER_LINE);
}
- glLineWidth(thick ? 3.0f : 1.0f);
+ GPU_basic_shader_line_width(thick ? 3.0f : 1.0f);
/* Draw rectangle itself. */
glBegin(GL_LINE_LOOP);
@@ -1169,8 +1177,12 @@ static void draw_plane_marker_ex(SpaceClip *sc, Scene *scene, MovieTrackingPlane
glPopAttrib();
}
- if (stipple)
- glDisable(GL_LINE_STIPPLE);
+ if (stipple) {
+ GPU_basic_shader_bind_disable(GPU_SHADER_LINE | GPU_SHADER_STIPPLE);
+ }
+ else {
+ GPU_basic_shader_bind_disable(GPU_SHADER_LINE);
+ }
}
/* Draw sliders. */
diff --git a/source/blender/editors/space_sequencer/sequencer_draw.c b/source/blender/editors/space_sequencer/sequencer_draw.c
index f6afd7a16e7..08e12134b51 100644
--- a/source/blender/editors/space_sequencer/sequencer_draw.c
+++ b/source/blender/editors/space_sequencer/sequencer_draw.c
@@ -1629,7 +1629,8 @@ void draw_timeline_seq(const bContext *C, ARegion *ar)
// NOTE: the gridlines are currently spaced every 25 frames, which is only fine for 25 fps, but maybe not for 30...
UI_view2d_constant_grid_draw(v2d);
- if (sseq->draw_flag & SEQ_DRAW_BACKDROP) {
+ /* Only draw backdrop in pure sequence view. */
+ if (sseq->view == SEQ_VIEW_SEQUENCE && sseq->draw_flag & SEQ_DRAW_BACKDROP) {
draw_image_seq(C, scene, ar, sseq, scene->r.cfra, 0, false, true);
UI_view2d_view_ortho(v2d);
}
diff --git a/source/blender/editors/space_sequencer/sequencer_select.c b/source/blender/editors/space_sequencer/sequencer_select.c
index 7475e8b27fd..48c49f36471 100644
--- a/source/blender/editors/space_sequencer/sequencer_select.c
+++ b/source/blender/editors/space_sequencer/sequencer_select.c
@@ -943,16 +943,26 @@ void SEQUENCER_OT_select_border(wmOperatorType *ot)
/* ****** Selected Grouped ****** */
+enum {
+ SEQ_SELECT_GROUP_TYPE,
+ SEQ_SELECT_GROUP_TYPE_BASIC,
+ SEQ_SELECT_GROUP_TYPE_EFFECT,
+ SEQ_SELECT_GROUP_DATA,
+ SEQ_SELECT_GROUP_EFFECT,
+ SEQ_SELECT_GROUP_EFFECT_LINK,
+ SEQ_SELECT_GROUP_OVERLAP,
+};
+
static EnumPropertyItem sequencer_prop_select_grouped_types[] = {
- {1, "TYPE", 0, "Type", "Shared strip type"},
- {2, "TYPE_BASIC", 0, "Global Type", "All strips of same basic type (Graphical or Sound)"},
- {3, "TYPE_EFFECT", 0, "Effect Type",
+ {SEQ_SELECT_GROUP_TYPE, "TYPE", 0, "Type", "Shared strip type"},
+ {SEQ_SELECT_GROUP_TYPE_BASIC, "TYPE_BASIC", 0, "Global Type", "All strips of same basic type (Graphical or Sound)"},
+ {SEQ_SELECT_GROUP_TYPE_EFFECT, "TYPE_EFFECT", 0, "Effect Type",
"Shared strip effect type (if active strip is not an effect one, select all non-effect strips)"},
- {4, "DATA", 0, "Data", "Shared data (scene, image, sound, etc.)"},
- {5, "EFFECT", 0, "Effect", "Shared effects"},
- {6, "EFFECT_LINK", 0, "Effect/Linked",
+ {SEQ_SELECT_GROUP_DATA, "DATA", 0, "Data", "Shared data (scene, image, sound, etc.)"},
+ {SEQ_SELECT_GROUP_EFFECT, "EFFECT", 0, "Effect", "Shared effects"},
+ {SEQ_SELECT_GROUP_EFFECT_LINK, "EFFECT_LINK", 0, "Effect/Linked",
"Other strips affected by the active one (sharing some time, and below or effect-assigned)"},
- {7, "OVERLAP", 0, "Overlap", "Overlapping time"},
+ {SEQ_SELECT_GROUP_OVERLAP, "OVERLAP", 0, "Overlap", "Overlapping time"},
{0, NULL, 0, NULL, NULL}
};
@@ -962,14 +972,16 @@ static EnumPropertyItem sequencer_prop_select_grouped_types[] = {
#define SEQ_USE_DATA(_seq) (ELEM(_seq->type, SEQ_TYPE_SCENE, SEQ_TYPE_MOVIECLIP, SEQ_TYPE_MASK) || SEQ_HAS_PATH(_seq))
-static bool select_grouped_type(Editing *ed, Sequence *actseq)
+#define SEQ_CHANNEL_CHECK(_seq, _chan) (ELEM((_chan), 0, (_seq)->machine))
+
+static bool select_grouped_type(Editing *ed, Sequence *actseq, const int channel)
{
Sequence *seq;
bool changed = false;
SEQP_BEGIN (ed, seq)
{
- if (seq->type == actseq->type) {
+ if (SEQ_CHANNEL_CHECK(seq, channel) && seq->type == actseq->type) {
seq->flag |= SELECT;
changed = true;
}
@@ -979,7 +991,7 @@ static bool select_grouped_type(Editing *ed, Sequence *actseq)
return changed;
}
-static bool select_grouped_type_basic(Editing *ed, Sequence *actseq)
+static bool select_grouped_type_basic(Editing *ed, Sequence *actseq, const int channel)
{
Sequence *seq;
bool changed = false;
@@ -987,7 +999,7 @@ static bool select_grouped_type_basic(Editing *ed, Sequence *actseq)
SEQP_BEGIN (ed, seq)
{
- if (is_sound ? SEQ_IS_SOUND(seq) : !SEQ_IS_SOUND(seq)) {
+ if (SEQ_CHANNEL_CHECK(seq, channel) && (is_sound ? SEQ_IS_SOUND(seq) : !SEQ_IS_SOUND(seq))) {
seq->flag |= SELECT;
changed = true;
}
@@ -997,7 +1009,7 @@ static bool select_grouped_type_basic(Editing *ed, Sequence *actseq)
return changed;
}
-static bool select_grouped_type_effect(Editing *ed, Sequence *actseq)
+static bool select_grouped_type_effect(Editing *ed, Sequence *actseq, const int channel)
{
Sequence *seq;
bool changed = false;
@@ -1005,7 +1017,7 @@ static bool select_grouped_type_effect(Editing *ed, Sequence *actseq)
SEQP_BEGIN (ed, seq)
{
- if (is_effect ? SEQ_IS_EFFECT(seq) : !SEQ_IS_EFFECT(seq)) {
+ if (SEQ_CHANNEL_CHECK(seq, channel) && (is_effect ? SEQ_IS_EFFECT(seq) : !SEQ_IS_EFFECT(seq))) {
seq->flag |= SELECT;
changed = true;
}
@@ -1015,7 +1027,7 @@ static bool select_grouped_type_effect(Editing *ed, Sequence *actseq)
return changed;
}
-static bool select_grouped_data(Editing *ed, Sequence *actseq)
+static bool select_grouped_data(Editing *ed, Sequence *actseq, const int channel)
{
Sequence *seq;
bool changed = false;
@@ -1027,7 +1039,7 @@ static bool select_grouped_data(Editing *ed, Sequence *actseq)
if (SEQ_HAS_PATH(actseq) && dir) {
SEQP_BEGIN (ed, seq)
{
- if (SEQ_HAS_PATH(seq) && seq->strip && STREQ(seq->strip->dir, dir)) {
+ if (SEQ_CHANNEL_CHECK(seq, channel) && SEQ_HAS_PATH(seq) && seq->strip && STREQ(seq->strip->dir, dir)) {
seq->flag |= SELECT;
changed = true;
}
@@ -1038,7 +1050,7 @@ static bool select_grouped_data(Editing *ed, Sequence *actseq)
Scene *sce = actseq->scene;
SEQP_BEGIN (ed, seq)
{
- if (seq->type == SEQ_TYPE_SCENE && seq->scene == sce) {
+ if (SEQ_CHANNEL_CHECK(seq, channel) && seq->type == SEQ_TYPE_SCENE && seq->scene == sce) {
seq->flag |= SELECT;
changed = true;
}
@@ -1049,7 +1061,7 @@ static bool select_grouped_data(Editing *ed, Sequence *actseq)
MovieClip *clip = actseq->clip;
SEQP_BEGIN (ed, seq)
{
- if (seq->type == SEQ_TYPE_MOVIECLIP && seq->clip == clip) {
+ if (SEQ_CHANNEL_CHECK(seq, channel) && seq->type == SEQ_TYPE_MOVIECLIP && seq->clip == clip) {
seq->flag |= SELECT;
changed = true;
}
@@ -1060,7 +1072,7 @@ static bool select_grouped_data(Editing *ed, Sequence *actseq)
struct Mask *mask = actseq->mask;
SEQP_BEGIN (ed, seq)
{
- if (seq->type == SEQ_TYPE_MASK && seq->mask == mask) {
+ if (SEQ_CHANNEL_CHECK(seq, channel) && seq->type == SEQ_TYPE_MASK && seq->mask == mask) {
seq->flag |= SELECT;
changed = true;
}
@@ -1071,7 +1083,7 @@ static bool select_grouped_data(Editing *ed, Sequence *actseq)
return changed;
}
-static bool select_grouped_effect(Editing *ed, Sequence *actseq)
+static bool select_grouped_effect(Editing *ed, Sequence *actseq, const int channel)
{
Sequence *seq;
bool changed = false;
@@ -1083,7 +1095,9 @@ static bool select_grouped_effect(Editing *ed, Sequence *actseq)
SEQP_BEGIN (ed, seq)
{
- if ((seq->type & SEQ_TYPE_EFFECT) && ELEM(actseq, seq->seq1, seq->seq2, seq->seq3)) {
+ if (SEQ_CHANNEL_CHECK(seq, channel) && (seq->type & SEQ_TYPE_EFFECT) &&
+ ELEM(actseq, seq->seq1, seq->seq2, seq->seq3))
+ {
effects[seq->type] = true;
}
}
@@ -1091,7 +1105,7 @@ static bool select_grouped_effect(Editing *ed, Sequence *actseq)
SEQP_BEGIN (ed, seq)
{
- if (effects[seq->type]) {
+ if (SEQ_CHANNEL_CHECK(seq, channel) && effects[seq->type]) {
if (seq->seq1) seq->seq1->flag |= SELECT;
if (seq->seq2) seq->seq2->flag |= SELECT;
if (seq->seq3) seq->seq3->flag |= SELECT;
@@ -1120,7 +1134,7 @@ static bool select_grouped_time_overlap(Editing *ed, Sequence *actseq)
return changed;
}
-static bool select_grouped_effect_link(Editing *ed, Sequence *actseq)
+static bool select_grouped_effect_link(Editing *ed, Sequence *actseq, const int channel)
{
Sequence *seq = NULL;
bool changed = false;
@@ -1144,7 +1158,8 @@ static bool select_grouped_effect_link(Editing *ed, Sequence *actseq)
/* Ignore all seqs already selected! */
/* Ignore all seqs not sharing some time with active one. */
/* Ignore all seqs of incompatible types (audio vs video). */
- if ((seq->flag & SELECT) || (seq->startdisp >= enddisp) || (seq->enddisp < startdisp) ||
+ if (!SEQ_CHANNEL_CHECK(seq, channel) ||
+ (seq->flag & SELECT) || (seq->startdisp >= enddisp) || (seq->enddisp < startdisp) ||
(!is_audio && SEQ_IS_SOUND(seq)) ||
(is_audio && !((seq->type == SEQ_TYPE_META) || SEQ_IS_SOUND(seq))))
{
@@ -1190,17 +1205,19 @@ static int sequencer_select_grouped_exec(bContext *C, wmOperator *op)
Scene *scene = CTX_data_scene(C);
Editing *ed = BKE_sequencer_editing_get(scene, false);
Sequence *seq, *actseq = BKE_sequencer_active_get(scene);
- int type = RNA_enum_get(op->ptr, "type");
- bool changed = false, extend;
- extend = RNA_boolean_get(op->ptr, "extend");
+ const int type = RNA_enum_get(op->ptr, "type");
+ const int channel = RNA_boolean_get(op->ptr, "use_active_channel") ? actseq->machine : 0;
+ const bool extend = RNA_boolean_get(op->ptr, "extend");
+
+ bool changed = false;
if (actseq == NULL) {
BKE_report(op->reports, RPT_ERROR, "No active sequence!");
return OPERATOR_CANCELLED;
}
- if (extend == 0) {
+ if (!extend) {
SEQP_BEGIN (ed, seq)
{
seq->flag &= ~SELECT;
@@ -1209,13 +1226,32 @@ static int sequencer_select_grouped_exec(bContext *C, wmOperator *op)
SEQ_END;
}
- if (type == 1) changed |= select_grouped_type(ed, actseq);
- else if (type == 2) changed |= select_grouped_type_basic(ed, actseq);
- else if (type == 3) changed |= select_grouped_type_effect(ed, actseq);
- else if (type == 4) changed |= select_grouped_data(ed, actseq);
- else if (type == 5) changed |= select_grouped_effect(ed, actseq);
- else if (type == 6) changed |= select_grouped_effect_link(ed, actseq);
- else if (type == 7) changed |= select_grouped_time_overlap(ed, actseq);
+ switch (type) {
+ case SEQ_SELECT_GROUP_TYPE:
+ changed |= select_grouped_type(ed, actseq, channel);
+ break;
+ case SEQ_SELECT_GROUP_TYPE_BASIC:
+ changed |= select_grouped_type_basic(ed, actseq, channel);
+ break;
+ case SEQ_SELECT_GROUP_TYPE_EFFECT:
+ changed |= select_grouped_type_effect(ed, actseq, channel);
+ break;
+ case SEQ_SELECT_GROUP_DATA:
+ changed |= select_grouped_data(ed, actseq, channel);
+ break;
+ case SEQ_SELECT_GROUP_EFFECT:
+ changed |= select_grouped_effect(ed, actseq, channel);
+ break;
+ case SEQ_SELECT_GROUP_EFFECT_LINK:
+ changed |= select_grouped_effect_link(ed, actseq, channel);
+ break;
+ case SEQ_SELECT_GROUP_OVERLAP:
+ changed |= select_grouped_time_overlap(ed, actseq);
+ break;
+ default:
+ BLI_assert(0);
+ break;
+ }
if (changed) {
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
@@ -1241,7 +1277,9 @@ void SEQUENCER_OT_select_grouped(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
- RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend selection instead of deselecting everything first");
ot->prop = RNA_def_enum(ot->srna, "type", sequencer_prop_select_grouped_types, 0, "Type", "");
+ RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend selection instead of deselecting everything first");
+ RNA_def_boolean(ot->srna, "use_active_channel", false, "Same Channel",
+ "Only consider strips on the same channel as the active one");
}
diff --git a/source/blender/editors/space_view3d/drawmesh.c b/source/blender/editors/space_view3d/drawmesh.c
index d8364011036..ee82a4c5072 100644
--- a/source/blender/editors/space_view3d/drawmesh.c
+++ b/source/blender/editors/space_view3d/drawmesh.c
@@ -1310,8 +1310,8 @@ void draw_mesh_paint_weight_edges(RegionView3D *rv3d, DerivedMesh *dm,
}
glColor4ub(255, 255, 255, 96);
- glEnable(GL_LINE_STIPPLE);
- glLineStipple(1, 0xAAAA);
+ GPU_basic_shader_bind_enable(GPU_SHADER_LINE | GPU_SHADER_STIPPLE);
+ GPU_basic_shader_line_stipple(1, 0xAAAA);
dm->drawMappedEdges(dm, (DMSetDrawOptions)edgemask_cb, user_data);
@@ -1323,7 +1323,7 @@ void draw_mesh_paint_weight_edges(RegionView3D *rv3d, DerivedMesh *dm,
glEnable(GL_DEPTH_TEST);
}
- glDisable(GL_LINE_STIPPLE);
+ GPU_basic_shader_bind_disable(GPU_SHADER_LINE | GPU_SHADER_STIPPLE);
if (use_alpha) {
glDisable(GL_BLEND);
diff --git a/source/blender/editors/space_view3d/view3d_draw.c b/source/blender/editors/space_view3d/view3d_draw.c
index 0e012b45cad..64b12498ae2 100644
--- a/source/blender/editors/space_view3d/view3d_draw.c
+++ b/source/blender/editors/space_view3d/view3d_draw.c
@@ -4056,6 +4056,10 @@ void view3d_main_region_draw(const bContext *C, ARegion *ar)
view3d_main_region_draw_info(C, scene, ar, v3d, grid_unit, render_border);
v3d->flag |= V3D_INVALID_BACKBUF;
+
+ BLI_assert(BLI_listbase_is_empty(&v3d->afterdraw_transp));
+ BLI_assert(BLI_listbase_is_empty(&v3d->afterdraw_xray));
+ BLI_assert(BLI_listbase_is_empty(&v3d->afterdraw_xraytransp));
}
#ifdef DEBUG_DRAW
diff --git a/source/blender/editors/transform/transform_conversions.c b/source/blender/editors/transform/transform_conversions.c
index 137f7c1656a..b7f8011725f 100644
--- a/source/blender/editors/transform/transform_conversions.c
+++ b/source/blender/editors/transform/transform_conversions.c
@@ -5704,6 +5704,7 @@ static void special_aftertrans_update__mesh(bContext *UNUSED(C), TransInfo *t)
BMEditMesh *em = BKE_editmesh_from_object(t->obedit);
BMesh *bm = em->bm;
char hflag;
+ bool has_face_sel = (bm->totfacesel != 0);
if (t->flag & T_MIRROR) {
TransData *td;
@@ -5727,8 +5728,10 @@ static void special_aftertrans_update__mesh(bContext *UNUSED(C), TransInfo *t)
EDBM_automerge(t->scene, t->obedit, true, hflag);
- if ((em->selectmode & SCE_SELECT_VERTEX) == 0) {
- EDBM_select_flush(em);
+ /* Special case, this is needed or faces won't re-select.
+ * Flush selected edges to faces. */
+ if (has_face_sel && (em->selectmode == SCE_SELECT_FACE)) {
+ EDBM_selectmode_flush_ex(em, SCE_SELECT_EDGE);
}
}
}
diff --git a/source/blender/editors/transform/transform_orientations.c b/source/blender/editors/transform/transform_orientations.c
index 719da14cb73..1d6a392aae6 100644
--- a/source/blender/editors/transform/transform_orientations.c
+++ b/source/blender/editors/transform/transform_orientations.c
@@ -636,7 +636,7 @@ int getTransformOrientation_ex(const bContext *C, float normal[3], float plane[3
BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
if (BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
- BM_face_calc_plane(efa, vec);
+ BM_face_calc_tangent_auto(efa, vec);
add_v3_v3(normal, efa->no);
add_v3_v3(plane, vec);
}
@@ -690,7 +690,7 @@ int getTransformOrientation_ex(const bContext *C, float normal[3], float plane[3
sub_v3_v3v3(plane, v_pair[0]->co, v_pair[1]->co);
}
else {
- BM_vert_tri_calc_plane(v_tri, plane);
+ BM_vert_tri_calc_tangent_edge(v_tri, plane);
}
}
else {
diff --git a/source/blender/gpu/GPU_basic_shader.h b/source/blender/gpu/GPU_basic_shader.h
index 6c78aec2ec7..d9bf3d1ced3 100644
--- a/source/blender/gpu/GPU_basic_shader.h
+++ b/source/blender/gpu/GPU_basic_shader.h
@@ -76,6 +76,9 @@ void GPU_basic_shaders_init(void);
void GPU_basic_shaders_exit(void);
void GPU_basic_shader_bind(int options);
+void GPU_basic_shader_bind_enable(int options);
+void GPU_basic_shader_bind_disable(int options);
+
int GPU_basic_shader_bound_options(void);
/* Only use for small blocks of code that don't support glsl shader. */
diff --git a/source/blender/gpu/intern/gpu_basic_shader.c b/source/blender/gpu/intern/gpu_basic_shader.c
index c5a2d07bdc8..a2b89239344 100644
--- a/source/blender/gpu/intern/gpu_basic_shader.c
+++ b/source/blender/gpu/intern/gpu_basic_shader.c
@@ -502,17 +502,18 @@ void GPU_basic_shader_bind(int options)
else if ((bound_options & GPU_SHADER_LINE) && (bound_options & GPU_SHADER_STIPPLE)) {
glDisable(GL_LINE_STIPPLE);
}
- else {
- if (options & GPU_SHADER_STIPPLE)
- glEnable(GL_POLYGON_STIPPLE);
- else if (bound_options & GPU_SHADER_STIPPLE)
- glDisable(GL_POLYGON_STIPPLE);
+
+ if (((options & GPU_SHADER_LINE) == 0) && (options & GPU_SHADER_STIPPLE)) {
+ glEnable(GL_POLYGON_STIPPLE);
+ }
+ else if (((bound_options & GPU_SHADER_LINE) == 0) && (bound_options & GPU_SHADER_STIPPLE)) {
+ glDisable(GL_POLYGON_STIPPLE);
}
if (options & GPU_SHADER_FLAT_NORMAL) {
glShadeModel(GL_FLAT);
}
- else {
+ else if (bound_options & GPU_SHADER_FLAT_NORMAL) {
glShadeModel(GL_SMOOTH);
}
}
@@ -520,6 +521,16 @@ void GPU_basic_shader_bind(int options)
GPU_MATERIAL_STATE.bound_options = options;
}
+void GPU_basic_shader_bind_enable(int options)
+{
+ GPU_basic_shader_bind(GPU_MATERIAL_STATE.bound_options | options);
+}
+
+void GPU_basic_shader_bind_disable(int options)
+{
+ GPU_basic_shader_bind(GPU_MATERIAL_STATE.bound_options & ~options);
+}
+
int GPU_basic_shader_bound_options(void)
{
/* ideally this should disappear, anything that uses this is making fragile
diff --git a/source/blender/gpu/intern/gpu_buffers.c b/source/blender/gpu/intern/gpu_buffers.c
index 35bfc687052..36d297fb9fe 100644
--- a/source/blender/gpu/intern/gpu_buffers.c
+++ b/source/blender/gpu/intern/gpu_buffers.c
@@ -1843,15 +1843,15 @@ void GPU_draw_pbvh_buffers(GPU_PBVH_Buffers *buffers, DMSetMaterial setMaterial,
if (buffers->vert_buf) {
char *base = NULL;
char *index_base = NULL;
- int bound_options = 0;
+ /* weak inspection of bound options, should not be necessary ideally */
+ const int bound_options_old = GPU_basic_shader_bound_options();
+ int bound_options_new = 0;
glEnableClientState(GL_VERTEX_ARRAY);
if (!wireframe) {
glEnableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
- /* weak inspection of bound options, should not be necessary ideally */
- bound_options = GPU_basic_shader_bound_options();
- GPU_basic_shader_bind(bound_options | GPU_SHADER_USE_COLOR);
+ bound_options_new |= GPU_SHADER_USE_COLOR;
}
GPU_buffer_bind(buffers->vert_buf, GPU_BINDING_ARRAY);
@@ -1867,9 +1867,13 @@ void GPU_draw_pbvh_buffers(GPU_PBVH_Buffers *buffers, DMSetMaterial setMaterial,
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
else {
- bound_options = GPU_basic_shader_bound_options();
- GPU_basic_shader_bind(bound_options | ((buffers->smooth || buffers->face_indices_len) ?
- 0 : GPU_SHADER_FLAT_NORMAL));
+ if ((buffers->smooth == false) && (buffers->face_indices_len == 0)) {
+ bound_options_new |= GPU_SHADER_FLAT_NORMAL;
+ }
+ }
+
+ if (bound_options_new & ~bound_options_old) {
+ GPU_basic_shader_bind(bound_options_old | bound_options_new);
}
if (buffers->tot_quad) {
@@ -1944,7 +1948,10 @@ void GPU_draw_pbvh_buffers(GPU_PBVH_Buffers *buffers, DMSetMaterial setMaterial,
if (!wireframe) {
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
- GPU_basic_shader_bind(bound_options);
+ }
+
+ if (bound_options_new & ~bound_options_old) {
+ GPU_basic_shader_bind(bound_options_old);
}
}
}
diff --git a/source/blender/python/bmesh/bmesh_py_types.c b/source/blender/python/bmesh/bmesh_py_types.c
index a0722af522b..fe4360d1e3b 100644
--- a/source/blender/python/bmesh/bmesh_py_types.c
+++ b/source/blender/python/bmesh/bmesh_py_types.c
@@ -1803,6 +1803,82 @@ static PyObject *bpy_bmface_calc_perimeter(BPy_BMFace *self)
}
+PyDoc_STRVAR(bpy_bmface_calc_tangent_edge_doc,
+".. method:: calc_tangent_edge()\n"
+"\n"
+" Return face tangent based on longest edge.\n"
+"\n"
+" :return: a normalized vector.\n"
+" :rtype: :class:`mathutils.Vector`\n"
+);
+static PyObject *bpy_bmface_calc_tangent_edge(BPy_BMFace *self)
+{
+ float tangent[3];
+
+ BPY_BM_CHECK_OBJ(self);
+ BM_face_calc_tangent_edge(self->f, tangent);
+ return Vector_CreatePyObject(tangent, 3, NULL);
+}
+
+
+PyDoc_STRVAR(bpy_bmface_calc_tangent_edge_pair_doc,
+".. method:: calc_tangent_edge_pair()\n"
+"\n"
+" Return face tangent based on the two longest disconected edges.\n"
+"\n"
+" - Tris: Use the edge pair with the most similar lengths.\n"
+" - Quads: Use the longest edge pair.\n"
+" - NGons: Use the two longest disconnected edges.\n"
+"\n"
+" :return: a normalized vector.\n"
+" :rtype: :class:`mathutils.Vector`\n"
+);
+static PyObject *bpy_bmface_calc_tangent_edge_pair(BPy_BMFace *self)
+{
+ float tangent[3];
+
+ BPY_BM_CHECK_OBJ(self);
+ BM_face_calc_tangent_edge_pair(self->f, tangent);
+ return Vector_CreatePyObject(tangent, 3, NULL);
+}
+
+
+PyDoc_STRVAR(bpy_bmface_calc_tangent_edge_diagonal_doc,
+".. method:: calc_tangent_edge_diagonal()\n"
+"\n"
+" Return face tangent based on the edge farthest from any vertex.\n"
+"\n"
+" :return: a normalized vector.\n"
+" :rtype: :class:`mathutils.Vector`\n"
+);
+static PyObject *bpy_bmface_calc_tangent_edge_diagonal(BPy_BMFace *self)
+{
+ float tangent[3];
+
+ BPY_BM_CHECK_OBJ(self);
+ BM_face_calc_tangent_edge_diagonal(self->f, tangent);
+ return Vector_CreatePyObject(tangent, 3, NULL);
+}
+
+
+PyDoc_STRVAR(bpy_bmface_calc_tangent_vert_diagonal_doc,
+".. method:: calc_tangent_vert_diagonal()\n"
+"\n"
+" Return face tangent based on the two most distent vertices.\n"
+"\n"
+" :return: a normalized vector.\n"
+" :rtype: :class:`mathutils.Vector`\n"
+);
+static PyObject *bpy_bmface_calc_tangent_vert_diagonal(BPy_BMFace *self)
+{
+ float tangent[3];
+
+ BPY_BM_CHECK_OBJ(self);
+ BM_face_calc_tangent_vert_diagonal(self->f, tangent);
+ return Vector_CreatePyObject(tangent, 3, NULL);
+}
+
+
PyDoc_STRVAR(bpy_bmface_calc_center_mean_doc,
".. method:: calc_center_median()\n"
"\n"
@@ -2702,6 +2778,10 @@ static struct PyMethodDef bpy_bmface_methods[] = {
{"calc_area", (PyCFunction)bpy_bmface_calc_area, METH_NOARGS, bpy_bmface_calc_area_doc},
{"calc_perimeter", (PyCFunction)bpy_bmface_calc_perimeter, METH_NOARGS, bpy_bmface_calc_perimeter_doc},
+ {"calc_tangent_edge", (PyCFunction)bpy_bmface_calc_tangent_edge, METH_NOARGS, bpy_bmface_calc_tangent_edge_doc},
+ {"calc_tangent_edge_pair", (PyCFunction)bpy_bmface_calc_tangent_edge_pair, METH_NOARGS, bpy_bmface_calc_tangent_edge_pair_doc},
+ {"calc_tangent_edge_diagonal", (PyCFunction)bpy_bmface_calc_tangent_edge_diagonal, METH_NOARGS, bpy_bmface_calc_tangent_edge_diagonal_doc},
+ {"calc_tangent_vert_diagonal", (PyCFunction)bpy_bmface_calc_tangent_vert_diagonal, METH_NOARGS, bpy_bmface_calc_tangent_vert_diagonal_doc},
{"calc_center_median", (PyCFunction)bpy_bmface_calc_center_mean, METH_NOARGS, bpy_bmface_calc_center_mean_doc},
{"calc_center_median_weighted", (PyCFunction)bpy_bmface_calc_center_mean_weighted, METH_NOARGS, bpy_bmface_calc_center_mean_weighted_doc},
{"calc_center_bounds", (PyCFunction)bpy_bmface_calc_center_bounds, METH_NOARGS, bpy_bmface_calc_center_bounds_doc},
diff --git a/source/blenderplayer/CMakeLists.txt b/source/blenderplayer/CMakeLists.txt
index 6f32d50c2fe..206007b8d8b 100644
--- a/source/blenderplayer/CMakeLists.txt
+++ b/source/blenderplayer/CMakeLists.txt
@@ -215,6 +215,14 @@ endif()
list(APPEND BLENDER_SORTED_LIBS bf_intern_locale)
endif()
+ if(WITH_GAMEENGINE_DECKLINK)
+ list(APPEND BLENDER_SORTED_LIBS bf_intern_decklink)
+ endif()
+
+ if(WIN32)
+ list(APPEND BLENDER_SORTED_LIBS bf_intern_gpudirect)
+ endif()
+
if(WITH_OPENSUBDIV)
list(APPEND BLENDER_SORTED_LIBS bf_intern_opensubdiv)
endif()
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 f0a7bd47ca3..1b52c61b816 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);
@@ -671,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/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);
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; i<mUniforms.size(); i++) {
+ mDirty |= mUniforms[i]->Apply(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);
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();
@@ -401,6 +405,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.
*/
void SetShowFramerate(bool frameRate);
@@ -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
@@ -172,6 +172,11 @@ protected:
bool m_isclearingZbuffer;
/**
+ * Does the shadow buffer needs calculing
+ */
+ bool m_isShadowDone;
+
+ /**
* The name of the scene
*/
STR_String m_sceneName;
@@ -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<unsigned short> KX_IndexArray;
typedef vector<RAS_TexVert> KX_VertexArray;
@@ -258,6 +260,18 @@ public:
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.
*/
virtual void SwapBuffers() = 0;
@@ -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 <stdio.h>
+
+#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 <stdio.h>
+
+#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/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 <memory.h>
+
+// 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<DeckLink*>(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<PyObject*>(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 <mode>[/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<char**>(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", &param, &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<PyObject*>(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<PyImage*>(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<PyObject*>(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<PyImage*>(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 <structmember.h>
+
+#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 08616e0c41c..9f82987ea62 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_GAMEENGINE_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/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 <stdint.h>
+#include <string.h>
+#ifndef WIN32
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/mman.h>
+#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 <mode>/<pixel>[/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 <displayMode>/<pixelFormat>[/3D][:<cacheSize>]
+ // <displayMode> 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')
+ // <pixelFormat> 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<PyImage*>(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<char**>(kwlist), &format, &capt))
+ return -1;
+
+ try {
+ // create video object
+ Video_init<VideoDeckLink>(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 <inttypes.h>
+#endif
+#include <map>
+#include <set>
+
+extern "C" {
+#include <pthread.h>
+#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<VideoDeckLink*>(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<void*, uint32_t> mAllocatedSize;
+ std::vector<void*> mBufferCache;
+ std::map<void *, TextureTransfer*> 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/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}
};
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;
@@ -145,6 +149,9 @@ static void registerAllTypes(void)
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");
pyImageTypes.add(&ImageRenderType, "ImageRender");
@@ -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);