]> git.sesse.net Git - casparcg/commitdiff
#55
authorHelge Norberg <helge.norberg@svt.se>
Wed, 24 Sep 2014 12:10:48 +0000 (14:10 +0200)
committerHelge Norberg <helge.norberg@svt.se>
Wed, 24 Sep 2014 12:10:48 +0000 (14:10 +0200)
Mixer
-----

  o Added support for perspective correct corner pinning.
  o Added support for mipmapped textures with anisotropic filtering for
    increased downscaling quality.
  o Added support for cropping a layer. Not the same as clipping.

22 files changed:
CHANGES.txt
core/mixer/gpu/device_buffer.cpp
core/mixer/gpu/device_buffer.h
core/mixer/gpu/ogl_device.cpp
core/mixer/gpu/ogl_device.h
core/mixer/image/image_kernel.cpp
core/mixer/image/image_mixer.cpp
core/mixer/image/shader/image_shader.cpp
core/mixer/mixer.cpp
core/mixer/mixer.h
core/mixer/write_frame.cpp
core/mixer/write_frame.h
core/producer/frame/frame_transform.cpp
core/producer/frame/frame_transform.h
core/thumbnail_generator.cpp
core/thumbnail_generator.h
modules/flash/producer/cg_producer.cpp
modules/image/producer/image_scroll_producer.cpp
protocol/amcp/AMCPCommandsImpl.cpp
protocol/cii/CIIProtocolStrategy.cpp
shell/casparcg.config
shell/server.cpp

index 3d46f34f4a29daf889d9ac2f4479a834faaa0db0..ab2cc048d516479de466b24aea6d82a6aab77d71 100644 (file)
@@ -18,6 +18,10 @@ Mixer
   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
@@ -35,6 +39,20 @@ AMCP
     - 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
index ceb3508a4827cbc233d48b59cadecc5536b9e608..c3a2fdd519890df35a3b1b7cc47d354d66c5a8c8 100644 (file)
@@ -46,30 +46,56 @@ static tbb::atomic<int> g_total_count;
 \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
@@ -104,6 +130,10 @@ public:
        {\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
@@ -114,7 +144,7 @@ public:
        }\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
index d6fd962e8d80b45982b41d3610271a9116bf4392..6739795a7a972f2c132e489d2e646c42581e4a3b 100644 (file)
@@ -36,7 +36,8 @@ public:
        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
@@ -44,7 +45,7 @@ public:
        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
index 12f14b3d75f58ecf9bbbfa91366ef6f7d02650f8..1ea4a74d121ab3e50a542b9365eac6ac23ff177a 100644 (file)
@@ -83,12 +83,12 @@ ogl_device::~ogl_device()
        });\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
@@ -98,7 +98,7 @@ safe_ptr<device_buffer> ogl_device::allocate_device_buffer(size_t width, size_t
                        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
@@ -109,14 +109,14 @@ safe_ptr<device_buffer> ogl_device::allocate_device_buffer(size_t width, size_t
        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
index 89a05a85966d6b2c1e1d0fe1373ae5d63fec8c66..2896412dcca1961ea29b3c0f03bae6fa9003db29 100644 (file)
@@ -73,7 +73,7 @@ class ogl_device : public std::enable_shared_from_this<ogl_device>, boost::nonco
 \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
@@ -117,7 +117,7 @@ public:
                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
@@ -126,7 +126,7 @@ public:
        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
index 354859892826e74ce89658ea0338bc78542ed353..c73b02fc8c1bd70fdc9c06ef1287858cd20cc9b4 100644 (file)
 #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
@@ -82,6 +156,51 @@ struct image_kernel::implementation : boost::noncopyable
 \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
@@ -221,42 +340,37 @@ struct image_kernel::implementation : boost::noncopyable
                        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
@@ -264,15 +378,15 @@ struct image_kernel::implementation : boost::noncopyable
                        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
index 99bbe3182573e4a5ae12a2ee5c8e435a026b057d..2691e40a22d7c2496df46aacbfde3ad7da2787a6 100644 (file)
@@ -238,7 +238,7 @@ private:
                        \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
index bb11a4d223ca86348b96e89a9b98f896c5e6a268..e0c54e0b73aa3cdccf19746c4a0654fd57e77ca3 100644 (file)
@@ -228,33 +228,33 @@ std::string get_fragment(bool blend_modes, bool chroma_key, bool post_processing
        "       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
index 0e699c728446ee1f07c1ace79821d9607bf3d789..89e707bbb4a3bdd94399d7c59cbb3c2b51ccc074 100644 (file)
 #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
@@ -68,7 +114,6 @@ struct mixer::implementation : boost::noncopyable
        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
@@ -77,7 +122,8 @@ struct mixer::implementation : boost::noncopyable
        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
@@ -99,7 +145,7 @@ public:
                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
@@ -144,13 +190,24 @@ public:
                        }       \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
@@ -201,6 +258,32 @@ public:
         }, 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
@@ -237,18 +320,14 @@ public:
        {\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
@@ -271,14 +350,17 @@ public:
 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
index 6c6ef1929b3978a6162f8b8e26ac74d99beb0227..8b1d51242a5d9aa98f49d28f18a15514e918f44a 100644 (file)
@@ -50,7 +50,6 @@ struct pixel_format;
 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
@@ -63,17 +62,20 @@ public:
                \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
index 4cdd32b5a05c96f9b04aeeebd1719de370f988e3..f60bff1f905ac820248eba72f8d55cae8175098c 100644 (file)
@@ -58,7 +58,7 @@ struct write_frame::implementation
                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
@@ -71,7 +71,7 @@ struct write_frame::implementation
                });\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
