]> git.sesse.net Git - casparcg/blobdiff - accelerator/ogl/image/image_kernel.cpp
set svn:eol-style native on .h and .cpp files
[casparcg] / accelerator / ogl / image / image_kernel.cpp
index 831b74223e65153c34d7d92aff9601ef145c0af5..3f77a2d07b83930bbe8f044550088319a6c8777c 100644 (file)
-/*\r
-* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>\r
-*\r
-* This file is part of CasparCG (www.casparcg.com).\r
-*\r
-* CasparCG is free software: you can redistribute it and/or modify\r
-* it under the terms of the GNU General Public License as published by\r
-* the Free Software Foundation, either version 3 of the License, or\r
-* (at your option) any later version.\r
-*\r
-* CasparCG is distributed in the hope that it will be useful,\r
-* but WITHOUT ANY WARRANTY; without even the implied warranty of\r
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
-* GNU General Public License for more details.\r
-*\r
-* You should have received a copy of the GNU General Public License\r
-* along with CasparCG. If not, see <http://www.gnu.org/licenses/>.\r
-*\r
-* Author: Robert Nagy, ronag89@gmail.com\r
-*/\r
-\r
-#include "../../stdafx.h"\r
-\r
-#include "image_kernel.h"\r
-\r
-#include "image_shader.h"\r
-#include "blending_glsl.h"\r
-\r
-#include "../util/shader.h"\r
-#include "../util/texture.h"\r
-#include "../util/device.h"\r
-\r
-#include <common/except.h>\r
-#include <common/gl/gl_check.h>\r
-#include <common/env.h>\r
-\r
-#include <core/video_format.h>\r
-#include <core/frame/pixel_format.h>\r
-#include <core/frame/frame_transform.h>\r
-\r
-#include <boost/lexical_cast.hpp>\r
-\r
-namespace caspar { namespace accelerator { namespace ogl {\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
-       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
-GLubyte lower_pattern[] = {\r
-       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, 0xff, 0xff, 0xff, 0xff, \r
-       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, 0xff, 0xff, 0xff, 0xff,\r
-       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, 0xff, 0xff, 0xff, 0xff,\r
-       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, 0xff, 0xff, 0xff, 0xff};\r
-\r
-struct image_kernel::impl\r
-{      \r
-       spl::shared_ptr<device> ogl_;\r
-       spl::shared_ptr<shader> shader_;\r
-       bool                                    blend_modes_;\r
-                                                       \r
-       impl(const spl::shared_ptr<device>& ogl)\r
-               : ogl_(ogl)\r
-               , shader_(ogl_->invoke([&]{return get_image_shader(blend_modes_);}))\r
-       {\r
-       }\r
-\r
-       void draw(draw_params params)\r
-       {\r
-               static const double epsilon = 0.001;            \r
-               \r
-               CASPAR_ASSERT(params.pix_desc.planes.size() == params.textures.size());\r
-\r
-               if(params.textures.empty() || !params.background)\r
-                       return;\r
-\r
-               if(params.transform.opacity < epsilon)\r
-                       return;\r
-                               \r
-               // Bind textures\r
-\r
-               for(int n = 0; n < params.textures.size(); ++n)\r
-                       params.textures[n]->bind(n);\r
-\r
-               if(params.local_key)\r
-                       params.local_key->bind(texture_id::local_key);\r
-               \r
-               if(params.layer_key)\r
-                       params.layer_key->bind(texture_id::layer_key);\r
-                       \r
-               // Setup shader\r
-                                                               \r
-               shader_->use();\r
-\r
-               shader_->set("plane[0]",                texture_id::plane0);\r
-               shader_->set("plane[1]",                texture_id::plane1);\r
-               shader_->set("plane[2]",                texture_id::plane2);\r
-               shader_->set("plane[3]",                texture_id::plane3);\r
-               for(int n = 0; n < params.textures.size(); ++n)\r
-                       shader_->set("plane_size[" + boost::lexical_cast<std::string>(n) + "]", \r
-                                                static_cast<float>(params.textures[n]->width()), \r
-                                                static_cast<float>(params.textures[n]->height()));\r
-\r
-               shader_->set("local_key",               texture_id::local_key);\r
-               shader_->set("layer_key",               texture_id::layer_key);\r
-               shader_->set("is_hd",                   params.pix_desc.planes.at(0).height > 700 ? 1 : 0);\r
-               shader_->set("has_local_key",   params.local_key);\r
-               shader_->set("has_layer_key",   params.layer_key);\r
-               shader_->set("pixel_format",    params.pix_desc.format.value());        \r
-               shader_->set("opacity",                 params.transform.is_key ? 1.0 : params.transform.opacity);      \r
-                               \r
-\r
-               // Setup blend_func\r
-               \r
-               if(params.transform.is_key)\r
-                       params.blend_mode = core::blend_mode::normal;\r
-\r
-               if(blend_modes_)\r
-               {\r
-                       params.background->bind(texture_id::background);\r
-\r
-                       shader_->set("background",      texture_id::background);\r
-                       shader_->set("blend_mode",      params.blend_mode.value());\r
-                       shader_->set("keyer",           params.keyer.value());\r
-               }\r
-               else\r
-               {\r
-                       GL(glEnable(GL_BLEND));\r
-\r
-                       switch(params.keyer.value())\r
-                       {\r
-                       case keyer::additive:\r
-                               GL(glBlendFunc(GL_ONE, GL_ONE));        \r
-                               break;\r
-                       case keyer::linear:\r
-                       default:                        \r
-                               GL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA));\r
-                       }               \r
-               }\r
-\r
-               // Setup image-adjustements\r
-               \r
-               if(params.transform.levels.min_input  > epsilon         ||\r
-                  params.transform.levels.max_input  < 1.0-epsilon     ||\r
-                  params.transform.levels.min_output > epsilon         ||\r
-                  params.transform.levels.max_output < 1.0-epsilon     ||\r
-                  std::abs(params.transform.levels.gamma - 1.0) > epsilon)\r
-               {\r
-                       shader_->set("levels", true);   \r
-                       shader_->set("min_input",       params.transform.levels.min_input);     \r
-                       shader_->set("max_input",       params.transform.levels.max_input);\r
-                       shader_->set("min_output",      params.transform.levels.min_output);\r
-                       shader_->set("max_output",      params.transform.levels.max_output);\r
-                       shader_->set("gamma",           params.transform.levels.gamma);\r
-               }\r
-               else\r
-                       shader_->set("levels", false);  \r
-\r
-               if(std::abs(params.transform.brightness - 1.0) > epsilon ||\r
-                  std::abs(params.transform.saturation - 1.0) > epsilon ||\r
-                  std::abs(params.transform.contrast - 1.0)   > epsilon)\r
-               {\r
-                       shader_->set("csb",     true);  \r
-                       \r
-                       shader_->set("brt", params.transform.brightness);       \r
-                       shader_->set("sat", params.transform.saturation);\r
-                       shader_->set("con", params.transform.contrast);\r
-               }\r
-               else\r
-                       shader_->set("csb",     false); \r
-               \r
-               // Setup interlacing\r
-               \r
-               if(params.transform.field_mode != core::field_mode::progressive)        \r
-               {\r
-                       GL(glEnable(GL_POLYGON_STIPPLE));\r
-\r
-                       if(params.transform.field_mode == core::field_mode::upper)\r
-                               glPolygonStipple(upper_pattern);\r
-                       else if(params.transform.field_mode == core::field_mode::lower)\r
-                               glPolygonStipple(lower_pattern);\r
-               }\r
-\r
-               // Setup drawing area\r
-               \r
-               GL(glViewport(0, 0, params.background->width(), params.background->height()));\r
-                                                                               \r
-               auto m_p = params.transform.clip_translation;\r
-               auto m_s = params.transform.clip_scale;\r
-\r
-               bool scissor = m_p[0] > std::numeric_limits<double>::epsilon()                  || m_p[1] > std::numeric_limits<double>::epsilon() ||\r
-                                          m_s[0] < (1.0 - std::numeric_limits<double>::epsilon())      || m_s[1] < (1.0 - std::numeric_limits<double>::epsilon());\r
-\r
-               if(scissor)\r
-               {\r
-                       double w = static_cast<double>(params.background->width());\r
-                       double h = static_cast<double>(params.background->height());\r
-               \r
-                       GL(glEnable(GL_SCISSOR_TEST));\r
-                       glScissor(static_cast<int>(m_p[0]*w), static_cast<int>(m_p[1]*h), static_cast<int>(m_s[0]*w), static_cast<int>(m_s[1]*h));\r
-               }\r
-\r
-               auto f_p = params.transform.fill_translation;\r
-               auto f_s = params.transform.fill_scale;\r
-               \r
-               // Synchronize and set render target\r
-                                                               \r
-               if(blend_modes_)\r
-               {\r
-                       // http://www.opengl.org/registry/specs/NV/texture_barrier.txt\r
-                       // This allows us to use framebuffer (background) both as source and target while blending.\r
-                       glTextureBarrierNV(); \r
-               }\r
-\r
-               params.background->attach();\r
-               \r
-               // Draw\r
-                               \r
-               glBegin(GL_QUADS);\r
-                       glMultiTexCoord2d(GL_TEXTURE0, 0.0, 0.0); glMultiTexCoord2d(GL_TEXTURE1,  f_p[0]        ,  f_p[1]        );             glVertex2d( f_p[0]        *2.0-1.0,  f_p[1]        *2.0-1.0);\r
-                       glMultiTexCoord2d(GL_TEXTURE0, 1.0, 0.0); glMultiTexCoord2d(GL_TEXTURE1, (f_p[0]+f_s[0]),  f_p[1]        );             glVertex2d((f_p[0]+f_s[0])*2.0-1.0,  f_p[1]        *2.0-1.0);\r
-                       glMultiTexCoord2d(GL_TEXTURE0, 1.0, 1.0); glMultiTexCoord2d(GL_TEXTURE1, (f_p[0]+f_s[0]), (f_p[1]+f_s[1]));             glVertex2d((f_p[0]+f_s[0])*2.0-1.0, (f_p[1]+f_s[1])*2.0-1.0);\r
-                       glMultiTexCoord2d(GL_TEXTURE0, 0.0, 1.0); glMultiTexCoord2d(GL_TEXTURE1,  f_p[0]        , (f_p[1]+f_s[1]));             glVertex2d( f_p[0]        *2.0-1.0, (f_p[1]+f_s[1])*2.0-1.0);\r
-               glEnd();\r
-               \r
-               // Cleanup\r
-               \r
-               GL(glDisable(GL_SCISSOR_TEST));\r
-               GL(glDisable(GL_POLYGON_STIPPLE));\r
-               GL(glDisable(GL_BLEND));\r
-       }\r
-};\r
-\r
-image_kernel::image_kernel(const spl::shared_ptr<device>& ogl) : impl_(new impl(ogl)){}\r
-image_kernel::~image_kernel(){}\r
-void image_kernel::draw(const draw_params& params){impl_->draw(params);}\r
-bool image_kernel::has_blend_modes() const{return impl_->blend_modes_;}\r
-\r
+/*
+* Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
+*
+* This file is part of CasparCG (www.casparcg.com).
+*
+* CasparCG is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* CasparCG is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
+*
+* Author: Robert Nagy, ronag89@gmail.com
+*/
+
+#include "../../stdafx.h"
+
+#include "image_kernel.h"
+
+#include "image_shader.h"
+#include "blending_glsl.h"
+
+#include "../util/shader.h"
+#include "../util/texture.h"
+#include "../util/device.h"
+
+#include <common/except.h>
+#include <common/gl/gl_check.h>
+#include <common/env.h>
+
+#include <core/video_format.h>
+#include <core/frame/pixel_format.h>
+#include <core/frame/frame_transform.h>
+
+#include <boost/lexical_cast.hpp>
+
+namespace caspar { namespace accelerator { namespace ogl {
+       
+GLubyte upper_pattern[] = {
+       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,
+       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,
+       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,
+       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};
+               
+GLubyte lower_pattern[] = {
+       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, 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, 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, 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, 0xff, 0xff, 0xff, 0xff};
+
+struct image_kernel::impl
+{      
+       spl::shared_ptr<device> ogl_;
+       spl::shared_ptr<shader> shader_;
+       bool                                    blend_modes_;
+                                                       
+       impl(const spl::shared_ptr<device>& ogl)
+               : ogl_(ogl)
+               , shader_(ogl_->invoke([&]{return get_image_shader(blend_modes_);}))
+       {
+       }
+
+       void draw(draw_params params)
+       {
+               static const double epsilon = 0.001;            
+               
+               CASPAR_ASSERT(params.pix_desc.planes.size() == params.textures.size());
+
+               if(params.textures.empty() || !params.background)
+                       return;
+
+               if(params.transform.opacity < epsilon)
+                       return;
+                               
+               // Bind textures
+
+               for(int n = 0; n < params.textures.size(); ++n)
+                       params.textures[n]->bind(n);
+
+               if(params.local_key)
+                       params.local_key->bind(texture_id::local_key);
+               
+               if(params.layer_key)
+                       params.layer_key->bind(texture_id::layer_key);
+                       
+               // Setup shader
+                                                               
+               shader_->use();
+
+               shader_->set("plane[0]",                texture_id::plane0);
+               shader_->set("plane[1]",                texture_id::plane1);
+               shader_->set("plane[2]",                texture_id::plane2);
+               shader_->set("plane[3]",                texture_id::plane3);
+               for(int n = 0; n < params.textures.size(); ++n)
+                       shader_->set("plane_size[" + boost::lexical_cast<std::string>(n) + "]", 
+                                                static_cast<float>(params.textures[n]->width()), 
+                                                static_cast<float>(params.textures[n]->height()));
+
+               shader_->set("local_key",               texture_id::local_key);
+               shader_->set("layer_key",               texture_id::layer_key);
+               shader_->set("is_hd",                   params.pix_desc.planes.at(0).height > 700 ? 1 : 0);
+               shader_->set("has_local_key",   params.local_key);
+               shader_->set("has_layer_key",   params.layer_key);
+               shader_->set("pixel_format",    params.pix_desc.format.value());        
+               shader_->set("opacity",                 params.transform.is_key ? 1.0 : params.transform.opacity);      
+                               
+
+               // Setup blend_func
+               
+               if(params.transform.is_key)
+                       params.blend_mode = core::blend_mode::normal;
+
+               if(blend_modes_)
+               {
+                       params.background->bind(texture_id::background);
+
+                       shader_->set("background",      texture_id::background);
+                       shader_->set("blend_mode",      params.blend_mode.value());
+                       shader_->set("keyer",           params.keyer.value());
+               }
+               else
+               {
+                       GL(glEnable(GL_BLEND));
+
+                       switch(params.keyer.value())
+                       {
+                       case keyer::additive:
+                               GL(glBlendFunc(GL_ONE, GL_ONE));        
+                               break;
+                       case keyer::linear:
+                       default:                        
+                               GL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
+                       }               
+               }
+
+               // Setup image-adjustements
+               
+               if(params.transform.levels.min_input  > epsilon         ||
+                  params.transform.levels.max_input  < 1.0-epsilon     ||
+                  params.transform.levels.min_output > epsilon         ||
+                  params.transform.levels.max_output < 1.0-epsilon     ||
+                  std::abs(params.transform.levels.gamma - 1.0) > epsilon)
+               {
+                       shader_->set("levels", true);   
+                       shader_->set("min_input",       params.transform.levels.min_input);     
+                       shader_->set("max_input",       params.transform.levels.max_input);
+                       shader_->set("min_output",      params.transform.levels.min_output);
+                       shader_->set("max_output",      params.transform.levels.max_output);
+                       shader_->set("gamma",           params.transform.levels.gamma);
+               }
+               else
+                       shader_->set("levels", false);  
+
+               if(std::abs(params.transform.brightness - 1.0) > epsilon ||
+                  std::abs(params.transform.saturation - 1.0) > epsilon ||
+                  std::abs(params.transform.contrast - 1.0)   > epsilon)
+               {
+                       shader_->set("csb",     true);  
+                       
+                       shader_->set("brt", params.transform.brightness);       
+                       shader_->set("sat", params.transform.saturation);
+                       shader_->set("con", params.transform.contrast);
+               }
+               else
+                       shader_->set("csb",     false); 
+               
+               // Setup interlacing
+               
+               if(params.transform.field_mode != core::field_mode::progressive)        
+               {
+                       GL(glEnable(GL_POLYGON_STIPPLE));
+
+                       if(params.transform.field_mode == core::field_mode::upper)
+                               glPolygonStipple(upper_pattern);
+                       else if(params.transform.field_mode == core::field_mode::lower)
+                               glPolygonStipple(lower_pattern);
+               }
+
+               // Setup drawing area
+               
+               GL(glViewport(0, 0, params.background->width(), params.background->height()));
+                                                                               
+               auto m_p = params.transform.clip_translation;
+               auto m_s = params.transform.clip_scale;
+
+               bool scissor = m_p[0] > std::numeric_limits<double>::epsilon()                  || m_p[1] > std::numeric_limits<double>::epsilon() ||
+                                          m_s[0] < (1.0 - std::numeric_limits<double>::epsilon())      || m_s[1] < (1.0 - std::numeric_limits<double>::epsilon());
+
+               if(scissor)
+               {
+                       double w = static_cast<double>(params.background->width());
+                       double h = static_cast<double>(params.background->height());
+               
+                       GL(glEnable(GL_SCISSOR_TEST));
+                       glScissor(static_cast<int>(m_p[0]*w), static_cast<int>(m_p[1]*h), static_cast<int>(m_s[0]*w), static_cast<int>(m_s[1]*h));
+               }
+
+               auto f_p = params.transform.fill_translation;
+               auto f_s = params.transform.fill_scale;
+               
+               // Synchronize and set render target
+                                                               
+               if(blend_modes_)
+               {
+                       // http://www.opengl.org/registry/specs/NV/texture_barrier.txt
+                       // This allows us to use framebuffer (background) both as source and target while blending.
+                       glTextureBarrierNV(); 
+               }
+
+               params.background->attach();
+               
+               // Draw
+                               
+               glBegin(GL_QUADS);
+                       glMultiTexCoord2d(GL_TEXTURE0, 0.0, 0.0); glMultiTexCoord2d(GL_TEXTURE1,  f_p[0]        ,  f_p[1]        );             glVertex2d( f_p[0]        *2.0-1.0,  f_p[1]        *2.0-1.0);
+                       glMultiTexCoord2d(GL_TEXTURE0, 1.0, 0.0); glMultiTexCoord2d(GL_TEXTURE1, (f_p[0]+f_s[0]),  f_p[1]        );             glVertex2d((f_p[0]+f_s[0])*2.0-1.0,  f_p[1]        *2.0-1.0);
+                       glMultiTexCoord2d(GL_TEXTURE0, 1.0, 1.0); glMultiTexCoord2d(GL_TEXTURE1, (f_p[0]+f_s[0]), (f_p[1]+f_s[1]));             glVertex2d((f_p[0]+f_s[0])*2.0-1.0, (f_p[1]+f_s[1])*2.0-1.0);
+                       glMultiTexCoord2d(GL_TEXTURE0, 0.0, 1.0); glMultiTexCoord2d(GL_TEXTURE1,  f_p[0]        , (f_p[1]+f_s[1]));             glVertex2d( f_p[0]        *2.0-1.0, (f_p[1]+f_s[1])*2.0-1.0);
+               glEnd();
+               
+               // Cleanup
+               
+               GL(glDisable(GL_SCISSOR_TEST));
+               GL(glDisable(GL_POLYGON_STIPPLE));
+               GL(glDisable(GL_BLEND));
+       }
+};
+
+image_kernel::image_kernel(const spl::shared_ptr<device>& ogl) : impl_(new impl(ogl)){}
+image_kernel::~image_kernel(){}
+void image_kernel::draw(const draw_params& params){impl_->draw(params);}
+bool image_kernel::has_blend_modes() const{return impl_->blend_modes_;}
+
 }}}
\ No newline at end of file