VSE: Skip rendering lower strips that are behind opaque strips above them
Often in previs setups, you have several "variations" of image/movie strips for review, where the topmost one completely covers the others under it. VSE rendering already had an optimization where if there's a fully opaque strip that covers the whole screen, then the lower strips are skipped from processing/rendering. However, it was not handling the case of non-fullscreen strips (e.g. you'd have a Color strip near top & bottom for letterboxing, and an opaque strip "in the middle"). This adds a simple "occluder tracking", and skips strips that are completely covered by any single opaque strip that is above it (like outlined in #117790 task). Playback of Gold previs between 1:42 and 1:55, on Windows/Ryzen5950X: - Average frame time 28.5ms -> 23.8ms - Median frame time 24.1ms -> 21.5ms - Two slowest frames: 263->189ms, 194->178ms Rendering the Gold previs movie: 325s -> 304s (93% of previous time) Pull Request: https://projects.blender.org/blender/blender/pulls/118396
This commit is contained in:
parent
c3a2c536f8
commit
f4f708a54f
|
@ -292,6 +292,79 @@ Vector<Sequence *> seq_get_shown_sequences(const Scene *scene,
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Strip corner coordinates in screen pixel space. Note that they might not be
|
||||||
|
* axis aligned when rotation is present. */
|
||||||
|
struct StripScreenQuad {
|
||||||
|
float2 v0, v1, v2, v3;
|
||||||
|
|
||||||
|
bool is_empty() const
|
||||||
|
{
|
||||||
|
return v0 == v1 && v2 == v3 && v0 == v2;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static StripScreenQuad get_strip_screen_quad(const SeqRenderData *context, const Sequence *seq)
|
||||||
|
{
|
||||||
|
Scene *scene = context->scene;
|
||||||
|
const int x = context->rectx;
|
||||||
|
const int y = context->recty;
|
||||||
|
float2 offset{x * 0.5f, y * 0.5f};
|
||||||
|
|
||||||
|
float quad[4][2];
|
||||||
|
SEQ_image_transform_final_quad_get(scene, seq, quad);
|
||||||
|
return StripScreenQuad{quad[0] + offset, quad[1] + offset, quad[2] + offset, quad[3] + offset};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Is quad `a` fully contained (i.e. covered by) quad `b`? For that to happen,
|
||||||
|
* all corners of `a` have to be inside `b`. */
|
||||||
|
static bool is_quad_a_inside_b(const StripScreenQuad &a, const StripScreenQuad &b)
|
||||||
|
{
|
||||||
|
return isect_point_quad_v2(a.v0, b.v0, b.v1, b.v2, b.v3) &&
|
||||||
|
isect_point_quad_v2(a.v1, b.v0, b.v1, b.v2, b.v3) &&
|
||||||
|
isect_point_quad_v2(a.v2, b.v0, b.v1, b.v2, b.v3) &&
|
||||||
|
isect_point_quad_v2(a.v3, b.v0, b.v1, b.v2, b.v3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tracking of "known to be opaque" strip quad coordinates, along with their
|
||||||
|
* order index within visible strips during rendering. */
|
||||||
|
|
||||||
|
struct OpaqueQuad {
|
||||||
|
StripScreenQuad quad;
|
||||||
|
int order_index;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct OpaqueQuadTracker {
|
||||||
|
Vector<OpaqueQuad, 4> opaques;
|
||||||
|
|
||||||
|
/* Determine if the input strip is completely behind opaque strips that are
|
||||||
|
* above it. Current implementation is simple and only checks if strip is
|
||||||
|
* completely covered by any other strip. It does not detect case where
|
||||||
|
* a strip is not covered by a single strip, but is behind of the union
|
||||||
|
* of the strips above. */
|
||||||
|
bool is_occluded(const SeqRenderData *context, const Sequence *seq, int order_index) const
|
||||||
|
{
|
||||||
|
StripScreenQuad quad = get_strip_screen_quad(context, seq);
|
||||||
|
if (quad.is_empty()) {
|
||||||
|
/* Strip size is not initialized/valid, we can't know if it is occluded. */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const OpaqueQuad &q : opaques) {
|
||||||
|
if (q.order_index > order_index && is_quad_a_inside_b(quad, q.quad)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_occluder(const SeqRenderData *context, const Sequence *seq, int order_index)
|
||||||
|
{
|
||||||
|
StripScreenQuad quad = get_strip_screen_quad(context, seq);
|
||||||
|
if (!quad.is_empty()) {
|
||||||
|
opaques.append({quad, order_index});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/** \} */
|
/** \} */
|
||||||
|
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
|
@ -465,36 +538,20 @@ static void sequencer_thumbnail_transform(ImBuf *in, ImBuf *out)
|
||||||
* image. If they do not the image will have transparent areas. */
|
* image. If they do not the image will have transparent areas. */
|
||||||
static bool seq_image_transform_transparency_gained(const SeqRenderData *context, Sequence *seq)
|
static bool seq_image_transform_transparency_gained(const SeqRenderData *context, Sequence *seq)
|
||||||
{
|
{
|
||||||
Scene *scene = context->scene;
|
float x0 = 0.0f;
|
||||||
const int x = context->rectx;
|
float y0 = 0.0f;
|
||||||
const int y = context->recty;
|
float x1 = float(context->rectx);
|
||||||
|
float y1 = float(context->recty);
|
||||||
float seq_image_quad[4][2];
|
float x_aspect = context->scene->r.xasp / context->scene->r.yasp;
|
||||||
SEQ_image_transform_final_quad_get(scene, seq, seq_image_quad);
|
if (x_aspect != 1.0f) {
|
||||||
for (int i = 0; i < 4; i++) {
|
float xmid = (x0 + x1) * 0.5f;
|
||||||
add_v2_v2(seq_image_quad[i], float2{x / 2.0f, y / 2.0f});
|
x0 = xmid - (xmid - x0) * x_aspect;
|
||||||
|
x1 = xmid + (x1 - xmid) * x_aspect;
|
||||||
}
|
}
|
||||||
|
StripScreenQuad quad = get_strip_screen_quad(context, seq);
|
||||||
|
StripScreenQuad screen{float2(x0, y0), float2(x1, y0), float2(x0, y1), float2(x1, y1)};
|
||||||
|
|
||||||
return !isect_point_quad_v2(float2{float(x), float(y)},
|
return !is_quad_a_inside_b(screen, quad);
|
||||||
seq_image_quad[0],
|
|
||||||
seq_image_quad[1],
|
|
||||||
seq_image_quad[2],
|
|
||||||
seq_image_quad[3]) ||
|
|
||||||
!isect_point_quad_v2(float2{0, float(y)},
|
|
||||||
seq_image_quad[0],
|
|
||||||
seq_image_quad[1],
|
|
||||||
seq_image_quad[2],
|
|
||||||
seq_image_quad[3]) ||
|
|
||||||
!isect_point_quad_v2(float2{float(x), 0},
|
|
||||||
seq_image_quad[0],
|
|
||||||
seq_image_quad[1],
|
|
||||||
seq_image_quad[2],
|
|
||||||
seq_image_quad[3]) ||
|
|
||||||
!isect_point_quad_v2(float2{0, 0},
|
|
||||||
seq_image_quad[0],
|
|
||||||
seq_image_quad[1],
|
|
||||||
seq_image_quad[2],
|
|
||||||
seq_image_quad[3]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Automatic filter:
|
/* Automatic filter:
|
||||||
|
@ -709,8 +766,10 @@ static ImBuf *seq_render_preprocess_ibuf(const SeqRenderData *context,
|
||||||
use_preprocess = true;
|
use_preprocess = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Proxies and effect strips are not stored in cache. */
|
/* Proxies and non-generator effect strips are not stored in cache. */
|
||||||
if (!is_proxy_image && (seq->type & SEQ_TYPE_EFFECT) == 0) {
|
const bool is_effect_with_inputs = (seq->type & SEQ_TYPE_EFFECT) != 0 &&
|
||||||
|
SEQ_effect_get_num_inputs(seq->type) != 0;
|
||||||
|
if (!is_proxy_image && !is_effect_with_inputs) {
|
||||||
seq_cache_put(context, seq, timeline_frame, SEQ_CACHE_STORE_RAW, ibuf);
|
seq_cache_put(context, seq, timeline_frame, SEQ_CACHE_STORE_RAW, ibuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1900,6 +1959,8 @@ static ImBuf *seq_render_strip_stack(const SeqRenderData *context,
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OpaqueQuadTracker opaques;
|
||||||
|
|
||||||
int64_t i;
|
int64_t i;
|
||||||
ImBuf *out = nullptr;
|
ImBuf *out = nullptr;
|
||||||
for (i = strips.size() - 1; i >= 0; i--) {
|
for (i = strips.size() - 1; i >= 0; i--) {
|
||||||
|
@ -1917,9 +1978,15 @@ static ImBuf *seq_render_strip_stack(const SeqRenderData *context,
|
||||||
|
|
||||||
StripEarlyOut early_out = seq_get_early_out_for_blend_mode(seq);
|
StripEarlyOut early_out = seq_get_early_out_for_blend_mode(seq);
|
||||||
|
|
||||||
|
if (early_out == StripEarlyOut::DoEffect && opaques.is_occluded(context, seq, i)) {
|
||||||
|
early_out = StripEarlyOut::UseInput1;
|
||||||
|
}
|
||||||
|
|
||||||
/* Early out for alpha over. It requires image to be rendered, so it can't use
|
/* Early out for alpha over. It requires image to be rendered, so it can't use
|
||||||
* `seq_get_early_out_for_blend_mode`. */
|
* `seq_get_early_out_for_blend_mode`. */
|
||||||
if (out == nullptr && seq->blend_mode == SEQ_TYPE_ALPHAOVER && seq->blend_opacity == 100.0f) {
|
if (out == nullptr && seq->blend_mode == SEQ_TYPE_ALPHAOVER &&
|
||||||
|
early_out == StripEarlyOut::DoEffect && seq->blend_opacity == 100.0f)
|
||||||
|
{
|
||||||
ImBuf *test = seq_render_strip(context, state, seq, timeline_frame);
|
ImBuf *test = seq_render_strip(context, state, seq, timeline_frame);
|
||||||
if (ELEM(test->planes, R_IMF_PLANES_BW, R_IMF_PLANES_RGB)) {
|
if (ELEM(test->planes, R_IMF_PLANES_BW, R_IMF_PLANES_RGB)) {
|
||||||
early_out = StripEarlyOut::UseInput2;
|
early_out = StripEarlyOut::UseInput2;
|
||||||
|
@ -1929,6 +1996,16 @@ static ImBuf *seq_render_strip_stack(const SeqRenderData *context,
|
||||||
}
|
}
|
||||||
/* Free the image. It is stored in cache, so this doesn't affect performance. */
|
/* Free the image. It is stored in cache, so this doesn't affect performance. */
|
||||||
IMB_freeImBuf(test);
|
IMB_freeImBuf(test);
|
||||||
|
|
||||||
|
/* Check whether the raw (before preprocessing, which can add alpha) strip content
|
||||||
|
* was opaque. */
|
||||||
|
ImBuf *ibuf_raw = seq_cache_get(context, seq, timeline_frame, SEQ_CACHE_STORE_RAW);
|
||||||
|
if (ibuf_raw != nullptr) {
|
||||||
|
if (ibuf_raw->planes != R_IMF_PLANES_RGBA) {
|
||||||
|
opaques.add_occluder(context, seq, i);
|
||||||
|
}
|
||||||
|
IMB_freeImBuf(ibuf_raw);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (early_out) {
|
switch (early_out) {
|
||||||
|
@ -1966,6 +2043,10 @@ static ImBuf *seq_render_strip_stack(const SeqRenderData *context,
|
||||||
for (; i < strips.size(); i++) {
|
for (; i < strips.size(); i++) {
|
||||||
Sequence *seq = strips[i];
|
Sequence *seq = strips[i];
|
||||||
|
|
||||||
|
if (opaques.is_occluded(context, seq, i)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (seq_get_early_out_for_blend_mode(seq) == StripEarlyOut::DoEffect) {
|
if (seq_get_early_out_for_blend_mode(seq) == StripEarlyOut::DoEffect) {
|
||||||
ImBuf *ibuf1 = out;
|
ImBuf *ibuf1 = out;
|
||||||
ImBuf *ibuf2 = seq_render_strip(context, state, seq, timeline_frame);
|
ImBuf *ibuf2 = seq_render_strip(context, state, seq, timeline_frame);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* SPDX-FileCopyrightText: 2004 Blender Authors
|
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
|
||||||
|
|
|
@ -676,7 +676,7 @@ static void seq_image_transform_quad_get_ex(const Scene *scene,
|
||||||
|
|
||||||
float quad_temp[4][3];
|
float quad_temp[4][3];
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
zero_v2(quad_temp[i]);
|
zero_v3(quad_temp[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
quad_temp[0][0] = (image_size[0] / 2) - crop->right;
|
quad_temp[0][0] = (image_size[0] / 2) - crop->right;
|
||||||
|
|
Loading…
Reference in New Issue