rendertask: remove sorting code and calculate duplicate hashes while retrieving

These are two changes that speed up when frames start getting rendered
and processed (particularly for larger scale projects):

* Sorting of time beforehand was done to optimize decoding but is actually
unnecessary since the frames are already sequential when they're retrieved
from the range. As long as we maintain that order, no sorting is required.
This speeds up caching IMMENSELY.

* Evaluate hashes concurrently while retrieving frames. Before, this was
done before any rendering and could therefore delay rendering by several
seconds. Doing them concurrently means frames start rendering almost
immediately and further hashes can be analyzed while we wait for frames to
finish rendering.
This commit is contained in:
itsmattkc
2020-06-16 03:07:53 +10:00
parent 406f61e546
commit 6a4f3f613c
+59 -75
View File
@@ -87,89 +87,25 @@ void RenderTask::Render(const TimeRangeList& video_range,
}
}
QMap<QByteArray, rational> times_to_render;
std::list<HashFrameFuturePair> render_lookup_table;
QList<rational> times;
QList<QByteArray> hashes;
QVector<rational> times;
QVector<QByteArray> hashes;
std::list<HashTimePair> frame_queue;
if (!video_range.isEmpty()) {
QList<QByteArray> existing_hashes;
{
QList<QByteArray> existing_hashes;
foreach (const TimeRange& r, video_range) {
total_length += r.length().toDouble();
}
times = viewer_->video_frame_cache()->GetFrameListFromTimeRange(video_range);
QFuture<QList<QByteArray> > hash_future = backend_.Hash(times);
hashes = hash_future.result();
// Determine any duplicates
for (int index=0;index<hashes.size();index++) {
const QByteArray& hash = hashes.at(index);
const rational& time = times.at(index);
bool map_contains_hash = times_to_render.contains(hash);
bool hash_exists = false;
if (use_disk_cache && !map_contains_hash) {
hash_exists = existing_hashes.contains(hash);
if (!hash_exists) {
hash_exists = QFileInfo::exists(viewer_->video_frame_cache()->CachePathName(hash));
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);
}
}
foreach (const TimeRange& r, video_range) {
total_length += r.length().toDouble();
}
// Render all frames necessary
{
QMap<QByteArray, rational>::const_iterator i;
std::list<HashTimePair>::iterator j;
times = viewer_->video_frame_cache()->GetFrameListFromTimeRange(video_range);
std::list<HashTimePair> sorted_times;
QFuture<QVector<QByteArray> > hash_future = backend_.Hash(times);
hashes = hash_future.result();
// 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)});
}
for (int i=0;i<times.size();i++) {
frame_queue.push_back({times.at(i), hashes.at(i)});
}
}
@@ -181,11 +117,57 @@ void RenderTask::Render(const TimeRangeList& video_range,
std::list<HashDownloadFuturePair>::iterator j;
std::list<RangeSampleFuturePair>::iterator k;
std::list<QByteArray> running_hashes;
std::list<QByteArray> existing_hashes;
while (!IsCancelled()
&& (!render_lookup_table.empty()
|| !frame_queue.empty()
|| !download_futures.empty()
|| !audio_lookup_table.empty())) {
if (!frame_queue.empty()) {
// Pop another frame off the frame queue
const HashTimePair& p = frame_queue.front();
// Check if we're already rendering this hash
bool rendering_hash = (std::find(running_hashes.begin(), running_hashes.end(), p.hash) != running_hashes.end());
// 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;
if (use_disk_cache) {
bool hash_exists = (std::find(existing_hashes.begin(), existing_hashes.end(), p.hash) != existing_hashes.end());
if (!hash_exists) {
hash_exists = QFileInfo::exists(viewer_->video_frame_cache()->CachePathName(p.hash));
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});
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, backend_.RenderFrame(p.time)});
running_hashes.push_back(p.hash);
}
}
// Remove first element
frame_queue.pop_front();
}
i = render_lookup_table.begin();
while (!IsCancelled() && i != render_lookup_table.end()) {
@@ -216,6 +198,8 @@ void RenderTask::Render(const TimeRangeList& video_range,
FrameDownloaded(j->hash, times_with_hash);
existing_hashes.push_back(j->hash);
// Signal process
progress_counter += times_with_hash.size() * video_frame_sz;
emit ProgressChanged(progress_counter / total_length);