diff --git a/src/accelerator/CMakeLists.txt b/src/accelerator/CMakeLists.txt index 3a1b576af4..2ab0cf60a7 100644 --- a/src/accelerator/CMakeLists.txt +++ b/src/accelerator/CMakeLists.txt @@ -5,6 +5,7 @@ set(SOURCES ogl/image/image_kernel.cpp ogl/image/image_mixer.cpp ogl/image/image_shader.cpp + ogl/image/frame_pool.cpp ogl/util/buffer.cpp ogl/util/device.cpp @@ -17,6 +18,7 @@ set(HEADERS ogl/image/image_kernel.h ogl/image/image_mixer.h ogl/image/image_shader.h + ogl/image/frame_pool.h ogl/util/buffer.h ogl/util/device.h diff --git a/src/accelerator/ogl/image/frame_pool.cpp b/src/accelerator/ogl/image/frame_pool.cpp new file mode 100644 index 0000000000..bdb6ca506e --- /dev/null +++ b/src/accelerator/ogl/image/frame_pool.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2011 Sveriges Television AB + * + * 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 . + * + * Author: Julian Waller, julian@superfly.tv + */ + +#include "frame_pool.h" + +#include + +namespace caspar::accelerator::ogl { + +frame_pool::frame_pool(std::shared_ptr frame_factory, const void* tag, core::pixel_format_desc desc) + : frame_factory_(std::move(frame_factory)) + , tag_(tag) + , desc_(std::move(desc)) +{ +} + +std::pair frame_pool::create_frame() +{ + // TODO - is there risk of order issues with the atomics? + + std::shared_ptr frame; + + // Find a frame which is not in use + for (const std::shared_ptr& candidate : pool_) { + if (!candidate->in_use) { + frame = candidate; + break; + } + } + + // Add a new buffer to the pool + if (!frame) { + frame = std::make_shared(); + + for (const core::pixel_format_desc::plane& plane : desc_.planes) { + frame->buffers.push_back(frame_factory_->create_buffer(plane.size)); + } + + pool_.push_back(frame); + } + + frame->in_use = true; + + // Make copies of the buffers + std::vector> buffers; + for (const caspar::array& buffer : frame->buffers) { + buffers.push_back(buffer.clone()); + } + + auto drop_hook = std::shared_ptr(nullptr, [frame](void*) { + // This means the copy of each buffer has completed, and is no longer in use + frame->in_use = false; + }); + + auto new_frame = frame_factory_->import_buffers(tag_, desc_, std::move(buffers), std::move(drop_hook)); + + return std::make_pair(std::move(new_frame), std::ref(frame->data)); +} + +void frame_pool::for_each(const std::function& fn) +{ + for (std::shared_ptr& candidate : pool_) { + fn(candidate->data); + } +} + +void frame_pool::pixel_format(core::pixel_format_desc desc) +{ + desc_ = desc; + + pool_.clear(); +} + +const core::pixel_format_desc& frame_pool::pixel_format() const { return desc_; } + +} // namespace caspar::accelerator::ogl \ No newline at end of file diff --git a/src/accelerator/ogl/image/frame_pool.h b/src/accelerator/ogl/image/frame_pool.h new file mode 100644 index 0000000000..ea1b7de548 --- /dev/null +++ b/src/accelerator/ogl/image/frame_pool.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2011 Sveriges Television AB + * + * 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 . + * + * Author: Julian Waller, julian@superfly.tv + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include "image_mixer.h" + +namespace caspar::accelerator::ogl { + +struct pooled_buffer +{ + std::vector> buffers; + std::atomic in_use; + + std::any data; +}; + +class frame_pool : public core::frame_pool +{ + public: + frame_pool(std::shared_ptr frame_factory, const void* tag, core::pixel_format_desc desc); + frame_pool(const frame_pool&) = delete; + + ~frame_pool() override = default; + + frame_pool& operator=(const frame_pool&) = delete; + + std::pair create_frame() override; + + void for_each(const std::function& fn) override; + + void pixel_format(core::pixel_format_desc desc) override; + [[nodiscard]] const core::pixel_format_desc& pixel_format() const override; + + private: + const std::shared_ptr frame_factory_; + const void* tag_; + core::pixel_format_desc desc_; + + std::vector> pool_; +}; + +} // namespace caspar::accelerator::ogl diff --git a/src/accelerator/ogl/image/image_mixer.cpp b/src/accelerator/ogl/image/image_mixer.cpp index e5add850a0..4f6f91c617 100644 --- a/src/accelerator/ogl/image/image_mixer.cpp +++ b/src/accelerator/ogl/image/image_mixer.cpp @@ -20,6 +20,7 @@ */ #include "image_mixer.h" +#include "frame_pool.h" #include "image_kernel.h" #include "../util/buffer.h" @@ -222,7 +223,7 @@ class image_renderer }; struct image_mixer::impl - : public core::frame_factory + : public frame_factory_gl , public std::enable_shared_from_this { spl::shared_ptr ogl_; @@ -281,7 +282,8 @@ struct image_mixer::impl item.textures.emplace_back(ogl_->copy_async(frame.image_data(n), item.pix_desc.planes[n].width, item.pix_desc.planes[n].height, - item.pix_desc.planes[n].stride)); + item.pix_desc.planes[n].stride, + nullptr)); } } @@ -299,20 +301,25 @@ struct image_mixer::impl return renderer_(std::move(layers_), format_desc); } - core::mutable_frame create_frame(const void* tag, const core::pixel_format_desc& desc) override + caspar::array create_buffer(int size) override { return ogl_->create_array(size); } + + core::mutable_frame import_buffers(const void* tag, + const core::pixel_format_desc& desc, + std::vector> buffers, + std::shared_ptr drop_hook) override { - std::vector> image_data; - for (auto& plane : desc.planes) { - image_data.push_back(ogl_->create_array(plane.size)); + if (desc.planes.size() != buffers.size()) { + CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(L"Mismatch in number of buffers and planes")); } std::weak_ptr weak_self = shared_from_this(); return core::mutable_frame( tag, - std::move(image_data), + std::move(buffers), array{}, desc, - [weak_self, desc](std::vector> image_data) -> std::any { + [weak_self, desc, drop_hook = std::move(drop_hook)]( + std::vector> image_data) -> std::any { auto self = weak_self.lock(); if (!self) { return std::any{}; @@ -320,11 +327,26 @@ struct image_mixer::impl std::vector textures; for (int n = 0; n < static_cast(desc.planes.size()); ++n) { textures.emplace_back(self->ogl_->copy_async( - image_data[n], desc.planes[n].width, desc.planes[n].height, desc.planes[n].stride)); + image_data[n], desc.planes[n].width, desc.planes[n].height, desc.planes[n].stride, drop_hook)); } return std::make_shared(std::move(textures)); }); } + + core::mutable_frame create_frame(const void* tag, const core::pixel_format_desc& desc) override + { + std::vector> image_data; + for (auto& plane : desc.planes) { + image_data.push_back(ogl_->create_array(plane.size)); + } + + return import_buffers(tag, desc, std::move(image_data), nullptr); + } + + std::unique_ptr create_frame_pool(const void* tag, const core::pixel_format_desc& desc) override + { + return std::make_unique(shared_from_this(), tag, desc); + } }; image_mixer::image_mixer(const spl::shared_ptr& ogl, const int channel_id, const size_t max_frame_size) @@ -343,5 +365,9 @@ core::mutable_frame image_mixer::create_frame(const void* tag, const core::pixel { return impl_->create_frame(tag, desc); } +std::unique_ptr image_mixer::create_frame_pool(const void* tag, const core::pixel_format_desc& desc) +{ + return impl_->create_frame_pool(tag, desc); +} }}} // namespace caspar::accelerator::ogl diff --git a/src/accelerator/ogl/image/image_mixer.h b/src/accelerator/ogl/image/image_mixer.h index c9034238d8..333b19fa63 100644 --- a/src/accelerator/ogl/image/image_mixer.h +++ b/src/accelerator/ogl/image/image_mixer.h @@ -33,6 +33,12 @@ namespace caspar { namespace accelerator { namespace ogl { +class frame_factory_gl: public core::frame_factory { + public: + virtual caspar::array create_buffer(int size) = 0; + virtual core::mutable_frame import_buffers(const void* tag, const core::pixel_format_desc& desc, std::vector> buffers, std::shared_ptr drop_hook) = 0; +}; + class image_mixer final : public core::image_mixer { public: @@ -46,6 +52,8 @@ class image_mixer final : public core::image_mixer std::future> operator()(const core::video_format_desc& format_desc) override; core::mutable_frame create_frame(const void* tag, const core::pixel_format_desc& desc) override; + std::unique_ptr create_frame_pool(const void* tag, const core::pixel_format_desc& desc) override; + // core::image_mixer void push(const core::frame_transform& frame) override; diff --git a/src/accelerator/ogl/util/device.cpp b/src/accelerator/ogl/util/device.cpp index 0576153f7f..b21b984f76 100644 --- a/src/accelerator/ogl/util/device.cpp +++ b/src/accelerator/ogl/util/device.cpp @@ -222,9 +222,9 @@ struct device::impl : public std::enable_shared_from_this } std::future> - copy_async(const array& source, int width, int height, int stride) + copy_async(const array& source, int width, int height, int stride, std::shared_ptr drop_hook) { - return dispatch_async([=] { + return dispatch_async([=, drop_hook = std::move(drop_hook)] { std::shared_ptr buf; auto tmp = source.storage>(); @@ -427,11 +427,14 @@ std::shared_ptr device::create_texture(int width, int height, int strid { return impl_->create_texture(width, height, stride, true); } -array device::create_array(int size) { return impl_->create_array(size); } -std::future> -device::copy_async(const array& source, int width, int height, int stride) +array device::create_array(int size) { return impl_->create_array(size); } +std::future> device::copy_async(const array& source, + int width, + int height, + int stride, + std::shared_ptr drop_hook) { - return impl_->copy_async(source, width, height, stride); + return impl_->copy_async(source, width, height, stride, drop_hook); } std::future> device::copy_async(const std::shared_ptr& source) { diff --git a/src/accelerator/ogl/util/device.h b/src/accelerator/ogl/util/device.h index d7f1cef1a4..29e53240bf 100644 --- a/src/accelerator/ogl/util/device.h +++ b/src/accelerator/ogl/util/device.h @@ -49,7 +49,7 @@ class device final array create_array(int size); std::future> - copy_async(const array& source, int width, int height, int stride); + copy_async(const array& source, int width, int height, int stride, std::shared_ptr drop_hook); std::future> copy_async(const std::shared_ptr& source); template auto dispatch_async(Func&& func) diff --git a/src/common/array.h b/src/common/array.h index 9dd676e46b..2fc1a1d297 100644 --- a/src/common/array.h +++ b/src/common/array.h @@ -70,6 +70,15 @@ class array final return *this; } + // Explicitly make a copy of the array, with shared backing storage + array clone() const { + caspar::array cloned; + cloned.ptr_ = ptr_; + cloned.size_ = size_; + cloned.storage_ = storage_; + return cloned; + } + T* begin() const { return ptr_; } T* data() const { return ptr_; } T* end() const { return ptr_ + size_; } diff --git a/src/core/frame/frame_factory.h b/src/core/frame/frame_factory.h index 965b7e9b40..f53cbaf040 100644 --- a/src/core/frame/frame_factory.h +++ b/src/core/frame/frame_factory.h @@ -21,18 +21,40 @@ #pragma once +#include + namespace caspar { namespace core { +class frame_pool +{ + public: + frame_pool() = default; + frame_pool& operator=(const frame_pool&) = delete; + virtual ~frame_pool() = default; + + frame_pool(const frame_pool&) = delete; + + virtual std::pair create_frame() = 0; + + virtual void for_each(const std::function& fn) = 0; + + virtual void pixel_format(core::pixel_format_desc desc) = 0; + [[nodiscard]] virtual const core::pixel_format_desc& pixel_format() const = 0; +}; + class frame_factory { public: - frame_factory() = default; + frame_factory() = default; frame_factory& operator=(const frame_factory&) = delete; virtual ~frame_factory() = default; frame_factory(const frame_factory&) = delete; virtual class mutable_frame create_frame(const void* video_stream_tag, const struct pixel_format_desc& desc) = 0; + + virtual std::unique_ptr create_frame_pool(const void* video_stream_tag, + const struct pixel_format_desc& desc) = 0; }; }} // namespace caspar::core diff --git a/src/core/mixer/image/image_mixer.h b/src/core/mixer/image/image_mixer.h index 0922e38261..e8ac48abeb 100644 --- a/src/core/mixer/image/image_mixer.h +++ b/src/core/mixer/image/image_mixer.h @@ -48,6 +48,8 @@ class image_mixer virtual std::future> operator()(const struct video_format_desc& format_desc) = 0; class mutable_frame create_frame(const void* tag, const struct pixel_format_desc& desc) override = 0; + + std::unique_ptr create_frame_pool(const void* video_stream_tag, const struct pixel_format_desc& desc) override = 0; }; }} // namespace caspar::core diff --git a/src/modules/html/CMakeLists.txt b/src/modules/html/CMakeLists.txt index c155dc6c0b..a831da0390 100644 --- a/src/modules/html/CMakeLists.txt +++ b/src/modules/html/CMakeLists.txt @@ -4,12 +4,14 @@ project (html) set(SOURCES producer/html_cg_proxy.cpp producer/html_producer.cpp + producer/rectangles.cpp html.cpp ) set(HEADERS producer/html_cg_proxy.h producer/html_producer.h + producer/rectangles.h html.h ) diff --git a/src/modules/html/producer/html_producer.cpp b/src/modules/html/producer/html_producer.cpp index 2ad42d07be..3790c257ad 100644 --- a/src/modules/html/producer/html_producer.cpp +++ b/src/modules/html/producer/html_producer.cpp @@ -56,11 +56,14 @@ #include #pragma warning(pop) +#include #include #include #include +#include #include "../html.h" +#include "rectangles.h" namespace caspar { namespace html { @@ -83,6 +86,7 @@ class html_client spl::shared_ptr frame_factory_; core::video_format_desc format_desc_; bool gpu_enabled_; + const bool use_dirty_regions_; tbb::concurrent_queue javascript_before_load_; std::atomic loaded_; std::queue> frames_; @@ -90,6 +94,8 @@ class html_client const size_t frames_max_size_ = 4; std::atomic closing_; + std::unique_ptr frame_pool_; + core::draw_frame last_frame_; std::int_least64_t last_frame_time_; @@ -100,12 +106,14 @@ class html_client const spl::shared_ptr& graph, core::video_format_desc format_desc, bool gpu_enabled, + bool use_dirty_regions, std::wstring url) : url_(std::move(url)) , graph_(graph) , frame_factory_(std::move(frame_factory)) , format_desc_(std::move(format_desc)) , gpu_enabled_(gpu_enabled) + , use_dirty_regions_(use_dirty_regions) { graph_->set_color("browser-tick-time", diagnostics::color(0.1f, 1.0f, 0.1f)); graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f)); @@ -116,6 +124,10 @@ class html_client graph_->set_text(print()); diagnostics::register_graph(graph_); + core::pixel_format_desc pixel_desc(core::pixel_format::bgra); + pixel_desc.planes.emplace_back(format_desc_.square_width, format_desc_.square_height, 4); + frame_pool_ = frame_factory_->create_frame_pool(this, pixel_desc); + { std::lock_guard lock(state_mutex_); state_["file/path"] = u8(url_); @@ -259,6 +271,22 @@ class html_client rect = CefRect(0, 0, format_desc_.square_width, format_desc_.square_height); } + inline void copy_whole_frame(char* src, char* dst, int width, int height) + { +#ifdef WIN32 + if (gpu_enabled_) { + int chunksize = height * width; + tbb::parallel_for(0, 4, [&](int y) { std::memcpy(dst + y * chunksize, src + y * chunksize, chunksize); }); + } else { + std::memcpy(dst, src, width * height * 4); + } +#else + // In my linux tests, doing a single memcpy doesn't have the same cost as windows, + // making using tbb excessive + std::memcpy(dst, src, width * height * 4); +#endif + } + void OnPaint(CefRefPtr browser, PaintElementType type, const RectList& dirtyRects, @@ -276,34 +304,76 @@ class html_client if (type != PET_VIEW) return; - core::pixel_format_desc pixel_desc(core::pixel_format::bgra); - pixel_desc.planes.emplace_back(width, height, 4); + if (use_dirty_regions_) { + // Update the dirty rectangles for each frame in the pool + frame_pool_->for_each([&](std::any& data) { + if (!data.has_value()) { + std::vector rects; + for (const auto& rect : dirtyRects) { + rects.emplace_back(rect); + } + data.emplace>(std::move(rects)); + } else { + auto& rects = std::any_cast&>(data); + for (const auto& rect : dirtyRects) { + rects.emplace_back(rect); + } + merge_rectangles(rects, width, height); + } + }); + } + + // Ensure the pool is using the correct format + auto pool_pixel_format = frame_pool_->pixel_format(); + if (pool_pixel_format.planes.empty() || pool_pixel_format.planes[0].height != height || + pool_pixel_format.planes[0].width != width) { + core::pixel_format_desc pixel_desc(core::pixel_format::bgra); + pixel_desc.planes.emplace_back(width, height, 4); + frame_pool_->pixel_format(pixel_desc); + } - core::mutable_frame frame = frame_factory_->create_frame(this, pixel_desc); - char* src = (char*)buffer; - char* dst = reinterpret_cast(frame.image_data(0).begin()); + std::pair frame = frame_pool_->create_frame(); + char* src = (char*)buffer; + char* dst = reinterpret_cast(frame.first.image_data(0).begin()); test_timer_.restart(); -#ifdef WIN32 - if (gpu_enabled_) { - int chunksize = height * width; - tbb::parallel_for(0, 4, [&](int y) { std::memcpy(dst + y * chunksize, src + y * chunksize, chunksize); }); + if (use_dirty_regions_ && frame.second.has_value()) { + // The frame has dirty rectangles, so selectively copy those portions + auto& rects = std::any_cast&>(frame.second); + + if (rects.empty() || (rects.size() == 1 && is_full_frame(rects[0], width, height))) { + copy_whole_frame(src, dst, width, height); + } else { + int linesize = width * 4; + tbb::parallel_for(static_cast(0), rects.size(), [&](std::size_t index) { + const Rectangle& rect = rects[index]; + int rowcount = rect.r_y - rect.l_y; + if (rect.r_x <= rect.l_x || rowcount <= 0) + return; // Sanity check + + int start_offset = rect.l_y * linesize + rect.l_x * 4; + int chunksize = (rect.r_x - rect.l_x) * 4; + + // TODO - parallel? + for (int y = 0; y < rowcount; ++y) { + std::memcpy(dst + start_offset + y * linesize, src + start_offset + y * linesize, chunksize); + } + }); + } + + rects.clear(); } else { - std::memcpy(dst, src, width * height * 4); + // Either a new frame, or dirty rectangles are disabled + copy_whole_frame(src, dst, width, height); } -#else - // On my one test linux machine, doing a single memcpy doesn't have the same cost as windows, - // making using tbb excessive - std::memcpy(dst, src, width * height * 4); -#endif graph_->set_value("memcpy", test_timer_.elapsed() * format_desc_.fps * 0.5 * 5); { std::lock_guard lock(frames_mutex_); - frames_.push(std::make_pair(now(), core::draw_frame(std::move(frame)))); - while (frames_.size() > 4) { + frames_.push(std::make_pair(now(), core::draw_frame(std::move(frame.first)))); + while (frames_.size() > frames_max_size_) { frames_.pop(); graph_->set_tag(diagnostics::tag_severity::WARNING, "dropped-frame"); } @@ -441,9 +511,10 @@ class html_producer : public core::frame_producer , url_(url) { html::invoke([&] { - const bool enable_gpu = env::properties().get(L"configuration.html.enable-gpu", false); + const bool enable_gpu = env::properties().get(L"configuration.html.enable-gpu", false); + const bool use_dirty_regions = env::properties().get(L"configuration.html.use-dirty-regions", false); - client_ = new html_client(frame_factory, graph_, format_desc, enable_gpu, url_); + client_ = new html_client(frame_factory, graph_, format_desc, enable_gpu, use_dirty_regions, url_); CefWindowInfo window_info; window_info.bounds.width = format_desc.square_width; diff --git a/src/modules/html/producer/rectangles.cpp b/src/modules/html/producer/rectangles.cpp new file mode 100644 index 0000000000..e5536a22d4 --- /dev/null +++ b/src/modules/html/producer/rectangles.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2011 Sveriges Television AB + * + * 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 . + * + * Author: Julian Waller, julian@superfly.tv + */ + +#define NOMINMAX + +#include "rectangles.h" + +#include + +namespace caspar::html { + +bool is_full_frame(const Rectangle& rect, int width, int height) +{ + // Use some tolerance, so that when we get close to being the full frame, we copy everything + int tolerance_n = 9; + int tolerance_d = 10; + + int target_pixel_count = (width * height * tolerance_n) / tolerance_d; + int rect_pixel_count = (rect.r_x - rect.l_x) * (rect.r_y - rect.l_y); + + return rect_pixel_count >= target_pixel_count; +} + +bool are_any_full_frame(const std::vector& rects, int width, int height) +{ + return std::any_of( + rects.begin(), rects.end(), [&](const Rectangle& rect) { return is_full_frame(rect, width, height); }); +} + +bool should_merge_rectanges(const Rectangle& a, const Rectangle& b) +{ + int x_tolerance = 100; // Use some collision tolerance, so that we don't do too many memcpy per line + + // If one rectangle is on left side of other + if (a.l_x > b.r_x + x_tolerance || b.l_x > a.r_x + x_tolerance) + return false; + + // If one rectangle is above other + if (a.r_y < b.l_y || b.r_y < a.l_y) + return false; + + return true; +} + +bool is_invalid(const Rectangle& rect) { return rect.invalid; } + +struct +{ + bool operator()(const Rectangle& a, const Rectangle& b) const + { + if (a.l_y == b.l_y) { + // Secondary sort by x + return a.l_x < b.l_x; + } else { + return a.l_y < b.l_y; + } + } +} compare_slices; + +bool merge_rectangles(std::vector& rects, int width, int height) +{ + // If there is 0 or 1 rectangles, there is nothing to merge + if (rects.size() <= 1) + return false; + + // If any are full-frame, then that is the only rectangle of interest + bool full_frame = are_any_full_frame(rects, width, height); + if (full_frame) { + rects.clear(); + rects.emplace_back(0, 0, width, height); + return true; + } + + std::sort(rects.begin(), rects.end(), compare_slices); + + bool has_changes = false; + + // Iterate through rectangles, merging any that overlap. + for (auto first = rects.begin(); first != rects.end(); ++first) { + // Skip over any slices that have been merged. + if (first->invalid) + continue; + + // Iterate through all following slices + for (auto second = first + 1; second != rects.end(); ++second) { + // Skip over any slices that have been merged. + if (second->invalid) + continue; + + if (should_merge_rectanges(*first, *second)) { + first->l_x = std::min(first->l_x, second->l_x); + first->l_y = std::min(first->l_y, second->l_y); + first->r_x = std::max(first->r_x, second->r_x); + first->r_y = std::max(first->r_y, second->r_y); + + // Mark the second slice as having been merged. + second->invalid = true; + has_changes = true; + } + } + } + + if (!has_changes) + return false; + + // Snip out any rectangles that are invalid + rects.erase(remove_if(rects.begin(), rects.end(), is_invalid), rects.end()); + + return true; +} + +} // namespace caspar::html diff --git a/src/modules/html/producer/rectangles.h b/src/modules/html/producer/rectangles.h new file mode 100644 index 0000000000..9d9a6d88ef --- /dev/null +++ b/src/modules/html/producer/rectangles.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2011 Sveriges Television AB + * + * 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 . + * + * Author: Julian Waller, julian@superfly.tv + */ + +#pragma once + +#include + +#pragma warning(push) +#pragma warning(disable : 4458) +#include +#include +#pragma warning(pop) + +namespace caspar::html { + +struct Rectangle +{ + Rectangle(int l_x, int l_y, int r_x, int r_y) + : l_x(l_x) + , l_y(l_y) + , r_x(r_x) + , r_y(r_y) + { + } + + Rectangle(const CefRect& rect) + : l_x(rect.x) + , l_y(rect.y) + , r_x(rect.x + rect.width) + , r_y(rect.y + rect.height) + { + } + + int l_x; + int l_y; + int r_x; + int r_y; + + bool invalid = false; +}; + +bool is_full_frame(const Rectangle& rect, int width, int height); + +bool merge_rectangles(std::vector& rects, int width, int height); + +} // namespace caspar::html \ No newline at end of file diff --git a/src/shell/casparcg.config b/src/shell/casparcg.config index 3eec0e9d8e..2fcf71b0d1 100644 --- a/src/shell/casparcg.config +++ b/src/shell/casparcg.config @@ -56,6 +56,7 @@ 0 [0|1024-65535] false [true|false] gl [|gl|d3d11|d3d9] + false [true|false] @@ -197,4 +198,4 @@ ---> +--> \ No newline at end of file