/*** Olive - Non-Linear Video Editor Copyright (C) 2019 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "render.h" #include "common/timecodefunctions.h" OLIVE_NAMESPACE_ENTER RenderTask::RenderTask(ViewerOutput* viewer, const VideoParams &vparams, const AudioParams &aparams) : viewer_(viewer), video_params_(vparams), audio_params_(aparams) { job_time_ = QDateTime::currentMSecsSinceEpoch(); // FIXME: This makes a full copy of the node graph every time it starts, there must be a better // way. backend_.SetViewerNode(viewer_); backend_.SetVideoParams(video_params_); backend_.SetAudioParams(audio_params_); } 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 download_future; }; void RenderTask::Render(const TimeRangeList& video_range, const TimeRangeList &audio_range, const QMatrix4x4& mat, bool use_disk_cache) { backend_.SetVideoDownloadMatrix(mat); double progress_counter = 0; double total_length = 0; double video_frame_sz = video_params_.time_base().toDouble(); std::list audio_lookup_table; if (!audio_range.isEmpty()) { foreach (const TimeRange& r, audio_range) { total_length += r.length().toDouble(); QList ranges = RenderBackend::SplitRangeIntoChunks(r); foreach (const TimeRange& split, ranges) { audio_lookup_table.push_back({split, backend_.RenderAudio(split)}); } } } QMap times_to_render; std::list render_lookup_table; QList times; QList hashes; if (!video_range.isEmpty()) { { QList existing_hashes; foreach (const TimeRange& r, video_range) { total_length += r.length().toDouble(); } times = viewer_->video_frame_cache()->GetFrameListFromTimeRange(video_range); QFuture > hash_future = backend_.Hash(times); hashes = hash_future.result(); // Determine any duplicates for (int index=0;indexvideo_frame_cache()->CachePathName(hash, video_params_.format())); if (hash_exists) { existing_hashes.append(hash); } } if (hash_exists) { // Already exists, no need to render it again FrameDownloaded(hash, {time}); progress_counter += video_frame_sz; emit ProgressChanged(progress_counter / total_length); } } if (!hash_exists && (!map_contains_hash || times_to_render.value(hash) > time)) { times_to_render.insert(hash, time); } } } // Render all frames necessary { QMap::const_iterator i; std::list::iterator j; std::list sorted_times; // Rendering is generally more efficient when done sequentially, so we sort the // times chronologically here for (i=times_to_render.begin(); i!=times_to_render.end(); i++) { bool inserted = false; for (j=sorted_times.begin(); j!=sorted_times.end(); j++) { if (j->time > i.value()) { sorted_times.insert(j, {i.value(), i.key()});\ inserted = true; break; } } if (!inserted) { sorted_times.push_back({i.value(), i.key()}); } } // Start render jobs in sorted order for (j=sorted_times.begin(); j!=sorted_times.end(); j++) { render_lookup_table.push_back({j->hash, backend_.RenderFrame(j->time)}); } } } // Start downloading frames that have finished std::list download_futures; // Iterators std::list::iterator i; std::list::iterator j; std::list::iterator k; while (!IsCancelled() && (!render_lookup_table.empty() || !download_futures.empty() || !audio_lookup_table.empty())) { i = render_lookup_table.begin(); while (!IsCancelled() && i != render_lookup_table.end()) { if (i->frame_future->IsFinished()) { FramePtr f = i->frame_future->Get().value(); // Start multithreaded download here download_futures.push_back({i->hash, DownloadFrame(f, i->hash)}); 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 times_with_hash; for (int k=0;khash) { times_with_hash.push_back(times.at(k)); } } FrameDownloaded(j->hash, times_with_hash); // Signal process progress_counter += times_with_hash.size() * video_frame_sz; emit ProgressChanged(progress_counter / total_length); j = download_futures.erase(j); } else { j++; } } k = audio_lookup_table.begin(); while (!IsCancelled() && k != audio_lookup_table.end()) { if (k->sample_future->IsFinished()) { AudioDownloaded(k->range, k->sample_future->Get().value()); progress_counter += k->range.length().toDouble(); emit ProgressChanged(progress_counter / total_length); k = audio_lookup_table.erase(k); } else { k++; } } } // `Close` will block until all jobs are done making a safe deletion backend_.Close(); } void RenderTask::SetAnchorPoint(const rational &r) { anchor_point_ = r; } OLIVE_NAMESPACE_EXIT