New mechanism for source update with callbacks

Similarly to Node update callbacks, sources now have SourceCallbacks called at the start of each update. Several SourceCallback are implemented to ensure thread safe update of more complex properties (mixing alpha, depth, etc.).
This commit is contained in:
Bruno Herbelin
2021-12-20 00:25:42 +01:00
parent 8deb364025
commit f921e7610c
9 changed files with 464 additions and 169 deletions

View File

@@ -537,6 +537,11 @@ void Source::setActive (bool on)
groups_[View::LAYER]->visible_ = active_;
}
void Source::setActive (float threshold)
{
Source::setActive( glm::length( glm::vec2(groups_[View::MIXING]->translation_) ) < threshold );
}
void Source::setLocked (bool on)
{
locked_ = on;
@@ -546,21 +551,27 @@ void Source::setLocked (bool on)
// Transfer functions from coordinates to alpha (1 - transparency)
// linear distance
float linear_(float x, float y) {
return 1.f - CLAMP( sqrt( ( x * x ) + ( y * y ) ), 0.f, 1.f );
}
//// linear distance
//float linear_(float x, float y) {
// return 1.f - CLAMP( sqrt( ( x * x ) + ( y * y ) ), 0.f, 1.f );
//}
// quadratic distance
float quad_(float x, float y) {
return 1.f - CLAMP( ( x * x ) + ( y * y ), 0.f, 1.f );
}
//// quadratic distance
//float quad_(float x, float y) {
// return 1.f - CLAMP( ( x * x ) + ( y * y ), 0.f, 1.f );
//}
// best alpha transfer function: quadratic sinusoidal shape
float sin_quad_(float x, float y) {
//// best alpha transfer function: quadratic sinusoidal shape
//float sin_quad_(float x, float y) {
// float D = sqrt( ( x * x ) + ( y * y ) );
// return 0.5f + 0.5f * cos( M_PI * CLAMP( D * sqrt(D), 0.f, 1.f ) );
//// return 0.5f + 0.5f * cos( M_PI * CLAMP( ( ( x * x ) + ( y * y ) ), 0.f, 1.f ) );
//}
float SourceCore::alphaFromCordinates(float x, float y)
{
float D = sqrt( ( x * x ) + ( y * y ) );
return 0.5f + 0.5f * cos( M_PI * CLAMP( D * sqrt(D), 0.f, 1.f ) );
// return 0.5f + 0.5f * cos( M_PI * CLAMP( ( ( x * x ) + ( y * y ) ), 0.f, 1.f ) );
}
@@ -569,44 +580,41 @@ float Source::depth() const
return group(View::RENDERING)->translation_.z;
}
void Source::setDepth(float d)
{
groups_[View::LAYER]->translation_.z = CLAMP(d, MIN_DEPTH, MAX_DEPTH);
touch();
}
float Source::mix_distance()
{
return glm::length( glm::vec2(groups_[View::MIXING]->translation_) );
}
float Source::alpha() const
{
return blendingShader()->color.a;
}
void Source::setAlpha(float a)
void Source::call(SourceCallback *callback, bool override)
{
a = CLAMP(a, 0.f, 1.f);
glm::vec2 dist = glm::vec2(groups_[View::MIXING]->translation_);
glm::vec2 step = glm::normalize(glm::vec2(1.f, 1.f));// step in diagonal by default
if (callback != nullptr) {
// step in direction of source translation if possible
if ( glm::length(dist) > DELTA_ALPHA)
step = glm::normalize(dist);
// lock access to callbacks list
access_callbacks_.lock();
// converge to reduce the difference of alpha
// using dichotomic algorithm
float delta = sin_quad_(dist.x, dist.y) - a;
while ( glm::abs(delta) > DELTA_ALPHA ){
dist += step * (delta / 2.f);
delta = sin_quad_(dist.x, dist.y) - a;
// remove similar callbacks if override
if (override) {
for (auto iter=update_callbacks_.begin(); iter != update_callbacks_.end(); )
{
// remove and delete all callbacks of same type
SourceCallback *c = *iter;
if (callback->type() == c->type() ) {
iter = update_callbacks_.erase(iter);
delete c;
}
// iterate
else
++iter;
}
}
// add callback to callbacks list
update_callbacks_.push_back(callback);
// release access to callbacks list
access_callbacks_.unlock();
}
// apply new mixing coordinates
groups_[View::MIXING]->translation_.x = dist.x;
groups_[View::MIXING]->translation_.y = dist.y;
touch();
}
void Source::update(float dt)
@@ -614,122 +622,153 @@ void Source::update(float dt)
// keep delta-t
dt_ = dt;
// update nodes if needed
if (renderbuffer_ && mixingsurface_ && maskbuffer_ && need_update_)
// if update is possible
if (renderbuffer_ && mixingsurface_ && maskbuffer_)
{
// ADJUST alpha based on MIXING node
// read position of the mixing node and interpret this as transparency of render output
glm::vec2 dist = glm::vec2(groups_[View::MIXING]->translation_);
// use the sinusoidal transfer function
blendingshader_->color = glm::vec4(1.f, 1.f, 1.f, sin_quad_( dist.x, dist.y ));
mixingshader_->color = blendingshader_->color;
// adjust scale of mixing icon : smaller if not active
groups_[View::MIXING]->scale_ = glm::vec3(MIXING_ICON_SCALE) - ( active_ ? glm::vec3(0.f, 0.f, 0.f) : glm::vec3(0.03f, 0.03f, 0.f) );
// MODIFY geometry based on GEOMETRY node
groups_[View::RENDERING]->translation_ = groups_[View::GEOMETRY]->translation_;
groups_[View::RENDERING]->rotation_ = groups_[View::GEOMETRY]->rotation_;
glm::vec3 s = groups_[View::GEOMETRY]->scale_;
// avoid any null scale
s.x = CLAMP_SCALE(s.x);
s.y = CLAMP_SCALE(s.y);
s.z = 1.f;
groups_[View::GEOMETRY]->scale_ = s;
groups_[View::RENDERING]->scale_ = s;
// MODIFY CROP projection based on GEOMETRY crop
renderbuffer_->setProjectionArea( glm::vec2(groups_[View::GEOMETRY]->crop_) );
// Mixing and layer icons scaled based on GEOMETRY crop
mixingsurface_->scale_ = groups_[View::GEOMETRY]->crop_;
mixingsurface_->scale_.x *= renderbuffer_->aspectRatio();
mixingsurface_->update(dt_);
// Layers icons are displayed in Perspective (diagonal)
groups_[View::LAYER]->translation_.x = -groups_[View::LAYER]->translation_.z;
groups_[View::LAYER]->translation_.y = groups_[View::LAYER]->translation_.x / LAYER_PERSPECTIVE;
// Update workspace based on depth, and
// adjust vertical position of icon depending on workspace
if (groups_[View::LAYER]->translation_.x < -LAYER_FOREGROUND) {
groups_[View::LAYER]->translation_.y -= 0.3f;
workspace_ = Source::FOREGROUND;
}
else if (groups_[View::LAYER]->translation_.x < -LAYER_BACKGROUND) {
groups_[View::LAYER]->translation_.y -= 0.15f;
workspace_ = Source::STAGE;
}
else
workspace_ = Source::BACKGROUND;
// MODIFY depth based on LAYER node
groups_[View::MIXING]->translation_.z = groups_[View::LAYER]->translation_.z;
groups_[View::GEOMETRY]->translation_.z = groups_[View::LAYER]->translation_.z;
groups_[View::RENDERING]->translation_.z = groups_[View::LAYER]->translation_.z;
// MODIFY texture projection based on APPEARANCE node
// UV to node coordinates
static glm::mat4 UVtoScene = GlmToolkit::transform(glm::vec3(1.f, -1.f, 0.f),
glm::vec3(0.f, 0.f, 0.f),
glm::vec3(-2.f, 2.f, 1.f));
// Aspect Ratio correction transform : coordinates of Appearance Frame are scaled by render buffer width
glm::mat4 Ar = glm::scale(glm::identity<glm::mat4>(), glm::vec3(renderbuffer_->aspectRatio(), 1.f, 1.f) );
// Translation : same as Appearance Frame (modified by Ar)
glm::mat4 Tra = glm::translate(glm::identity<glm::mat4>(), groups_[View::TEXTURE]->translation_);
// Scaling : inverse scaling (larger UV when smaller Appearance Frame)
glm::vec2 scale = glm::vec2(groups_[View::TEXTURE]->scale_.x,groups_[View::TEXTURE]->scale_.y);
scale = glm::sign(scale) * glm::max( glm::vec2(glm::epsilon<float>()), glm::abs(scale));
glm::mat4 Sca = glm::scale(glm::identity<glm::mat4>(), glm::vec3(scale, 1.f));
// Rotation : same angle than Appearance Frame, inverted axis
glm::mat4 Rot = glm::rotate(glm::identity<glm::mat4>(), groups_[View::TEXTURE]->rotation_.z, glm::vec3(0.f, 0.f, -1.f) );
// Combine transformations (non transitive) in this order:
// 1. switch to Scene coordinate system
// 2. Apply the aspect ratio correction
// 3. Apply the translation
// 4. Apply the rotation (centered after translation)
// 5. Revert aspect ration correction
// 6. Apply the Scaling (independent of aspect ratio)
// 7. switch back to UV coordinate system
texturesurface_->shader()->iTransform = glm::inverse(UVtoScene) * glm::inverse(Sca) * glm::inverse(Ar) * Rot * Tra * Ar * UVtoScene;
// if a mask image was given to be updated
if (mask_need_update_) {
// fill the mask buffer (once)
if (maskbuffer_->fill(maskimage_) )
mask_need_update_ = false;
}
// otherwise, render the mask buffer
else
// lock access to callbacks list
access_callbacks_.lock();
// call all callbacks
for (auto iter=update_callbacks_.begin(); iter != update_callbacks_.end(); )
{
// draw mask in mask frame buffer
maskbuffer_->begin(false);
// loopback maskbuffer texture for painting
masksurface_->setTextureIndex(maskbuffer_->texture());
// fill surface with mask texture
masksurface_->draw(glm::identity<glm::mat4>(), maskbuffer_->projection());
maskbuffer_->end();
}
SourceCallback *callback = *iter;
// set the rendered mask as mask for blending
blendingshader_->mask_texture = maskbuffer_->texture();
// call update for active callbacks
if (callback->active()) {
callback->update(this, dt);
need_update_ = true;
}
// inform mixing group
if (mixinggroup_)
mixinggroup_->setAction(MixingGroup::ACTION_UPDATE);
// do not update next frame
need_update_ = false;
}
if (processingshader_link_.connected() && imageProcessingEnabled()) {
Source *ref_source = processingshader_link_.source();
if (ref_source!=nullptr) {
if (ref_source->imageProcessingEnabled())
processingshader_->copy( *ref_source->processingShader() );
// remove and delete finished callbacks
if (callback->finished()) {
iter = update_callbacks_.erase(iter);
delete callback;
}
// iterate
else
processingshader_link_.disconnect();
++iter;
}
// release access to callbacks list
access_callbacks_.unlock();
// update nodes if needed
if (need_update_)
{
// ADJUST alpha based on MIXING node
// read position of the mixing node and interpret this as transparency of render output
glm::vec2 dist = glm::vec2(groups_[View::MIXING]->translation_);
// use the sinusoidal transfer function
blendingshader_->color = glm::vec4(1.f, 1.f, 1.f, SourceCore::alphaFromCordinates( dist.x, dist.y ));
mixingshader_->color = blendingshader_->color;
// adjust scale of mixing icon : smaller if not active
groups_[View::MIXING]->scale_ = glm::vec3(MIXING_ICON_SCALE) - ( active_ ? glm::vec3(0.f, 0.f, 0.f) : glm::vec3(0.03f, 0.03f, 0.f) );
// MODIFY geometry based on GEOMETRY node
groups_[View::RENDERING]->translation_ = groups_[View::GEOMETRY]->translation_;
groups_[View::RENDERING]->rotation_ = groups_[View::GEOMETRY]->rotation_;
glm::vec3 s = groups_[View::GEOMETRY]->scale_;
// avoid any null scale
s.x = CLAMP_SCALE(s.x);
s.y = CLAMP_SCALE(s.y);
s.z = 1.f;
groups_[View::GEOMETRY]->scale_ = s;
groups_[View::RENDERING]->scale_ = s;
// MODIFY CROP projection based on GEOMETRY crop
renderbuffer_->setProjectionArea( glm::vec2(groups_[View::GEOMETRY]->crop_) );
// Mixing and layer icons scaled based on GEOMETRY crop
mixingsurface_->scale_ = groups_[View::GEOMETRY]->crop_;
mixingsurface_->scale_.x *= renderbuffer_->aspectRatio();
mixingsurface_->update(dt_);
// Layers icons are displayed in Perspective (diagonal)
groups_[View::LAYER]->translation_.x = -groups_[View::LAYER]->translation_.z;
groups_[View::LAYER]->translation_.y = groups_[View::LAYER]->translation_.x / LAYER_PERSPECTIVE;
// Update workspace based on depth, and
// adjust vertical position of icon depending on workspace
if (groups_[View::LAYER]->translation_.x < -LAYER_FOREGROUND) {
groups_[View::LAYER]->translation_.y -= 0.3f;
workspace_ = Source::FOREGROUND;
}
else if (groups_[View::LAYER]->translation_.x < -LAYER_BACKGROUND) {
groups_[View::LAYER]->translation_.y -= 0.15f;
workspace_ = Source::STAGE;
}
else
workspace_ = Source::BACKGROUND;
// MODIFY depth based on LAYER node
groups_[View::MIXING]->translation_.z = groups_[View::LAYER]->translation_.z;
groups_[View::GEOMETRY]->translation_.z = groups_[View::LAYER]->translation_.z;
groups_[View::RENDERING]->translation_.z = groups_[View::LAYER]->translation_.z;
// MODIFY texture projection based on APPEARANCE node
// UV to node coordinates
static glm::mat4 UVtoScene = GlmToolkit::transform(glm::vec3(1.f, -1.f, 0.f),
glm::vec3(0.f, 0.f, 0.f),
glm::vec3(-2.f, 2.f, 1.f));
// Aspect Ratio correction transform : coordinates of Appearance Frame are scaled by render buffer width
glm::mat4 Ar = glm::scale(glm::identity<glm::mat4>(), glm::vec3(renderbuffer_->aspectRatio(), 1.f, 1.f) );
// Translation : same as Appearance Frame (modified by Ar)
glm::mat4 Tra = glm::translate(glm::identity<glm::mat4>(), groups_[View::TEXTURE]->translation_);
// Scaling : inverse scaling (larger UV when smaller Appearance Frame)
glm::vec2 scale = glm::vec2(groups_[View::TEXTURE]->scale_.x,groups_[View::TEXTURE]->scale_.y);
scale = glm::sign(scale) * glm::max( glm::vec2(glm::epsilon<float>()), glm::abs(scale));
glm::mat4 Sca = glm::scale(glm::identity<glm::mat4>(), glm::vec3(scale, 1.f));
// Rotation : same angle than Appearance Frame, inverted axis
glm::mat4 Rot = glm::rotate(glm::identity<glm::mat4>(), groups_[View::TEXTURE]->rotation_.z, glm::vec3(0.f, 0.f, -1.f) );
// Combine transformations (non transitive) in this order:
// 1. switch to Scene coordinate system
// 2. Apply the aspect ratio correction
// 3. Apply the translation
// 4. Apply the rotation (centered after translation)
// 5. Revert aspect ration correction
// 6. Apply the Scaling (independent of aspect ratio)
// 7. switch back to UV coordinate system
texturesurface_->shader()->iTransform = glm::inverse(UVtoScene) * glm::inverse(Sca) * glm::inverse(Ar) * Rot * Tra * Ar * UVtoScene;
// if a mask image was given to be updated
if (mask_need_update_) {
// fill the mask buffer (once)
if (maskbuffer_->fill(maskimage_) )
mask_need_update_ = false;
}
// otherwise, render the mask buffer
else
{
// draw mask in mask frame buffer
maskbuffer_->begin(false);
// loopback maskbuffer texture for painting
masksurface_->setTextureIndex(maskbuffer_->texture());
// fill surface with mask texture
masksurface_->draw(glm::identity<glm::mat4>(), maskbuffer_->projection());
maskbuffer_->end();
}
// set the rendered mask as mask for blending
blendingshader_->mask_texture = maskbuffer_->texture();
// inform mixing group
if (mixinggroup_)
mixinggroup_->setAction(MixingGroup::ACTION_UPDATE);
// do not update next frame
need_update_ = false;
}
if (processingshader_link_.connected() && imageProcessingEnabled()) {
Source *ref_source = processingshader_link_.source();
if (ref_source!=nullptr) {
if (ref_source->imageProcessingEnabled())
processingshader_->copy( *ref_source->processingShader() );
else
processingshader_link_.disconnect();
}
}
}
}
FrameBuffer *Source::frame() const