Fix #147729: Crash scrubbing w/ snapping to sec and FPS < 0.5

Fix playhead snapping to seconds, when the frame rate is less than 0.5
FPS.

This makes it possible to snap to fractions of frames, to support setups
with multiple seconds per frame. Of course this only has any effect
(apart from not crashing) when sub-frames are enabled.

I've also added unit tests, and verified that the values are the same
from before this refactor.

Pull Request: https://projects.blender.org/blender/blender/pulls/148074
This commit is contained in:
Sybren A. Stüvel
2025-10-15 12:54:36 +02:00
parent 3f7db9c4b7
commit bbf40d214c
4 changed files with 58 additions and 8 deletions

View File

@@ -138,7 +138,7 @@ const char *BKE_scene_find_marker_name(const Scene *scene, int frame);
*/
const char *BKE_scene_find_last_marker_name(const Scene *scene, int frame);
int BKE_scene_frame_snap_by_seconds(Scene *scene, double interval_in_seconds, int frame);
float BKE_scene_frame_snap_by_seconds(const Scene *scene, double interval_in_seconds, float frame);
/**
* Checks for cycle, returns true if it's all OK.

View File

@@ -814,6 +814,7 @@ if(WITH_GTESTS)
intern/main_test.cc
intern/nla_test.cc
intern/path_templates_test.cc
intern/scene_test.cc
intern/subdiv_ccg_test.cc
intern/tracking_test.cc
intern/volume_test.cc

View File

@@ -2291,14 +2291,19 @@ const char *BKE_scene_find_last_marker_name(const Scene *scene, int frame)
return best_marker ? best_marker->name : nullptr;
}
int BKE_scene_frame_snap_by_seconds(Scene *scene, double interval_in_seconds, int frame)
float BKE_scene_frame_snap_by_seconds(const Scene *scene,
const double interval_in_seconds,
const float frame)
{
const int fps = round_db_to_int(scene->frames_per_second() * interval_in_seconds);
const int second_prev = frame - mod_i(frame, fps);
const int second_next = second_prev + fps;
const int delta_prev = frame - second_prev;
const int delta_next = second_next - frame;
return (delta_prev < delta_next) ? second_prev : second_next;
BLI_assert(interval_in_seconds > 0);
BLI_assert(scene->frames_per_second() > 0);
const double interval_in_frames = scene->frames_per_second() * interval_in_seconds;
const double second_prev = interval_in_frames * floor(frame / interval_in_frames);
const double second_next = second_prev + ceil(interval_in_frames);
const double delta_prev = frame - second_prev;
const double delta_next = second_next - frame;
return float((delta_prev < delta_next) ? second_prev : second_next);
}
void BKE_scene_remove_rigidbody_object(Main *bmain, Scene *scene, Object *ob, const bool free_us)

View File

@@ -0,0 +1,44 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_scene.hh"
#include "DNA_scene_types.h"
#include "testing/testing.h"
namespace blender::bke::tests {
TEST(scene, frame_snap_by_seconds)
{
Scene fake_scene = {};
/* Regular 24 FPS snapping. */
fake_scene.r.frs_sec = 24;
fake_scene.r.frs_sec_base = 1.0;
EXPECT_FLOAT_EQ(48.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 1.0, 47));
EXPECT_FLOAT_EQ(48.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 1.0, 49));
EXPECT_FLOAT_EQ(48.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 1.0, 59));
EXPECT_FLOAT_EQ(72.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 1.0, 60));
EXPECT_FLOAT_EQ(9984.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 2.0, 10000.0));
/* 12 FPS snapping by incrementing the base. */
fake_scene.r.frs_sec = 24;
fake_scene.r.frs_sec_base = 2.0;
EXPECT_FLOAT_EQ(48.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 1.0, 47));
EXPECT_FLOAT_EQ(48.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 1.0, 49));
EXPECT_FLOAT_EQ(48.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 1.0, 53));
EXPECT_FLOAT_EQ(60.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 1.0, 54));
EXPECT_FLOAT_EQ(9996.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 1.0, 10000.0));
/* 0.1 FPS snapping to 2-second intervals. */
fake_scene.r.frs_sec = 1;
fake_scene.r.frs_sec_base = 10.0;
EXPECT_FLOAT_EQ(48.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 2.0, 48.0));
EXPECT_FLOAT_EQ(48.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 2.0, 48.1));
EXPECT_FLOAT_EQ(48.2, BKE_scene_frame_snap_by_seconds(&fake_scene, 2.0, 48.2));
EXPECT_FLOAT_EQ(10000.0, BKE_scene_frame_snap_by_seconds(&fake_scene, 2.0, 10000.0));
}
} // namespace blender::bke::tests