reimplemented export process

This commit is contained in:
itsmattkc
2020-11-13 22:05:43 +11:00
parent 8776bb9c4a
commit c9a8b96c89
31 changed files with 535 additions and 318 deletions
+1 -1
View File
@@ -232,7 +232,7 @@ void EncodingParams::Save(QXmlStreamWriter *writer) const
Encoder* Encoder::CreateFromID(const QString &id, const EncodingParams& params)
{
Q_UNUSED(id)
return new FFmpegEncoder(params);
}
+5
View File
@@ -122,6 +122,11 @@ public:
virtual void Close() = 0;
virtual PixelFormat::Format GetDesiredPixelFormat() const
{
return PixelFormat::PIX_FMT_INVALID;
}
private:
EncodingParams params_;
+5
View File
@@ -47,6 +47,11 @@ public:
virtual void Close() override;
virtual PixelFormat::Format GetDesiredPixelFormat() const override
{
return video_conversion_fmt_;
}
private:
/**
* @brief Handle an error
+42 -1
View File
@@ -75,7 +75,7 @@ QString FileFunctions::GetTempFilePath()
{
QString temp_path = QDir(QDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation))
.filePath(QCoreApplication::organizationName()))
.filePath(QCoreApplication::applicationName());
.filePath(QCoreApplication::applicationName());
// Ensure it exists
QDir(temp_path).mkpath(".");
@@ -199,4 +199,45 @@ QString FileFunctions::ReadFileAsString(const QString &filename)
return file_data;
}
QString FileFunctions::GetSafeTemporaryFilename(const QString &original)
{
int counter = 0;
QFileInfo original_info(original);
QString basename = original_info.baseName();
QString complete_suffix = original_info.completeSuffix();
// If we have a complete suffix, make sure there's a period in it
if (!complete_suffix.isEmpty()) {
complete_suffix.prepend('.');
}
QString temp_abs_path;
do {
temp_abs_path = original_info.dir().filePath(
QStringLiteral("%1.tmp%2%3").arg(basename,
QString::number(counter),
complete_suffix));
counter++;
} while (QFileInfo::exists(temp_abs_path));
return temp_abs_path;
}
bool FileFunctions::RenameFileAllowOverwrite(const QString &from, const QString &to)
{
if (QFileInfo::exists(to) && !QFile::remove(to)) {
qCritical() << "Couldn't remove existing file" << to << "for overwrite";
return false;
}
// By this point, we can assume `to` either never existed or has now been deleted
if (!QFile::rename(from, to)) {
qCritical() << "Failed to rename file" << from << "to" << to;
return false;
}
return true;
}
OLIVE_NAMESPACE_EXIT
+17
View File
@@ -67,6 +67,23 @@ public:
static QString ReadFileAsString(const QString& filename);
/**
* @brief Returns a temporary filename that can be used while writing rather than the original
*
* If overwriting a file, it's safest to write to a new file first and then only replace it at
* the end so that if the program crashes or the user cancels the save half way through, the
* original file is still intact.
*
* This function returns a slight variant of the filename provided that's guaranteed to not exist
* and therefore won't overwrite anything important.
*/
static QString GetSafeTemporaryFilename(const QString& original);
/**
* @brief Renames a file from `from` to `to`, deleting `to` if such a file already exists first
*/
static bool RenameFileAllowOverwrite(const QString& from, const QString& to);
};
+9 -1
View File
@@ -204,7 +204,10 @@ void Config::Load()
void Config::Save()
{
QFile config_file(GetConfigFilePath());
QString real_filename = GetConfigFilePath();
QString temp_filename = FileFunctions::GetSafeTemporaryFilename(real_filename);
QFile config_file(temp_filename);
if (!config_file.open(QFile::WriteOnly)) {
QMessageBox::critical(Core::instance()->main_window(),
@@ -243,6 +246,11 @@ void Config::Save()
writer.writeEndDocument();
config_file.close();
if (!FileFunctions::RenameFileAllowOverwrite(temp_filename, real_filename)) {
qWarning() << QStringLiteral("Failed to overwrite \"%1\". Config has been saved as \"%2\" instead.")
.arg(real_filename, temp_filename);
}
}
QVariant Config::operator[](const QString &key) const
+27 -20
View File
@@ -179,6 +179,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
video_tab_->height_slider()->SetDefaultValue(viewer_node_->video_params().height());
video_tab_->frame_rate_combobox()->SetFrameRate(viewer_node_->video_params().time_base().flipped());
video_tab_->pixel_aspect_combobox()->SetPixelAspectRatio(viewer_node_->video_params().pixel_aspect_ratio());
video_tab_->pixel_format_field()->SetPixelFormat(PixelFormat::instance()->GetConfiguredFormatForMode(RenderMode::kOnline));
video_tab_->interlaced_combobox()->SetInterlaceMode(viewer_node_->video_params().interlacing());
audio_tab_->sample_rate_combobox()->SetSampleRate(viewer_node_->audio_params().sample_rate());
audio_tab_->channel_layout_combobox()->SetChannelLayout(viewer_node_->audio_params().channel_layout());
@@ -285,25 +286,36 @@ void ExportDialog::StartExport()
}
// Validate video resolution
if (video_enabled_->isChecked()) {
if (video_tab_->width_slider()->GetValue() % 2 != 0
|| video_tab_->height_slider()->GetValue() % 2 != 0) {
QMessageBox b(this);
b.setIcon(QMessageBox::Critical);
b.setWindowModality(Qt::WindowModal);
b.setWindowTitle(tr("Invalid parameters"));
b.setText(tr("Width and height must be multiples of 2."));
b.exec();
return;
}
if (video_enabled_->isChecked()
&& video_tab_->GetSelectedCodec() == ExportCodec::kCodecH264
&& (video_tab_->width_slider()->GetValue()%2 != 0 || video_tab_->height_slider()->GetValue()%2 != 0)) {
QMessageBox b(this);
b.setIcon(QMessageBox::Critical);
b.setWindowModality(Qt::WindowModal);
b.setWindowTitle(tr("Invalid Parameters"));
b.setText(tr("Width and height must be multiples of 2."));
b.exec();
return;
}
ExportTask* task = new ExportTask(viewer_node_, color_manager_, GenerateParams());
TaskDialog* td = new TaskDialog(task, tr("Export"), this);
connect(td, &TaskDialog::TaskSucceeded, this, &QDialog::accept);
connect(td, &TaskDialog::TaskSucceeded, this, &ExportDialog::ExportFinished);
td->open();
}
void ExportDialog::ExportFinished()
{
TaskDialog* td = static_cast<TaskDialog*>(sender());
if (td->GetTask()->IsCancelled()) {
// If this task was cancelled, we stay open so the user can potentially queue another export
} else {
// Accept this dialog and close
this->accept();
}
}
void ExportDialog::closeEvent(QCloseEvent *e)
{
preview_viewer_->ConnectViewerNode(nullptr);
@@ -373,7 +385,7 @@ void ExportDialog::ResolutionChanged()
new_width *= video_aspect_ratio_;
// Align to even number and set
video_tab_->width_slider()->SetValue(AlignEvenNumber(new_width));
video_tab_->width_slider()->SetValue(new_width);
} else {
@@ -384,7 +396,7 @@ void ExportDialog::ResolutionChanged()
new_height /= video_aspect_ratio_;
// Align to even number and set
video_tab_->height_slider()->SetValue(AlignEvenNumber(new_height));
video_tab_->height_slider()->SetValue(new_height);
}
}
@@ -414,17 +426,12 @@ void ExportDialog::SetDefaultFilename()
filename_edit_->setText(file_location);
}
int ExportDialog::AlignEvenNumber(double d)
{
return qCeil(d * 0.5) * 2;
}
ExportParams ExportDialog::GenerateParams() const
{
VideoParams video_render_params(static_cast<int>(video_tab_->width_slider()->GetValue()),
static_cast<int>(video_tab_->height_slider()->GetValue()),
video_tab_->frame_rate_combobox()->GetFrameRate().flipped(),
PixelFormat::instance()->GetConfiguredFormatForMode(RenderMode::kOnline),
video_tab_->pixel_format_field()->GetPixelFormat(),
video_tab_->pixel_aspect_combobox()->GetPixelAspectRatio(),
video_tab_->interlaced_combobox()->GetInterlaceMode(),
1);
+2 -2
View File
@@ -49,8 +49,6 @@ private:
void LoadPresets();
void SetDefaultFilename();
static int AlignEvenNumber(double d);
ExportParams GenerateParams() const;
ViewerOutput* viewer_node_;
@@ -85,6 +83,8 @@ private slots:
void StartExport();
void ExportFinished();
};
OLIVE_NAMESPACE_EXIT
+7
View File
@@ -115,6 +115,13 @@ QWidget* ExportVideoTab::SetupResolutionSection()
interlaced_combobox_ = new InterlacedComboBox();
layout->addWidget(interlaced_combobox_, row, 1);
row++;
layout->addWidget(new QLabel(tr("Quality:")), row, 0);
pixel_format_field_ = new PixelFormatComboBox(true);
layout->addWidget(pixel_format_field_, row, 1);
return resolution_group;
}
+6
View File
@@ -111,6 +111,11 @@ public:
return pixel_aspect_combobox_;
}
PixelFormatComboBox* pixel_format_field() const
{
return pixel_format_field_;
}
const int& threads() const
{
return threads_;
@@ -149,6 +154,7 @@ private:
InterlacedComboBox* interlaced_combobox_;
PixelAspectRatioComboBox* pixel_aspect_combobox_;
PixelFormatComboBox* pixel_format_field_;
int threads_;
@@ -74,7 +74,7 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidg
preview_resolution_label_ = new QLabel();
preview_layout->addWidget(preview_resolution_label_, row, 2);
row++;
preview_layout->addWidget(new QLabel(tr("Format:")), row, 0);
preview_layout->addWidget(new QLabel(tr("Quality:")), row, 0);
preview_format_field_ = new PixelFormatComboBox(true);
preview_layout->addWidget(preview_format_field_, row, 1, 1, 2);
layout->addWidget(preview_group);
+3 -1
View File
@@ -70,8 +70,10 @@ private:
};
using ColorProcessorChain = QList<ColorProcessorPtr>;
using ColorProcessorChain = QVector<ColorProcessorPtr>;
OLIVE_NAMESPACE_EXIT
Q_DECLARE_METATYPE(OLIVE_NAMESPACE::ColorProcessorPtr);
#endif // COLORPROCESSOR_H
-12
View File
@@ -36,16 +36,6 @@ public:
iterative_input_ = nullptr;
}
const QMatrix4x4& GetMatrix() const
{
return matrix_;
}
void SetMatrix(const QMatrix4x4& matrix)
{
matrix_ = matrix;
}
const QString& GetShaderID() const
{
return shader_id_;
@@ -101,8 +91,6 @@ private:
QHash<QString, Texture::Interpolation> interpolation_;
QMatrix4x4 matrix_;
};
OLIVE_NAMESPACE_EXIT
+3 -2
View File
@@ -431,8 +431,9 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video
static_cast<GLfloat>(destination_params.width()),
static_cast<GLfloat>(destination_params.height()));
// Set matrix to identity
shader->setUniformValue("ove_mvpmat", job.GetMatrix());
// Ensure matrix is set, at least to identity
shader->setUniformValue("ove_mvpmat",
job.GetValue(QStringLiteral("ove_mvpmat")).data.value<QMatrix4x4>());
// Set the viewport to the "physical" resolution of the destination
functions_->glViewport(0, 0,
+2 -1
View File
@@ -127,7 +127,8 @@ PixelFormat *PixelFormat::instance()
PixelFormat::Format PixelFormat::GetConfiguredFormatForMode(RenderMode::Mode mode)
{
return static_cast<PixelFormat::Format>(Core::GetPreferenceForRenderMode(mode, QStringLiteral("PixelFormat")).toInt());
return static_cast<PixelFormat::Format>(
Core::GetPreferenceForRenderMode(mode, QStringLiteral("PixelFormat")).toInt());
}
void PixelFormat::SetConfiguredFormatForMode(RenderMode::Mode mode, PixelFormat::Format format)
+8 -12
View File
@@ -23,7 +23,6 @@
#include <QFloat16>
#include "common/ocioutils.h"
#include "render/colormanager.h"
OLIVE_NAMESPACE_ENTER
@@ -57,14 +56,14 @@ TexturePtr Renderer::CreateTexture(const VideoParams &params, const void *data,
return CreateTexture(params, Texture::k2D, Texture::kRGBA, data, linesize);
}
void Renderer::BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, Texture *destination, bool flipped)
void Renderer::BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, Texture *destination, const QMatrix4x4 &matrix)
{
BlitColorManagedInternal(color_processor, source, destination, destination->params(), flipped);
BlitColorManagedInternal(color_processor, source, destination, destination->params(), matrix);
}
void Renderer::BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, VideoParams params, bool flipped)
void Renderer::BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, VideoParams params, const QMatrix4x4& matrix)
{
BlitColorManagedInternal(color_processor, source, nullptr, params, flipped);
BlitColorManagedInternal(color_processor, source, nullptr, params, matrix);
}
void Renderer::Destroy()
@@ -195,7 +194,7 @@ bool Renderer::GetColorContext(ColorProcessorPtr color_processor, Renderer::Colo
}
}
void Renderer::BlitColorManagedInternal(ColorProcessorPtr color_processor, TexturePtr source, Texture *destination, VideoParams params, bool flipped)
void Renderer::BlitColorManagedInternal(ColorProcessorPtr color_processor, TexturePtr source, Texture *destination, VideoParams params, const QMatrix4x4& matrix)
{
ColorContext color_ctx;
if (!GetColorContext(color_processor, &color_ctx)) {
@@ -203,7 +202,10 @@ void Renderer::BlitColorManagedInternal(ColorProcessorPtr color_processor, Textu
}
ShaderJob job;
job.InsertValue(QStringLiteral("ove_maintex"), ShaderValue(QVariant::fromValue(source), NodeParam::kTexture));
job.InsertValue(QStringLiteral("ove_mvpmat"), ShaderValue(matrix, NodeParam::kMatrix));
foreach (const ColorContext::LUT& l, color_ctx.lut3d_textures) {
job.InsertValue(l.name, ShaderValue(QVariant::fromValue(l.texture), NodeParam::kTexture));
job.SetInterpolation(l.name, l.interpolation);
@@ -213,12 +215,6 @@ void Renderer::BlitColorManagedInternal(ColorProcessorPtr color_processor, Textu
job.SetInterpolation(l.name, l.interpolation);
}
if (flipped) {
QMatrix4x4 mat;
mat.scale(1, -1, 1);
job.SetMatrix(mat);
}
if (destination) {
BlitToTexture(color_ctx.compiled_shader, job, destination);
} else {
+3 -3
View File
@@ -60,8 +60,8 @@ public:
Blit(shader, job, nullptr, params);
}
void BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, Texture* destination, bool flipped = false);
void BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, VideoParams params, bool flipped = false);
void BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, Texture* destination, const QMatrix4x4& matrix = QMatrix4x4());
void BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr source, VideoParams params, const QMatrix4x4& matrix = QMatrix4x4());
void Destroy();
@@ -108,7 +108,7 @@ private:
bool GetColorContext(ColorProcessorPtr color_processor, ColorContext* ctx);
void BlitColorManagedInternal(ColorProcessorPtr color_processor, TexturePtr source,
Texture* destination, VideoParams params, bool flipped);
Texture* destination, VideoParams params, const QMatrix4x4 &matrix);
QHash<QString, ColorContext> color_cache_;
+42 -11
View File
@@ -56,8 +56,10 @@ RenderManager::RenderManager(QObject *parent) :
still_cache_ = new StillImageCache();
decoder_cache_ = new DecoderCache();
shader_cache_ = new ShaderCache();
default_shader_ = context_->CreateNativeShader(ShaderCode(QString(), QString()));
} else {
qCritical() << "Tried to initialize unknown graphics backend";
context_ = nullptr;
still_cache_ = nullptr;
decoder_cache_ = nullptr;
}
@@ -65,12 +67,16 @@ RenderManager::RenderManager(QObject *parent) :
RenderManager::~RenderManager()
{
delete shader_cache_;
delete decoder_cache_;
delete still_cache_;
if (context_) {
context_->DestroyNativeShader(default_shader_);
context_->Destroy();
delete context_;
delete shader_cache_;
delete decoder_cache_;
delete still_cache_;
context_->Destroy();
delete context_;
}
}
QByteArray RenderManager::Hash(const Node *n, const VideoParams &params, const rational &time)
@@ -93,19 +99,31 @@ QByteArray RenderManager::Hash(const Node *n, const VideoParams &params, const r
return hasher.result();
}
RenderTicketPtr RenderManager::RenderFrame(ViewerOutput *viewer, ColorManager *color_manager, const rational &time, RenderMode::Mode mode, FrameHashCache *cache, bool prioritize)
RenderTicketPtr RenderManager::RenderFrame(ViewerOutput* viewer, ColorManager* color_manager,
const rational& time, RenderMode::Mode mode,
FrameHashCache* cache, bool prioritize)
{
return RenderFrame(viewer,
color_manager,
time,
mode,
viewer->video_params(),
viewer->audio_params(),
QSize(0, 0),
QMatrix4x4(),
PixelFormat::PIX_FMT_INVALID,
nullptr,
cache,
prioritize);
}
RenderTicketPtr RenderManager::RenderFrame(ViewerOutput* viewer, ColorManager* color_manager, const rational &time, RenderMode::Mode mode, const QSize &force_size, const QMatrix4x4 &matrix, FrameHashCache *cache, bool prioritize)
RenderTicketPtr RenderManager::RenderFrame(ViewerOutput* viewer, ColorManager* color_manager,
const rational& time, RenderMode::Mode mode,
const VideoParams &video_params, const AudioParams &audio_params,
const QSize& force_size,
const QMatrix4x4& force_matrix, PixelFormat::Format force_format,
ColorProcessorPtr force_color_output,
FrameHashCache* cache, bool prioritize)
{
// Create ticket
RenderTicketPtr ticket = std::make_shared<RenderTicket>();
@@ -113,11 +131,18 @@ RenderTicketPtr RenderManager::RenderFrame(ViewerOutput* viewer, ColorManager* c
ticket->setProperty("viewer", Node::PtrToValue(viewer));
ticket->setProperty("time", QVariant::fromValue(time));
ticket->setProperty("size", force_size);
ticket->setProperty("matrix", matrix);
ticket->setProperty("matrix", force_matrix);
ticket->setProperty("format", force_format);
ticket->setProperty("mode", mode);
ticket->setProperty("type", kTypeVideo);
ticket->setProperty("cache", cache->GetCacheDirectory());
ticket->setProperty("colormanager", Node::PtrToValue(color_manager));
ticket->setProperty("coloroutput", QVariant::fromValue(force_color_output));
ticket->setProperty("vparam", QVariant::fromValue(video_params));
ticket->setProperty("aparam", QVariant::fromValue(audio_params));
if (cache) {
ticket->setProperty("cache", cache->GetCacheDirectory());
}
// Queue appending the ticket and running the next job on our thread to make this function thread-safe
QMetaObject::invokeMethod(this, "AddTicket", Qt::AutoConnection,
@@ -127,7 +152,12 @@ RenderTicketPtr RenderManager::RenderFrame(ViewerOutput* viewer, ColorManager* c
return ticket;
}
RenderTicketPtr RenderManager::RenderAudio(ViewerOutput* viewer, const TimeRange &r, bool generate_waveforms, bool prioritize)
RenderTicketPtr RenderManager::RenderAudio(ViewerOutput* viewer, const TimeRange& r, bool generate_waveforms, bool prioritize)
{
return RenderAudio(viewer, r, viewer->audio_params(), generate_waveforms, prioritize);
}
RenderTicketPtr RenderManager::RenderAudio(ViewerOutput* viewer, const TimeRange &r, const AudioParams &params, bool generate_waveforms, bool prioritize)
{
// Create ticket
RenderTicketPtr ticket = std::make_shared<RenderTicket>();
@@ -136,6 +166,7 @@ RenderTicketPtr RenderManager::RenderAudio(ViewerOutput* viewer, const TimeRange
ticket->setProperty("time", QVariant::fromValue(r));
ticket->setProperty("type", kTypeAudio);
ticket->setProperty("waveforms", generate_waveforms);
ticket->setProperty("aparam", QVariant::fromValue(params));
// Queue appending the ticket and running the next job on our thread to make this function thread-safe
QMetaObject::invokeMethod(this, "AddTicket", Qt::AutoConnection,
@@ -165,7 +196,7 @@ RenderTicketPtr RenderManager::SaveFrameToCache(FrameHashCache *cache, FramePtr
void RenderManager::RunTicket(RenderTicketPtr ticket) const
{
RenderProcessor::Process(ticket, context_, still_cache_, decoder_cache_, shader_cache_);
RenderProcessor::Process(ticket, context_, still_cache_, decoder_cache_, shader_cache_, default_shader_);
}
OLIVE_NAMESPACE_EXIT
+13 -2
View File
@@ -80,8 +80,16 @@ public:
*
* This function is thread-safe.
*/
RenderTicketPtr RenderFrame(ViewerOutput* viewer, ColorManager* color_manager, const rational& time, RenderMode::Mode mode, FrameHashCache* cache = nullptr, bool prioritize = false);
RenderTicketPtr RenderFrame(ViewerOutput* viewer, ColorManager* color_manager, const rational& time, RenderMode::Mode mode, const QSize& force_size, const QMatrix4x4& matrix, FrameHashCache* cache = nullptr, bool prioritize = false);
RenderTicketPtr RenderFrame(ViewerOutput* viewer, ColorManager* color_manager,
const rational& time, RenderMode::Mode mode,
FrameHashCache* cache = nullptr, bool prioritize = false);
RenderTicketPtr RenderFrame(ViewerOutput* viewer, ColorManager* color_manager,
const rational& time, RenderMode::Mode mode,
const VideoParams& video_params, const AudioParams& audio_params,
const QSize& force_size,
const QMatrix4x4& force_matrix, PixelFormat::Format force_format,
ColorProcessorPtr force_color_output,
FrameHashCache* cache = nullptr, bool prioritize = false);
/**
* @brief Asynchronously generate a chunk of audio
@@ -93,6 +101,7 @@ public:
*
* This function is thread-safe.
*/
RenderTicketPtr RenderAudio(ViewerOutput* viewer, const TimeRange& r, const AudioParams& params, bool generate_waveforms, bool prioritize = false);
RenderTicketPtr RenderAudio(ViewerOutput* viewer, const TimeRange& r, bool generate_waveforms, bool prioritize = false);
RenderTicketPtr SaveFrameToCache(FrameHashCache* cache, FramePtr frame, const QByteArray& hash, bool prioritize = false);
@@ -129,6 +138,8 @@ private:
ShaderCache* shader_cache_;
QVariant default_shader_;
};
OLIVE_NAMESPACE_EXIT
+48 -18
View File
@@ -29,12 +29,13 @@
OLIVE_NAMESPACE_ENTER
RenderProcessor::RenderProcessor(RenderTicketPtr ticket, Renderer *render_ctx, StillImageCache* still_image_cache, DecoderCache* decoder_cache, ShaderCache *shader_cache) :
RenderProcessor::RenderProcessor(RenderTicketPtr ticket, Renderer *render_ctx, StillImageCache* still_image_cache, DecoderCache* decoder_cache, ShaderCache *shader_cache, QVariant default_shader) :
ticket_(ticket),
render_ctx_(render_ctx),
still_image_cache_(still_image_cache),
decoder_cache_(decoder_cache),
shader_cache_(shader_cache)
shader_cache_(shader_cache),
default_shader_(default_shader)
{
}
@@ -49,14 +50,16 @@ void RenderProcessor::Run()
case RenderManager::kTypeVideo:
{
ViewerOutput* viewer = Node::ValueToPtr<ViewerOutput>(ticket_->property("viewer"));
const VideoParams& video_params = ticket_->property("vparam").value<VideoParams>();
rational time = ticket_->property("time").value<rational>();
NodeValueTable table = ProcessInput(viewer->texture_input(),
TimeRange(time, time + viewer->video_params().time_base()));
TimeRange(time, time + video_params.time_base()));
TexturePtr texture = table.Get(NodeParam::kTexture).value<TexturePtr>();
VideoParams frame_params = viewer->video_params();
// Set up output frame parameters
VideoParams frame_params = ticket_->property("vparam").value<VideoParams>();
QSize frame_size = ticket_->property("size").value<QSize>();
if (!frame_size.isNull()) {
@@ -64,6 +67,11 @@ void RenderProcessor::Run()
frame_params.set_height(frame_size.height());
}
PixelFormat::Format frame_format = static_cast<PixelFormat::Format>(ticket_->property("format").toInt());
if (frame_format != PixelFormat::PIX_FMT_INVALID) {
frame_params.set_format(frame_format);
}
FramePtr frame = Frame::Create();
frame->set_timestamp(time);
frame->set_video_params(frame_params);
@@ -74,10 +82,31 @@ void RenderProcessor::Run()
memset(frame->data(), 0, frame->allocated_size());
} else {
// Dump texture contents to frame
ColorProcessorPtr output_color_transform = ticket_->property("coloroutput").value<ColorProcessorPtr>();
const VideoParams& tex_params = texture->params();
if (tex_params.width() != frame->width() || tex_params.height() != frame->height()) {
// FIXME: Blit this shit
if (tex_params.effective_width() != frame_params.effective_width()
|| tex_params.effective_height() != frame_params.effective_height()
|| tex_params.format() != frame_params.format()
|| output_color_transform) {
TexturePtr blit_tex = render_ctx_->CreateTexture(frame_params);
QMatrix4x4 matrix = ticket_->property("matrix").value<QMatrix4x4>();
if (output_color_transform) {
// Yes color transform, blit color managed
render_ctx_->BlitColorManaged(output_color_transform, texture, blit_tex.get(), matrix);
} else {
// No color transform, just blit
ShaderJob job;
job.InsertValue(QStringLiteral("ove_maintex"), {QVariant::fromValue(texture), NodeParam::kTexture});
job.InsertValue(QStringLiteral("ove_mvpmat"), {matrix, NodeParam::kMatrix});
render_ctx_->BlitToTexture(default_shader_, job, blit_tex.get());
}
// Replace texture that we're going to download in the next step
texture = blit_tex;
}
render_ctx_->DownloadFromTexture(texture.get(), frame->data(), frame->linesize_pixels());
@@ -138,9 +167,9 @@ DecoderPtr RenderProcessor::ResolveDecoderFromInput(StreamPtr stream)
return decoder;
}
void RenderProcessor::Process(RenderTicketPtr ticket, Renderer *render_ctx, StillImageCache *still_image_cache, DecoderCache *decoder_cache, ShaderCache *shader_cache)
void RenderProcessor::Process(RenderTicketPtr ticket, Renderer *render_ctx, StillImageCache *still_image_cache, DecoderCache *decoder_cache, ShaderCache *shader_cache, QVariant default_shader)
{
RenderProcessor p(ticket, render_ctx, still_image_cache, decoder_cache, shader_cache);
RenderProcessor p(ticket, render_ctx, still_image_cache, decoder_cache, shader_cache, default_shader);
p.Run();
}
@@ -148,7 +177,7 @@ NodeValueTable RenderProcessor::GenerateBlockTable(const TrackOutput *track, con
{
if (track->track_type() == Timeline::kTrackTypeAudio) {
const AudioParams& audio_params = Node::ValueToPtr<ViewerOutput>(ticket_->property("viewer"))->audio_params();
const AudioParams& audio_params = ticket_->property("aparam").value<AudioParams>();
QList<Block*> active_blocks = track->BlocksAtTimeRange(range);
@@ -228,11 +257,13 @@ QVariant RenderProcessor::ProcessVideoFootage(StreamPtr stream, const rational &
// and color managing them for every frame is a waste of time, so we implement a small cache here
// to optimize such a situation
VideoStreamPtr video_stream = std::static_pointer_cast<VideoStream>(stream);
const VideoParams& video_params = Node::ValueToPtr<ViewerOutput>(ticket_->property("viewer"))->video_params();
const VideoParams& video_params = ticket_->property("vparam").value<VideoParams>();
ColorManager* color_manager = Node::ValueToPtr<ColorManager>(ticket_->property("colormanager"));
StillImageCache::Entry want_entry = {nullptr,
stream,
ColorProcessor::GenerateID(Node::ValueToPtr<ColorManager>(ticket_->property("colormanager")), video_stream->colorspace(), ColorTransform(OCIO::ROLE_SCENE_LINEAR)),
ColorProcessor::GenerateID(color_manager, video_stream->colorspace(), color_manager->GetReferenceColorSpace()),
video_stream->premultiplied_alpha(),
video_params.divider(),
(video_stream->video_type() == VideoStream::kVideoTypeStill) ? 0 : input_time};
@@ -297,10 +328,9 @@ QVariant RenderProcessor::ProcessVideoFootage(StreamPtr stream, const rational &
qDebug() << "FIXME: Accessing video_stream->colorspace() may cause race conditions";
ColorManager* color_manager = Node::ValueToPtr<ColorManager>(ticket_->property("colormanager"));
ColorProcessorPtr processor = ColorProcessor::Create(color_manager,
video_stream->colorspace(),
ColorTransform(OCIO::ROLE_SCENE_LINEAR));
color_manager->GetReferenceColorSpace());
render_ctx_->BlitColorManaged(processor, unmanaged_texture, value.get());
@@ -327,7 +357,7 @@ QVariant RenderProcessor::ProcessAudioFootage(StreamPtr stream, const TimeRange
DecoderPtr decoder = ResolveDecoderFromInput(stream);
if (decoder) {
const AudioParams& audio_params = Node::ValueToPtr<ViewerOutput>(ticket_->property("viewer"))->audio_params();
const AudioParams& audio_params = ticket_->property("aparam").value<AudioParams>();
SampleBufferPtr frame = decoder->RetrieveAudio(input_time, audio_params, &IsCancelled());
@@ -359,7 +389,7 @@ QVariant RenderProcessor::ProcessShader(const Node *node, const TimeRange &range
}
}
const VideoParams& video_params = Node::ValueToPtr<ViewerOutput>(ticket_->property("viewer"))->video_params();
const VideoParams& video_params = ticket_->property("vparam").value<VideoParams>();
TexturePtr destination = render_ctx_->CreateTexture(video_params);
@@ -378,7 +408,7 @@ QVariant RenderProcessor::ProcessSamples(const Node *node, const TimeRange &rang
SampleBufferPtr output_buffer = SampleBuffer::CreateAllocated(job.samples()->audio_params(), job.samples()->sample_count());
NodeValueDatabase value_db;
const AudioParams& audio_params = Node::ValueToPtr<ViewerOutput>(ticket_->property("viewer"))->audio_params();
const AudioParams& audio_params = ticket_->property("aparam").value<AudioParams>();
for (int i=0;i<job.samples()->sample_count();i++) {
// Calculate the exact rational time at this sample
@@ -416,7 +446,7 @@ QVariant RenderProcessor::ProcessFrameGeneration(const Node *node, const Generat
{
FramePtr frame = Frame::Create();
const VideoParams& video_params = Node::ValueToPtr<ViewerOutput>(ticket_->property("viewer"))->video_params();
const VideoParams& video_params = ticket_->property("vparam").value<VideoParams>();
frame->set_video_params(video_params);
frame->allocate();
@@ -436,7 +466,7 @@ QVariant RenderProcessor::GetCachedFrame(const Node *node, const rational &time)
{
if (!ticket_->property("cache").toString().isEmpty()
&& node->id() == QStringLiteral("org.olivevideoeditor.Olive.videoinput")) {
const VideoParams& video_params = Node::ValueToPtr<ViewerOutput>(ticket_->property("viewer"))->video_params();
const VideoParams& video_params = ticket_->property("vparam").value<VideoParams>();
QByteArray hash = RenderManager::Hash(node, video_params, time);
+4 -2
View File
@@ -32,7 +32,7 @@ OLIVE_NAMESPACE_ENTER
class RenderProcessor : public NodeTraverser
{
public:
static void Process(RenderTicketPtr ticket, Renderer* render_ctx, StillImageCache* still_image_cache, DecoderCache* decoder_cache, ShaderCache* shader_cache);
static void Process(RenderTicketPtr ticket, Renderer* render_ctx, StillImageCache* still_image_cache, DecoderCache* decoder_cache, ShaderCache* shader_cache, QVariant default_shader);
struct RenderedWaveform {
const TrackOutput* track;
@@ -56,7 +56,7 @@ protected:
virtual QVariant GetCachedFrame(const Node *node, const rational &time) override;
private:
RenderProcessor(RenderTicketPtr ticket, Renderer* render_ctx, StillImageCache* still_image_cache, DecoderCache* decoder_cache, ShaderCache* shader_cache);
RenderProcessor(RenderTicketPtr ticket, Renderer* render_ctx, StillImageCache* still_image_cache, DecoderCache* decoder_cache, ShaderCache* shader_cache, QVariant default_shader);
void Run();
@@ -72,6 +72,8 @@ private:
ShaderCache* shader_cache_;
QVariant default_shader_;
};
OLIVE_NAMESPACE_EXIT
+56 -32
View File
@@ -39,7 +39,16 @@ bool ExportTask::Run()
{
TimeRange range;
// For safety, if we're overwriting, we save to a temporary filename and then only overwrite it
// at the end
QString real_filename = params_.filename();
if (QFileInfo::exists(params_.filename())) {
// Generate a filename that definitely doesn't exist
params_.SetFilename(FileFunctions::GetSafeTemporaryFilename(real_filename));
}
encoder_ = Encoder::CreateFromID(params_.encoder(), params_);
if (!encoder_) {
SetError(tr("Failed to create encoder"));
return false;
@@ -61,17 +70,26 @@ bool ExportTask::Run()
frame_time_ = Timecode::time_to_timestamp(range.in(), viewer()->video_params().time_base());
QSize video_force_size;
QMatrix4x4 video_force_matrix;
if (params_.video_enabled()) {
// If a transformation matrix is applied to this video, create it here
if (params_.video_scaling_method() != ExportParams::kStretch) {
// FIXME: Re-implement this
if (viewer()->video_params().width() != params_.video_params().width()
|| params_.video_params().height() != params_.video_params().height()) {
video_force_size = QSize(params_.video_params().width(), params_.video_params().height());
/*QMatrix4x4 mat = ExportParams::GenerateMatrix(params_.video_scaling_method(),
viewer()->video_params().width(),
viewer()->video_params().height(),
params_.video_params().width(),
params_.video_params().height());*/
if (params_.video_scaling_method() != ExportParams::kStretch) {
video_force_matrix = ExportParams::GenerateMatrix(params_.video_scaling_method(),
viewer()->video_params().width(),
viewer()->video_params().height(),
params_.video_params().width(),
params_.video_params().height());
}
} else {
// Disables forcing size in the renderer
video_force_size = QSize(0, 0);
}
// Create color processor
@@ -96,7 +114,9 @@ bool ExportTask::Run()
audio_data_.SetLength(range.length());
}
Render(video_range, audio_range, RenderMode::kOnline, false);
Render(color_manager_, video_range, audio_range, RenderMode::kOnline, nullptr,
video_force_size, video_force_matrix, encoder_->GetDesiredPixelFormat(),
color_processor_);
bool success = true;
@@ -107,35 +127,28 @@ bool ExportTask::Run()
encoder_->Close();
encoder_->deleteLater();
delete encoder_;
// If cancelled, delete the file we made, which is always a file we created since we write to a
// temp file during the actual encoding process
if (IsCancelled()) {
QFile::remove(params_.filename());
} else if (params_.filename() != real_filename) {
// If we were writing to a temp file, overwrite now
if (!FileFunctions::RenameFileAllowOverwrite(params_.filename(), real_filename)) {
SetError(tr("Failed to overwrite \"%1\". Export has been saved as \"%2\" instead.")
.arg(real_filename, params_.filename()));
success = false;
}
}
return success;
}
void FrameColorConvert(ColorProcessorPtr processor, FramePtr frame)
{
// Color conversion must be done with unassociated alpha, and the pipeline is always associated
ColorManager::DisassociateAlpha(frame);
// Convert color space
processor->ConvertFrame(frame);
// Re-associate alpha
ColorManager::ReassociateAlpha(frame);
}
QFuture<void> ExportTask::DownloadFrame(FramePtr frame, const QByteArray &hash)
{
rendered_frame_.insert(hash, frame);
return QtConcurrent::run(FrameColorConvert, color_processor_, frame);
}
void ExportTask::FrameDownloaded(const QByteArray &hash, const std::list<rational> &times, qint64 job_time)
void ExportTask::FrameDownloaded(FramePtr f, const QByteArray &hash, const QVector<rational> &times, qint64 job_time)
{
Q_UNUSED(job_time)
FramePtr f = rendered_frame_.value(hash);
Q_UNUSED(hash)
foreach (const rational& t, times) {
time_map_.insert(t, f);
@@ -154,10 +167,21 @@ void ExportTask::FrameDownloaded(const QByteArray &hash, const std::list<rationa
encoder_->WriteFrame(time_map_.take(real_time), real_time);
frame_time_++;
}
}
void FrameColorConvert(ColorProcessorPtr processor, FramePtr frame)
{
// Color conversion must be done with unassociated alpha, and the pipeline is always associated
ColorManager::DisassociateAlpha(frame);
// Convert color space
processor->ConvertFrame(frame);
// Re-associate alpha
ColorManager::ReassociateAlpha(frame);
}
void ExportTask::AudioDownloaded(const TimeRange &range, SampleBufferPtr samples, qint64 job_time)
{
Q_UNUSED(job_time)
+6 -5
View File
@@ -38,15 +38,16 @@ public:
protected:
virtual bool Run() override;
virtual QFuture<void> DownloadFrame(FramePtr frame, const QByteArray &hash) override;
virtual void FrameDownloaded(const QByteArray& hash, const std::list<rational>& times, qint64 job_time) override;
virtual void FrameDownloaded(FramePtr frame, const QByteArray& hash, const QVector<rational>& times, qint64 job_time) override;
virtual void AudioDownloaded(const TimeRange& range, SampleBufferPtr samples, qint64 job_time) override;
private:
QHash<QByteArray, FramePtr> rendered_frame_;
virtual bool TwoStepFrameRendering() const override
{
return false;
}
private:
QHash<rational, FramePtr> time_map_;
ColorManager* color_manager_;
+9 -9
View File
@@ -20,6 +20,8 @@
#include "precachetask.h"
#include "project/project.h"
OLIVE_NAMESPACE_ENTER
PreCacheTask::PreCacheTask(VideoStreamPtr footage, Sequence* sequence) :
@@ -61,23 +63,21 @@ bool PreCacheTask::Run()
}
*/
Render(video_range, TimeRangeList(), RenderMode::kOnline, true);
download_threads_.waitForDone();
Render(footage_->footage()->project()->color_manager(),
video_range,
TimeRangeList(),
RenderMode::kOnline,
viewer()->video_frame_cache());
return true;
}
QFuture<void> PreCacheTask::DownloadFrame(FramePtr frame, const QByteArray &hash)
{
return QtConcurrent::run(&download_threads_, viewer()->video_frame_cache(), &FrameHashCache::SaveCacheFrame, hash, frame);
}
void PreCacheTask::FrameDownloaded(const QByteArray &hash, const std::list<rational> &times, qint64 job_time)
void PreCacheTask::FrameDownloaded(FramePtr frame, const QByteArray &hash, const QVector<rational> &times, qint64 job_time)
{
// Do nothing. Pre-cache essentially just creates more frames in the cache, it doesn't need to do
// anything else.
Q_UNUSED(frame)
Q_UNUSED(hash)
Q_UNUSED(times)
Q_UNUSED(job_time)
+1 -5
View File
@@ -38,9 +38,7 @@ public:
protected:
virtual bool Run() override;
virtual QFuture<void> DownloadFrame(FramePtr frame, const QByteArray &hash) override;
virtual void FrameDownloaded(const QByteArray& hash, const std::list<rational>& times, qint64 job_time) override;
virtual void FrameDownloaded(FramePtr frame, const QByteArray& hash, const QVector<rational>& times, qint64 job_time) override;
virtual void AudioDownloaded(const TimeRange& range, SampleBufferPtr samples, qint64 job_time) override;
@@ -49,8 +47,6 @@ private:
VideoInput* video_node_;
QThreadPool download_threads_;
};
OLIVE_NAMESPACE_EXIT
+5 -6
View File
@@ -38,7 +38,7 @@ ProjectSaveTask::ProjectSaveTask(ProjectPtr project) :
bool ProjectSaveTask::Run()
{
// File to temporarily save to (ensures we can't half-write the user's main file and crash)
QString temp_save = QDir(FileFunctions::GetTempFilePath()).filePath(QStringLiteral("tempsv"));
QString temp_save = FileFunctions::GetSafeTemporaryFilename(project_->filename());
QFile project_file(temp_save);
@@ -74,16 +74,15 @@ bool ProjectSaveTask::Run()
}
// Save was successful, we can now rewrite the original file
QFile original(project_->filename());
if ((!original.exists() || original.remove())
&& QFile::copy(temp_save, project_->filename())) {
if (FileFunctions::RenameFileAllowOverwrite(temp_save, project_->filename())) {
return true;
} else {
SetError(tr("Failed to write to \"%1\".").arg(project_->filename()));
SetError(tr("Failed to overwrite \"%1\". Project has been saved as \"%2\" instead.")
.arg(project_->filename(), temp_save));
return false;
}
} else {
SetError(tr("Failed to open file \"%1\" for writing.").arg(project_->filename()));
SetError(tr("Failed to open temporary file \"%1\" for writing.").arg(temp_save));
return false;
}
}
+160 -162
View File
@@ -22,14 +22,14 @@
#include "common/timecodefunctions.h"
#include "render/rendermanager.h"
#include "threading/threadticket.h"
OLIVE_NAMESPACE_ENTER
RenderTask::RenderTask(ViewerOutput* viewer, const VideoParams &vparams, const AudioParams &aparams) :
viewer_(viewer),
video_params_(vparams),
audio_params_(aparams)
audio_params_(aparams),
running_tickets_(0)
{
}
@@ -37,207 +37,205 @@ RenderTask::~RenderTask()
{
}
struct TimeHashFuturePair {
rational time;
RenderTicketPtr hash_future;
};
struct HashTimePair {
rational time;
QByteArray hash;
};
struct HashFrameFuturePair {
QByteArray hash;
RenderTicketPtr frame_future;
};
struct RangeSampleFuturePair {
TimeRange range;
RenderTicketPtr sample_future;
};
struct HashDownloadFuturePair {
QByteArray hash;
QFuture<void> download_future;
qint64 job_time;
};
void RenderTask::Render(const TimeRangeList& video_range,
bool RenderTask::Render(ColorManager* manager,
const TimeRangeList& video_range,
const TimeRangeList &audio_range,
RenderMode::Mode mode,
bool use_disk_cache)
FrameHashCache* cache, const QSize &force_size,
const QMatrix4x4 &force_matrix, PixelFormat::Format force_format,
ColorProcessorPtr force_color_output)
{
/*
// Run watchers in another thread so they can accept signals even while this thread is blocked
QThread watcher_thread;
watcher_thread.start();
double progress_counter = 0;
double total_length = 0;
double video_frame_sz = video_params().time_base().toDouble();
std::list<TimeRange> audio_queue;
std::list<RangeSampleFuturePair> audio_lookup_table;
if (!audio_range.isEmpty()) {
foreach (const TimeRange& r, audio_range) {
total_length += r.length().toDouble();
// Store real time before any rendering takes place
qint64 job_time = QDateTime::currentMSecsSinceEpoch();
std::list<TimeRange> ranges = r.Split(2);
audio_queue.insert(audio_queue.end(), ranges.begin(), ranges.end());
}
// Queue audio jobs
foreach (const TimeRange& r, audio_range) {
// Don't count audio progress, since it's generally a lot faster than video and is weighted at
// 50%, which makes the progress bar look weird to the uninitiated
//total_length += r.length().toDouble();
IncrementRunningTickets();
RenderTicketWatcher* watcher = CreateWatcher(&watcher_thread);
watcher->setProperty("range", QVariant::fromValue(r));
watcher->SetTicket(RenderManager::instance()->RenderAudio(viewer_, r, audio_params_, false));
}
std::list<HashFrameFuturePair> render_lookup_table;
QVector<rational> times;
QVector<QByteArray> hashes;
std::list<HashTimePair> frame_queue;
qint64 hash_job_time = 0;
// Look up hashes
QMap<QByteArray, QVector<rational> > time_map;
if (!video_range.isEmpty()) {
times = viewer()->video_frame_cache()->GetFrameListFromTimeRange(video_range);
// Get list of discrete frames from range
QVector<rational> times = viewer()->video_frame_cache()->GetFrameListFromTimeRange(video_range);
QVector<QByteArray> hashes(times.size());
// Add to "total progress"
total_length += video_frame_sz * times.size();
RenderTicketPtr hash_future = RenderManager::instance()->Hash(viewer(), times);
hashes = hash_future->Get().value<QVector<QByteArray> >();
hash_job_time = hash_future->GetJobTime();
// Generate hashes
for (int i=0; i<times.size(); i++) {
if (IsCancelled()) {
return true;
}
if (!hash_future->WasCancelled()) {
for (int i=0;i<times.size();i++) {
frame_queue.push_back({times.at(i), hashes.at(i)});
hashes[i] = RenderManager::instance()->Hash(viewer(), video_params_, times.at(i));
}
// Filter out duplicates
for (int i=0; i<hashes.size(); i++) {
if (IsCancelled()) {
return true;
}
const QByteArray& hash = hashes.at(i);
time_map[hash].append(times.at(i));
if (time_map[hash].size() == 1) {
// This is the first frame with this hash, so we signal a render
RenderTicketWatcher* watcher = CreateWatcher(&watcher_thread);
watcher->setProperty("hash", hash);
IncrementRunningTickets();
watcher->SetTicket(RenderManager::instance()->RenderFrame(viewer_, manager, times.at(i),
mode, video_params_, audio_params_,
force_size, force_matrix,
force_format, force_color_output,
cache));
}
}
}
// Start downloading frames that have finished
std::list<HashDownloadFuturePair> download_futures;
finished_watcher_mutex_.lock();
// Iterators
std::list<HashFrameFuturePair>::iterator i;
std::list<HashDownloadFuturePair>::iterator j;
std::list<RangeSampleFuturePair>::iterator k;
while (!IsCancelled()) {
while (!finished_watchers_.empty() && !IsCancelled()) {
RenderTicketWatcher* watcher = finished_watchers_.front();
finished_watchers_.pop_front();
std::list<QByteArray> running_hashes;
std::list<QByteArray> existing_hashes;
finished_watcher_mutex_.unlock();
while (!IsCancelled()
&& (!render_lookup_table.empty()
|| !frame_queue.empty()
|| !audio_queue.empty()
|| !download_futures.empty()
|| !audio_lookup_table.empty())) {
// Analyze watcher here
RenderManager::TicketType ticket_type = watcher->GetTicket()->property("type").value<RenderManager::TicketType>();
while (!IsCancelled() && !frame_queue.empty()) {
if (ticket_type == RenderManager::kTypeAudio) {
// Pop another frame off the frame queue
const HashTimePair& p = frame_queue.front();
TimeRange range = watcher->property("range").value<TimeRange>();
// Check if we're already rendering this hash
bool rendering_hash = (std::find(running_hashes.begin(), running_hashes.end(), p.hash) != running_hashes.end());
AudioDownloaded(range,
watcher->Get().value<SampleBufferPtr>(),
job_time);
// Skip this hash if we're already rendering it
if (!rendering_hash) {
// Check if this frame already exists (has already been rendered previously or during this job)
bool hash_exists = false;
// Don't count audio progress, since it's generally a lot faster than video and is weighted at
// 50%, which makes the progress bar look weird to the uninitiated
//progress_counter += range.length().toDouble();
//emit ProgressChanged(progress_counter / total_length);
if (use_disk_cache) {
// Check if this hash is in our "existing hashes" list
hash_exists = (std::find(existing_hashes.begin(), existing_hashes.end(), p.hash) != existing_hashes.end());
} else if (ticket_type == RenderManager::kTypeVideo && TwoStepFrameRendering()) {
// If not, check if it's in the filesystem
if (!hash_exists) {
hash_exists = QFileInfo::exists(viewer()->video_frame_cache()->CachePathName(p.hash));
DownloadFrame(&watcher_thread,
watcher->Get().value<FramePtr>(),
watcher->property("hash").toByteArray());
// If so, add it to the list so we don't have to check the filesystem again later
if (hash_exists) {
existing_hashes.push_back(p.hash);
}
}
if (hash_exists) {
// Already exists, no need to render it again
FrameDownloaded(p.hash, {p.time}, hash_job_time);
progress_counter += video_frame_sz;
emit ProgressChanged(progress_counter / total_length);
}
}
// If no existing disk cache was found, queue it now
if (!hash_exists) {
render_lookup_table.push_back({p.hash, RenderManager::instance()->RenderFrame(viewer(), p.time, mode)});
running_hashes.push_back(p.hash);
}
}
// Remove first element
frame_queue.pop_front();
}
while (!IsCancelled() && !audio_queue.empty()) {
audio_lookup_table.push_back({audio_queue.front(), RenderManager::instance()->RenderAudio(viewer(), audio_queue.front())});
audio_queue.pop_front();
}
i = render_lookup_table.begin();
while (!IsCancelled() && i != render_lookup_table.end()) {
if (i->frame_future->IsFinished()) {
if (!i->frame_future->WasCancelled()) {
FramePtr f = i->frame_future->Get().value<FramePtr>();
// Start multithreaded download here
download_futures.push_back({i->hash, DownloadFrame(f, i->hash), i->frame_future->GetJobTime()});
}
i = render_lookup_table.erase(i);
} else {
i++;
}
}
j = download_futures.begin();
while (!IsCancelled() && j != download_futures.end()) {
if (j->download_future.isFinished()) {
// Place it in the cache
std::list<rational> times_with_hash;
for (int hash_index=0;hash_index<hashes.size();hash_index++) {
if (hashes.at(hash_index) == j->hash) {
times_with_hash.push_back(times.at(hash_index));
}
}
FrameDownloaded(j->hash, times_with_hash, j->job_time);
existing_hashes.push_back(j->hash);
// Signal process
progress_counter += times_with_hash.size() * video_frame_sz;
progress_counter += video_frame_sz * 0.5;
emit ProgressChanged(progress_counter / total_length);
j = download_futures.erase(j);
} else {
j++;
}
}
k = audio_lookup_table.begin();
// Assume single-step video or video download ticket
QByteArray rendered_hash = watcher->property("hash").toByteArray();
FrameDownloaded(watcher->Get().value<FramePtr>(), rendered_hash, time_map.value(rendered_hash), job_time);
while (!IsCancelled() && k != audio_lookup_table.end()) {
if (k->sample_future->IsFinished()) {
AudioDownloaded(k->range,
k->sample_future->Get().value<SampleBufferPtr>(),
k->sample_future->GetJobTime());
double progress_to_add = video_frame_sz;
if (TwoStepFrameRendering()) {
progress_to_add *= 0.5;
}
progress_counter += progress_to_add;
progress_counter += k->range.length().toDouble();
emit ProgressChanged(progress_counter / total_length);
k = audio_lookup_table.erase(k);
} else {
k++;
}
delete watcher;
running_watchers_.removeOne(watcher);
finished_watcher_mutex_.lock();
}
if (IsCancelled()) {
break;
}
// Run out of finished watchers. If we still have running tickets, wait for the next one to finish.
if (running_tickets_ > 0) {
finished_watcher_wait_cond_.wait(&finished_watcher_mutex_);
} else {
// No more running tickets or finished tickets, wem ust be
break;
}
}
*/
finished_watcher_mutex_.unlock();
if (IsCancelled()) {
// Cancel every watcher we created
foreach (RenderTicketWatcher* watcher, running_watchers_) {
disconnect(watcher, &RenderTicketWatcher::Finished, this, &RenderTask::TicketDone);
watcher->Cancel();
}
}
watcher_thread.quit();
watcher_thread.wait();
return true;
}
void RenderTask::DownloadFrame(QThread *thread, FramePtr frame, const QByteArray &hash)
{
RenderTicketWatcher* watcher = CreateWatcher(thread);
watcher->setProperty("hash", hash);
IncrementRunningTickets();
watcher->SetTicket(RenderManager::instance()->SaveFrameToCache(viewer_->video_frame_cache(),
frame,
hash));
}
RenderTicketWatcher *RenderTask::CreateWatcher(QThread *thread)
{
RenderTicketWatcher* watcher = new RenderTicketWatcher();
watcher->moveToThread(thread);
connect(watcher, &RenderTicketWatcher::Finished, this, &RenderTask::TicketDone, Qt::DirectConnection);
running_watchers_.append(watcher);
return watcher;
}
void RenderTask::IncrementRunningTickets()
{
finished_watcher_mutex_.lock();
running_tickets_++;
finished_watcher_mutex_.unlock();
}
void RenderTask::TicketDone(RenderTicketWatcher* watcher)
{
finished_watcher_mutex_.lock();
finished_watchers_.push_back(watcher);
finished_watcher_wait_cond_.wakeAll();
running_tickets_--;
finished_watcher_mutex_.unlock();
}
OLIVE_NAMESPACE_EXIT
+36 -4
View File
@@ -24,25 +24,32 @@
#include <QtConcurrent/QtConcurrent>
#include "node/output/viewer/viewer.h"
#include "render/colormanager.h"
#include "task/task.h"
#include "threading/threadticket.h"
#include "threading/threadticketwatcher.h"
OLIVE_NAMESPACE_ENTER
class RenderTask : public Task
{
Q_OBJECT
public:
RenderTask(ViewerOutput* viewer, const VideoParams &vparams, const AudioParams &aparams);
virtual ~RenderTask() override;
protected:
void Render(const TimeRangeList &video_range,
bool Render(ColorManager *manager, const TimeRangeList &video_range,
const TimeRangeList &audio_range, RenderMode::Mode mode,
bool use_disk_cache);
FrameHashCache *cache, const QSize& force_size = QSize(0, 0),
const QMatrix4x4& force_matrix = QMatrix4x4(),
PixelFormat::Format force_format = PixelFormat::PIX_FMT_INVALID,
ColorProcessorPtr force_color_output = nullptr);
virtual QFuture<void> DownloadFrame(FramePtr frame, const QByteArray &hash) = 0;
virtual void DownloadFrame(QThread* thread, FramePtr frame, const QByteArray &hash);
virtual void FrameDownloaded(const QByteArray& hash, const std::list<rational>& times, qint64 job_time) = 0;
virtual void FrameDownloaded(FramePtr frame, const QByteArray& hash, const QVector<rational>& times, qint64 job_time) = 0;
virtual void AudioDownloaded(const TimeRange& range, SampleBufferPtr samples, qint64 job_time) = 0;
@@ -61,13 +68,38 @@ protected:
return audio_params_;
}
virtual void CancelEvent() override
{
finished_watcher_mutex_.lock();
finished_watcher_wait_cond_.wakeAll();
finished_watcher_mutex_.unlock();
}
virtual bool TwoStepFrameRendering() const
{
return true;
}
private:
RenderTicketWatcher* CreateWatcher(QThread *thread);
void IncrementRunningTickets();
ViewerOutput* viewer_;
VideoParams video_params_;
AudioParams audio_params_;
QVector<RenderTicketWatcher*> running_watchers_;
std::list<RenderTicketWatcher*> finished_watchers_;
int running_tickets_;
QMutex finished_watcher_mutex_;
QWaitCondition finished_watcher_wait_cond_;
private slots:
void TicketDone(RenderTicketWatcher *watcher);
};
OLIVE_NAMESPACE_EXIT
+7 -2
View File
@@ -46,9 +46,9 @@ void RenderTicketWatcher::SetTicket(RenderTicketPtr ticket)
if (ticket_->IsFinished(false)) {
locker.unlock();
emit Finished();
emit Finished(this);
} else {
connect(ticket_.get(), &RenderTicket::Finished, this, &RenderTicketWatcher::Finished);
connect(ticket_.get(), &RenderTicket::Finished, this, &RenderTicketWatcher::TicketFinished);
}
}
@@ -93,4 +93,9 @@ void RenderTicketWatcher::Cancel()
}
}
void RenderTicketWatcher::TicketFinished()
{
emit Finished(this);
}
OLIVE_NAMESPACE_EXIT
+3 -1
View File
@@ -49,9 +49,11 @@ public:
QVariant Get();
signals:
void Finished();
void Finished(RenderTicketWatcher* watcher);
private:
void TicketFinished();
RenderTicketPtr ticket_;
};
+4 -2
View File
@@ -84,7 +84,7 @@ QMatrix4x4 ViewerDisplayWidget::GetCompleteMatrixFlippedYTranslation()
{
QMatrix4x4 mat = combined_matrix_;
mat.data()[13] *= -1.0f;
mat.scale(1, -1, 1);
return mat;
}
@@ -298,7 +298,9 @@ void ViewerDisplayWidget::OnPaint()
}
// Draw texture through color transform
renderer()->BlitColorManaged(color_service(), texture_, VideoParams(width(), height(), PixelFormat::PIX_FMT_RGBA16F), true);
renderer()->BlitColorManaged(color_service(), texture_,
VideoParams(width(), height(), PixelFormat::PIX_FMT_RGBA16F),
GetCompleteMatrixFlippedYTranslation());
}
QTransform world_transform = GenerateWorldTransform();