* Exposed mipmap and is_key setting to scene_producer.
* Fixed bug in async destruction of consumers and producers where counter was uninitialized.
13 files changed:
if(format_desc.field_mode != core::field_mode::progressive)
{ // Remove jitter from still.
for (auto& layer : layers)
if(format_desc.field_mode != core::field_mode::progressive)
{ // Remove jitter from still.
for (auto& layer : layers)
// Remove first field stills.
boost::range::remove_erase_if(layer.items, [&](const item& item)
{
return item.transform.is_still && item.transform.field_mode == format_desc.field_mode; // only us last field for stills.
});
// Remove first field stills.
boost::range::remove_erase_if(layer.items, [&](const item& item)
{
return item.transform.is_still && item.transform.field_mode == format_desc.field_mode; // only us last field for stills.
});
// Stills are progressive
for (auto& item : layer.items)
{
// Stills are progressive
for (auto& item : layer.items)
{
return flatten(ogl_->begin_invoke([=]() mutable -> std::shared_future<array<const std::uint8_t>>
{
return flatten(ogl_->begin_invoke([=]() mutable -> std::shared_future<array<const std::uint8_t>>
{
- auto target_texture = ogl_->create_texture(format_desc.width, format_desc.height, 4);
+ auto target_texture = ogl_->create_texture(format_desc.width, format_desc.height, 4, false);
if (format_desc.field_mode != core::field_mode::progressive)
{
if (format_desc.field_mode != core::field_mode::progressive)
{
if(layer.blend_mode != core::blend_mode::normal)
{
if(layer.blend_mode != core::blend_mode::normal)
{
- auto layer_texture = ogl_->create_texture(target_texture->width(), target_texture->height(), 4);
+ auto layer_texture = ogl_->create_texture(target_texture->width(), target_texture->height(), 4, false);
for (auto& item : layer.items)
draw(layer_texture, std::move(item), layer_key_texture, local_key_texture, local_mix_texture, format_desc);
for (auto& item : layer.items)
draw(layer_texture, std::move(item), layer_key_texture, local_key_texture, local_mix_texture, format_desc);
if(item.transform.is_key)
{
if(item.transform.is_key)
{
- local_key_texture = local_key_texture ? local_key_texture : ogl_->create_texture(target_texture->width(), target_texture->height(), 1);
+ local_key_texture = local_key_texture ? local_key_texture : ogl_->create_texture(target_texture->width(), target_texture->height(), 1, draw_params.transform.use_mipmap);
draw_params.background = local_key_texture;
draw_params.local_key = nullptr;
draw_params.background = local_key_texture;
draw_params.local_key = nullptr;
}
else if(item.transform.is_mix)
{
}
else if(item.transform.is_mix)
{
- local_mix_texture = local_mix_texture ? local_mix_texture : ogl_->create_texture(target_texture->width(), target_texture->height(), 4);
+ local_mix_texture = local_mix_texture ? local_mix_texture : ogl_->create_texture(target_texture->width(), target_texture->height(), 4, draw_params.transform.use_mipmap);
draw_params.background = local_mix_texture;
draw_params.local_key = std::move(local_key_texture);
draw_params.background = local_mix_texture;
draw_params.local_key = std::move(local_key_texture);
// NOTE: Once we have copied the arrays they are no longer valid for reading!!! Check for alternative solution e.g. transfer with AMD_pinned_memory.
for(int n = 0; n < static_cast<int>(item.pix_desc.planes.size()); ++n)
// NOTE: Once we have copied the arrays they are no longer valid for reading!!! Check for alternative solution e.g. transfer with AMD_pinned_memory.
for(int n = 0; n < static_cast<int>(item.pix_desc.planes.size()); ++n)
- item.textures.push_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.textures.push_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.transform.use_mipmap));
layers_.back().items.push_back(item);
}
layers_.back().items.push_back(item);
}
std::unique_ptr<sf::Context> device_;
std::unique_ptr<sf::Context> device_;
- std::array<tbb::concurrent_unordered_map<std::size_t, tbb::concurrent_bounded_queue<std::shared_ptr<texture>>>, 4> device_pools_;
+ std::array<tbb::concurrent_unordered_map<std::size_t, tbb::concurrent_bounded_queue<std::shared_ptr<texture>>>, 8> device_pools_;
std::array<tbb::concurrent_unordered_map<std::size_t, tbb::concurrent_bounded_queue<std::shared_ptr<buffer>>>, 2> host_pools_;
GLuint fbo_;
std::array<tbb::concurrent_unordered_map<std::size_t, tbb::concurrent_bounded_queue<std::shared_ptr<buffer>>>, 2> host_pools_;
GLuint fbo_;
- spl::shared_ptr<texture> create_texture(int width, int height, int stride, bool clear = false)
+ spl::shared_ptr<texture> create_texture(int width, int height, int stride, bool mipmapped, bool clear)
{
CASPAR_VERIFY(stride > 0 && stride < 5);
CASPAR_VERIFY(width > 0 && height > 0);
{
CASPAR_VERIFY(stride > 0 && stride < 5);
CASPAR_VERIFY(width > 0 && height > 0);
if(!executor_.is_current())
CASPAR_THROW_EXCEPTION(invalid_operation() << msg_info("Operation only valid in an OpenGL Context."));
if(!executor_.is_current())
CASPAR_THROW_EXCEPTION(invalid_operation() << msg_info("Operation only valid in an OpenGL Context."));
- auto pool = &device_pools_[stride-1][((width << 16) & 0xFFFF0000) | (height & 0x0000FFFF)];
+ auto pool = &device_pools_[stride - 1 + (mipmapped ? 4 : 0)][((width << 16) & 0xFFFF0000) | (height & 0x0000FFFF)];
std::shared_ptr<texture> tex;
if(!pool->try_pop(tex))
std::shared_ptr<texture> tex;
if(!pool->try_pop(tex))
- tex = spl::make_shared<texture>(width, height, stride);
+ tex = spl::make_shared<texture>(width, height, stride, mipmapped);
CASPAR_LOG(debug) << L"[ogl-device] Performance warning. Buffer allocation blocked: " << timer.elapsed();
}
CASPAR_LOG(debug) << L"[ogl-device] Performance warning. Buffer allocation blocked: " << timer.elapsed();
}
- auto self = shared_from_this(); // buffers can leave the device context, take a hold on life-time.
+ std::weak_ptr<impl> self = shared_from_this(); // buffers can leave the device context, take a hold on life-time.
return spl::shared_ptr<buffer>(buf.get(), [=](buffer*) mutable
return spl::shared_ptr<buffer>(buf.get(), [=](buffer*) mutable
- {
- texture_cache_.erase(buf.get());
- pool->push(buf);
+ {
+ auto strong = self.lock();
+
+ if (strong)
+ {
+ strong->texture_cache_.erase(buf.get());
+ pool->push(buf);
+ }
+ else
+ {
+ CASPAR_LOG(info) << L"Buffer outlived ogl device";
+ }
}
// TODO: Since the returned texture is cached it SHOULD NOT be modified.
}
// TODO: Since the returned texture is cached it SHOULD NOT be modified.
- std::future<std::shared_ptr<texture>> copy_async(const array<const std::uint8_t>& source, int width, int height, int stride)
+ std::future<std::shared_ptr<texture>> copy_async(const array<const std::uint8_t>& source, int width, int height, int stride, bool mipmapped)
{
std::shared_ptr<buffer> buf = copy_to_buf(source);
{
std::shared_ptr<buffer> buf = copy_to_buf(source);
if(texture_cache_.find(a, buf.get()))
return spl::make_shared_ptr(a->second);
if(texture_cache_.find(a, buf.get()))
return spl::make_shared_ptr(a->second);
- auto texture = create_texture(width, height, stride);
- texture->copy_from(*buf);
+ auto texture = create_texture(width, height, stride, mipmapped, false);
+ texture->copy_from(*buf);
texture_cache_.insert(std::make_pair(buf.get(), texture));
texture_cache_.insert(std::make_pair(buf.get(), texture));
}, task_priority::high_priority);
}
}, task_priority::high_priority);
}
- std::future<std::shared_ptr<texture>> copy_async(const array<std::uint8_t>& source, int width, int height, int stride)
+ std::future<std::shared_ptr<texture>> copy_async(const array<std::uint8_t>& source, int width, int height, int stride, bool mipmapped)
{
std::shared_ptr<buffer> buf = copy_to_buf(source);
return executor_.begin_invoke([=]() -> std::shared_ptr<texture>
{
{
std::shared_ptr<buffer> buf = copy_to_buf(source);
return executor_.begin_invoke([=]() -> std::shared_ptr<texture>
{
- auto texture = create_texture(width, height, stride, false);
+ auto texture = create_texture(width, height, stride, mipmapped, false);
texture->copy_from(*buf);
return texture;
texture->copy_from(*buf);
return texture;
: executor_(L"OpenGL Rendering Context")
, impl_(new impl(executor_)){}
device::~device(){}
: executor_(L"OpenGL Rendering Context")
, impl_(new impl(executor_)){}
device::~device(){}
-spl::shared_ptr<texture> device::create_texture(int width, int height, int stride){return impl_->create_texture(width, height, stride, true);}
+spl::shared_ptr<texture> device::create_texture(int width, int height, int stride, bool mipmapped){ return impl_->create_texture(width, height, stride, mipmapped, true); }
array<std::uint8_t> device::create_array(int size){return impl_->create_array(size);}
array<std::uint8_t> device::create_array(int size){return impl_->create_array(size);}
-std::future<std::shared_ptr<texture>> device::copy_async(const array<const std::uint8_t>& source, int width, int height, int stride){return impl_->copy_async(source, width, height, stride);}
-std::future<std::shared_ptr<texture>> device::copy_async(const array<std::uint8_t>& source, int width, int height, int stride){return impl_->copy_async(source, width, height, stride);}
+std::future<std::shared_ptr<texture>> device::copy_async(const array<const std::uint8_t>& source, int width, int height, int stride, bool mipmapped){return impl_->copy_async(source, width, height, stride, mipmapped);}
+std::future<std::shared_ptr<texture>> device::copy_async(const array<std::uint8_t>& source, int width, int height, int stride, bool mipmapped){ return impl_->copy_async(source, width, height, stride, mipmapped); }
std::future<array<const std::uint8_t>> device::copy_async(const spl::shared_ptr<texture>& source){return impl_->copy_async(source);}
std::wstring device::version() const{return impl_->version();}
std::future<array<const std::uint8_t>> device::copy_async(const spl::shared_ptr<texture>& source){return impl_->copy_async(source);}
std::wstring device::version() const{return impl_->version();}
- spl::shared_ptr<texture> create_texture(int width, int height, int stride);
+ spl::shared_ptr<texture> create_texture(int width, int height, int stride, bool mipmapped);
array<std::uint8_t> create_array(int size);
// NOTE: Since the returned texture is cached it SHOULD NOT be modified.
array<std::uint8_t> create_array(int size);
// NOTE: Since the returned texture is cached it SHOULD NOT be modified.
- std::future<std::shared_ptr<texture>> copy_async(const array<const std::uint8_t>& source, int width, int height, int stride);
+ std::future<std::shared_ptr<texture>> copy_async(const array<const std::uint8_t>& source, int width, int height, int stride, bool mipmapped);
- std::future<std::shared_ptr<texture>> copy_async(const array<std::uint8_t>& source, int width, int height, int stride);
+ std::future<std::shared_ptr<texture>> copy_async(const array<std::uint8_t>& source, int width, int height, int stride, bool mipmapped);
std::future<array<const std::uint8_t>> copy_async(const spl::shared_ptr<texture>& source);
template<typename Func>
std::future<array<const std::uint8_t>> copy_async(const spl::shared_ptr<texture>& source);
template<typename Func>
- const int width_;
- const int height_;
- const int stride_;
+ const int width_;
+ const int height_;
+ const int stride_;
+ const bool mipmapped_;
- impl(int width, int height, int stride)
+ impl(int width, int height, int stride, bool mipmapped)
: width_(width)
, height_(height)
, stride_(stride)
: width_(width)
, height_(height)
, stride_(stride)
+ , mipmapped_(mipmapped)
{
GL(glGenTextures(1, &id_));
GL(glBindTexture(GL_TEXTURE_2D, id_));
{
GL(glGenTextures(1, &id_));
GL(glBindTexture(GL_TEXTURE_2D, id_));
- 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(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (mipmapped ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR)));
+ GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
+ GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
+ GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
GL(glTexImage2D(GL_TEXTURE_2D, 0, INTERNAL_FORMAT[stride_], width_, height_, 0, FORMAT[stride_], TYPE[stride_], NULL));
GL(glTexImage2D(GL_TEXTURE_2D, 0, INTERNAL_FORMAT[stride_], width_, height_, 0, FORMAT[stride_], TYPE[stride_], NULL));
+
+ if (mipmapped)
+ {
+ enable_anosotropic_filtering_if_available();
+ GL(glGenerateMipmap(GL_TEXTURE_2D));
+ }
+
GL(glBindTexture(GL_TEXTURE_2D, 0));
//CASPAR_LOG(trace) << "[texture] [" << ++g_total_count << L"] allocated size:" << width*height*stride;
}
GL(glBindTexture(GL_TEXTURE_2D, 0));
//CASPAR_LOG(trace) << "[texture] [" << ++g_total_count << L"] allocated size:" << width*height*stride;
}
+ void enable_anosotropic_filtering_if_available()
+ {
+ static auto AVAILABLE = glewIsSupported("GL_EXT_texture_filter_anisotropic");
+
+ if (!AVAILABLE)
+ return;
+
+ static GLfloat MAX_ANISOTROPY = []() -> GLfloat
+ {
+ GLfloat anisotropy;
+ glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropy);
+ return anisotropy;
+ }();
+
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, MAX_ANISOTROPY);
+ }
+
~impl()
{
glDeleteTextures(1, &id_);
~impl()
{
glDeleteTextures(1, &id_);
source.bind();
GL(glBindTexture(GL_TEXTURE_2D, id_));
GL(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, FORMAT[stride_], TYPE[stride_], NULL));
source.bind();
GL(glBindTexture(GL_TEXTURE_2D, id_));
GL(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, FORMAT[stride_], TYPE[stride_], NULL));
+
+ if (mipmapped_)
+ GL(glGenerateMipmap(GL_TEXTURE_2D));
+
GL(glBindTexture(GL_TEXTURE_2D, 0));
source.unbind();
source.map(); // Just map it back since map will orphan buffer.
GL(glBindTexture(GL_TEXTURE_2D, 0));
source.unbind();
source.map(); // Just map it back since map will orphan buffer.
-texture::texture(int width, int height, int stride) : impl_(new impl(width, height, stride)){}
+texture::texture(int width, int height, int stride, bool mipmapped) : impl_(new impl(width, height, stride, mipmapped)){}
texture::texture(texture&& other) : impl_(std::move(other.impl_)){}
texture::~texture(){}
texture& texture::operator=(texture&& other){impl_ = std::move(other.impl_); return *this;}
texture::texture(texture&& other) : impl_(std::move(other.impl_)){}
texture::~texture(){}
texture& texture::operator=(texture&& other){impl_ = std::move(other.impl_); return *this;}
int texture::width() const { return impl_->width_; }
int texture::height() const { return impl_->height_; }
int texture::stride() const { return impl_->stride_; }
int texture::width() const { return impl_->width_; }
int texture::height() const { return impl_->height_; }
int texture::stride() const { return impl_->stride_; }
+bool texture::mipmapped() const { return impl_->mipmapped_; }
std::size_t texture::size() const { return static_cast<std::size_t>(impl_->width_*impl_->height_*impl_->stride_); }
int texture::id() const{ return impl_->id_;}
std::size_t texture::size() const { return static_cast<std::size_t>(impl_->width_*impl_->height_*impl_->stride_); }
int texture::id() const{ return impl_->id_;}
- texture(int width, int height, int stride);
+ texture(int width, int height, int stride, bool mipmapped);
texture(texture&& other);
~texture();
texture(texture&& other);
~texture();
int width() const;
int height() const;
int stride() const;
int width() const;
int height() const;
int stride() const;
+ bool mipmapped() const;
std::size_t size() const;
int id() const;
std::size_t size() const;
int id() const;
~destroy_consumer_proxy()
{
~destroy_consumer_proxy()
{
- static tbb::atomic<int> counter = tbb::atomic<int>();
+ static tbb::atomic<int> counter = []
+ {
+ tbb::atomic<int> c;
+ c = 0;
+ return c;
+ }();
++counter;
CASPAR_VERIFY(counter < 8);
++counter;
CASPAR_VERIFY(counter < 8);
is_key |= other.is_key;
is_mix |= other.is_mix;
is_still |= other.is_still;
is_key |= other.is_key;
is_mix |= other.is_mix;
is_still |= other.is_still;
+ use_mipmap |= other.use_mipmap;
result.is_key = source.is_key | dest.is_key;
result.is_mix = source.is_mix | dest.is_mix;
result.is_still = source.is_still | dest.is_still;
result.is_key = source.is_key | dest.is_key;
result.is_mix = source.is_mix | dest.is_mix;
result.is_still = source.is_still | dest.is_still;
+ result.use_mipmap = source.use_mipmap | dest.use_mipmap;
do_tween_rectangle(source.crop, dest.crop, result.crop, time, duration, tween);
do_tween_corners(source.perspective, dest.perspective, result.perspective, time, duration, tween);
do_tween_rectangle(source.crop, dest.crop, result.crop, time, duration, tween);
do_tween_corners(source.perspective, dest.perspective, result.perspective, time, duration, tween);
lhs.is_key == rhs.is_key &&
lhs.is_mix == rhs.is_mix &&
lhs.is_still == rhs.is_still &&
lhs.is_key == rhs.is_key &&
lhs.is_mix == rhs.is_mix &&
lhs.is_still == rhs.is_still &&
+ lhs.use_mipmap == rhs.use_mipmap &&
lhs.crop == rhs.crop &&
lhs.perspective == rhs.perspective;
}
lhs.crop == rhs.crop &&
lhs.perspective == rhs.perspective;
}
bool is_key = false;
bool is_mix = false;
bool is_still = false;
bool is_key = false;
bool is_mix = false;
bool is_still = false;
+ bool use_mipmap = false;
image_transform& operator*=(const image_transform &other);
image_transform operator*(const image_transform &other) const;
image_transform& operator*=(const image_transform &other);
image_transform operator*(const image_transform &other) const;
virtual ~destroy_producer_proxy()
{
virtual ~destroy_producer_proxy()
{
- static tbb::atomic<int> counter = tbb::atomic<int>();
+ static tbb::atomic<int> counter = []
+ {
+ tbb::atomic<int> c;
+ c = 0;
+ return c;
+ }();
if(producer_ == core::frame_producer::empty())
return;
if(producer_ == core::frame_producer::empty())
return;
transform.image_transform.opacity = layer.adjustments.opacity.get();
transform.image_transform.is_key = layer.is_key.get();
transform.image_transform.opacity = layer.adjustments.opacity.get();
transform.image_transform.is_key = layer.is_key.get();
+ transform.image_transform.use_mipmap = layer.use_mipmap.get();
continue;
draw_frame frame(layer.producer.get()->receive());
continue;
draw_frame frame(layer.producer.get()->receive());
- frame.transform() = get_transform(layer);;
+ frame.transform() = get_transform(layer);
frames.push_back(frame);
}
frames.push_back(frame);
}
binding<spl::shared_ptr<frame_producer>> producer;
binding<bool> hidden;
binding<bool> is_key;
binding<spl::shared_ptr<frame_producer>> producer;
binding<bool> hidden;
binding<bool> is_key;
+ binding<bool> use_mipmap;
explicit layer(const std::wstring& name, const spl::shared_ptr<frame_producer>& producer);
};
explicit layer(const std::wstring& name, const spl::shared_ptr<frame_producer>& producer);
};
layer.perspective.lower_left.y = scene->create_variable<double>(variable_prefix + L"perspective_lower_left_y", false, elem.second.get<std::wstring>(L"perspective_lower_left_y", layer.producer.get()->pixel_constraints().height.as<std::wstring>().get()));
layer.adjustments.opacity = scene->create_variable<double>(variable_prefix + L"adjustment.opacity", false, elem.second.get(L"adjustments.opacity", L"1.0"));
layer.perspective.lower_left.y = scene->create_variable<double>(variable_prefix + L"perspective_lower_left_y", false, elem.second.get<std::wstring>(L"perspective_lower_left_y", layer.producer.get()->pixel_constraints().height.as<std::wstring>().get()));
layer.adjustments.opacity = scene->create_variable<double>(variable_prefix + L"adjustment.opacity", false, elem.second.get(L"adjustments.opacity", L"1.0"));
+ layer.is_key = scene->create_variable<bool>(variable_prefix + L"is_key", false, elem.second.get(L"is_key", L"false"));
+ layer.use_mipmap = scene->create_variable<bool>(variable_prefix + L"use_mipmap", false, elem.second.get(L"use_mipmap", L"false"));
scene->create_variable<double>(variable_prefix + L"width", false) = layer.producer.get()->pixel_constraints().width;
scene->create_variable<double>(variable_prefix + L"height", false) = layer.producer.get()->pixel_constraints().height;
scene->create_variable<double>(variable_prefix + L"width", false) = layer.producer.get()->pixel_constraints().width;
scene->create_variable<double>(variable_prefix + L"height", false) = layer.producer.get()->pixel_constraints().height;
+ else if (boost::iequals(parameters()[0], L"MIPMAP"))
+ {
+ if (parameters().size() == 1)
+ return reply_value([](const frame_transform& t) { return t.image_transform.use_mipmap ? 1 : 0; });
+
+ bool value = boost::lexical_cast<int>(parameters().at(1));
+ transforms.push_back(stage::transform_tuple_t(layer_index(), [=](frame_transform transform) -> frame_transform
+ {
+ transform.image_transform.use_mipmap = value;
+ return transform;
+ }, 0, L"linear"));
+ }
else if(boost::iequals(parameters()[0], L"BLEND"))
{
if (parameters().size() == 1)
else if(boost::iequals(parameters()[0], L"BLEND"))
{
if (parameters().size() == 1)