diff options
author | Campbell Barton <ideasman42@gmail.com> | 2016-06-13 12:26:56 +0300 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2016-06-13 12:26:56 +0300 |
commit | 5864269b2c60b150e8ad4244ae6dfccc04f0b44a (patch) | |
tree | fb11460098269de11de2843ca29e9a61c8fa7bda /source | |
parent | 46e1d85e61aac190e382359d722fafbd533d35ee (diff) | |
parent | 617c4d6adbfe64b3a72b5c48f918f39d30aa18dc (diff) |
Merge branch 'master' into blender2.8
Diffstat (limited to 'source')
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", ¶m, &ts) || !PyBool_Check(param)) { + // report error + PyErr_SetString(PyExc_TypeError, "The value must be a bool"); + return NULL; + } + // some trick here: we are in the business of loading a key frame in decklink, + // no use to do it if we are still in the same rendering frame. + // We find this out by looking at the engine current clock time + KX_KetsjiEngine* engine = KX_GetActiveEngine(); + if (engine->GetClockTime() != self->m_lastClock) + { + self->m_lastClock = engine->GetClockTime(); + // set source refresh + bool refreshSource = (param == Py_True); + uint32_t *leftEye = NULL; + uint32_t *rightEye = NULL; + // try to process key frame from source + try { + // check if optimization is possible + if (self->m_leftEye != NULL) { + ImageBase *leftImage = self->m_leftEye->m_image; + short * srcSize = leftImage->getSize(); + self->mLeftFrame->GetBytes((void **)&leftEye); + if (srcSize[0] == self->mSize[0] && srcSize[1] == self->mSize[1]) + { + // buffer has same size, can load directly + if (!leftImage->loadImage(leftEye, self->mFrameSize, GL_BGRA, ts)) + leftEye = NULL; + } + else { + // scaling is required, go the hard way + unsigned int *src = leftImage->getImage(0, ts); + if (src != NULL) + decklink_ConvImage(leftEye, self->mSize, src, srcSize, self->mUseExtend); + else + leftEye = NULL; + } + } + if (leftEye) { + if (self->mUse3D && self->m_rightEye != NULL) { + ImageBase *rightImage = self->m_rightEye->m_image; + short * srcSize = rightImage->getSize(); + self->mRightFrame->GetBytes((void **)&rightEye); + if (srcSize[0] == self->mSize[0] && srcSize[1] == self->mSize[1]) + { + // buffer has same size, can load directly + rightImage->loadImage(rightEye, self->mFrameSize, GL_BGRA, ts); + } + else { + // scaling is required, go the hard way + unsigned int *src = rightImage->getImage(0, ts); + if (src != NULL) + decklink_ConvImage(rightEye, self->mSize, src, srcSize, self->mUseExtend); + } + } + if (self->mUse3D) { + DeckLink3DFrameWrapper frame3D( + (IDeckLinkVideoFrame*)self->mLeftFrame, + (IDeckLinkVideoFrame*)self->mRightFrame); + self->mDLOutput->DisplayVideoFrameSync(&frame3D); + } + else { + self->mDLOutput->DisplayVideoFrameSync((IDeckLinkVideoFrame*)self->mLeftFrame); + } + } + // refresh texture source, if required + if (refreshSource) { + if (self->m_leftEye) + self->m_leftEye->m_image->refresh(); + if (self->m_rightEye) + self->m_rightEye->m_image->refresh(); + } + } + CATCH_EXCP; + } + Py_RETURN_NONE; +} + +// get source object +static PyObject *DeckLink_getSource(DeckLink *self, PyObject *value, void *closure) +{ + // if source exists + if (self->m_leftEye != NULL) { + Py_INCREF(self->m_leftEye); + return reinterpret_cast<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); |