]> git.sesse.net Git - casparcg/blob - accelerator/cpu/image/image_mixer.cpp
2.1.0: cpu/image_mixer: Added support for image conversion and blending. Only interla...
[casparcg] / accelerator / cpu / image / image_mixer.cpp
1 /*\r
2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>\r
3 *\r
4 * This file is part of CasparCG (www.casparcg.com).\r
5 *\r
6 * CasparCG is free software: you can redistribute it and/or modify\r
7 * it under the terms of the GNU General Public License as published by\r
8 * the Free Software Foundation, either version 3 of the License, or\r
9 * (at your option) any later version.\r
10 *\r
11 * CasparCG is distributed in the hope that it will be useful,\r
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
14 * GNU General Public License for more details.\r
15 *\r
16 * You should have received a copy of the GNU General Public License\r
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.\r
18 *\r
19 * Author: Robert Nagy, ronag89@gmail.com\r
20 */\r
21 \r
22 #include "../../stdafx.h"\r
23 \r
24 #include "image_mixer.h"\r
25 \r
26 #include "../util/write_frame.h"\r
27 #include "../util/blend.h"\r
28 \r
29 #include <common/assert.h>\r
30 #include <common/gl/gl_check.h>\r
31 #include <common/concurrency/async.h>\r
32 #include <common/memory/memcpy.h>\r
33 \r
34 #include <core/frame/write_frame.h>\r
35 #include <core/frame/frame_transform.h>\r
36 #include <core/frame/pixel_format.h>\r
37 #include <core/video_format.h>\r
38 \r
39 #include <modules/ffmpeg/producer/util/util.h>\r
40 \r
41 #include <asmlib.h>\r
42 \r
43 #include <gl/glew.h>\r
44 \r
45 #include <tbb/cache_aligned_allocator.h>\r
46 #include <tbb/parallel_for_each.h>\r
47 \r
48 #include <boost/assign.hpp>\r
49 #include <boost/foreach.hpp>\r
50 #include <boost/range.hpp>\r
51 #include <boost/range/algorithm_ext/erase.hpp>\r
52 #include <boost/thread/future.hpp>\r
53 \r
54 #include <algorithm>\r
55 #include <vector>\r
56 \r
57 #if defined(_MSC_VER)\r
58 #pragma warning (push)\r
59 #pragma warning (disable : 4244)\r
60 #endif\r
61 extern "C" \r
62 {\r
63         #include <libswscale/swscale.h>\r
64         #include <libavcodec/avcodec.h>\r
65         #include <libavformat/avformat.h>\r
66 }\r
67 #if defined(_MSC_VER)\r
68 #pragma warning (pop)\r
69 #endif\r
70 \r
71 namespace caspar { namespace accelerator { namespace cpu {\r
72                 \r
73 struct item\r
74 {\r
75         core::pixel_format_desc                                         pix_desc;\r
76         std::vector<spl::shared_ptr<host_buffer>>       buffers;\r
77         core::frame_transform                                           transform;\r
78 \r
79         item()\r
80                 : pix_desc(core::pixel_format::invalid)\r
81         {\r
82         }\r
83 };\r
84 \r
85 bool operator==(const item& lhs, const item& rhs)\r
86 {\r
87         return lhs.buffers == rhs.buffers && lhs.transform == rhs.transform;\r
88 }\r
89 \r
90 bool operator!=(const item& lhs, const item& rhs)\r
91 {\r
92         return !(lhs == rhs);\r
93 }\r
94 \r
95 struct layer\r
96 {\r
97         std::vector<item>       items;\r
98 \r
99         layer()\r
100         {\r
101         }\r
102 \r
103         layer(std::vector<item> items)\r
104                 : items(std::move(items))\r
105         {\r
106         }\r
107 };\r
108 \r
109 bool operator==(const layer& lhs, const layer& rhs)\r
110 {\r
111         return lhs.items == rhs.items;\r
112 }\r
113 \r
114 bool operator!=(const layer& lhs, const layer& rhs)\r
115 {\r
116         return !(lhs == rhs);\r
117 }\r
118 \r
119 class image_renderer\r
120 {\r
121         std::pair<std::vector<layer>, boost::shared_future<boost::iterator_range<const uint8_t*>>>      last_image_;\r
122         std::map<int, std::shared_ptr<SwsContext>>                                                                                                      sws_contexts_;\r
123 public: \r
124         boost::shared_future<boost::iterator_range<const uint8_t*>> operator()(std::vector<layer> layers, const core::video_format_desc& format_desc)\r
125         {       \r
126                 if(last_image_.first == layers && last_image_.second.has_value())\r
127                         return last_image_.second;\r
128 \r
129                 auto image      = render(layers, format_desc);\r
130                 last_image_ = std::make_pair(std::move(layers), image);\r
131                 return image;\r
132         }\r
133 \r
134 private:\r
135         boost::shared_future<boost::iterator_range<const uint8_t*>> render(std::vector<layer> layers, const core::video_format_desc& format_desc)\r
136         {\r
137                 static const auto empty = spl::make_shared<const std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>>>(2048*2048*4, 0);\r
138                 CASPAR_VERIFY(empty->size() >= format_desc.size);\r
139                 \r
140                 std::vector<item> items;\r
141                 BOOST_FOREACH(auto& layer, layers)\r
142                         items.insert(items.end(), layer.items.begin(), layer.items.end());\r
143 \r
144                 if(items.empty())\r
145                 {\r
146                         return async(launch_policy::deferred, [=]\r
147                         {\r
148                                 return boost::iterator_range<const uint8_t*>(empty->data(), empty->data() + format_desc.size);\r
149                         });             \r
150                 }\r
151 \r
152                 convert(items.begin(), items.end(), format_desc);                       \r
153                 blend(items.begin(), items.end());\r
154                 \r
155                 auto buffer = items.front().buffers.at(0);\r
156                 return async(launch_policy::deferred, [=]\r
157                 {\r
158                         return boost::iterator_range<const uint8_t*>(buffer->data(), buffer->data() + format_desc.size);\r
159                 });             \r
160         }\r
161 \r
162         template<typename I>\r
163         void blend(I begin, I end)\r
164         {\r
165                 for(auto it = begin + 1; it != end; ++it)\r
166                 {\r
167                         auto size    = begin->buffers.at(0)->size();\r
168                         auto dest    = begin->buffers.at(0)->data();\r
169                         auto source2 = it->buffers.at(0)->data();\r
170                         cpu::blend(dest, dest, source2, size);\r
171                 }\r
172         }\r
173         \r
174         template<typename I>\r
175         void convert(I begin, I end, const core::video_format_desc& format_desc)\r
176         {\r
177                 tbb::parallel_for_each(begin, end, [&](item& item)\r
178                 {\r
179                         if(item.pix_desc.format == core::pixel_format::bgra)\r
180                                 return;\r
181 \r
182                         auto input_av_frame = ffmpeg::make_av_frame(item.buffers, item.pix_desc);\r
183                                                                 \r
184                         int key = ((input_av_frame->width << 22) & 0xFFC00000) | ((input_av_frame->height << 6) & 0x003FC000) | ((input_av_frame->format << 7) & 0x00007F00);\r
185                                                                         \r
186                         auto& sws_context = sws_contexts_[key];\r
187                         if(!sws_context)\r
188                         {\r
189                                 double param;\r
190                                 sws_context.reset(sws_getContext(input_av_frame->width, input_av_frame->height, static_cast<PixelFormat>(input_av_frame->format), format_desc.width, format_desc.height, PIX_FMT_BGRA, SWS_BILINEAR, nullptr, nullptr, &param), sws_freeContext);\r
191                         }\r
192                         \r
193                         if(!sws_context)                                \r
194                                 BOOST_THROW_EXCEPTION(operation_failed() << msg_info("Could not create software scaling context.") << boost::errinfo_api_function("sws_getContext"));                           \r
195                 \r
196                         auto dest = spl::make_shared<host_buffer>(format_desc.size);\r
197 \r
198                         spl::shared_ptr<AVFrame> av_frame(avcodec_alloc_frame(), av_free);      \r
199                         avcodec_get_frame_defaults(av_frame.get());                     \r
200                         avpicture_fill(reinterpret_cast<AVPicture*>(av_frame.get()), dest->data(), PIX_FMT_BGRA, format_desc.width, format_desc.height);\r
201                                 \r
202                         sws_scale(sws_context.get(), input_av_frame->data, input_av_frame->linesize, 0, input_av_frame->height, av_frame->data, av_frame->linesize);    \r
203 \r
204                         item.buffers.clear();\r
205                         item.buffers.push_back(dest);\r
206                         item.pix_desc = core::pixel_format_desc(core::pixel_format::bgra);\r
207                         item.pix_desc.planes.clear();\r
208                         item.pix_desc.planes.push_back(core::pixel_format_desc::plane(format_desc.width, format_desc.height, 4));\r
209                 });\r
210         }\r
211 };\r
212                 \r
213 struct image_mixer::impl : boost::noncopyable\r
214 {       \r
215         image_renderer                                          renderer_;\r
216         std::vector<core::frame_transform>      transform_stack_;\r
217         std::vector<layer>                                      layers_; // layer/stream/items\r
218 public:\r
219         impl() \r
220                 : transform_stack_(1)   \r
221         {\r
222                 CASPAR_LOG(info) << L"Initialized CPU Accelerated Image Mixer";\r
223         }\r
224 \r
225         void begin_layer(core::blend_mode blend_mode)\r
226         {\r
227                 layers_.push_back(layer(std::vector<item>()));\r
228         }\r
229                 \r
230         void push(core::frame_transform& transform)\r
231         {\r
232                 transform_stack_.push_back(transform_stack_.back()*transform);\r
233         }\r
234                 \r
235         void visit(core::data_frame& frame2)\r
236         {                       \r
237                 write_frame* frame = dynamic_cast<write_frame*>(&frame2);\r
238                 if(frame == nullptr)\r
239                         return;\r
240 \r
241                 if(frame->get_pixel_format_desc().format == core::pixel_format::invalid)\r
242                         return;\r
243 \r
244                 if(frame->get_buffers().empty())\r
245                         return;\r
246 \r
247                 if(transform_stack_.back().field_mode == core::field_mode::empty)\r
248                         return;\r
249 \r
250                 item item;\r
251                 item.pix_desc                   = frame->get_pixel_format_desc();\r
252                 item.buffers                    = frame->get_buffers();                         \r
253                 item.transform                  = transform_stack_.back();\r
254                 item.transform.volume   = core::frame_transform().volume; // Set volume to default since we don't care about it here.\r
255 \r
256                 layers_.back().items.push_back(item);\r
257         }\r
258 \r
259         void pop()\r
260         {\r
261                 transform_stack_.pop_back();\r
262         }\r
263 \r
264         void end_layer()\r
265         {               \r
266         }\r
267         \r
268         boost::shared_future<boost::iterator_range<const uint8_t*>> render(const core::video_format_desc& format_desc)\r
269         {\r
270                 // Remove empty layers.\r
271                 boost::range::remove_erase_if(layers_, [](const layer& layer)\r
272                 {\r
273                         return layer.items.empty();\r
274                 });\r
275 \r
276                 return renderer_(std::move(layers_), format_desc);\r
277         }\r
278         \r
279         virtual spl::shared_ptr<cpu::write_frame> create_frame(const void* tag, const core::pixel_format_desc& desc)\r
280         {\r
281                 return spl::make_shared<cpu::write_frame>(tag, desc);\r
282         }\r
283 };\r
284 \r
285 image_mixer::image_mixer() : impl_(new impl()){}\r
286 void image_mixer::push(core::frame_transform& transform){impl_->push(transform);}\r
287 void image_mixer::visit(core::data_frame& frame){impl_->visit(frame);}\r
288 void image_mixer::pop(){impl_->pop();}\r
289 boost::shared_future<boost::iterator_range<const uint8_t*>> image_mixer::operator()(const core::video_format_desc& format_desc){return impl_->render(format_desc);}\r
290 void image_mixer::begin_layer(core::blend_mode blend_mode){impl_->begin_layer(blend_mode);}\r
291 void image_mixer::end_layer(){impl_->end_layer();}\r
292 spl::shared_ptr<core::write_frame> image_mixer::create_frame(const void* tag, const core::pixel_format_desc& desc) {return impl_->create_frame(tag, desc);}\r
293 \r
294 }}}