o Added support for rotation.\r
o Added support for changing the anchor point around which fill_translation,\r
fill_scale and rotation will be done from.\r
+ o Added support for perspective correct corner pinning.\r
+ o Added support for mipmapped textures with anisotropic filtering for\r
+ increased downscaling quality.\r
+ o Added support for cropping a layer. Not the same as clipping.\r
\r
AMCP\r
----\r
- MIXER ROTATION -- will return or modify the angle of which a layer is\r
rotated by (clockwise degrees) around the point specified by ANCHOR.\r
\r
+ - MIXER PERSPECTIVE -- will return or modify the corners of the perspective\r
+ transformation of a layer. One X Y pair for each corner (order upper left,\r
+ upper right, lower right and lower left). Example:\r
+ MIXER 1-10 PERSPECTIVE 0.4 0.4 0.6 0.4 1 1 0 1\r
+\r
+ - MIXER MIPMAP -- will return or modify whether to enable mipmapping of\r
+ textures produced on a layer. Only frames produced after a change will be\r
+ affected. So for example image_producer will not be affected while the\r
+ image is displayed.\r
+\r
+ - MIXER CROP -- will return or modify how textures on a layer will be\r
+ cropped. One X Y pair each for the upper left corner and for the lower\r
+ right corner.\r
+\r
Consumers\r
---------\r
\r
\r
struct device_buffer::implementation : boost::noncopyable\r
{\r
- GLuint id_;\r
+ GLuint id_;\r
\r
- const size_t width_;\r
- const size_t height_;\r
- const size_t stride_;\r
+ const size_t width_;\r
+ const size_t height_;\r
+ const size_t stride_;\r
+ const bool mipmapped_;\r
\r
- fence fence_;\r
+ fence fence_;\r
\r
public:\r
- implementation(size_t width, size_t height, size_t stride) \r
+ implementation(size_t width, size_t height, size_t stride, bool mipmapped) \r
: width_(width)\r
, height_(height)\r
, stride_(stride)\r
+ , mipmapped_(mipmapped)\r
{ \r
GL(glGenTextures(1, &id_));\r
GL(glBindTexture(GL_TEXTURE_2D, id_));\r
- GL(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));\r
- GL(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));\r
- GL(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));\r
- GL(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));\r
+ GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (mipmapped ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR)));\r
+ GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));\r
+ GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));\r
+ GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));\r
GL(glTexImage2D(GL_TEXTURE_2D, 0, INTERNAL_FORMAT[stride_], width_, height_, 0, FORMAT[stride_], GL_UNSIGNED_BYTE, NULL));\r
+\r
+ if (mipmapped)\r
+ {\r
+ enable_anosotropic_filtering_if_available();\r
+ GL(glGenerateMipmap(GL_TEXTURE_2D));\r
+ }\r
+\r
GL(glBindTexture(GL_TEXTURE_2D, 0));\r
CASPAR_LOG(trace) << "[device_buffer] [" << ++g_total_count << L"] allocated size:" << width*height*stride; \r
- } \r
+ }\r
+\r
+ void enable_anosotropic_filtering_if_available()\r
+ {\r
+ static auto AVAILABLE = glewIsSupported("GL_EXT_texture_filter_anisotropic");\r
+\r
+ if (!AVAILABLE)\r
+ return;\r
+\r
+ static GLfloat MAX_ANISOTROPY = []() -> GLfloat\r
+ {\r
+ GLfloat anisotropy;\r
+ glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropy);\r
+ return anisotropy;\r
+ }();\r
+\r
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, MAX_ANISOTROPY);\r
+ }\r
\r
~implementation()\r
{\r
{\r
bind();\r
GL(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, FORMAT[stride_], GL_UNSIGNED_BYTE, NULL));\r
+\r
+ if (mipmapped_)\r
+ GL(glGenerateMipmap(GL_TEXTURE_2D));\r
+\r
unbind();\r
fence_.set();\r
}\r
}\r
};\r
\r
-device_buffer::device_buffer(size_t width, size_t height, size_t stride) : impl_(new implementation(width, height, stride)){}\r
+device_buffer::device_buffer(size_t width, size_t height, size_t stride, bool mipmapped) : impl_(new implementation(width, height, stride, mipmapped)){}\r
size_t device_buffer::stride() const { return impl_->stride_; }\r
size_t device_buffer::width() const { return impl_->width_; }\r
size_t device_buffer::height() const { return impl_->height_; }\r
size_t stride() const; \r
size_t width() const;\r
size_t height() const;\r
- \r
+ bool mipmapped() const;\r
+\r
void bind(int index);\r
void unbind();\r
\r
bool ready() const;\r
private:\r
friend class ogl_device;\r
- device_buffer(size_t width, size_t height, size_t stride);\r
+ device_buffer(size_t width, size_t height, size_t stride, bool mipmapped);\r
\r
int id() const;\r
\r
});\r
}\r
\r
-safe_ptr<device_buffer> ogl_device::allocate_device_buffer(size_t width, size_t height, size_t stride)\r
+safe_ptr<device_buffer> ogl_device::allocate_device_buffer(size_t width, size_t height, size_t stride, bool mipmapped)\r
{\r
std::shared_ptr<device_buffer> buffer;\r
try\r
{\r
- buffer.reset(new device_buffer(width, height, stride));\r
+ buffer.reset(new device_buffer(width, height, stride, mipmapped));\r
}\r
catch(...)\r
{\r
gc().wait();\r
\r
// Try again\r
- buffer.reset(new device_buffer(width, height, stride));\r
+ buffer.reset(new device_buffer(width, height, stride, mipmapped));\r
}\r
catch(...)\r
{\r
return make_safe_ptr(buffer);\r
}\r
\r
-safe_ptr<device_buffer> ogl_device::create_device_buffer(size_t width, size_t height, size_t stride)\r
+safe_ptr<device_buffer> ogl_device::create_device_buffer(size_t width, size_t height, size_t stride, bool mipmapped)\r
{\r
CASPAR_VERIFY(stride > 0 && stride < 5);\r
CASPAR_VERIFY(width > 0 && height > 0);\r
- auto& pool = device_pools_[stride-1][((width << 16) & 0xFFFF0000) | (height & 0x0000FFFF)];\r
+ auto& pool = device_pools_[stride-1 + (mipmapped ? 4 : 0)][((width << 16) & 0xFFFF0000) | (height & 0x0000FFFF)];\r
std::shared_ptr<device_buffer> buffer;\r
if(!pool->items.try_pop(buffer)) \r
- buffer = executor_.invoke([&]{return allocate_device_buffer(width, height, stride);}, high_priority); \r
+ buffer = executor_.invoke([&]{return allocate_device_buffer(width, height, stride, mipmapped);}, high_priority); \r
\r
//++pool->usage_count;\r
\r
\r
std::unique_ptr<sf::Context> context_;\r
\r
- std::array<tbb::concurrent_unordered_map<size_t, safe_ptr<buffer_pool<device_buffer>>>, 4> device_pools_;\r
+ std::array<tbb::concurrent_unordered_map<size_t, safe_ptr<buffer_pool<device_buffer>>>, 8> device_pools_;\r
std::array<tbb::concurrent_unordered_map<size_t, safe_ptr<buffer_pool<host_buffer>>>, 2> host_pools_;\r
\r
GLuint fbo_;\r
return executor_.invoke(std::forward<Func>(func), priority);\r
}\r
\r
- safe_ptr<device_buffer> create_device_buffer(size_t width, size_t height, size_t stride);\r
+ safe_ptr<device_buffer> create_device_buffer(size_t width, size_t height, size_t stride, bool mipmapped);\r
safe_ptr<host_buffer> create_host_buffer(size_t size, host_buffer::usage_t usage);\r
\r
void yield();\r
std::wstring version();\r
\r
private:\r
- safe_ptr<device_buffer> allocate_device_buffer(size_t width, size_t height, size_t stride);\r
+ safe_ptr<device_buffer> allocate_device_buffer(size_t width, size_t height, size_t stride, bool mipmapped);\r
safe_ptr<host_buffer> allocate_host_buffer(size_t size, host_buffer::usage_t usage);\r
};\r
\r
#include <boost/noncopyable.hpp>\r
\r
namespace caspar { namespace core {\r
- \r
+\r
+// http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect\r
+bool get_line_intersection(\r
+ double p0_x, double p0_y,\r
+ double p1_x, double p1_y, \r
+ double p2_x, double p2_y,\r
+ double p3_x, double p3_y,\r
+ double& result_x, double& result_y)\r
+{\r
+ double s1_x = p1_x - p0_x;\r
+ double s1_y = p1_y - p0_y;\r
+ double s2_x = p3_x - p2_x;\r
+ double s2_y = p3_y - p2_y;\r
+\r
+ double s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y);\r
+ double t = ( s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y);\r
+\r
+ if (s >= 0 && s <= 1 && t >= 0 && t <= 1)\r
+ {\r
+ // Collision detected\r
+ result_x = p0_x + (t * s1_x);\r
+ result_y = p0_y + (t * s1_y);\r
+\r
+ return true;\r
+ }\r
+\r
+ return false; // No collision\r
+}\r
+\r
+double hypotenuse(double x1, double y1, double x2, double y2)\r
+{\r
+ auto x = x2 - x1;\r
+ auto y = y2 - y1;\r
+\r
+ return std::sqrt(x * x + y * y);\r
+}\r
+\r
+double calc_q(double close_diagonal, double distant_diagonal)\r
+{\r
+ return (close_diagonal + distant_diagonal) / distant_diagonal;\r
+}\r
+\r
+bool is_above_screen(double y)\r
+{\r
+ return y < 0.0;\r
+}\r
+\r
+bool is_below_screen(double y)\r
+{\r
+ return y > 1.0;\r
+}\r
+\r
+bool is_left_of_screen(double x)\r
+{\r
+ return x < 0.0;\r
+}\r
+\r
+bool is_right_of_screen(double x)\r
+{\r
+ return x > 1.0;\r
+}\r
+\r
+bool is_outside_screen(\r
+ double x1, double y1,\r
+ double x2, double y2,\r
+ double x3, double y3,\r
+ double x4, double y4)\r
+{\r
+ // Every point needs to be outside the screen on the *same* side in order to be considered outside the screen.\r
+ return (is_above_screen(y1) && is_above_screen(y2) && is_above_screen(y3) && is_above_screen(y4))\r
+ || (is_below_screen(y1) && is_below_screen(y2) && is_below_screen(y3) && is_below_screen(y4))\r
+ || (is_left_of_screen(x1) && is_left_of_screen(x2) && is_left_of_screen(x3) && is_left_of_screen(x4))\r
+ || (is_right_of_screen(x1) && is_right_of_screen(x2) && is_right_of_screen(x3) && is_right_of_screen(x4));\r
+}\r
+\r
GLubyte upper_pattern[] = {\r
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,\r
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,\r
\r
if(params.transform.opacity < epsilon)\r
return;\r
+\r
+ auto f_p = params.transform.fill_translation;\r
+ auto f_s = params.transform.fill_scale;\r
+\r
+ // Calculate rotation\r
+ auto aspect = params.aspect_ratio;\r
+ auto angle = params.transform.angle;\r
+\r
+ auto rotate = [angle, aspect](double orig_x, double orig_y) -> boost::array<double, 2>\r
+ {\r
+ boost::array<double, 2> result;\r
+ result[0] = orig_x * std::cos(angle) - orig_y * std::sin(angle);\r
+ result[1] = orig_x * std::sin(angle) + orig_y * std::cos(angle);\r
+ result[1] *= aspect;\r
+\r
+ return result;\r
+ };\r
+\r
+ auto anchor = params.transform.anchor;\r
+ auto crop = params.transform.crop;\r
+ auto pers = params.transform.perspective;\r
+\r
+ auto ul = rotate((-anchor[0] + pers.ul[0]) * f_s[0], (-anchor[1] + pers.ul[1]) * f_s[1] / aspect);\r
+ auto ur = rotate((-anchor[0] + pers.ur[0]) * f_s[0], (-anchor[1] + pers.ur[1]) * f_s[1] / aspect);\r
+ auto lr = rotate((-anchor[0] + pers.lr[0]) * f_s[0], (-anchor[1] + pers.lr[1]) * f_s[1] / aspect);\r
+ auto ll = rotate((-anchor[0] + pers.ll[0]) * f_s[0], (-anchor[1] + pers.ll[1]) * f_s[1] / aspect);\r
+\r
+ auto upper_left_x = f_p[0] + ul[0];\r
+ auto upper_left_y = f_p[1] + ul[1];\r
+ auto upper_right_x = f_p[0] + ur[0];\r
+ auto upper_right_y = f_p[1] + ur[1];\r
+ auto lower_right_x = f_p[0] + lr[0];\r
+ auto lower_right_y = f_p[1] + lr[1];\r
+ auto lower_left_x = f_p[0] + ll[0];\r
+ auto lower_left_y = f_p[1] + ll[1];\r
+\r
+ // Skip drawing if the QUAD will be outside the screen.\r
+ if (is_outside_screen(\r
+ upper_left_x, upper_left_y,\r
+ upper_right_x, upper_right_y,\r
+ lower_right_x, lower_right_y,\r
+ lower_left_x, lower_left_y))\r
+ {\r
+ return;\r
+ }\r
\r
if(!std::all_of(params.textures.begin(), params.textures.end(), std::mem_fn(&device_buffer::ready)))\r
{\r
ogl_->scissor(static_cast<size_t>(m_p[0]*w), static_cast<size_t>(m_p[1]*h), static_cast<size_t>(m_s[0]*w), static_cast<size_t>(m_s[1]*h));\r
}\r
\r
- auto f_p = params.transform.fill_translation;\r
- auto f_s = params.transform.fill_scale;\r
- \r
// Set render target\r
\r
ogl_->attach(*params.background);\r
\r
- // Calculate rotation\r
- auto aspect = params.aspect_ratio;\r
- auto angle = params.transform.angle;\r
-\r
- auto rotate = [angle, aspect](double orig_x, double orig_y) -> boost::array<double, 2>\r
+ // Perspective correction\r
+ auto ulq = 1.0;\r
+ auto urq = 1.0;\r
+ auto lrq = 1.0;\r
+ auto llq = 1.0;\r
+ double diagonal_intersection_x;\r
+ double diagonal_intersection_y;\r
+\r
+ if (get_line_intersection(\r
+ pers.ul[0], pers.ul[1],\r
+ pers.lr[0], pers.lr[1],\r
+ pers.ur[0], pers.ur[1],\r
+ pers.ll[0], pers.ll[1],\r
+ diagonal_intersection_x,\r
+ diagonal_intersection_y))\r
{\r
- boost::array<double, 2> result;\r
- result[0] = orig_x * std::cos(angle) - orig_y * std::sin(angle);\r
- result[1] = orig_x * std::sin(angle) + orig_y * std::cos(angle);\r
- result[1] *= aspect;\r
-\r
- return result;\r
- };\r
-\r
- auto anchor = params.transform.anchor;\r
-\r
- auto ul = rotate( -anchor[0] * f_s[0], -anchor[1] *f_s[1] / aspect);\r
- auto ur = rotate((-anchor[0] + 1) * f_s[0], -anchor[1] *f_s[1] / aspect);\r
- auto lr = rotate((-anchor[0] + 1) * f_s[0], (-anchor[1] + 1) *f_s[1] / aspect);\r
- auto ll = rotate( -anchor[0] * f_s[0], (-anchor[1] + 1) *f_s[1] / aspect);\r
-\r
- auto upper_left_x = f_p[0] + ul[0];\r
- auto upper_left_y = f_p[1] + ul[1];\r
- auto upper_right_x = f_p[0] + ur[0];\r
- auto upper_right_y = f_p[1] + ur[1];\r
- auto lower_right_x = f_p[0] + lr[0];\r
- auto lower_right_y = f_p[1] + lr[1];\r
- auto lower_left_x = f_p[0] + ll[0];\r
- auto lower_left_y = f_p[1] + ll[1];\r
+ // http://www.reedbeta.com/blog/2012/05/26/quadrilateral-interpolation-part-1/\r
+ auto d0 = hypotenuse(pers.ll[0], pers.ll[1], diagonal_intersection_x, diagonal_intersection_y);\r
+ auto d1 = hypotenuse(pers.lr[0], pers.lr[1], diagonal_intersection_x, diagonal_intersection_y);\r
+ auto d2 = hypotenuse(pers.ur[0], pers.ur[1], diagonal_intersection_x, diagonal_intersection_y);\r
+ auto d3 = hypotenuse(pers.ul[0], pers.ul[1], diagonal_intersection_x, diagonal_intersection_y);\r
+\r
+ ulq = calc_q(d3, d1);\r
+ urq = calc_q(d2, d0);\r
+ lrq = calc_q(d1, d3);\r
+ llq = calc_q(d0, d2);\r
+ }\r
\r
// Draw\r
/*\r
GL_TEXTURE1 are texture coordinates to background- / key-material, that which will have to be taken in consideration when blending. These are set to the rectangle over which the source will be rendered\r
*/\r
glBegin(GL_QUADS);\r
- glMultiTexCoord2d(GL_TEXTURE0, 0.0, 0.0); glMultiTexCoord2d(GL_TEXTURE1, upper_left_x, upper_left_y ); glVertex2d(upper_left_x * 2.0 - 1.0, upper_left_y * 2.0 - 1.0);\r
- glMultiTexCoord2d(GL_TEXTURE0, 1.0, 0.0); glMultiTexCoord2d(GL_TEXTURE1, upper_right_x, upper_right_y); glVertex2d(upper_right_x * 2.0 - 1.0, upper_right_y * 2.0 - 1.0);\r
- glMultiTexCoord2d(GL_TEXTURE0, 1.0, 1.0); glMultiTexCoord2d(GL_TEXTURE1, lower_right_x, lower_right_y); glVertex2d(lower_right_x * 2.0 - 1.0, lower_right_y * 2.0 - 1.0);\r
- glMultiTexCoord2d(GL_TEXTURE0, 0.0, 1.0); glMultiTexCoord2d(GL_TEXTURE1, lower_left_x, lower_left_y ); glVertex2d(lower_left_x * 2.0 - 1.0, lower_left_y * 2.0 - 1.0);\r
+ glMultiTexCoord4d(GL_TEXTURE0, crop.ul[0] * ulq, crop.ul[1] * ulq, 0, ulq); glMultiTexCoord2d(GL_TEXTURE1, upper_left_x, upper_left_y); glVertex2d(upper_left_x * 2.0 - 1.0, upper_left_y * 2.0 - 1.0);\r
+ glMultiTexCoord4d(GL_TEXTURE0, crop.lr[0] * urq, crop.ul[1] * urq, 0, urq); glMultiTexCoord2d(GL_TEXTURE1, upper_right_x, upper_right_y); glVertex2d(upper_right_x * 2.0 - 1.0, upper_right_y * 2.0 - 1.0);\r
+ glMultiTexCoord4d(GL_TEXTURE0, crop.lr[0] * lrq, crop.lr[1] * lrq, 0, lrq); glMultiTexCoord2d(GL_TEXTURE1, lower_right_x, lower_right_y); glVertex2d(lower_right_x * 2.0 - 1.0, lower_right_y * 2.0 - 1.0);\r
+ glMultiTexCoord4d(GL_TEXTURE0, crop.ul[0] * llq, crop.lr[1] * llq, 0, llq); glMultiTexCoord2d(GL_TEXTURE1, lower_left_x, lower_left_y); glVertex2d(lower_left_x * 2.0 - 1.0, lower_left_y * 2.0 - 1.0);\r
glEnd();\r
\r
// Cleanup\r
\r
- ogl_->disable(GL_SCISSOR_TEST); \r
+ ogl_->disable(GL_SCISSOR_TEST);\r
\r
params.textures.clear();\r
ogl_->yield(); // Return resources to pool as early as possible.\r
\r
safe_ptr<device_buffer> create_mixer_buffer(size_t stride, const video_format_desc& format_desc)\r
{\r
- auto buffer = ogl_->create_device_buffer(format_desc.width, format_desc.height, stride);\r
+ auto buffer = ogl_->create_device_buffer(format_desc.width, format_desc.height, stride, false);\r
ogl_->clear(*buffer);\r
return buffer;\r
}\r
" switch(pixel_format) \n"\r
" { \n"\r
" case 0: //gray \n"\r
- " return vec4(texture2D(plane[0], gl_TexCoord[0].st).rrr, 1.0); \n"\r
+ " return vec4(texture2D(plane[0], gl_TexCoord[0].st / gl_TexCoord[0].q).rrr, 1.0); \n"\r
" case 1: //bgra, \n"\r
- " return texture2D(plane[0], gl_TexCoord[0].st).bgra; \n"\r
+ " return texture2D(plane[0], gl_TexCoord[0].st / gl_TexCoord[0].q).bgra; \n"\r
" case 2: //rgba, \n"\r
- " return texture2D(plane[0], gl_TexCoord[0].st).rgba; \n"\r
+ " return texture2D(plane[0], gl_TexCoord[0].st / gl_TexCoord[0].q).rgba; \n"\r
" case 3: //argb, \n"\r
- " return texture2D(plane[0], gl_TexCoord[0].st).argb; \n"\r
+ " return texture2D(plane[0], gl_TexCoord[0].st / gl_TexCoord[0].q).argb; \n"\r
" case 4: //abgr, \n"\r
- " return texture2D(plane[0], gl_TexCoord[0].st).gbar; \n"\r
+ " return texture2D(plane[0], gl_TexCoord[0].st / gl_TexCoord[0].q).gbar; \n"\r
" case 5: //ycbcr, \n"\r
" { \n"\r
- " float y = texture2D(plane[0], gl_TexCoord[0].st).r; \n"\r
- " float cb = texture2D(plane[1], gl_TexCoord[0].st).r; \n"\r
- " float cr = texture2D(plane[2], gl_TexCoord[0].st).r; \n"\r
+ " float y = texture2D(plane[0], gl_TexCoord[0].st / gl_TexCoord[0].q).r; \n"\r
+ " float cb = texture2D(plane[1], gl_TexCoord[0].st / gl_TexCoord[0].q).r; \n"\r
+ " float cr = texture2D(plane[2], gl_TexCoord[0].st / gl_TexCoord[0].q).r; \n"\r
" return ycbcra_to_rgba(y, cb, cr, 1.0); \n"\r
" } \n"\r
" case 6: //ycbcra \n"\r
" { \n"\r
- " float y = texture2D(plane[0], gl_TexCoord[0].st).r; \n"\r
- " float cb = texture2D(plane[1], gl_TexCoord[0].st).r; \n"\r
- " float cr = texture2D(plane[2], gl_TexCoord[0].st).r; \n"\r
- " float a = texture2D(plane[3], gl_TexCoord[0].st).r; \n"\r
+ " float y = texture2D(plane[0], gl_TexCoord[0].st / gl_TexCoord[0].q).r; \n"\r
+ " float cb = texture2D(plane[1], gl_TexCoord[0].st / gl_TexCoord[0].q).r; \n"\r
+ " float cr = texture2D(plane[2], gl_TexCoord[0].st / gl_TexCoord[0].q).r; \n"\r
+ " float a = texture2D(plane[3], gl_TexCoord[0].st / gl_TexCoord[0].q).r; \n"\r
" return ycbcra_to_rgba(y, cb, cr, a); \n"\r
" } \n"\r
" case 7: //luma \n"\r
" { \n"\r
- " vec3 y3 = texture2D(plane[0], gl_TexCoord[0].st).rrr; \n"\r
+ " vec3 y3 = texture2D(plane[0], gl_TexCoord[0].st / gl_TexCoord[0].q).rrr; \n"\r
" return vec4((y3-0.065)/0.859, 1.0); \n"\r
" } \n"\r
" } \n"\r
#include <unordered_map>\r
\r
namespace caspar { namespace core {\r
+\r
+class layer_specific_frame_factory : public frame_factory\r
+{\r
+ safe_ptr<ogl_device> ogl_;\r
+ mutable tbb::spin_mutex format_desc_mutex_;\r
+ video_format_desc format_desc_;\r
+ tbb::atomic<bool> mipmapping_;\r
+public:\r
+ layer_specific_frame_factory(const safe_ptr<ogl_device>& ogl, const video_format_desc& format_desc)\r
+ : ogl_(ogl)\r
+ , format_desc_(format_desc)\r
+ {\r
+ mipmapping_ = false;\r
+ }\r
+\r
+ void set_mipmapping(bool mipmapping)\r
+ {\r
+ mipmapping_ = mipmapping;\r
+ }\r
+\r
+ bool get_mipmapping() const\r
+ {\r
+ return mipmapping_;\r
+ }\r
+\r
+ void set_video_format_desc(const video_format_desc& format_desc)\r
+ {\r
+ tbb::spin_mutex::scoped_lock lock(format_desc_mutex_);\r
+ format_desc_ = format_desc;\r
+ }\r
+\r
+ safe_ptr<core::write_frame> create_frame(\r
+ const void* tag,\r
+ const core::pixel_format_desc& desc,\r
+ const channel_layout& audio_channel_layout) override\r
+ {\r
+ return make_safe<write_frame>(\r
+ ogl_, tag, desc, audio_channel_layout, mipmapping_);\r
+ }\r
+\r
+ video_format_desc get_video_format_desc() const override\r
+ {\r
+ tbb::spin_mutex::scoped_lock lock(format_desc_mutex_);\r
+ return format_desc_;\r
+ }\r
+};\r
\r
struct mixer::implementation : boost::noncopyable\r
{ \r
tbb::atomic<int64_t> current_mix_time_;\r
\r
safe_ptr<mixer::target_t> target_;\r
- mutable tbb::spin_mutex format_desc_mutex_;\r
video_format_desc format_desc_;\r
safe_ptr<ogl_device> ogl_;\r
channel_layout audio_channel_layout_;\r
audio_mixer audio_mixer_;\r
image_mixer image_mixer_;\r
\r
- std::unordered_map<int, blend_mode> blend_modes_;\r
+ std::unordered_map<int, blend_mode> blend_modes_;\r
+ std::unordered_map<int, safe_ptr<layer_specific_frame_factory>> frame_factories_;\r
\r
executor executor_;\r
safe_ptr<monitor::subject> monitor_subject_;\r
current_mix_time_ = 0;\r
executor_.invoke([&]\r
{\r
- set_current_aspect_ratio(\r
+ detail::set_current_aspect_ratio(\r
static_cast<double>(format_desc.square_width)\r
/ static_cast<double>(format_desc.square_height));\r
});\r
} \r
}); \r
}\r
- \r
- safe_ptr<core::write_frame> create_frame(\r
- const void* tag,\r
- const core::pixel_format_desc& desc,\r
- const channel_layout& audio_channel_layout)\r
- { \r
- return make_safe<write_frame>(ogl_, tag, desc, audio_channel_layout);\r
+\r
+ safe_ptr<layer_specific_frame_factory> get_frame_factory(int layer_index)\r
+ {\r
+ return executor_.invoke([=]() -> safe_ptr<layer_specific_frame_factory>\r
+ {\r
+ auto found = frame_factories_.find(layer_index);\r
+\r
+ if (found == frame_factories_.end())\r
+ {\r
+ auto factory = make_safe<layer_specific_frame_factory>(ogl_, format_desc_);\r
+\r
+ frame_factories_.insert(std::make_pair(layer_index, factory));\r
+\r
+ return factory;\r
+ }\r
+\r
+ return found->second;\r
+ });\r
}\r
\r
blend_mode::type get_blend_mode(int index)\r
}, high_priority);\r
}\r
\r
+ bool get_mipmap(int index)\r
+ {\r
+ return get_frame_factory(index)->get_mipmapping();\r
+ }\r
+\r
+ void set_mipmap(int index, bool mipmap)\r
+ {\r
+ get_frame_factory(index)->set_mipmapping(mipmap);\r
+ }\r
+\r
+ void clear_mipmap(int index)\r
+ {\r
+ executor_.begin_invoke([=]\r
+ {\r
+ frame_factories_.erase(index);\r
+ }, high_priority);\r
+ }\r
+\r
+ void clear_mipmap()\r
+ {\r
+ executor_.begin_invoke([=]\r
+ {\r
+ frame_factories_.clear();\r
+ }, high_priority);\r
+ }\r
+\r
void set_straight_alpha_output(bool value)\r
{\r
executor_.begin_invoke([=]\r
{\r
executor_.begin_invoke([=]\r
{\r
- tbb::spin_mutex::scoped_lock lock(format_desc_mutex_);\r
format_desc_ = format_desc;\r
- set_current_aspect_ratio(\r
+ detail::set_current_aspect_ratio(\r
static_cast<double>(format_desc.square_width)\r
/ static_cast<double>(format_desc.square_height));\r
- });\r
- }\r
\r
- core::video_format_desc get_video_format_desc() const // nothrow\r
- {\r
- tbb::spin_mutex::scoped_lock lock(format_desc_mutex_);\r
- return format_desc_;\r
+ BOOST_FOREACH(auto& factory, frame_factories_)\r
+ factory.second->set_video_format_desc(format_desc);\r
+ });\r
}\r
\r
boost::unique_future<boost::property_tree::wptree> info() const\r
mixer::mixer(const safe_ptr<diagnostics::graph>& graph, const safe_ptr<target_t>& target, const video_format_desc& format_desc, const safe_ptr<ogl_device>& ogl, const channel_layout& audio_channel_layout) \r
: impl_(new implementation(graph, target, format_desc, ogl, audio_channel_layout)){}\r
void mixer::send(const std::pair<std::map<int, safe_ptr<core::basic_frame>>, std::shared_ptr<void>>& frames){ impl_->send(frames);}\r
-core::video_format_desc mixer::get_video_format_desc() const { return impl_->get_video_format_desc(); }\r
-safe_ptr<core::write_frame> mixer::create_frame(const void* tag, const core::pixel_format_desc& desc, const channel_layout& audio_channel_layout){ return impl_->create_frame(tag, desc, audio_channel_layout); } \r
+safe_ptr<frame_factory> mixer::get_frame_factory(int layer_index) { return impl_->get_frame_factory(layer_index); }\r
blend_mode::type mixer::get_blend_mode(int index) { return impl_->get_blend_mode(index); }\r
void mixer::set_blend_mode(int index, blend_mode::type value){impl_->set_blend_mode(index, value);}\r
chroma mixer::get_chroma(int index) { return impl_->get_chroma(index); }\r
void mixer::set_chroma(int index, const chroma & value){impl_->set_chroma(index, value);}\r
void mixer::clear_blend_mode(int index) { impl_->clear_blend_mode(index); }\r
void mixer::clear_blend_modes() { impl_->clear_blend_modes(); }\r
+bool mixer::get_mipmap(int index) { return impl_->get_mipmap(index); }\r
+void mixer::set_mipmap(int index, bool mipmap) { impl_->set_mipmap(index, mipmap); }\r
+void mixer::clear_mipmap(int index) { impl_->clear_mipmap(index); }\r
+void mixer::clear_mipmap() { impl_->clear_mipmap(); }\r
void mixer::set_straight_alpha_output(bool value) { impl_->set_straight_alpha_output(value); }\r
bool mixer::get_straight_alpha_output() { return impl_->get_straight_alpha_output(); }\r
float mixer::get_master_volume() { return impl_->get_master_volume(); }\r
struct channel_layout;\r
\r
class mixer : public target<std::pair<std::map<int, safe_ptr<core::basic_frame>>, std::shared_ptr<void>>>\r
- , public core::frame_factory\r
{\r
public: \r
typedef target<std::pair<safe_ptr<read_frame>, std::shared_ptr<void>>> target_t;\r
\r
// mixer\r
\r
- safe_ptr<core::write_frame> create_frame(const void* tag, const core::pixel_format_desc& desc, const channel_layout& audio_channel_layout); \r
+ safe_ptr<frame_factory> get_frame_factory(int layer_index);\r
\r
- core::video_format_desc get_video_format_desc() const; // nothrow\r
void set_video_format_desc(const video_format_desc& format_desc);\r
\r
blend_mode::type get_blend_mode(int index);\r
void set_blend_mode(int index, blend_mode::type value);\r
chroma get_chroma(int index);\r
void set_chroma(int index, const chroma& value);\r
+ bool get_mipmap(int index);\r
+ void set_mipmap(int index, bool mipmap);\r
void clear_blend_mode(int index);\r
void clear_blend_modes();\r
+ void clear_mipmap(int index);\r
+ void clear_mipmap();\r
void set_straight_alpha_output(bool value);\r
bool get_straight_alpha_output();\r
\r
recorded_frame_age_ = -1;\r
}\r
\r
- implementation(const safe_ptr<ogl_device>& ogl, const void* tag, const core::pixel_format_desc& desc, const channel_layout& channel_layout) \r
+ implementation(const safe_ptr<ogl_device>& ogl, const void* tag, const core::pixel_format_desc& desc, const channel_layout& channel_layout, bool mipmapping) \r
: ogl_(ogl)\r
, desc_(desc)\r
, channel_layout_(channel_layout)\r
});\r
std::transform(desc.planes.begin(), desc.planes.end(), std::back_inserter(textures_), [&](const core::pixel_format_desc::plane& plane)\r
{\r
- return ogl_->create_device_buffer(plane.width, plane.height, plane.channels); \r
+ return ogl_->create_device_buffer(plane.width, plane.height, plane.channels, mipmapping); \r
});\r
\r
recorded_frame_age_ = -1;\r
const safe_ptr<ogl_device>& ogl,\r
const void* tag,\r
const core::pixel_format_desc& desc,\r
- const channel_layout& channel_layout)\r
- : impl_(new implementation(ogl, tag, desc, channel_layout))\r
+ const channel_layout& channel_layout,\r
+ bool mipmapping)\r
+ : impl_(new implementation(ogl, tag, desc, channel_layout, mipmapping))\r
{\r
}\r
write_frame::write_frame(const write_frame& other) : impl_(new implementation(*other.impl_)){}\r
{\r
public: \r
explicit write_frame(const void* tag, const channel_layout& channel_layout);\r
- explicit write_frame(const safe_ptr<ogl_device>& ogl, const void* tag, const core::pixel_format_desc& desc, const channel_layout& channel_layout);\r
+ explicit write_frame(const safe_ptr<ogl_device>& ogl, const void* tag, const core::pixel_format_desc& desc, const channel_layout& channel_layout, bool mipmapping);\r
\r
write_frame(const write_frame& other);\r
write_frame(write_frame&& other);\r
#include <boost/thread.hpp>\r
\r
namespace caspar { namespace core {\r
+\r
+corners::corners()\r
+{\r
+ ul[0] = 0.0;\r
+ ul[1] = 0.0;\r
+ ur[0] = 1.0;\r
+ ur[1] = 0.0;\r
+ lr[0] = 1.0;\r
+ lr[1] = 1.0;\r
+ ll[0] = 0.0;\r
+ ll[1] = 1.0;\r
+}\r
+\r
+rectangle::rectangle()\r
+{\r
+ ul[0] = 0.0;\r
+ ul[1] = 0.0;\r
+ lr[0] = 1.0;\r
+ lr[1] = 1.0;\r
+}\r
\r
frame_transform::frame_transform() \r
: volume(1.0)\r
std::fill(clip_scale.begin(), clip_scale.end(), 1.0);\r
}\r
\r
+template<typename Rect>\r
+void transform_rect(Rect& self, const Rect& other)\r
+{\r
+ self.ul[0] += other.ul[0];\r
+ self.ul[1] += other.ul[1];\r
+ self.lr[0] *= other.lr[0];\r
+ self.lr[1] *= other.lr[1];\r
+}\r
+\r
+void transform_corners(corners& self, const corners& other)\r
+{\r
+ transform_rect(self, other);\r
+\r
+ self.ur[0] *= other.ur[0];\r
+ self.ur[1] += other.ur[1];\r
+ self.ll[0] += other.ll[0];\r
+ self.ll[1] *= other.ll[1];\r
+\r
+ // TODO: figure out the math to compose perspective transforms correctly.\r
+}\r
+\r
frame_transform& frame_transform::operator*=(const frame_transform &other)\r
{\r
volume *= other.volume;\r
\r
// TODO: can this be done in any way without knowing the aspect ratio of the\r
// actual video mode? Thread local to the rescue\r
- auto aspect_ratio = get_current_aspect_ratio();\r
+ auto aspect_ratio = detail::get_current_aspect_ratio();\r
+ aspect_ratio *= fill_scale[0] / fill_scale[1];\r
boost::array<double, 2> rotated;\r
auto orig_x = other.fill_translation[0];\r
auto orig_y = other.fill_translation[1] / aspect_ratio;\r
rotated[0] = orig_x * std::cos(angle) - orig_y * std::sin(angle);\r
rotated[1] = orig_x * std::sin(angle) + orig_y * std::cos(angle);\r
- rotated[1] *= aspect_ratio;\r
+ rotated[1] *= aspect_ratio;\r
\r
anchor[0] += other.anchor[0]*fill_scale[0];\r
anchor[1] += other.anchor[1]*fill_scale[1];\r
clip_scale[0] *= other.clip_scale[0];\r
clip_scale[1] *= other.clip_scale[1];\r
angle += other.angle;\r
+ transform_rect(crop, other.crop);\r
+ transform_corners(perspective, other.perspective);\r
levels.min_input = std::max(levels.min_input, other.levels.min_input);\r
levels.max_input = std::min(levels.max_input, other.levels.max_input); \r
levels.min_output = std::max(levels.min_output, other.levels.min_output);\r
return frame_transform(*this) *= other;\r
}\r
\r
+double do_tween(double time, double source, double dest, double duration, const tweener_t& tweener)\r
+{\r
+ return tweener(time, source, dest-source, duration);\r
+};\r
+\r
+template<typename Rect>\r
+void do_tween_rectangle(const Rect& source, const Rect& dest, Rect& out, double time, double duration, const tweener_t& tweener)\r
+{\r
+ out.ul[0] = do_tween(time, source.ul[0], dest.ul[0], duration, tweener);\r
+ out.ul[1] = do_tween(time, source.ul[1], dest.ul[1], duration, tweener);\r
+ out.lr[0] = do_tween(time, source.lr[0], dest.lr[0], duration, tweener);\r
+ out.lr[1] = do_tween(time, source.lr[1], dest.lr[1], duration, tweener);\r
+}\r
+\r
+void do_tween_corners(const corners& source, const corners& dest, corners& out, double time, double duration, const tweener_t& tweener)\r
+{\r
+ do_tween_rectangle(source, dest, out, time, duration, tweener);\r
+ out.ur[0] = do_tween(time, source.ur[0], dest.ur[0], duration, tweener);\r
+ out.ur[1] = do_tween(time, source.ur[1], dest.ur[1], duration, tweener);\r
+ out.ll[0] = do_tween(time, source.ll[0], dest.ll[0], duration, tweener);\r
+ out.ll[1] = do_tween(time, source.ll[1], dest.ll[1], duration, tweener);\r
+};\r
+\r
frame_transform tween(double time, const frame_transform& source, const frame_transform& dest, double duration, const tweener_t& tweener)\r
{ \r
- auto do_tween = [](double time, double source, double dest, double duration, const tweener_t& tweener)\r
- {\r
- return tweener(time, source, dest-source, duration);\r
- };\r
- \r
frame_transform result; \r
result.volume = do_tween(time, source.volume, dest.volume, duration, tweener);\r
result.brightness = do_tween(time, source.brightness, dest.brightness, duration, tweener);\r
result.field_mode = static_cast<field_mode::type>(source.field_mode & dest.field_mode);\r
result.is_key = source.is_key | dest.is_key;\r
result.is_mix = source.is_mix | dest.is_mix;\r
+\r
+ do_tween_rectangle(source.crop, dest.crop, result.crop, time, duration, tweener);\r
+ do_tween_corners(source.perspective, dest.perspective, result.perspective, time, duration, tweener);\r
+\r
return result;\r
}\r
\r
return !(lhs == rhs);\r
}\r
\r
+namespace detail {\r
+\r
boost::thread_specific_ptr<double>& get_thread_local_aspect_ratio()\r
{\r
static boost::thread_specific_ptr<double> aspect_ratio;\r
return *get_thread_local_aspect_ratio();\r
}\r
\r
-}}\r
+}}}\r
double max_output;\r
};\r
\r
+struct corners\r
+{\r
+ boost::array<double, 2> ul;\r
+ boost::array<double, 2> ur;\r
+ boost::array<double, 2> lr;\r
+ boost::array<double, 2> ll;\r
+\r
+ corners();\r
+};\r
+\r
+struct rectangle\r
+{\r
+ boost::array<double, 2> ul;\r
+ boost::array<double, 2> lr;\r
+\r
+ rectangle();\r
+};\r
+\r
struct frame_transform \r
{\r
public:\r
boost::array<double, 2> clip_translation;\r
boost::array<double, 2> clip_scale;\r
double angle;\r
+ rectangle crop;\r
+ corners perspective;\r
levels levels;\r
\r
field_mode::type field_mode;\r
bool operator==(const frame_transform& lhs, const frame_transform& rhs);\r
bool operator!=(const frame_transform& lhs, const frame_transform& rhs);\r
\r
+namespace detail {\r
+\r
void set_current_aspect_ratio(double aspect_ratio);\r
double get_current_aspect_ratio();\r
\r
-}}\r
+}}}\r
const safe_ptr<ogl_device>& ogl,\r
int generate_delay_millis,\r
const thumbnail_creator& thumbnail_creator,\r
- safe_ptr<media_info_repository> media_info_repo)\r
+ safe_ptr<media_info_repository> media_info_repo,\r
+ bool mipmap)\r
: media_path_(media_path)\r
, thumbnails_path_(thumbnails_path)\r
, width_(width)\r
graph_->set_text(L"thumbnail-channel");\r
graph_->auto_reset();\r
diagnostics::register_graph(graph_);\r
+ mixer_->set_mipmap(0, mipmap);\r
//monitor_->initial_scan_completion().get();\r
//output_->sleep_millis = 2000;\r
}\r
\r
try\r
{\r
- producer = create_thumbnail_producer(mixer_, media_file);\r
+ producer = create_thumbnail_producer(mixer_->get_frame_factory(0), media_file);\r
}\r
catch (const boost::thread_interrupted&)\r
{\r
const safe_ptr<ogl_device>& ogl,\r
int generate_delay_millis,\r
const thumbnail_creator& thumbnail_creator,\r
- safe_ptr<media_info_repository> media_info_repo)\r
+ safe_ptr<media_info_repository> media_info_repo,\r
+ bool mipmap)\r
: impl_(new implementation(\r
monitor_factory,\r
media_path,\r
ogl,\r
generate_delay_millis,\r
thumbnail_creator,\r
- media_info_repo))\r
+ media_info_repo,\r
+ mipmap))\r
{\r
}\r
\r
const safe_ptr<ogl_device>& ogl,\r
int generate_delay_millis,\r
const thumbnail_creator& thumbnail_creator,\r
- safe_ptr<media_info_repository> media_info_repo);\r
+ safe_ptr<media_info_repository> media_info_repo,\r
+ bool mipmap);\r
~thumbnail_generator();\r
void generate(const std::wstring& media_file);\r
void generate_all();\r
"No flash producer on layer "\r
+ boost::lexical_cast<std::string>(layer_index)));\r
\r
- flash_producer = flash::create_producer(video_channel->mixer(), boost::assign::list_of<std::wstring>());\r
+ flash_producer = flash::create_producer(video_channel->mixer()->get_frame_factory(layer_index), boost::assign::list_of<std::wstring>());\r
}\r
\r
if (expect_existing && flash_producer->call(L"?").get() == L"0")\r
\r
CASPAR_LOG(info) << print() << L" Initialized";\r
}\r
-\r
- std::vector<safe_ptr<core::basic_frame>> get_visible()\r
- {\r
- std::vector<safe_ptr<core::basic_frame>> result;\r
- result.reserve(frames_.size());\r
-\r
- BOOST_FOREACH(auto& frame, frames_)\r
- {\r
- auto& fill_translation = frame->get_frame_transform().fill_translation;\r
-\r
- if (width_ == format_desc_.width)\r
- {\r
- auto motion_offset_in_screens = (static_cast<double>(start_offset_y_) + delta_) / static_cast<double>(format_desc_.height);\r
- auto vertical_offset = fill_translation[1] + motion_offset_in_screens;\r
-\r
- if (vertical_offset < -1.0 || vertical_offset > 1.0)\r
- {\r
- continue;\r
- }\r
- }\r
- else\r
- {\r
- auto motion_offset_in_screens = (static_cast<double>(start_offset_x_) + delta_) / static_cast<double>(format_desc_.width);\r
- auto horizontal_offset = fill_translation[0] + motion_offset_in_screens;\r
-\r
- if (horizontal_offset < -1.0 || horizontal_offset > 1.0)\r
- {\r
- continue;\r
- }\r
- }\r
-\r
- result.push_back(frame);\r
- }\r
-\r
- return std::move(result);\r
- }\r
\r
// frame_producer\r
\r
if(frames_.empty())\r
return core::basic_frame::eof();\r
\r
- auto result = make_safe<core::basic_frame>(get_visible());\r
+ auto result = make_safe<core::basic_frame>(frames_);\r
auto& fill_translation = result->get_frame_transform().fill_translation;\r
\r
if (width_ == format_desc_.width)\r
{\r
if(channel != self)\r
{\r
- auto producer = create_channel_producer(self->mixer(), channel); \r
+ auto producer = create_channel_producer(self->mixer()->get_frame_factory(index), channel); \r
self->stage()->load(index, producer, false);\r
self->stage()->play(index);\r
index++;\r
return transform;\r
}, duration, tween));\r
}\r
+ else if(_parameters[0] == L"CROP")\r
+ {\r
+ if (_parameters.size() == 1)\r
+ {\r
+ auto crop = get_current_transform().crop;\r
+ SetReplyString(\r
+ L"201 MIXER OK\r\n" \r
+ + lexical_cast<std::wstring>(crop.ul[0]) + L" "\r
+ + lexical_cast<std::wstring>(crop.ul[1]) + L" "\r
+ + lexical_cast<std::wstring>(crop.lr[0]) + L" "\r
+ + lexical_cast<std::wstring>(crop.lr[1]) + L"\r\n");\r
+ return true;\r
+ }\r
+\r
+ int duration = _parameters.size() > 5 ? boost::lexical_cast<int>(_parameters[5]) : 0;\r
+ std::wstring tween = _parameters.size() > 6 ? _parameters[6] : L"linear";\r
+ double ul_x = boost::lexical_cast<double>(_parameters.at(1));\r
+ double ul_y = boost::lexical_cast<double>(_parameters.at(2));\r
+ double lr_x = boost::lexical_cast<double>(_parameters.at(3));\r
+ double lr_y = boost::lexical_cast<double>(_parameters.at(4));\r
+\r
+ transforms.push_back(stage::transform_tuple_t(GetLayerIndex(), [=](frame_transform transform) mutable -> frame_transform\r
+ {\r
+ transform.crop.ul[0] = ul_x;\r
+ transform.crop.ul[1] = ul_y;\r
+ transform.crop.lr[0] = lr_x;\r
+ transform.crop.lr[1] = lr_y;\r
+ return transform;\r
+ }, duration, tween));\r
+ }\r
+ else if(_parameters[0] == L"PERSPECTIVE")\r
+ {\r
+ if (_parameters.size() == 1)\r
+ {\r
+ auto perspective = get_current_transform().perspective;\r
+ SetReplyString(\r
+ L"201 MIXER OK\r\n" \r
+ + lexical_cast<std::wstring>(perspective.ul[0]) + L" "\r
+ + lexical_cast<std::wstring>(perspective.ul[1]) + L" "\r
+ + lexical_cast<std::wstring>(perspective.ur[0]) + L" "\r
+ + lexical_cast<std::wstring>(perspective.ur[1]) + L" "\r
+ + lexical_cast<std::wstring>(perspective.lr[0]) + L" "\r
+ + lexical_cast<std::wstring>(perspective.lr[1]) + L" "\r
+ + lexical_cast<std::wstring>(perspective.ll[0]) + L" "\r
+ + lexical_cast<std::wstring>(perspective.ll[1]) + L"\r\n");\r
+ return true;\r
+ }\r
+\r
+ int duration = _parameters.size() > 9 ? boost::lexical_cast<int>(_parameters[9]) : 0;\r
+ std::wstring tween = _parameters.size() > 10 ? _parameters[10] : L"linear";\r
+ double ul_x = boost::lexical_cast<double>(_parameters.at(1));\r
+ double ul_y = boost::lexical_cast<double>(_parameters.at(2));\r
+ double ur_x = boost::lexical_cast<double>(_parameters.at(3));\r
+ double ur_y = boost::lexical_cast<double>(_parameters.at(4));\r
+ double lr_x = boost::lexical_cast<double>(_parameters.at(5));\r
+ double lr_y = boost::lexical_cast<double>(_parameters.at(6));\r
+ double ll_x = boost::lexical_cast<double>(_parameters.at(7));\r
+ double ll_y = boost::lexical_cast<double>(_parameters.at(8));\r
+\r
+ transforms.push_back(stage::transform_tuple_t(GetLayerIndex(), [=](frame_transform transform) mutable -> frame_transform\r
+ {\r
+ transform.perspective.ul[0] = ul_x;\r
+ transform.perspective.ul[1] = ul_y;\r
+ transform.perspective.ur[0] = ur_x;\r
+ transform.perspective.ur[1] = ur_y;\r
+ transform.perspective.lr[0] = lr_x;\r
+ transform.perspective.lr[1] = lr_y;\r
+ transform.perspective.ll[0] = ll_x;\r
+ transform.perspective.ll[1] = ll_y;\r
+ return transform;\r
+ }, duration, tween));\r
+ }\r
else if(_parameters[0] == L"GRID")\r
{\r
int duration = _parameters.size() > 2 ? boost::lexical_cast<int>(_parameters[2]) : 0;\r
blend_mode::type blend = get_blend_mode(blend_str);\r
GetChannel()->mixer()->set_blend_mode(GetLayerIndex(), blend); \r
}\r
+ else if(_parameters[0] == L"MIPMAP")\r
+ {\r
+ if (_parameters.size() == 1)\r
+ {\r
+ auto mipmap = GetChannel()->mixer()->get_mipmap(GetLayerIndex());\r
+ SetReplyString(L"201 MIXER OK\r\n" \r
+ + boost::lexical_cast<std::wstring>(mipmap)\r
+ + L"\r\n");\r
+ return true;\r
+ }\r
+\r
+ GetChannel()->mixer()->set_mipmap(GetLayerIndex(), _parameters.at(1) == L"1");\r
+ }\r
else if(_parameters[0] == L"CHROMA")\r
{\r
if (_parameters.size() == 1)\r
{\r
GetChannel()->stage()->clear_transforms();\r
GetChannel()->mixer()->clear_blend_modes();\r
+ GetChannel()->mixer()->clear_mipmap();\r
}\r
else\r
{\r
GetChannel()->stage()->clear_transforms(layer);\r
GetChannel()->mixer()->clear_blend_mode(layer);\r
+ GetChannel()->mixer()->clear_mipmap(layer);\r
}\r
}\r
else if(_parameters[0] == L"COMMIT")\r
\r
// Find the source layer (if one is given)\r
if (is_channel_layer_spec)\r
- pFP = create_layer_producer(command.GetChannel()->mixer(), (*src_channel)->stage(), src_layer_index);\r
+ pFP = create_layer_producer(command.GetChannel()->mixer()->get_frame_factory(command.GetLayerIndex()), (*src_channel)->stage(), src_layer_index);\r
else \r
- pFP = create_channel_producer(command.GetChannel()->mixer(), *src_channel);\r
+ pFP = create_channel_producer(command.GetChannel()->mixer()->get_frame_factory(command.GetLayerIndex()), *src_channel);\r
}\r
return pFP;\r
}\r
}\r
if (pFP == frame_producer::empty())\r
{\r
- pFP = create_producer(GetChannel()->mixer(), _parameters);\r
+ pFP = create_producer(GetChannel()->mixer()->get_frame_factory(GetLayerIndex()), _parameters);\r
}\r
GetChannel()->stage()->load(GetLayerIndex(), pFP, true);\r
\r
}\r
if (pFP == frame_producer::empty())\r
{\r
- pFP = create_producer(GetChannel()->mixer(), _parameters);\r
+ pFP = create_producer(GetChannel()->mixer()->get_frame_factory(GetLayerIndex()), _parameters);\r
}\r
if(pFP == frame_producer::empty())\r
BOOST_THROW_EXCEPTION(file_not_found() << msg_info(_parameters.size() > 0 ? narrow(_parameters[0]) : ""));\r
\r
if(!fullFilename.empty())\r
{\r
+ filename.append(extension);\r
auto call = (boost::wformat(L"ADD %1% \"%2%\" %3% %4% %5%") % layer % filename % bDoStart % label % (std::wstring() + (pDataString ? pDataString : L""))).str();\r
auto producer = GetChannel()->stage()->foreground(GetLayerIndex(9999)).get();\r
\r
if(producer->print().find(L"flash") == std::string::npos)\r
{ \r
- producer = flash::create_producer(GetChannel()->mixer(), boost::assign::list_of<std::wstring>());\r
+ producer = flash::create_producer(GetChannel()->mixer()->get_frame_factory(GetLayerIndex(9999)), boost::assign::list_of<std::wstring>());\r
\r
if (producer != core::frame_producer::empty())\r
{\r
{ \r
filename.append(extension);\r
std::vector<std::wstring> parameters = boost::assign::list_of<std::wstring>(filename);\r
- auto producer = html::create_producer(GetChannel()->mixer(), core::parameters(parameters)); \r
+ auto producer = html::create_producer(GetChannel()->mixer()->get_frame_factory(GetLayerIndex(9999)), core::parameters(parameters)); \r
\r
if (producer != core::frame_producer::empty())\r
{\r
return;\r
}\r
\r
- auto producer = flash::create_producer(this->GetChannel()->mixer(), boost::assign::list_of(env::template_folder()+TEXT("CG.fth")));\r
+ auto producer = flash::create_producer(this->GetChannel()->mixer()->get_frame_factory(0), boost::assign::list_of(env::template_folder()+TEXT("CG.fth")));\r
\r
std::wstringstream flashParam;\r
flashParam << TEXT("<invoke name=\"Add\" returntype=\"xml\"><arguments><number>1</number><string>") << currentProfile_ << '/' << templateName << TEXT("</string><number>0</number><true/><string> </string><string><![CDATA[ ") << xmlData << TEXT(" ]]></string></arguments></invoke>");\r
transition.type = transition::mix;\r
transition.duration = 12;\r
\r
- auto pFP = create_producer(GetChannel()->mixer(), filename);\r
+ auto pFP = create_producer(GetChannel()->mixer()->get_frame_factory(0), filename);\r
auto pTransition = create_transition_producer(GetChannel()->get_video_format_desc().field_mode, pFP, transition);\r
\r
try\r
<scan-interval-millis>5000</scan-interval-millis>\r
<generate-delay-millis>2000</generate-delay-millis>\r
<video-mode>720p2500</video-mode>\r
+ <mipmap>true</mipmap>\r
</thumbnails>\r
<channels>\r
<channel>\r
ogl_,\r
pt.get(L"configuration.thumbnails.generate-delay-millis", 2000),\r
&image::write_cropped_png,\r
- media_info_repo_));\r
+ media_info_repo_,\r
+ pt.get(L"configuration.thumbnails.mipmap", true)));\r
\r
CASPAR_LOG(info) << L"Initialized thumbnail generator.";\r
}\r