From 1f0b96cc522d9567519a7a18985a6b70cd701c8c Mon Sep 17 00:00:00 2001 From: ronag Date: Fri, 5 Nov 2010 15:29:39 +0000 Subject: [PATCH] 2.0.0.2: - Refactoring. - Started creating some test for transition_producer. git-svn-id: https://casparcg.svn.sourceforge.net/svnroot/casparcg/server/branches/2.0.0.2@230 362d55ac-95cf-4e76-9f9a-cbaa9c17b72d --- common/common.vcxproj | 10 ++ common/common.vcxproj.filters | 12 ++ common/gl/frame_buffer_object.cpp | 54 +++++++ common/gl/frame_buffer_object.h | 21 +++ common/gl/pixel_buffer_object.cpp | 140 ++++++++++++++++++ common/gl/pixel_buffer_object.h | 43 ++++++ common/utility/memory.cpp | 5 +- common/utility/memory.h | 2 +- .../decklink/DecklinkVideoConsumer.cpp | 2 +- core/consumer/ogl/ogl_consumer.cpp | 98 ++++++------ core/frame/gpu_composite_frame.cpp | 27 ++-- core/frame/gpu_composite_frame.h | 8 +- core/frame/gpu_frame.cpp | 115 ++++---------- core/frame/gpu_frame.h | 28 ++-- core/frame/gpu_frame_processor.cpp | 64 ++++---- core/producer/color/color_producer.cpp | 6 +- core/producer/flash/flash_producer.cpp | 2 +- core/producer/image/image_producer.cpp | 2 +- core/producer/image/image_scroll_producer.cpp | 2 +- .../transition/transition_producer.cpp | 35 +++-- shell/caspar.config | 2 +- test/mock/mock_frame.h | 12 ++ test/mock/mock_frame_factory.h | 12 ++ test/mock/mock_frame_producer.h | 14 +- .../transition/transition_producer_test.cpp | 90 +++++++++++ test/test.vcxproj | 3 + test/test.vcxproj.filters | 15 ++ 27 files changed, 585 insertions(+), 239 deletions(-) create mode 100644 common/gl/frame_buffer_object.cpp create mode 100644 common/gl/frame_buffer_object.h create mode 100644 common/gl/pixel_buffer_object.cpp create mode 100644 common/gl/pixel_buffer_object.h create mode 100644 test/mock/mock_frame.h create mode 100644 test/mock/mock_frame_factory.h create mode 100644 test/producer/transition/transition_producer_test.cpp diff --git a/common/common.vcxproj b/common/common.vcxproj index 8d1421d3a..347e08e8d 100644 --- a/common/common.vcxproj +++ b/common/common.vcxproj @@ -97,7 +97,9 @@ + + @@ -118,6 +120,14 @@ ../StdAfx.h ../StdAfx.h + + ../StdAfx.h + ../StdAfx.h + + + ../StdAfx.h + ../StdAfx.h + ../StdAfx.h ../StdAfx.h diff --git a/common/common.vcxproj.filters b/common/common.vcxproj.filters index 4c4210e22..ea6f5f56d 100644 --- a/common/common.vcxproj.filters +++ b/common/common.vcxproj.filters @@ -49,6 +49,12 @@ Source\utility + + Source\gl + + + Source\gl + @@ -102,5 +108,11 @@ Source\gl + + Source\gl + + + Source\gl + \ No newline at end of file diff --git a/common/gl/frame_buffer_object.cpp b/common/gl/frame_buffer_object.cpp new file mode 100644 index 000000000..403d6bfe5 --- /dev/null +++ b/common/gl/frame_buffer_object.cpp @@ -0,0 +1,54 @@ +#include "../StdAfx.h" + +#include "frame_buffer_object.h" + +#include "../../common/gl/gl_check.h" + +#include + +#include + +namespace caspar { namespace common { namespace gl { + +struct frame_buffer_object::implementation +{ +public: + implementation(size_t width, size_t height, GLenum mode) : mode_(mode) + { + GL(glGenTextures(1, &texture_)); + GL(glBindTexture(GL_TEXTURE_2D, texture_)); + GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, + GL_UNSIGNED_BYTE, NULL)); + GL(glGenFramebuffersEXT(1, &fbo_)); + GL(glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_)); + GL(glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, mode_, GL_TEXTURE_2D, + texture_, 0)); + } + + ~implementation() + { + glDeleteFramebuffersEXT(1, &fbo_); + glDeleteTextures(1, &texture_); + } + + void bind_pixel_source() + { + GL(glReadBuffer(mode_)); + } + + GLuint texture_; + GLuint fbo_; + GLenum mode_; + size_t width_; + size_t height_; +}; + +frame_buffer_object::frame_buffer_object(){} +frame_buffer_object::frame_buffer_object(size_t width, size_t height, GLenum mode) + : impl_(new implementation(width, height, mode)){} +void frame_buffer_object::create(size_t width, size_t height, GLenum mode) +{ + impl_.reset(new implementation(width, height, mode)); +} +void frame_buffer_object::bind_pixel_source() {impl_->bind_pixel_source();} +}}} \ No newline at end of file diff --git a/common/gl/frame_buffer_object.h b/common/gl/frame_buffer_object.h new file mode 100644 index 000000000..8859927ca --- /dev/null +++ b/common/gl/frame_buffer_object.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include + +namespace caspar { namespace common { namespace gl { + +class frame_buffer_object +{ +public: + frame_buffer_object(); + frame_buffer_object(size_t width, size_t height, GLenum mode = GL_COLOR_ATTACHMENT0_EXT); + void create(size_t width, size_t height, GLenum mode = GL_COLOR_ATTACHMENT0_EXT); + void bind_pixel_source(); +private: + struct implementation; + std::shared_ptr impl_; +}; + +}}} \ No newline at end of file diff --git a/common/gl/pixel_buffer_object.cpp b/common/gl/pixel_buffer_object.cpp new file mode 100644 index 000000000..e3e6b7aa3 --- /dev/null +++ b/common/gl/pixel_buffer_object.cpp @@ -0,0 +1,140 @@ +#include "../StdAfx.h" + +#include "pixel_buffer_object.h" + +#include "../../common/exception/exceptions.h" +#include "../../common/gl/gl_check.h" +#include "../../common/utility/memory.h" + +namespace caspar { namespace common { namespace gl { + +struct pixel_buffer_object::implementation : boost::noncopyable +{ + implementation(size_t width, size_t height) + : width_(width), height_(height), size_(width*height*4), pbo_(0), + texture_(0), writing_(false), reading_(false), mapped_(false){} + + ~implementation() + { + if(pbo_ != 0) + glDeleteBuffers(1, &pbo_); + } + + void bind_pbo(GLenum mode) + { + if(pbo_ == 0) + GL(glGenBuffers(1, &pbo_)); + GL(glBindBuffer(mode, pbo_)); + } + + void unbind_pbo(GLenum mode) + { + GL(glBindBuffer(mode, 0)); + } + + void bind_texture() + { + if(texture_ == 0) + { + GL(glGenTextures(1, &texture_)); + + GL(glBindTexture(GL_TEXTURE_2D, texture_)); + + GL(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + GL(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + GL(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); + GL(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); + + GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width_, height_, 0, GL_BGRA, + GL_UNSIGNED_BYTE, NULL)); + } + GL(glBindTexture(GL_TEXTURE_2D, texture_)); + } + + void begin_write() + { + bind_pbo(GL_PIXEL_UNPACK_BUFFER); + if(mapped_) + GL(glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER)); + mapped_ = false; + bind_texture(); + GL(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, GL_BGRA, + GL_UNSIGNED_BYTE, NULL)); + unbind_pbo(GL_PIXEL_UNPACK_BUFFER); + writing_ = true; + } + + void* end_write() + { + if(mapped_) + BOOST_THROW_EXCEPTION(invalid_operation()); + + bind_pbo(GL_PIXEL_UNPACK_BUFFER); + GL(glBufferData(GL_PIXEL_UNPACK_BUFFER, size_, NULL, GL_STREAM_DRAW)); + auto data = static_cast(glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY)); + unbind_pbo(GL_PIXEL_UNPACK_BUFFER); + if(!data) + BOOST_THROW_EXCEPTION(invalid_operation() + << msg_info("glMapBuffer failed")); + writing_ = false; + mapped_ = true; + return data; + } + + void begin_read() + { + bind_pbo(GL_PIXEL_PACK_BUFFER); + if(mapped_) + GL(glUnmapBuffer(GL_PIXEL_PACK_BUFFER)); + mapped_ = false; + GL(glBufferData(GL_PIXEL_PACK_BUFFER, size_, NULL, GL_STREAM_READ)); + GL(glReadPixels(0, 0, width_, height_, GL_BGRA, GL_UNSIGNED_BYTE, NULL)); + unbind_pbo(GL_PIXEL_PACK_BUFFER); + reading_ = true; + } + + void* end_read() + { + if(mapped_) + BOOST_THROW_EXCEPTION(invalid_operation()); + + bind_pbo(GL_PIXEL_PACK_BUFFER); + auto data = static_cast(glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY)); + unbind_pbo(GL_PIXEL_PACK_BUFFER); + if(!data) + BOOST_THROW_EXCEPTION(std::bad_alloc()); + reading_ = false; + mapped_ = true; + return data; + } + + + GLuint pbo_; + GLuint texture_; + size_t width_; + size_t height_; + size_t size_; + + bool mapped_; + bool writing_; + bool reading_; +}; + +pixel_buffer_object::pixel_buffer_object(){} +pixel_buffer_object::pixel_buffer_object(size_t width, size_t height) + : impl_(new implementation(width, height)){} +void pixel_buffer_object::create(size_t width, size_t height) +{ + impl_.reset(new implementation(width, height)); +} +void pixel_buffer_object::begin_write() { impl_->begin_write();} +void* pixel_buffer_object::end_write() {return impl_->end_write();} +void pixel_buffer_object::begin_read() { impl_->begin_read();} +void* pixel_buffer_object::end_read(){return impl_->end_read();} +void pixel_buffer_object::bind_texture() {impl_->bind_texture();} +size_t pixel_buffer_object::width() const {return impl_->width_;} +size_t pixel_buffer_object::heigth() const {return impl_->height_;} +size_t pixel_buffer_object::size() const {return impl_->size_;} +bool pixel_buffer_object::is_reading() const { return impl_->reading_;} +bool pixel_buffer_object::is_writing() const { return impl_->writing_;} +}}} \ No newline at end of file diff --git a/common/gl/pixel_buffer_object.h b/common/gl/pixel_buffer_object.h new file mode 100644 index 000000000..858c24456 --- /dev/null +++ b/common/gl/pixel_buffer_object.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include + +#include + +#include +#include + +namespace caspar { namespace common { namespace gl { + +class pixel_buffer_object : boost::noncopyable +{ +public: + pixel_buffer_object(); + pixel_buffer_object(size_t width, size_t height); + void create(size_t width, size_t height); + ~pixel_buffer_object(){} + + void begin_write(); + void* end_write(); + + void begin_read(); + void* end_read(); + + void bind_texture(); + + size_t width() const; + size_t heigth() const; + size_t size() const; + + bool is_reading() const; + bool is_writing() const; + +private: + struct implementation; + std::shared_ptr impl_; +}; +typedef std::shared_ptr pixel_buffer_object_ptr; + +}}} \ No newline at end of file diff --git a/common/utility/memory.cpp b/common/utility/memory.cpp index 0be280d31..7f6d38391 100644 --- a/common/utility/memory.cpp +++ b/common/utility/memory.cpp @@ -53,8 +53,11 @@ void* memcpy_SSE2(void* dest, const void* source, size_t num) return dest; } -void* copy(void* dest, const void* source, size_t num) +void* aligned_memcpy(void* dest, const void* source, size_t num) { + if(num < 128) + return memcpy(dest, source, num); + tbb::parallel_for(tbb::blocked_range(0, num/128), [&](const tbb::blocked_range& r) { memcpy_SSE2(reinterpret_cast(dest) + r.begin()*128, reinterpret_cast(source) + r.begin()*128, r.size()*128); diff --git a/common/utility/memory.h b/common/utility/memory.h index 6c9b7671f..56ff5a17a 100644 --- a/common/utility/memory.h +++ b/common/utility/memory.h @@ -2,7 +2,7 @@ namespace caspar { namespace common { -void* copy(void* dest, const void* source, size_t size); +void* aligned_memcpy(void* dest, const void* source, size_t size); void* clear(void* dest, size_t size); }} \ No newline at end of file diff --git a/core/consumer/decklink/DecklinkVideoConsumer.cpp b/core/consumer/decklink/DecklinkVideoConsumer.cpp index bdff0d80e..36de5f8e0 100644 --- a/core/consumer/decklink/DecklinkVideoConsumer.cpp +++ b/core/consumer/decklink/DecklinkVideoConsumer.cpp @@ -129,7 +129,7 @@ struct DecklinkVideoConsumer::Implementation : public IDeckLinkVideoOutputCallba std::shared_ptr pTempFrame = GetReservedFrame(); if(pTempFrame && frame->size() == pTempFrame->size()) { - common::copy(pTempFrame->data(), frame->data(), pTempFrame->size()); + common::aligned_memcpy(pTempFrame->data(), frame->data(), pTempFrame->size()); DoRender(pTempFrame); } else diff --git a/core/consumer/ogl/ogl_consumer.cpp b/core/consumer/ogl/ogl_consumer.cpp index 968d25fc7..f6396ee93 100644 --- a/core/consumer/ogl/ogl_consumer.cpp +++ b/core/consumer/ogl/ogl_consumer.cpp @@ -30,12 +30,12 @@ #include "../../frame/gpu_frame.h" #include "../../../common/utility/memory.h" #include "../../../common/gl/gl_check.h" +#include "../../../common/gl/pixel_buffer_object.h" #include #include #include -#include #include @@ -44,10 +44,8 @@ namespace caspar { namespace core { namespace ogl{ struct consumer::implementation : boost::noncopyable { implementation(const frame_format_desc& format_desc, unsigned int screen_index, stretch stretch, bool windowed) - : format_desc_(format_desc), stretch_(stretch), pbo_index_(0), screen_width_(0), screen_height_(0), windowed_(windowed) - { - pbos_[0] = pbos_[1] = 0; - + : index_(0), format_desc_(format_desc), stretch_(stretch), screen_width_(0), screen_height_(0), windowed_(windowed) + { #ifdef _WIN32 DISPLAY_DEVICE dDevice; memset(&dDevice,0,sizeof(dDevice)); @@ -94,9 +92,6 @@ struct consumer::implementation : boost::noncopyable { frame_buffer_.push(nullptr), thread_.join(); - - if(pbos_[0] && pbos_[1]) - glDeleteBuffers(2, pbos_); } void init() @@ -106,7 +101,15 @@ struct consumer::implementation : boost::noncopyable window_.SetPosition(screenX_, screenY_); window_.SetSize(screen_width_, screen_height_); window_.SetActive(); - + GL(glEnable(GL_TEXTURE_2D)); + GL(glDisable(GL_DEPTH_TEST)); + GL(glClearColor(0.0, 0.0, 0.0, 0.0)); + GL(glViewport(0, 0, format_desc_.width, format_desc_.height)); + glLoadIdentity(); + + wratio_ = static_cast(format_desc_.width)/static_cast(format_desc_.width); + hratio_ = static_cast(format_desc_.height)/static_cast(format_desc_.height); + std::pair target_ratio = None(); if(stretch_ == ogl::fill) target_ratio = Fill(); @@ -115,19 +118,11 @@ struct consumer::implementation : boost::noncopyable else if(stretch_ == ogl::uniform_to_fill) target_ratio = UniformToFill(); - float wSize = target_ratio.first; - float hSize = target_ratio.second; + wSize_ = target_ratio.first; + hSize_ = target_ratio.second; - image_.Create(format_desc_.width, format_desc_.height); - sprite_.SetImage(image_); - - GL(glGenBuffersARB(2, pbos_)); - GL(glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbos_[0])); - GL(glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB, format_desc_.size, 0, GL_STREAM_DRAW)); - GL(glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pbos_[1])); - GL(glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB, format_desc_.size, 0, GL_STREAM_DRAW)); - - pbo_index_ = 0; + pbos_[0].create(format_desc_.width, format_desc_.height); + pbos_[1].create(format_desc_.width, format_desc_.height); } std::pair None() @@ -165,31 +160,23 @@ struct consumer::implementation : boost::noncopyable } void render(const gpu_frame_ptr& frame) - { - // Render - window_.Clear(); - - image_.Bind(); - GL(glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbos_[pbo_index_])); - GL(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, format_desc_.width, format_desc_.height, GL_BGRA, GL_UNSIGNED_BYTE, 0)); - - window_.Draw(sprite_); - - // Update - int nextPboIndex = pbo_index_ ^ 1; - - GL(glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbos_[nextPboIndex])); - GL(glBufferData(GL_PIXEL_UNPACK_BUFFER, format_desc_.size, NULL, GL_STREAM_DRAW)); - GLubyte* ptr = static_cast(glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY)); - - if(ptr != NULL) - { - common::copy(ptr, frame->data(), frame->size()); - GL(glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER)); - } - - // Swap - pbo_index_ = nextPboIndex; + { + index_ = (index_ + 1) % 2; + int next_index = (index_ + 1) % 2; + + auto ptr = pbos_[index_].end_write(); + common::aligned_memcpy(ptr, frame->data(), frame->size()); + + GL(glClear(GL_COLOR_BUFFER_BIT)); + pbos_[next_index].bind_texture(); + glBegin(GL_QUADS); + glTexCoord2f(0.0f, hratio_); glVertex2f(-wSize_, -hSize_); + glTexCoord2f(wratio_, hratio_); glVertex2f( wSize_, -hSize_); + glTexCoord2f(wratio_, 0.0f); glVertex2f( wSize_, hSize_); + glTexCoord2f(0.0f, 0.0f); glVertex2f(-wSize_, hSize_); + glEnd(); + + pbos_[next_index].begin_write(); } void display(const gpu_frame_ptr& frame) @@ -228,10 +215,16 @@ struct consumer::implementation : boost::noncopyable } } while(frame != nullptr); - } - + } - GLuint dlist_; + float wratio_; + float hratio_; + + float wSize_; + float hSize_; + + int index_; + common::gl::pixel_buffer_object pbos_[2]; bool windowed_; unsigned int screen_width_; @@ -239,9 +232,6 @@ struct consumer::implementation : boost::noncopyable unsigned int screenX_; unsigned int screenY_; - GLuint pbos_[2]; - int pbo_index_; - stretch stretch_; frame_format_desc format_desc_; @@ -249,9 +239,7 @@ struct consumer::implementation : boost::noncopyable boost::thread thread_; tbb::concurrent_bounded_queue frame_buffer_; - sf::Image image_; - sf::Sprite sprite_; - sf::RenderWindow window_; + sf::Window window_; }; consumer::consumer(const frame_format_desc& format_desc, unsigned int screen_index, stretch stretch, bool windowed) diff --git a/core/frame/gpu_composite_frame.cpp b/core/frame/gpu_composite_frame.cpp index defc90e9f..917fb1f34 100644 --- a/core/frame/gpu_composite_frame.cpp +++ b/core/frame/gpu_composite_frame.cpp @@ -17,27 +17,24 @@ struct gpu_composite_frame::implementation : boost::noncopyable { implementation(gpu_composite_frame* self) : self_(self){} - void write_lock() + void begin_write() { - boost::range::for_each(frames_, std::mem_fn(&gpu_frame::write_lock)); + boost::range::for_each(frames_, std::mem_fn(&gpu_frame::begin_write)); } - bool write_unlock() + void end_write() { - return std::all_of(frames_.begin(), frames_.end(), - std::mem_fn(&gpu_frame::write_unlock)); + boost::range::for_each(frames_, std::mem_fn(&gpu_frame::end_write)); } - void read_lock(GLenum mode) + void begin_read() { - boost::range::for_each(frames_, std::bind(&gpu_frame::read_lock, - std::placeholders::_1, mode)); + boost::range::for_each(frames_, std::mem_fn(&gpu_frame::begin_read)); } - bool read_unlock() + void end_read() { - return std::all_of(frames_.begin(), frames_.end(), - std::mem_fn(&gpu_frame::read_unlock)); + boost::range::for_each(frames_, std::mem_fn(&gpu_frame::end_read)); } void draw() @@ -86,10 +83,10 @@ struct gpu_composite_frame::implementation : boost::noncopyable gpu_composite_frame::gpu_composite_frame() : gpu_frame(0, 0), impl_(new implementation(this)){} -void gpu_composite_frame::write_lock(){impl_->write_lock();} -bool gpu_composite_frame::write_unlock(){return impl_->write_unlock();} -void gpu_composite_frame::read_lock(GLenum mode){impl_->read_lock(mode);} -bool gpu_composite_frame::read_unlock(){return impl_->read_unlock();} +void gpu_composite_frame::begin_write(){impl_->begin_write();} +void gpu_composite_frame::end_write(){impl_->end_write();} +void gpu_composite_frame::begin_read(){impl_->begin_read();} +void gpu_composite_frame::end_read(){impl_->end_read();} void gpu_composite_frame::draw(){impl_->draw();} unsigned char* gpu_composite_frame::data(){return impl_->data();} void gpu_composite_frame::add(const gpu_frame_ptr& frame){impl_->add(frame);} diff --git a/core/frame/gpu_composite_frame.h b/core/frame/gpu_composite_frame.h index dc7754811..e9f76a250 100644 --- a/core/frame/gpu_composite_frame.h +++ b/core/frame/gpu_composite_frame.h @@ -20,10 +20,10 @@ public: private: virtual unsigned char* data(); - virtual void write_lock(); - virtual bool write_unlock(); - virtual void read_lock(GLenum mode); - virtual bool read_unlock(); + virtual void begin_write(); + virtual void end_write(); + virtual void begin_read(); + virtual void end_read(); virtual void draw(); struct implementation; diff --git a/core/frame/gpu_frame.cpp b/core/frame/gpu_frame.cpp index 2bbc4d5e8..f66b35815 100644 --- a/core/frame/gpu_frame.cpp +++ b/core/frame/gpu_frame.cpp @@ -3,6 +3,7 @@ #include "gpu_frame.h" #include "../../common/utility/memory.h" #include "../../common/gl/gl_check.h" +#include "../../common/gl/pixel_buffer_object.h" namespace caspar { namespace core { @@ -64,99 +65,35 @@ GLubyte lower_pattern[] = { struct gpu_frame::implementation : boost::noncopyable { implementation(size_t width, size_t height) - : pbo_(0), data_(nullptr), width_(width), height_(height), - size_(width*height*4), reading_(false), texture_(0), alpha_(1.0f), + : pbo_(width, height), data_(nullptr), width_(width), height_(height), + size_(width*height*4), reading_(false), alpha_(1.0f), x_(0.0f), y_(0.0f), mode_(video_mode::progressive), - texcoords_(0.0, 1.0, 1.0, 0.0) + texcoords_(0.0, 1.0, 1.0, 0.0), writing_(false), mapped_(false) { + if(width > 0 && height > 0) + end_write(); } - - ~implementation() + + void begin_write() { - if(pbo_ != 0) - glDeleteBuffers(1, &pbo_); - if(texture_ != 0) - glDeleteTextures(1, &texture_); - } - - GLuint pbo() - { - if(pbo_ == 0) - GL(glGenBuffers(1, &pbo_)); - return pbo_; + data_ = nullptr; + pbo_.begin_write(); } - void write_lock() + void end_write() { - if(texture_ == 0) - { - GL(glGenTextures(1, &texture_)); - - GL(glBindTexture(GL_TEXTURE_2D, texture_)); - - GL(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); - GL(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); - GL(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); - GL(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); - - GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width_, height_, 0, GL_BGRA, - GL_UNSIGNED_BYTE, NULL)); - } - - GL(glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo())); - if(data_ != nullptr) - { - GL(glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER)); - data_ = nullptr; - } - GL(glBindTexture(GL_TEXTURE_2D, texture_)); - GL(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, GL_BGRA, - GL_UNSIGNED_BYTE, NULL)); - GL(glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0)); - } - - bool write_unlock() - { - if(data_ != nullptr) - return false; - GL(glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo())); - GL(glBufferData(GL_PIXEL_UNPACK_BUFFER, size_, NULL, GL_STREAM_DRAW)); - void* ptr = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); - GL(glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0)); - data_ = reinterpret_cast(ptr); - if(!data_) - BOOST_THROW_EXCEPTION(invalid_operation() - << msg_info("glMapBuffer failed")); - return true; + data_ = static_cast(pbo_.end_write()); } - void read_lock(GLenum mode) + void begin_read() { - GL(glReadBuffer(mode)); - GL(glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo())); - if(data_ != nullptr) - { - GL(glUnmapBuffer(GL_PIXEL_PACK_BUFFER)); - data_ = nullptr; - } - GL(glBufferData(GL_PIXEL_PACK_BUFFER, size_, NULL, GL_STREAM_READ)); - GL(glReadPixels(0, 0, width_, height_, GL_BGRA, GL_UNSIGNED_BYTE, NULL)); - GL(glBindBuffer(GL_PIXEL_PACK_BUFFER, 0)); - reading_ = true; + data_ = nullptr; + pbo_.begin_read(); } - bool read_unlock() + void end_read() { - if(data_ != nullptr || !reading_) - return false; - GL(glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo())); - void* ptr = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY); - GL(glBindBuffer(GL_PIXEL_PACK_BUFFER, 0)); - data_ = reinterpret_cast(ptr); - if(!data_) - BOOST_THROW_EXCEPTION(std::bad_alloc()); - reading_ = false; - return true; + data_ = static_cast(pbo_.end_read()); } void draw() @@ -172,7 +109,7 @@ struct gpu_frame::implementation : boost::noncopyable else if(mode_ == video_mode::lower) glPolygonStipple(lower_pattern); - GL(glBindTexture(GL_TEXTURE_2D, texture_)); + pbo_.bind_texture(); glBegin(GL_QUADS); glTexCoord2d(texcoords_.left, texcoords_.bottom); glVertex2d(-1.0, -1.0); glTexCoord2d(texcoords_.right, texcoords_.bottom); glVertex2d( 1.0, -1.0); @@ -199,14 +136,17 @@ struct gpu_frame::implementation : boost::noncopyable mode_ = video_mode::progressive; } + common::gl::pixel_buffer_object pbo_; gpu_frame* self_; - GLuint pbo_; - GLuint texture_; unsigned char* data_; size_t width_; size_t height_; size_t size_; + bool reading_; + bool writing_; + bool mapped_; + std::vector audio_data_; double alpha_; @@ -218,12 +158,11 @@ struct gpu_frame::implementation : boost::noncopyable gpu_frame::gpu_frame(size_t width, size_t height) : impl_(new implementation(width, height)){} -void gpu_frame::write_lock(){impl_->write_lock();} -bool gpu_frame::write_unlock(){return impl_->write_unlock();} -void gpu_frame::read_lock(GLenum mode){impl_->read_lock(mode);} -bool gpu_frame::read_unlock(){return impl_->read_unlock();} +void gpu_frame::begin_write(){impl_->begin_write();} +void gpu_frame::end_write(){impl_->end_write();} +void gpu_frame::begin_read(){impl_->begin_read();} +void gpu_frame::end_read(){impl_->end_read();} void gpu_frame::draw(){impl_->draw();} -bool gpu_frame::valid() const { return impl_->data_ != nullptr;} unsigned char* gpu_frame::data(){return impl_->data();} size_t gpu_frame::size() const { return impl_->size_; } size_t gpu_frame::width() const { return impl_->width_;} diff --git a/core/frame/gpu_frame.h b/core/frame/gpu_frame.h index 4759d518a..fffe00c89 100644 --- a/core/frame/gpu_frame.h +++ b/core/frame/gpu_frame.h @@ -26,23 +26,13 @@ struct rectangle class gpu_frame : boost::noncopyable { public: - gpu_frame(size_t width, size_t height); virtual ~gpu_frame(){} - virtual void write_lock(); - virtual bool write_unlock(); - virtual void read_lock(GLenum mode); - virtual bool read_unlock(); - virtual void draw(); - - virtual bool valid() const; - + virtual unsigned char* data(); virtual size_t size() const; virtual size_t width() const; virtual size_t height() const; - - virtual void reset(); - + virtual const std::vector& audio_data() const; virtual std::vector& audio_data(); @@ -59,10 +49,22 @@ public: static std::shared_ptr null() { - static auto my_null_frame = std::make_shared(0,0); + static auto my_null_frame = std::shared_ptr(new gpu_frame(0,0)); return my_null_frame; } +protected: + gpu_frame(size_t width, size_t height); + + friend class gpu_frame_processor; + + virtual void begin_write(); + virtual void end_write(); + virtual void begin_read(); + virtual void end_read(); + virtual void draw(); + virtual void reset(); + private: struct implementation; std::shared_ptr impl_; diff --git a/core/frame/gpu_frame_processor.cpp b/core/frame/gpu_frame_processor.cpp index 82d674e73..73183644a 100644 --- a/core/frame/gpu_frame_processor.cpp +++ b/core/frame/gpu_frame_processor.cpp @@ -10,6 +10,7 @@ #include "../../common/concurrency/executor.h" #include "../../common/utility/memory.h" #include "../../common/gl/gl_check.h" +#include "../../common/gl/frame_buffer_object.h" #include #include @@ -49,33 +50,21 @@ struct gpu_frame_processor::implementation : boost::noncopyable GL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); GL(glClearColor(0.0, 0.0, 0.0, 0.0)); GL(glViewport(0, 0, format_desc_.width, format_desc_.height)); - glLoadIdentity(); - - // Create and bind a framebuffer - GL(glGenTextures(1, &render_texture_)); - GL(glBindTexture(GL_TEXTURE_2D, render_texture_)); - GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, format_desc_.width, - format_desc_.height, 0, GL_BGRA, - GL_UNSIGNED_BYTE, NULL)); - GL(glGenFramebuffersEXT(1, &fbo_)); - GL(glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_)); - GL(glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, - GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, - render_texture_, 0)); - + glLoadIdentity(); + + fbo_.create(format_desc_.width, format_desc_.height); + fbo_.bind_pixel_source(); + writing_.resize(2, std::make_shared()); - output_frame_ = std::make_shared(format_desc_.width, - format_desc_.height); + + // Fill pipeline + for(int n = 0; n < 2; ++n) + composite(std::vector()); }); - // Fill pipeline - composite(std::vector()); - composite(std::vector()); - composite(std::vector()); } ~implementation() { - glDeleteFramebuffersEXT(1, &fbo_); executor_.stop(); } @@ -102,27 +91,33 @@ struct gpu_frame_processor::implementation : boost::noncopyable // 1. Start asynchronous DMA transfer to video memory. writing_[index_] = std::move(frame); // Lock frame and give pointer ownership to OpenGL. - writing_[index_]->write_lock(); + writing_[index_]->begin_write(); // 3. Output to external buffer. - if(output_frame_->read_unlock()) + if(output_frame_) + { + output_frame_->end_read(); output_.push(output_frame_); - + } + // Clear framebuffer. - glClear(GL_COLOR_BUFFER_BIT); + GL(glClear(GL_COLOR_BUFFER_BIT)); // 2. Draw to framebuffer and start asynchronous DMA transfer // to page-locked memory. writing_[next_index]->draw(); // Create an output frame - output_frame_ = create_output_frame(); + auto temp_frame = create_output_frame(); // Read from framebuffer into page-locked memory. - output_frame_->read_lock(GL_COLOR_ATTACHMENT0_EXT); - output_frame_->audio_data() = std::move(writing_[next_index]->audio_data()); + temp_frame->begin_read(); + temp_frame->audio_data() = std::move(writing_[next_index]->audio_data()); + + output_frame_ = temp_frame; // Return frames to pool. + writing_[next_index]->end_write(); writing_[next_index] = nullptr; } catch(...) @@ -136,8 +131,7 @@ struct gpu_frame_processor::implementation : boost::noncopyable { gpu_frame_ptr frame; if(!reading_pool_.try_pop(frame)) - frame = std::make_shared(format_desc_.width, - format_desc_.height); + frame.reset(new gpu_frame(format_desc_.width, format_desc_.height)); return gpu_frame_ptr(frame.get(), [=](gpu_frame*) { @@ -154,17 +148,14 @@ struct gpu_frame_processor::implementation : boost::noncopyable gpu_frame_ptr frame; if(!pool.try_pop(frame)) { - frame = executor_.invoke([=]() -> gpu_frame_ptr + frame = executor_.invoke([&] { - auto frame = std::make_shared(width, height); - frame->write_unlock(); - return frame; + return std::shared_ptr(new gpu_frame(width, height)); }); } auto destructor = [=] { - frame->write_unlock(); frame->reset(); writing_pools_[key].push(frame); }; @@ -197,8 +188,7 @@ struct gpu_frame_processor::implementation : boost::noncopyable common::executor executor_; - GLuint render_texture_; - GLuint fbo_; + common::gl::frame_buffer_object fbo_; }; gpu_frame_processor::gpu_frame_processor(const frame_format_desc& format_desc) : impl_(new implementation(format_desc)){} diff --git a/core/producer/color/color_producer.cpp b/core/producer/color/color_producer.cpp index 0164daece..58d2fd531 100644 --- a/core/producer/color/color_producer.cpp +++ b/core/producer/color/color_producer.cpp @@ -37,21 +37,19 @@ public: gpu_frame_ptr get_frame() { - frame_ = factory_->create_frame(format_desc_); - __stosd(reinterpret_cast(frame_->data()), color_value_, frame_->size() / sizeof(unsigned long)); return frame_; } const frame_format_desc& get_frame_format_desc() const { return format_desc_; } void initialize(const frame_factory_ptr& factory) { - factory_ = factory; + frame_ = factory->create_frame(format_desc_); + __stosd(reinterpret_cast(frame_->data()), color_value_, frame_->size() / sizeof(unsigned long)); } frame_format_desc format_desc_; gpu_frame_ptr frame_; unsigned int color_value_; - frame_factory_ptr factory_; }; union Color diff --git a/core/producer/flash/flash_producer.cpp b/core/producer/flash/flash_producer.cpp index 9e3502ed0..eddbe023f 100644 --- a/core/producer/flash/flash_producer.cpp +++ b/core/producer/flash/flash_producer.cpp @@ -268,7 +268,7 @@ struct flash_producer::implementation } auto frame = factory_->create_frame(format_desc_); - common::copy(frame->data(), current_frame_->data(), current_frame_->size()); + common::aligned_memcpy(frame->data(), current_frame_->data(), current_frame_->size()); return frame; } diff --git a/core/producer/image/image_producer.cpp b/core/producer/image/image_producer.cpp index 69ce620bb..07135b9ae 100644 --- a/core/producer/image/image_producer.cpp +++ b/core/producer/image/image_producer.cpp @@ -33,7 +33,7 @@ struct image_producer : public frame_producer FreeImage_FlipVertical(bitmap.get()); frame_ = factory->create_frame(format_desc_); - common::copy(frame_->data(), FreeImage_GetBits(bitmap.get()), frame_->size()); + common::aligned_memcpy(frame_->data(), FreeImage_GetBits(bitmap.get()), frame_->size()); } const frame_format_desc& get_frame_format_desc() const { return format_desc_; } diff --git a/core/producer/image/image_scroll_producer.cpp b/core/producer/image/image_scroll_producer.cpp index 47d909f00..ee1243a32 100644 --- a/core/producer/image/image_scroll_producer.cpp +++ b/core/producer/image/image_scroll_producer.cpp @@ -80,7 +80,7 @@ struct image_scroll_producer : public frame_producer unsigned char* pBits = FreeImage_GetBits(pBitmap.get()); for (size_t i = 0; i < height; ++i) - common::copy(&image_.get()[i * image_width_ * 4], &pBits[i* width * 4], width * 4); + common::aligned_memcpy(&image_.get()[i * image_width_ * 4], &pBits[i* width * 4], width * 4); } gpu_frame_ptr render_frame() diff --git a/core/producer/transition/transition_producer.cpp b/core/producer/transition/transition_producer.cpp index 17faf10be..81b86af1d 100644 --- a/core/producer/transition/transition_producer.cpp +++ b/core/producer/transition/transition_producer.cpp @@ -55,7 +55,7 @@ struct transition_producer::implementation : boost::noncopyable gpu_frame_ptr get_frame() { - if(++current_frame_ >= info_.duration) + if(current_frame_++ >= info_.duration) return nullptr; gpu_frame_ptr source; @@ -128,31 +128,42 @@ struct transition_producer::implementation : boost::noncopyable auto composite = std::make_shared(); if(src_frame) composite->add(src_frame); - else - src_frame = std::make_shared(0, 0); composite->add(dest_frame); - if(info_.type == transition_type::mix) - dest_frame->alpha(alpha); - else if(info_.type == transition_type::slide) + switch(info_.type) { + case transition_type::mix: + dest_frame->alpha(alpha); + break; + case transition_type::slide: if(info_.direction == transition_direction::from_left) dest_frame->translate(-1.0+alpha, 0.0); else if(info_.direction == transition_direction::from_right) - dest_frame->translate(1.0-alpha, 0.0); - } - else if(info_.type == transition_type::push) - { + dest_frame->translate(1.0-alpha, 0.0); + break; + case transition_type::push: if(info_.direction == transition_direction::from_left) { dest_frame->translate(-1.0+alpha, 0.0); - src_frame->translate(0.0+alpha, 0.0); + if(src_frame) + src_frame->translate(0.0+alpha, 0.0); } else if(info_.direction == transition_direction::from_right) { dest_frame->translate(1.0-alpha, 0.0); - src_frame->translate(0.0-alpha, 0.0); + if(src_frame) + src_frame->translate(0.0-alpha, 0.0); } + break; + } + + if(info_.type == transition_type::mix) + dest_frame->alpha(alpha); + else if(info_.type == transition_type::slide) + { + } + else if(info_.type == transition_type::push) + { } else if(info_.type == transition_type::wipe) { diff --git a/shell/caspar.config b/shell/caspar.config index 195f1ee0c..6678d2514 100644 --- a/shell/caspar.config +++ b/shell/caspar.config @@ -8,7 +8,7 @@ - PAL + 1080p2500 1 diff --git a/test/mock/mock_frame.h b/test/mock/mock_frame.h new file mode 100644 index 000000000..85f9fc928 --- /dev/null +++ b/test/mock/mock_frame.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +struct mock_frame : public caspar::core::gpu_frame +{ + mock_frame(void* tag, short volume = 100) : caspar::core::gpu_frame(0,0), tag(tag) + { + audio_data().resize(100, volume); + } + void* tag; +}; \ No newline at end of file diff --git a/test/mock/mock_frame_factory.h b/test/mock/mock_frame_factory.h new file mode 100644 index 000000000..483f3861e --- /dev/null +++ b/test/mock/mock_frame_factory.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +struct mock_frame_factory : public caspar::core::frame_factory +{ + virtual gpu_frame_ptr create_frame(size_t width, size_t height) + { + return std::make_shared(); + } +}; \ No newline at end of file diff --git a/test/mock/mock_frame_producer.h b/test/mock/mock_frame_producer.h index 0f8eded66..8c8b551eb 100644 --- a/test/mock/mock_frame_producer.h +++ b/test/mock/mock_frame_producer.h @@ -1,6 +1,7 @@ #pragma once -#include +#include "mock_frame.h" + #include #include @@ -11,7 +12,8 @@ class mock_frame_producer : public frame_producer { public: mock_frame_producer(bool null = false, bool throws = false) - : null_(null), throws_(throws){} + : null_(null), throws_(throws), initialized_(false), volume_(100){} + void set_volume(short volume) { volume_ = volume;} gpu_frame_ptr get_frame() { if(throws_) @@ -19,7 +21,7 @@ public: if(leading_) return leading_->get_frame(); if(!null_) - return std::make_shared(0, 0); + return std::make_shared(this, volume_); return nullptr; } std::shared_ptr get_following_producer() const @@ -32,12 +34,16 @@ public: return format; } void initialize(const frame_factory_ptr& factory) - {} + {initialized_ = true;} void set_following_producer(const std::shared_ptr& following) {following_ = following;} + + bool is_initialized() const { return initialized_;} private: std::shared_ptr following_; std::shared_ptr leading_; bool null_; bool throws_; + bool initialized_; + short volume_; }; \ No newline at end of file diff --git a/test/producer/transition/transition_producer_test.cpp b/test/producer/transition/transition_producer_test.cpp new file mode 100644 index 000000000..d80cd06e9 --- /dev/null +++ b/test/producer/transition/transition_producer_test.cpp @@ -0,0 +1,90 @@ +#include + +#include "../../mock/mock_frame_producer.h" +#include "../../mock/mock_frame.h" + +#include +#include +#include +#include + +using namespace caspar; +using namespace caspar::core; + +frame_format_desc test_format = frame_format_desc::format_descs[frame_format::pal]; + +TEST(transition_producer, constructor_dest_nullptr) +{ + ASSERT_THROW(transition_producer(nullptr, transition_info(), test_format); + , null_argument); +} + +TEST(transition_producer, get_following_producer) +{ + auto dest = std::make_shared(); + + transition_producer producer(dest, transition_info(), test_format); + + ASSERT_TRUE(producer.get_following_producer() == dest); +} + +TEST(transition_producer, get_frame_format_desc) +{ + auto dest = std::make_shared(); + + transition_producer producer(dest, transition_info(), test_format); + + ASSERT_TRUE(producer.get_frame_format_desc() == test_format); +} + +TEST(transition_producer, null_dest_get_frame) +{ + auto source = std::make_shared(); + + transition_producer producer(nullptr, transition_info(), test_format); + producer.set_leading_producer(source); + + ASSERT_TRUE(producer.get_frame() == nullptr); +} + +TEST(transition_producer, null_source_get_frame_cut) +{ + auto dest = std::make_shared(); + + transition_info info; + info.type = transition_type::cut; + + transition_producer producer(dest, info, test_format); + + ASSERT_TRUE(producer.get_frame() == nullptr); +} + +TEST(transition_producer, initialize) +{ + auto dest = std::make_shared(); + + transition_producer producer(dest, transition_info(), test_format); + producer.initialize(nullptr); + + ASSERT_TRUE(dest->is_initialized()); +} + +TEST(transition_producer, duration) +{ + auto dest = std::make_shared(); + auto source = std::make_shared(); + + transition_info info; + info.type = transition_type::cut; + info.duration = 3; + + transition_producer producer(dest, info, test_format); + producer.set_leading_producer(source); + + for(int n = 0; n < info.duration; ++n) + { + auto frame = producer.get_frame(); + ASSERT_TRUE(std::static_pointer_cast(frame)->tag == source.get()); + } + ASSERT_TRUE(producer.get_frame() == nullptr); +} \ No newline at end of file diff --git a/test/test.vcxproj b/test/test.vcxproj index 23bd65c68..413cd2a21 100644 --- a/test/test.vcxproj +++ b/test/test.vcxproj @@ -15,6 +15,7 @@ NotUsing NotUsing + @@ -26,6 +27,8 @@ + + diff --git a/test/test.vcxproj.filters b/test/test.vcxproj.filters index 6b0544b29..e7367252d 100644 --- a/test/test.vcxproj.filters +++ b/test/test.vcxproj.filters @@ -10,6 +10,12 @@ {420ab80e-66c8-477b-b8c6-d8f4ceb08102} + + {2d766f33-578d-40a7-a018-8a0a8b031e08} + + + {cb38fc85-f618-4461-94bf-d7eff61b49fe} + @@ -18,10 +24,19 @@ Source\renderer + + Source\producer\transition + Source\mock + + Source\mock + + + Source\mock + \ No newline at end of file -- 2.39.2