fixed various caching issues when rapidly changing values

This commit is contained in:
itsmattkc
2019-09-09 04:07:51 +10:00
parent 6fcfd01c8f
commit ba7a10177f
37 changed files with 495 additions and 217 deletions
+1
View File
@@ -337,6 +337,7 @@ void Core::DeclareTypesForQt()
qRegisterMetaType<Task::Status>("Task::Status");
qRegisterMetaType<NodeDependency>();
qRegisterMetaType<rational>();
qRegisterMetaType<RenderTexturePtr>();
}
void Core::StartGUI(bool full_screen)
+1
View File
@@ -251,6 +251,7 @@ FramePtr FFmpegDecoder::Retrieve(const rational &timecode, const rational &lengt
frame_container->set_height(frame_->height);
frame_container->set_format(output_fmt_);
frame_container->set_timestamp(rational(frame_->pts * avstream_->time_base.num, avstream_->time_base.den));
frame_container->set_native_timestamp(frame_->pts);
frame_container->allocate();
// Convert pixel format/linesize if necessary
+14 -1
View File
@@ -27,7 +27,10 @@
Frame::Frame() :
width_(0),
height_(0)
height_(0),
format_(-1),
timestamp_(0),
native_timestamp_(0)
{
}
@@ -66,6 +69,16 @@ void Frame::set_timestamp(const rational &timestamp)
timestamp_ = timestamp;
}
const int64_t &Frame::native_timestamp()
{
return native_timestamp_;
}
void Frame::set_native_timestamp(const int64_t &timestamp)
{
native_timestamp_ = timestamp;
}
const int &Frame::format()
{
return format_;
+5
View File
@@ -65,6 +65,9 @@ public:
const rational& timestamp();
void set_timestamp(const rational& timestamp);
const int64_t& native_timestamp();
void set_native_timestamp(const int64_t& timestamp);
/**
* @brief Get frame's format
*
@@ -110,6 +113,8 @@ private:
rational timestamp_;
int64_t native_timestamp_;
};
#endif // FRAME_H
+2 -2
View File
@@ -109,7 +109,7 @@ void Block::EdgeAddedSlot(NodeEdgePtr edge)
RefreshFollowing();
// Entire track will have shifted, so the whole cache needs to be re-validated
InvalidateCache(0, RATIONAL_MAX);
SendInvalidateCache(0, RATIONAL_MAX);
}
}
@@ -182,7 +182,7 @@ void Block::set_media_in(const rational &media_in)
media_in_ = media_in;
// Signal that this clips contents have changed
InvalidateCache(in(), out());
SendInvalidateCache(in(), out());
}
}
+1 -3
View File
@@ -69,11 +69,9 @@ NodeInput *ClipBlock::texture_input()
QVariant ClipBlock::Value(NodeOutput* param, const rational& time)
{
QVariant value = Block::Value(param, time);
if (param == texture_output()) {
// If the time retrieved is within this block, get texture information
if (time >= in() && time < out()) {
if (texture_input()->IsConnected() && time >= in() && time < out()) {
// We convert the time given (timeline time) to media time
rational media_time = SequenceToMediaTime(time);
+2
View File
@@ -29,6 +29,8 @@ OpacityNode::OpacityNode()
opacity_input_ = new NodeInput("opacity_in");
opacity_input_->add_data_input(NodeParam::kFloat);
opacity_input_->set_value(100);
opacity_input_->set_minimum(0);
opacity_input_->set_maximum(100);
AddParameter(opacity_input_);
texture_input_ = new NodeInput("tex_in");
+36 -2
View File
@@ -26,7 +26,9 @@
NodeInput::NodeInput(const QString& id) :
NodeParam(id),
keyframing_(false),
dependent_(true)
dependent_(true),
has_minimum_(false),
has_maximum_(false)
{
// Have at least one keyframe/value active at any time
keyframes_.append(NodeKeyframe());
@@ -71,7 +73,7 @@ QVariant NodeInput::get_value(const rational& time)
{
QVariant v;
if (time_ != time) {
if (time_ != time || !value_caching_) {
// Retrieve the value
if (!edges_.isEmpty()) {
// A connection - use the output of the connected Node
@@ -128,6 +130,38 @@ void NodeInput::set_dependent(bool d)
dependent_ = d;
}
const QVariant &NodeInput::minimum()
{
return minimum_;
}
bool NodeInput::has_minimum()
{
return has_minimum_;
}
void NodeInput::set_minimum(const QVariant &min)
{
minimum_ = min;
has_minimum_ = true;
}
const QVariant &NodeInput::maximum()
{
return maximum_;
}
bool NodeInput::has_maximum()
{
return has_maximum_;
}
void NodeInput::set_maximum(const QVariant &max)
{
maximum_ = max;
has_maximum_ = true;
}
NodeParam::DataType NodeInput::data_type()
{
if (inputs_.isEmpty()) {
+28
View File
@@ -124,6 +124,14 @@ public:
*/
void set_dependent(bool d);
const QVariant& minimum();
bool has_minimum();
void set_minimum(const QVariant& min);
const QVariant& maximum();
bool has_maximum();
void set_maximum(const QVariant& max);
virtual DataType data_type() override;
/**
@@ -165,6 +173,26 @@ private:
*/
bool dependent_;
/**
* @brief Sets whether this param has a minimum value or not
*/
bool has_minimum_;
/**
* @brief Internal minimum value
*/
QVariant minimum_;
/**
* @brief Sets whether this param has a maximum value or not
*/
bool has_maximum_;
/**
* @brief Internal maximum value
*/
QVariant maximum_;
};
#endif // NODEINPUT_H
+57 -49
View File
@@ -34,7 +34,8 @@ MediaInput::MediaInput() :
decoder_(nullptr),
color_service_(nullptr),
pipeline_(nullptr),
ocio_texture_(0)
ocio_texture_(0),
frame_(nullptr)
{
footage_input_ = new NodeInput("footage_in");
footage_input_->add_data_input(NodeInput::kFootage);
@@ -46,6 +47,7 @@ MediaInput::MediaInput() :
texture_output_ = new NodeOutput("tex_out");
texture_output_->set_data_type(NodeOutput::kTexture);
texture_output_->SetValueCachingEnabled(false);
AddParameter(texture_output_);
}
@@ -73,6 +75,7 @@ void MediaInput::Release()
{
internal_tex_.Destroy();
frame_ = nullptr;
decoder_ = nullptr;
color_service_ = nullptr;
pipeline_ = nullptr;
@@ -128,62 +131,67 @@ QVariant MediaInput::Value(NodeOutput *output, const rational &time)
return 0;
}
// Get frame from Decoder
FramePtr frame = decoder_->Retrieve(time);
// Check if we need to get a frame or not
if (frame_ == nullptr || frame_native_ts_ != decoder_->GetTimestampFromTime(time)) {
// Get frame from Decoder
frame_ = decoder_->Retrieve(time);
if (frame == nullptr) {
return 0;
}
if (color_service_ == nullptr) {
// FIXME: Hardcoded values for testing
color_service_ = std::make_shared<ColorService>("srgb", OCIO::ROLE_SCENE_LINEAR);
}
// OpenColorIO v1's color transforms can be done on GPU, which improves performance but reduces accuracy. When
// online, we prefer accuracy over performance so we use the CPU path instead:
// NOTE: OCIO v2 boasts 1:1 results with the CPU and GPU path so this won't be necessary forever
if (renderer->mode() == olive::RenderMode::kOnline) {
// Convert to 32F, which is required for OpenColorIO's color transformation
frame = PixelService::ConvertPixelFormat(frame, olive::PIX_FMT_RGBA32F);
if (alpha_is_associated) {
// Unassociate alpha here if associated
ColorService::DisassociateAlpha(frame);
if (frame_ == nullptr) {
return 0;
}
// Transform color to reference space
color_service_->ConvertFrame(frame);
frame_native_ts_ = frame_->native_timestamp();
if (alpha_is_associated) {
// If alpha was associated, reassociate here
ColorService::ReassociateAlpha(frame);
if (color_service_ == nullptr) {
// FIXME: Hardcoded values for testing
color_service_ = std::make_shared<ColorService>("srgb", OCIO::ROLE_SCENE_LINEAR);
}
// OpenColorIO v1's color transforms can be done on GPU, which improves performance but reduces accuracy. When
// online, we prefer accuracy over performance so we use the CPU path instead:
// NOTE: OCIO v2 boasts 1:1 results with the CPU and GPU path so this won't be necessary forever
if (renderer->mode() == olive::RenderMode::kOnline) {
// Convert to 32F, which is required for OpenColorIO's color transformation
frame_ = PixelService::ConvertPixelFormat(frame_, olive::PIX_FMT_RGBA32F);
if (alpha_is_associated) {
// Unassociate alpha here if associated
ColorService::DisassociateAlpha(frame_);
}
// Transform color to reference space
color_service_->ConvertFrame(frame_);
if (alpha_is_associated) {
// If alpha was associated, reassociate here
ColorService::ReassociateAlpha(frame_);
} else {
// If alpha was not associated, associate here
ColorService::AssociateAlpha(frame_);
}
}
// We use an internal texture to bring the texture into GPU space before performing transformations
// Ensure the texture is the accurate to the frame
if (internal_tex_.width() != frame_->width()
|| internal_tex_.height() != frame_->height()
|| internal_tex_.format() != frame_->format()) {
internal_tex_.Destroy();
}
// Create or upload the new data to the texture
if (!internal_tex_.IsCreated()) {
internal_tex_.Create(renderer->context(),
frame_->width(),
frame_->height(),
static_cast<olive::PixelFormat>(frame_->format()),
frame_->data());
} else {
// If alpha was not associated, associate here
ColorService::AssociateAlpha(frame);
internal_tex_.Upload(frame_->data());
}
}
// We use an internal texture to bring the texture into GPU space before performing transformations
// Ensure the texture is the accurate to the frame
if (internal_tex_.width() != frame->width()
|| internal_tex_.height() != frame->height()
|| internal_tex_.format() != frame->format()) {
internal_tex_.Destroy();
}
// Create or upload the new data to the texture
if (!internal_tex_.IsCreated()) {
internal_tex_.Create(renderer->context(),
frame->width(),
frame->height(),
static_cast<olive::PixelFormat>(frame->format()),
frame->data());
} else {
internal_tex_.Upload(frame->data());
}
// Create new texture in reference space to send throughout the rest of the graph
RenderTexturePtr output_texture = std::make_shared<RenderTexture>();
+3 -2
View File
@@ -53,8 +53,6 @@ public:
virtual void Hash(QCryptographicHash *hash, NodeOutput* from, const rational &time) override;
protected:
virtual QVariant Value(NodeOutput* output, const rational& time) override;
@@ -78,6 +76,9 @@ private:
QOpenGLContext* ocio_ctx_;
GLuint ocio_texture_;
FramePtr frame_;
int64_t frame_native_ts_;
};
#endif // IMAGE_H
+24 -16
View File
@@ -71,30 +71,28 @@ void Node::InvalidateCache(const rational &start_range, const rational &end_rang
{
Q_UNUSED(from)
ClearCachedValuesInParameters(start_range, end_range);
SendInvalidateCache(start_range, end_range);
}
void Node::SendInvalidateCache(const rational &start_range, const rational &end_range)
{
QList<NodeParam *> params = parameters();
// Loop through all parameters (there should be no children that are not NodeParams)
foreach (NodeParam* param, params) {
// If the Node is an output, relay the signal to any Nodes that are connected to it
if (param->type() == NodeParam::kOutput) {
QVector<NodeEdgePtr> edges = param->edges();
foreach (NodeEdgePtr edge, edges) {
NodeInput* connected_input = edge->input();
Node* connected_node = connected_input->parent();
// Only send this signal if the Node isn't ignoring invalidate cache signals from this input
if (!connected_node->ignore_invalid_cache_inputs_.contains(connected_input)) {
// Clear values cached in the parameters
connected_input->ClearCachedValue();
edge->output()->ClearCachedValue();
// Send clear cache signal to the Node
connected_node->InvalidateCache(start_range, end_range, connected_input);
}
// Send clear cache signal to the Node
connected_node->InvalidateCache(start_range, end_range, connected_input);
}
}
}
@@ -130,11 +128,6 @@ void Node::CopyInputs(Node *source, Node *destination)
}
}
void Node::IgnoreCacheInvalidationFrom(NodeInput *input)
{
ignore_invalid_cache_inputs_.append(input);
}
rational Node::LastProcessedTime()
{
rational t;
@@ -161,6 +154,21 @@ NodeOutput *Node::LastProcessedOutput()
return o;
}
void Node::ClearCachedValuesInParameters(const rational &start_range, const rational &end_range)
{
Q_UNUSED(start_range)
Q_UNUSED(end_range)
QList<NodeParam *> params = parameters();
// Loop through all parameters and clear cached values
foreach (NodeParam* param, params) {
//if (param->LastRequestedTime() >= start_range && param->LastRequestedTime() <= end_range) {
param->ClearCachedValue();
//}
}
}
QVariant Node::Run(NodeOutput* output, const rational& time)
{
return Value(output, time);
+4 -10
View File
@@ -209,11 +209,6 @@ protected:
*/
void RemoveParameter(NodeParam* param);
/**
* @brief If we receive a signal from NodeInput `input`, don't propagate it.
*/
void IgnoreCacheInvalidationFrom(NodeInput* input);
/**
* @brief The main processing function
*
@@ -239,6 +234,10 @@ protected:
*/
NodeOutput* LastProcessedOutput();
void ClearCachedValuesInParameters(const rational& start_range, const rational& end_range);
void SendInvalidateCache(const rational& start_range, const rational& end_range);
public slots:
signals:
@@ -266,11 +265,6 @@ private:
*/
bool HasParamWithID(const QString& id);
/**
* @brief Internal list of inputs to ignore InvalidateCache() signals from
*/
QList<NodeInput*> ignore_invalid_cache_inputs_;
/**
* @brief The last timecode Process() was called with
*/
+7 -1
View File
@@ -46,7 +46,7 @@ QVariant NodeOutput::get_value(const rational& time)
{
QVariant v;
if (time_ != time) {
if (time_ != time || !value_caching_) {
// Update the value
value_ = parent()->Run(this, time);
@@ -58,3 +58,9 @@ QVariant NodeOutput::get_value(const rational& time)
return v;
}
void NodeOutput::push_value(const QVariant &v, const rational &time)
{
value_ = v;
time_ = time;
}
+2
View File
@@ -63,6 +63,8 @@ public:
*/
virtual QVariant get_value(const rational &time);
void push_value(const QVariant& v, const rational& time);
private:
DataType data_type_;
+15 -11
View File
@@ -21,8 +21,7 @@
#include "viewer.h"
ViewerOutput::ViewerOutput() :
attached_viewer_(nullptr),
current_time_(0)
attached_viewer_(nullptr)
{
texture_input_ = new NodeInput("tex_out");
texture_input_->add_data_input(NodeInput::kTexture);
@@ -68,6 +67,9 @@ void ViewerOutput::AttachViewer(ViewerPanel *viewer)
// Disconnect old viewer if there's one attached
if (attached_viewer_ != nullptr) {
disconnect(attached_viewer_, SIGNAL(TimeChanged(const rational&)), this, SLOT(ViewerTimeChanged(const rational&)));
// Clear any existing texture
attached_viewer_->SetTexture(0);
}
// FIXME: Currently this attaches to ViewerPanels, but should it attached to Viewers instead?
@@ -76,24 +78,28 @@ void ViewerOutput::AttachViewer(ViewerPanel *viewer)
if (attached_viewer_ != nullptr) {
connect(attached_viewer_, SIGNAL(TimeChanged(const rational&)), this, SLOT(ViewerTimeChanged(const rational&)));
SetTimebase(timebase_);
// Update the texture
ViewerTimeChanged(attached_viewer_->GetTime());
}
}
void ViewerOutput::InvalidateCache(const rational &start_range, const rational &end_range, NodeInput *from)
{
if (start_range == current_time_ || end_range == current_time_) {
Node::InvalidateCache(start_range, end_range, from);
if (attached_viewer_ != nullptr
&& (start_range == attached_viewer_->GetTime() || end_range == attached_viewer_->GetTime())) {
// Update any attached viewer
UpdateViewer();
ForceUpdateViewer();
}
Node::InvalidateCache(start_range, end_range, from);
SendInvalidateCache(start_range, end_range);
}
void ViewerOutput::UpdateViewer()
void ViewerOutput::ForceUpdateViewer()
{
texture_input_->ClearCachedValue();
ViewerTimeChanged(current_time_);
ViewerTimeChanged(attached_viewer_->GetTime());
}
QVariant ViewerOutput::Value(NodeOutput *output, const rational &time)
@@ -115,6 +121,4 @@ void ViewerOutput::ViewerTimeChanged(const rational &t)
} else {
attached_viewer_->SetTexture(0);
}
current_time_ = t;
}
+1 -3
View File
@@ -53,7 +53,7 @@ protected:
virtual QVariant Value(NodeOutput* output, const rational& time) override;
private:
void UpdateViewer();
void ForceUpdateViewer();
NodeInput* texture_input_;
@@ -61,8 +61,6 @@ private:
rational timebase_;
rational current_time_;
private slots:
void ViewerTimeChanged(const rational& t);
+6
View File
@@ -28,6 +28,7 @@
NodeParam::NodeParam(const QString &id) :
time_(-1),
value_caching_(true),
id_(id)
{
Q_ASSERT(!id_.isEmpty());
@@ -251,3 +252,8 @@ const rational &NodeParam::LastRequestedTime()
{
return time_;
}
void NodeParam::SetValueCachingEnabled(bool enabled)
{
value_caching_ = enabled;
}
+7
View File
@@ -250,6 +250,8 @@ public:
*/
const rational& LastRequestedTime();
void SetValueCachingEnabled(bool enabled);
virtual DataType data_type() = 0;
signals:
@@ -285,6 +287,11 @@ protected:
*/
rational time_;
/**
* @brief Internal value for whether value caching is enabled
*/
bool value_caching_;
private:
/**
* @brief Internal name string
+110 -62
View File
@@ -99,30 +99,23 @@ QVariant RendererProcessor::Value(NodeOutput* output, const rational& time)
// Find frame in map
if (time_hash_map_.contains(time)) {
QString fn = CachePathName(time_hash_map_[time]);
if (QFileInfo::exists(fn)) {
qDebug() << "Reading" << fn;
auto in = OIIO::ImageInput::open(fn.toStdString());
if (in) {
in->read_image(PixelService::GetPixelFormatInfo(format_).oiio_desc, cache_frame_load_buffer_.data());
in->close();
qDebug() << "Stopped reading" << fn;
master_texture_->Upload(cache_frame_load_buffer_.data());
return QVariant::fromValue(master_texture_);
} else {
qDebug() << "OIIO failed to read EXR:" << OIIO::geterror().c_str();
qWarning() << "OIIO Error:" << OIIO::geterror().c_str();
}
} else {
qDebug() << "Texture did NOT exist";
}
} else {
qDebug() << "Hash map did NOT have this time";
}
}
@@ -138,14 +131,18 @@ void RendererProcessor::InvalidateCache(const rational &start_range, const ratio
{
Q_UNUSED(from)
//ClearCachedValuesInParameters(start_range, end_range);
texture_input_->ClearCachedValue();
length_input_->ClearCachedValue();
// Adjust range to min/max values
rational start_range_adj = qMax(rational(0), start_range);
rational end_range_adj = qMin(length_input()->get_value(0).value<rational>(), end_range);
qDebug() << "[RendererProcessor] Cache invalidated between"
/*qDebug() << "[RendererProcessor] Cache invalidated between"
<< start_range_adj.toDouble()
<< "and"
<< end_range_adj.toDouble();
<< end_range_adj.toDouble();*/
// Snap start_range to timebase
double start_range_dbl = start_range_adj.toDouble();
@@ -265,10 +262,30 @@ void RendererProcessor::Start()
// Ensure this connection is "Queued" so that it always runs in this object's threaded rather than any of the
// other threads
connect(threads_[i].get(), SIGNAL(FinishedPath()), this, SLOT(ThreadCallback()), Qt::QueuedConnection);
connect(threads_[i].get(), SIGNAL(RequestSibling(NodeDependency)), this, SLOT(ThreadRequestSibling(NodeDependency)), Qt::QueuedConnection);
connect(threads_.at(i).get(),
SIGNAL(RequestSibling(NodeDependency)),
this,
SLOT(ThreadRequestSibling(NodeDependency)),
Qt::QueuedConnection);
}
// Connect first thread (master thread) to the callback
connect(threads_.first().get(),
SIGNAL(CachedFrame(RenderTexturePtr, const rational&, const QByteArray&)),
this,
SLOT(ThreadCallback(RenderTexturePtr, const rational&, const QByteArray&)),
Qt::QueuedConnection);
connect(threads_.first().get(),
SIGNAL(FrameExists(const rational&, const QByteArray&)),
this,
SLOT(ThreadFrameAlreadyExists(const rational&, const QByteArray&)),
Qt::QueuedConnection);
connect(threads_.first().get(),
SIGNAL(FrameIgnored()),
this,
SLOT(ThreadIgnoredFrame()),
Qt::QueuedConnection);
download_threads_.resize(background_thread_count);
for (int i=0;i<download_threads_.size();i++) {
@@ -279,7 +296,8 @@ void RendererProcessor::Start()
connect(download_threads_[i].get(),
SIGNAL(Downloaded(const rational&, const QByteArray&)),
this,
SLOT(DownloadThreadFinished(const rational&, const QByteArray&)));
SLOT(MapHashToTimecode(const rational&, const QByteArray&)),
Qt::QueuedConnection);
}
last_download_thread_ = 0;
@@ -344,12 +362,11 @@ void RendererProcessor::CacheNext()
// Make sure cache has started
Start();
cache_frame_ = cache_queue_.takeFirst();
rational cache_frame = cache_queue_.takeFirst();
qDebug() << "[RendererProcessor] Caching" << cache_frame_.toDouble();
//qDebug() << "[RendererProcessor] Caching" << cache_frame.toDouble();
master_thread_ = threads_.at(0).get();
master_thread_->Queue(NodeDependency(texture_input_->get_connected_output(), cache_frame_), true);
threads_.first()->Queue(NodeDependency(texture_input_->get_connected_output(), cache_frame), true);
caching_ = true;
}
@@ -366,13 +383,33 @@ QString RendererProcessor::CachePathName(const QByteArray &hash)
bool RendererProcessor::HasHash(const QByteArray &hash)
{
download_list_mutex_.lock();
return QFileInfo::exists(CachePathName(hash));
}
bool dl_list_contains = download_list_.contains(hash);
bool RendererProcessor::IsCaching(const QByteArray &hash)
{
cache_hash_list_mutex_.lock();
download_list_mutex_.unlock();
bool is_caching = cache_hash_list_.contains(hash);
return QFileInfo::exists(CachePathName(hash)) || dl_list_contains;
cache_hash_list_mutex_.unlock();
return is_caching;
}
bool RendererProcessor::TryCache(const QByteArray &hash)
{
cache_hash_list_mutex_.lock();
bool is_caching = cache_hash_list_.contains(hash);
if (!is_caching) {
cache_hash_list_.append(hash);
}
cache_hash_list_mutex_.unlock();
return !is_caching;
}
void RendererProcessor::CalculateEffectiveDimensions()
@@ -381,68 +418,79 @@ void RendererProcessor::CalculateEffectiveDimensions()
effective_height_ = height_ / divider_;
}
void RendererProcessor::ThreadCallback()
void RendererProcessor::ThreadCallback(RenderTexturePtr texture, const rational& time, const QByteArray& hash)
{
if (sender() == master_thread_) {
// Threads are all done now, time to proceed
caching_ = false;
// Threads are all done now, time to proceed
caching_ = false;
RenderTexturePtr texture = master_thread_->texture();
if (texture != nullptr) {
// We received a texture, time to start downloading it
QString fn = CachePathName(hash);
bool hash_exists = HasHash(master_thread_->hash());
download_threads_[last_download_thread_%download_threads_.size()]->Queue(texture,
fn,
time,
hash);
if (!hash_exists) {
if (texture == nullptr) {
// We didn't receive a texture to download, but the viewer may still need updating
DownloadThreadFinished(cache_frame_, master_thread_->hash());
} else {
// We received a texture, time to start downloading it
download_list_mutex_.lock();
download_list_.append(master_thread_->hash());
download_list_mutex_.unlock();
QString fn = CachePathName(master_thread_->hash());
download_threads_[last_download_thread_%download_threads_.size()]->Queue(texture,
fn,
cache_frame_,
master_thread_->hash());
last_download_thread_++;
}
}
CacheNext();
last_download_thread_++;
} else {
// There was no texture here, we must update the viewer
MapHashToTimecode(time, hash);
}
// If the connected output is using this time, signal it to update
if (texture_output_->IsConnected()
&& texture_output_->LastRequestedTime() == time) {
texture_output_->push_value(QVariant::fromValue(texture), time);
SendInvalidateCache(time, time);
}
CacheNext();
}
void RendererProcessor::ThreadRequestSibling(NodeDependency dep)
{
// Try to queue another thread to run this dep in advance
for (int i=0;i<threads_.size();i++) {
if (threads_.at(i).get() != master_thread_
&& threads_.at(i)->Queue(dep, false)) {
for (int i=1;i<threads_.size();i++) {
if (threads_.at(i)->Queue(dep, false)) {
return;
}
}
}
void RendererProcessor::DownloadThreadFinished(const rational& time, const QByteArray& hash)
void RendererProcessor::ThreadFrameAlreadyExists(const rational &time, const QByteArray &hash)
{
caching_ = false;
// Update hash map with new hash
MapHashToTimecode(time, hash);
// Signal output to update value
if (texture_output_->IsConnected()
&& texture_output_->LastRequestedTime() == time) {
texture_output_->ClearCachedValue();
SendInvalidateCache(time, time);
}
// Start caching the next frame
CacheNext();
}
void RendererProcessor::MapHashToTimecode(const rational& time, const QByteArray& hash)
{
// Insert into hash map
time_hash_map_.insert(time, hash);
download_list_mutex_.lock();
download_list_.removeAll(hash);
download_list_mutex_.unlock();
cache_hash_list_mutex_.lock();
cache_hash_list_.removeAll(hash);
cache_hash_list_mutex_.unlock();
}
// Check if we just downloaded (aka finished caching) the frame we're currently on
if (texture_output_->IsConnected()
&& texture_output_->LastRequestedTime() == time) {
texture_output_->ClearCachedValue();
void RendererProcessor::ThreadIgnoredFrame()
{
caching_ = false;
Node::InvalidateCache(time, time, nullptr);
}
CacheNext();
}
RendererThreadBase* RendererProcessor::CurrentThread()
+20 -6
View File
@@ -89,6 +89,16 @@ public:
*/
bool HasHash(const QByteArray& hash);
/**
* @brief Return whether a frame is currently being cached
*/
bool IsCaching(const QByteArray& hash);
/**
* @brief Check if a frame is currently being cached, and if not reserve it
*/
bool TryCache(const QByteArray& hash);
/**
* @brief Return current instance of a RenderThread (or nullptr if there is none)
*
@@ -131,6 +141,8 @@ private:
*/
void CacheNext();
bool ShouldPushTexture(const rational &time);
/**
* @brief Return the path of the cached image at this time
*/
@@ -174,8 +186,6 @@ private:
QString cache_id_;
bool caching_;
RendererProcessThread* master_thread_;
rational cache_frame_;
QVector<uchar*> cache_frame_load_buffer_;
QVector<RendererDownloadThreadPtr> download_threads_;
@@ -185,15 +195,19 @@ private:
QMap<rational, QByteArray> time_hash_map_;
QMutex download_list_mutex_;
QVector<QByteArray> download_list_;
QMutex cache_hash_list_mutex_;
QVector<QByteArray> cache_hash_list_;
private slots:
void ThreadCallback();
void ThreadCallback(RenderTexturePtr texture, const rational& time, const QByteArray& hash);
void ThreadRequestSibling(NodeDependency dep);
void DownloadThreadFinished(const rational &time, const QByteArray &hash);
void ThreadFrameAlreadyExists(const rational &time, const QByteArray &hash);
void MapHashToTimecode(const rational &time, const QByteArray &hash);
void ThreadIgnoredFrame();
};
@@ -113,11 +113,9 @@ void RendererDownloadThread::ProcessLoop()
std::unique_ptr<OIIO::ImageOutput> out = OIIO::ImageOutput::create(working_fn_std);
if (out) {
qDebug() << "Writing" << entry.filename;
out->open(working_fn_std, spec);
out->write_image(format_info.oiio_desc, data_buffer.data());
out->close();
qDebug() << "Stopped writing" << entry.filename;
emit Downloaded(entry.time, entry.hash);
} else {
@@ -61,16 +61,6 @@ bool RendererProcessThread::Queue(const NodeDependency& dep, bool wait)
return true;
}
const QByteArray &RendererProcessThread::hash()
{
return hash_;
}
RenderTexturePtr RendererProcessThread::texture()
{
return texture_;
}
void RendererProcessThread::Cancel()
{
cancelled_ = true;
@@ -115,20 +105,27 @@ void RendererProcessThread::ProcessLoop()
texture_ = nullptr;
if (!parent_->HasHash(hash_)) {
QList<NodeDependency> deps = node_to_process->RunDependencies(output_to_process, path_.time());
bool has_hash = parent_->HasHash(hash_);
bool can_cache = false;
// Ask for other threads to run these deps while we're here
if (!deps.isEmpty()) {
for (int i=1;i<deps.size();i++) {
emit RequestSibling(deps.at(i));
if (!has_hash){
if ((can_cache = parent_->TryCache(hash_))) {
QList<NodeDependency> deps = node_to_process->RunDependencies(output_to_process, path_.time());
// Ask for other threads to run these deps while we're here
if (!deps.isEmpty()) {
for (int i=1;i<deps.size();i++) {
emit RequestSibling(deps.at(i));
}
}
// Get the requested value
texture_ = output_to_process->get_value(path_.time()).value<RenderTexturePtr>();
render_instance()->context()->functions()->glFinish();
}
// Get the requested value
texture_ = output_to_process->get_value(path_.time()).value<RenderTexturePtr>();
render_instance()->context()->functions()->glFinish();
}
foreach (Node* dep, all_deps) {
@@ -137,6 +134,15 @@ void RendererProcessThread::ProcessLoop()
node_to_process->Unlock();
emit FinishedPath();
if (has_hash && !parent_->IsCaching(hash_)) {
// This hash already exists, no need to cache, just record it
emit FrameExists(path_.time(), hash_);
} else if (can_cache) {
// We cached this frame, signal that it will need to be downloaded to disk
emit CachedFrame(texture_, path_.time(), hash_);
} else {
// Some other dork is caching this frame, skip it
emit FrameIgnored();
}
}
}
@@ -38,10 +38,6 @@ public:
bool Queue(const NodeDependency &dep, bool wait);
const QByteArray& hash();
RenderTexturePtr texture();
public slots:
virtual void Cancel() override;
@@ -51,15 +47,17 @@ protected:
signals:
void RequestSibling(NodeDependency dep);
void FinishedPath();
void CachedFrame(RenderTexturePtr texture, const rational& time, const QByteArray& hash);
void FrameExists(const rational& time, const QByteArray& hash);
void FrameIgnored();
private:
RendererProcessor* parent_;
NodeDependency path_;
rational time_;
QByteArray hash_;
RenderTexturePtr texture_;
+5
View File
@@ -74,6 +74,11 @@ void ViewerPanel::SetTimebase(const rational &timebase)
viewer_->SetTimebase(timebase);
}
rational ViewerPanel::GetTime()
{
return viewer_->GetTime();
}
void ViewerPanel::SetTexture(GLuint tex)
{
viewer_->SetTexture(tex);
+2
View File
@@ -50,6 +50,8 @@ public:
void SetTimebase(const rational& timebase);
rational GetTime();
public slots:
/**
* @brief Set the texture to draw and draw it
@@ -64,9 +64,20 @@ void NodeParamViewWidgetBridge::CreateWidgets()
case NodeParam::kFloat:
{
FloatSlider* slider = new FloatSlider();
slider->SetValue(base_input->get_value(0).toDouble());
widgets_.append(slider);
if (base_input->has_minimum()) {
slider->SetMinimum(base_input->minimum().toDouble());
}
if (base_input->has_maximum()) {
slider->SetMaximum(base_input->maximum().toDouble());
}
connect(slider, SIGNAL(ValueChanged(double)), this, SLOT(WidgetCallback()));
widgets_.append(slider);
break;
}
case NodeParam::kVec2:
+10
View File
@@ -36,6 +36,16 @@ void FloatSlider::SetValue(const double &d)
SliderBase::SetValue(d);
}
void FloatSlider::SetMinimum(const double &d)
{
SetMinimumInternal(d);
}
void FloatSlider::SetMaximum(const double &d)
{
SetMaximumInternal(d);
}
void FloatSlider::SetDecimalPlaces(int i)
{
decimal_places_ = i;
+4
View File
@@ -33,6 +33,10 @@ public:
void SetValue(const double& d);
void SetMinimum(const double& d);
void SetMaximum(const double& d);
void SetDecimalPlaces(int i);
signals:
+10
View File
@@ -36,6 +36,16 @@ void IntegerSlider::SetValue(const int &v)
SliderBase::SetValue(v);
}
void IntegerSlider::SetMinimum(const int &d)
{
SetMinimumInternal(d);
}
void IntegerSlider::SetMaximum(const int &d)
{
SetMaximumInternal(d);
}
void IntegerSlider::ConvertValue(QVariant v)
{
emit ValueChanged(v.toInt());
+4
View File
@@ -33,6 +33,10 @@ public:
void SetValue(const int& v);
void SetMinimum(const int& d);
void SetMaximum(const int& d);
signals:
void ValueChanged(int);
+45 -12
View File
@@ -28,6 +28,8 @@ SliderBase::SliderBase(Mode mode, QWidget *parent) :
QStackedWidget(parent),
decimal_places_(1),
drag_multiplier_(1.0),
has_min_(false),
has_max_(false),
mode_(mode),
dragged_(false)
{
@@ -49,16 +51,14 @@ SliderBase::SliderBase(Mode mode, QWidget *parent) :
switch (mode_) {
case kString:
setCursor(Qt::PointingHandCursor);
value_ = "";
SetValue("");
break;
case kInteger:
case kFloat:
setCursor(Qt::SizeHorCursor);
value_ = 0;
SetValue(0);
break;
}
UpdateLabel(value_);
}
void SliderBase::SetDragMultiplier(const double &d)
@@ -77,11 +77,33 @@ const QVariant &SliderBase::Value()
void SliderBase::SetValue(const QVariant &v)
{
value_ = v;
value_ = ClampValue(v);
UpdateLabel(value_);
}
void SliderBase::SetMinimumInternal(const QVariant &v)
{
min_value_ = v;
has_min_ = true;
// Limit value by this new minimum value
if (value_ < min_value_) {
SetValue(min_value_);
}
}
void SliderBase::SetMaximumInternal(const QVariant &v)
{
max_value_ = v;
has_max_ = true;
// Limit value by this new maximum value
if (value_ > max_value_) {
SetValue(max_value_);
}
}
void SliderBase::changeEvent(QEvent *e)
{
if (e->type() == QEvent::LanguageChange) {
@@ -90,6 +112,19 @@ void SliderBase::changeEvent(QEvent *e)
QStackedWidget::changeEvent(e);
}
const QVariant &SliderBase::ClampValue(const QVariant &v)
{
if (has_min_ && v < min_value_) {
return min_value_;
}
if (has_max_ && v > max_value_) {
return max_value_;
}
return v;
}
void SliderBase::UpdateLabel(const QVariant &v)
{
switch (mode_) {
@@ -132,14 +167,12 @@ void SliderBase::LabelClicked()
// No-op
break;
case kInteger:
value_ = qRound(dragged_diff_);
SetValue(qRound(dragged_diff_));
break;
case kFloat:
value_ = dragged_diff_;
SetValue(dragged_diff_);
break;
}
UpdateLabel(value_);
} else {
// This was a simple click
@@ -177,6 +210,8 @@ void SliderBase::LabelDragged(int i)
temp_dragged_value_ = dragged_diff_;
}
temp_dragged_value_ = ClampValue(temp_dragged_value_);
UpdateLabel(temp_dragged_value_);
emit ValueChanged(temp_dragged_value_);
break;
@@ -210,9 +245,7 @@ void SliderBase::LineEditConfirmed()
}
if (is_valid) {
value_ = test_val;
UpdateLabel(value_);
SetValue(test_val);
emit ValueChanged(value_);
+12
View File
@@ -48,6 +48,10 @@ protected:
void SetValue(const QVariant& v);
void SetMinimumInternal(const QVariant& v);
void SetMaximumInternal(const QVariant& v);
void UpdateLabel(const QVariant& v);
virtual void changeEvent(QEvent* e) override;
@@ -57,12 +61,20 @@ protected:
double drag_multiplier_;
private:
const QVariant& ClampValue(const QVariant& v);
SliderLabel* label_;
SliderLineEdit* editor_;
QVariant value_;
bool has_min_;
QVariant min_value_;
bool has_max_;
QVariant max_value_;
Mode mode_;
bool dragged_;
+1 -1
View File
@@ -92,7 +92,7 @@ void TimeRuler::SetTimebase(const rational &r)
update();
}
const int64_t &TimeRuler::Time()
const int64_t &TimeRuler::GetTime()
{
return time_;
}
+1 -1
View File
@@ -42,7 +42,7 @@ public:
void SetCenteredText(bool c);
const int64_t& Time();
const int64_t& GetTime();
public slots:
void SetTime(const int64_t &r);
+8 -3
View File
@@ -91,6 +91,11 @@ const double &ViewerWidget::scale()
return ruler_->scale();
}
rational ViewerWidget::GetTime()
{
return rational(ruler_->GetTime()) * time_base_;
}
void ViewerWidget::SetScale(const double &scale_)
{
ruler_->SetScale(scale_);
@@ -145,7 +150,7 @@ void ViewerWidget::Play()
}
start_msec_ = QDateTime::currentMSecsSinceEpoch();
start_timestamp_ = ruler_->Time();
start_timestamp_ = ruler_->GetTime();
playback_timer_.start();
@@ -170,14 +175,14 @@ void ViewerWidget::PrevFrame()
{
Pause();
SetTime(ruler_->Time() - 1);
SetTime(qMax(static_cast<int64_t>(0), ruler_->GetTime() - 1));
}
void ViewerWidget::NextFrame()
{
Pause();
SetTime(ruler_->Time() + 1);
SetTime(ruler_->GetTime() + 1);
}
void ViewerWidget::GoToEnd()
+2
View File
@@ -49,6 +49,8 @@ public:
const double& scale();
rational GetTime();
void SetScale(const double& scale_);
void SetTime(const int64_t& time);