@@ -137,8 +137,9 @@ write_frame::write_frame(
                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
index a096ebd9a474b97e3ef8990e8a3553414172851f..6547ddefe764422d044415b924c83083b4940212 100644 (file)
@@ -45,7 +45,7 @@ class write_frame : public core::basic_frame, boost::noncopyable
 {\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
index d81186e6120a4230950a6c0a99603b462dd860b2..d7987bf020579f2fb55beae0e1183a6426380795 100644 (file)
 #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
@@ -47,6 +67,27 @@ frame_transform::frame_transform()
        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
@@ -57,13 +98,14 @@ frame_transform& frame_transform::operator*=(const frame_transform &other)
 \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
@@ -77,6 +119,8 @@ frame_transform& frame_transform::operator*=(const frame_transform &other)
        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
@@ -94,13 +138,31 @@ frame_transform frame_transform::operator*(const frame_transform &other) const
        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
@@ -126,6 +188,10 @@ frame_transform tween(double time, const frame_transform& source, const frame_tr
        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
@@ -144,6 +210,8 @@ bool operator!=(const frame_transform& lhs, const frame_transform& rhs)
        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
@@ -164,4 +232,4 @@ double get_current_aspect_ratio()
        return *get_thread_local_aspect_ratio();\r
 }\r
 \r
-}}\r
+}}}\r
index a903c8279d5b9389fc14ac1cf280346aa106297d..c8743f0a3546ebce7fd7789d12083f37f1e415fb 100644 (file)
@@ -48,6 +48,24 @@ struct levels
        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
@@ -65,6 +83,8 @@ public:
        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
@@ -81,7 +101,9 @@ bool operator<(const frame_transform& lhs, const frame_transform& rhs);
 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
index b76831799a82c8dfd3322f26ca1300eecf85585c..4a8614b5fe3c74ba9aaa02d753a32668c98f66c2 100644 (file)
@@ -117,7 +117,8 @@ public:
                        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
@@ -149,6 +150,7 @@ public:
                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
@@ -266,7 +268,7 @@ public:
 \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
@@ -361,7 +363,8 @@ thumbnail_generator::thumbnail_generator(
                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
@@ -371,7 +374,8 @@ thumbnail_generator::thumbnail_generator(
                                ogl,\r
                                generate_delay_millis,\r
                                thumbnail_creator,\r
-                               media_info_repo))\r
+                               media_info_repo,\r
+                               mipmap))\r
 {\r
 }\r
 \r
index 27e1c8c828716277139d618dfbf3c8795451c2fe..2f1ad6514215814702099d2f8ea4e3ef943acc3f 100644 (file)
@@ -53,7 +53,8 @@ public:
                        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
index 85cd830f23e3c3db93287232c7863267e748325d..2dd0925b00215631cbdb064d27c38b1cd0e2d454 100644 (file)
@@ -224,7 +224,7 @@ void with_default_cg_producer(
                                                "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
index 7b9804429e87128c7959070722461a6c8e2aee89..b8151bda8cf5bdd32c33547ffee50f71191b36f3 100644 (file)
@@ -241,42 +241,6 @@ struct image_scroll_producer : public core::frame_producer
 \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
@@ -285,7 +249,7 @@ struct image_scroll_producer : public core::frame_producer
                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
index 1981a2f5f51bc2f45bf3ad4f660d13d90c1d7f86..f49dc79ff3c7713fdd462faf224e9845ceb5fccd 100644 (file)
@@ -359,7 +359,7 @@ bool ChannelGridCommand::DoExecute()
        {\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
@@ -579,6 +579,78 @@ bool MixerCommand::DoExecute()
                                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
@@ -621,6 +693,19 @@ bool MixerCommand::DoExecute()
                        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
@@ -789,11 +874,13 @@ bool MixerCommand::DoExecute()
                        {\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
@@ -999,9 +1086,9 @@ safe_ptr<core::frame_producer> RouteCommand::TryCreateProducer(AMCPCommand& comm
 \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
@@ -1052,7 +1139,7 @@ bool LoadCommand::DoExecute()
                }\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
@@ -1166,7 +1253,7 @@ bool LoadbgCommand::DoExecute()
                }\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
@@ -1423,12 +1510,13 @@ bool CGCommand::DoExecuteAdd() {
 \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
@@ -1451,7 +1539,7 @@ bool CGCommand::DoExecuteAdd() {
        {                       \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
index 50306738f0f3c1e4fec4d21be9d08bad46e29cbf..80c94931b782a5072b9b7c6ae41a1c88595a2295 100644 (file)
@@ -184,7 +184,7 @@ void CIIProtocolStrategy::WriteTemplateData(const std::wstring& templateName, co
                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
@@ -217,7 +217,7 @@ void CIIProtocolStrategy::DisplayMediaFile(const std::wstring& filename)
        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
index ed2fba76119c9c9a50d0dbc00509f46ee70869af..471bd0bf0a14de3304f13e5afc2ac63a0cbd2300 100644 (file)
@@ -56,6 +56,7 @@
     <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
index 0e6a4351215e2957b72fcd6eacf7766d08471834..de47b1f5aa3e823974e9968b3a12f99395cca0f8 100644 (file)
@@ -370,7 +370,8 @@ struct server::implementation : boost::noncopyable
                                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