60Hz
This commit is contained in:
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
void Dockspace::tick(Manager& manager, Settings& settings)
|
||||
void Dockspace::tick(Manager& manager, Settings& settings, float deltaSeconds)
|
||||
{
|
||||
if (auto document = manager.get(); document)
|
||||
if (settings.windowIsAnimationPreview) animationPreview.tick(manager, settings);
|
||||
if (settings.windowIsAnimationPreview) animationPreview.tick(manager, settings, deltaSeconds);
|
||||
}
|
||||
|
||||
void Dockspace::update(Taskbar& taskbar, Documents& documents, Manager& manager, Settings& settings,
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace anm2ed::imgui
|
||||
Welcome welcome;
|
||||
|
||||
public:
|
||||
void tick(Manager&, Settings&);
|
||||
void tick(Manager&, Settings&, float);
|
||||
void update(Taskbar&, Documents&, Manager&, Settings&, Resources&, Dialog&, Clipboard&);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -34,8 +34,6 @@ namespace anm2ed::imgui
|
||||
constexpr auto POINT_SIZE = vec2(4, 4);
|
||||
constexpr auto TRIGGER_TEXT_COLOR_DARK = ImVec4(1.0f, 1.0f, 1.0f, 0.5f);
|
||||
constexpr auto TRIGGER_TEXT_COLOR_LIGHT = ImVec4(0.0f, 0.0f, 0.0f, 0.5f);
|
||||
constexpr auto PLAYBACK_TICK_RATE = 30.0f;
|
||||
|
||||
namespace
|
||||
{
|
||||
std::filesystem::path render_destination_directory(const std::filesystem::path& path, int type)
|
||||
@@ -153,7 +151,7 @@ namespace anm2ed::imgui
|
||||
|
||||
AnimationPreview::AnimationPreview() : Canvas(vec2()) {}
|
||||
|
||||
void AnimationPreview::tick(Manager& manager, Settings& settings)
|
||||
void AnimationPreview::tick(Manager& manager, Settings& settings, float deltaSeconds)
|
||||
{
|
||||
auto& document = *manager.get();
|
||||
auto& anm2 = document.anm2;
|
||||
@@ -176,8 +174,9 @@ namespace anm2ed::imgui
|
||||
auto& path = settings.renderPath;
|
||||
auto pathString = path::to_utf8(path);
|
||||
auto& type = settings.renderType;
|
||||
auto renderFrameRate = std::max(settings.playbackTickRate, 1);
|
||||
|
||||
if (playback.time > end || playback.isFinished)
|
||||
if (playback.time >= (float)end + 1.0f || playback.isFinished)
|
||||
{
|
||||
if (settings.timelineIsSound) audioStream.capture_end(mixer);
|
||||
|
||||
@@ -262,7 +261,7 @@ namespace anm2ed::imgui
|
||||
{
|
||||
if (settings.timelineIsSound && type != render::GIF)
|
||||
{
|
||||
if (!render_audio_stream_generate(audioStream, anm2.content.sounds, renderFrameSoundIDs, anm2.info.fps))
|
||||
if (!render_audio_stream_generate(audioStream, anm2.content.sounds, renderFrameSoundIDs, renderFrameRate))
|
||||
{
|
||||
toasts.push(localize.get(TOAST_EXPORT_RENDERED_ANIMATION_FAILED));
|
||||
logger.error("Failed to generate deterministic render audio stream; exporting without audio.");
|
||||
@@ -273,7 +272,7 @@ namespace anm2ed::imgui
|
||||
audioStream.stream.clear();
|
||||
|
||||
if (animation_render(ffmpegPath, path, renderTempFrames, renderTempFrameDurations, audioStream,
|
||||
(render::Type)type, anm2.info.fps))
|
||||
(render::Type)type, renderFrameRate))
|
||||
{
|
||||
toasts.push(std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION), std::make_format_args(pathString)));
|
||||
logger.info(std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION, anm2ed::ENGLISH),
|
||||
@@ -325,8 +324,10 @@ namespace anm2ed::imgui
|
||||
auto frameSoundID = -1;
|
||||
if (settings.timelineIsSound && !anm2.content.sounds.empty())
|
||||
{
|
||||
if (auto animation = document.animation_get();
|
||||
animation && animation->triggers.isVisible && (!settings.timelineIsOnlyShowLayers || manager.isRecording))
|
||||
auto soundTime = (int)std::floor(playback.time);
|
||||
if (auto animation = document.animation_get(); soundTime != renderFrameSoundTimePrev && animation &&
|
||||
animation->triggers.isVisible &&
|
||||
(!settings.timelineIsOnlyShowLayers || manager.isRecording))
|
||||
{
|
||||
if (auto trigger = animation->triggers.frame_generate(playback.time, anm2::TRIGGER); trigger.isVisible)
|
||||
{
|
||||
@@ -341,6 +342,7 @@ namespace anm2ed::imgui
|
||||
}
|
||||
}
|
||||
}
|
||||
renderFrameSoundTimePrev = soundTime;
|
||||
}
|
||||
renderFrameSoundIDs.push_back(frameSoundID);
|
||||
|
||||
@@ -352,26 +354,10 @@ namespace anm2ed::imgui
|
||||
if (Texture::write_pixels_png(framePath, size, pixels.data()))
|
||||
{
|
||||
renderTempFrames.push_back(framePath);
|
||||
auto nowCounter = SDL_GetPerformanceCounter();
|
||||
auto counterFrequency = SDL_GetPerformanceFrequency();
|
||||
auto fallbackDuration = 1.0 / (double)std::max(anm2.info.fps, 1);
|
||||
|
||||
if (renderTempFrames.size() == 1)
|
||||
{
|
||||
renderCaptureCounterPrev = nowCounter;
|
||||
auto fallbackDuration = 1.0 / (double)renderFrameRate;
|
||||
renderTempFrameDurations.push_back(fallbackDuration);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto elapsedCounter = nowCounter - renderCaptureCounterPrev;
|
||||
auto frameDuration = counterFrequency > 0 ? (double)elapsedCounter / (double)counterFrequency : 0.0;
|
||||
frameDuration = std::max(frameDuration, 1.0 / 1000.0);
|
||||
renderTempFrameDurations.back() = frameDuration;
|
||||
renderTempFrameDurations.push_back(frameDuration);
|
||||
renderCaptureCounterPrev = nowCounter;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
toasts.push(
|
||||
std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION_FAILED), std::make_format_args(pathString)));
|
||||
@@ -416,7 +402,6 @@ namespace anm2ed::imgui
|
||||
}
|
||||
|
||||
auto fps = std::max(anm2.info.fps, 1);
|
||||
auto deltaSeconds = manager.isRecording ? (1.0f / (float)fps) : (1.0f / PLAYBACK_TICK_RATE);
|
||||
playback.tick(fps, animation->frameNum, (animation->isLoop || settings.playbackIsLoop) && !manager.isRecording,
|
||||
deltaSeconds);
|
||||
|
||||
@@ -657,7 +642,7 @@ namespace anm2ed::imgui
|
||||
renderTempFrames.clear();
|
||||
renderTempFrameDurations.clear();
|
||||
renderFrameSoundIDs.clear();
|
||||
renderCaptureCounterPrev = 0;
|
||||
renderFrameSoundTimePrev = -1;
|
||||
if (settings.renderType == render::PNGS)
|
||||
{
|
||||
renderTempDirectory = settings.renderPath;
|
||||
|
||||
@@ -33,11 +33,11 @@ namespace anm2ed::imgui
|
||||
std::vector<std::filesystem::path> renderTempFrames{};
|
||||
std::vector<double> renderTempFrameDurations{};
|
||||
std::vector<int> renderFrameSoundIDs{};
|
||||
Uint64 renderCaptureCounterPrev{};
|
||||
int renderFrameSoundTimePrev{-1};
|
||||
|
||||
public:
|
||||
AnimationPreview();
|
||||
void tick(Manager&, Settings&);
|
||||
void tick(Manager&, Settings&, float);
|
||||
void update(Manager&, Settings&, Resources&);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1312,7 +1312,7 @@ namespace anm2ed::imgui
|
||||
document.frameTime = playback.time;
|
||||
}
|
||||
|
||||
playback.clamp(settings.playbackIsClamp ? length : anm2::FRAME_NUM_MAX);
|
||||
if (!playback.isPlaying) playback.clamp(settings.playbackIsClamp ? length : anm2::FRAME_NUM_MAX);
|
||||
|
||||
if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) isDragging = false;
|
||||
|
||||
|
||||
@@ -32,6 +32,13 @@ namespace anm2ed::imgui::wizard
|
||||
ImGui::Checkbox(localize.get(LABEL_VSYNC), &temporary.isVsync);
|
||||
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_VSYNC));
|
||||
|
||||
ImGui::SeparatorText(localize.get(LABEL_PLAYBACK_MENU));
|
||||
ImGui::RadioButton("30 Hz", &temporary.playbackTickRate, 30);
|
||||
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_PLAYBACK_TICK_RATE));
|
||||
ImGui::SameLine();
|
||||
ImGui::RadioButton("60 Hz", &temporary.playbackTickRate, 60);
|
||||
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_PLAYBACK_TICK_RATE));
|
||||
|
||||
ImGui::SeparatorText(localize.get(LABEL_LOCALIZATION));
|
||||
ImGui::Combo(localize.get(LABEL_LANGUAGE), &temporary.language, LANGUAGE_STRINGS, LANGUAGE_COUNT);
|
||||
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_LANGUAGE));
|
||||
|
||||
+7
-10
@@ -21,24 +21,22 @@ namespace anm2ed
|
||||
|
||||
void Playback::tick(int fps, int length, bool isLoop, float deltaSeconds)
|
||||
{
|
||||
if (isLoop) isFinished = false;
|
||||
if (isFinished || !isPlaying || fps <= 0 || length <= 0) return;
|
||||
if (deltaSeconds <= 0.0f) return;
|
||||
|
||||
auto frameDuration = 1.0f / (float)fps;
|
||||
tickAccumulator += deltaSeconds;
|
||||
auto steps = (int)std::floor(tickAccumulator / frameDuration);
|
||||
if (steps <= 0) return;
|
||||
tickAccumulator -= frameDuration * (float)steps;
|
||||
time += deltaSeconds * (float)fps;
|
||||
|
||||
time += (float)steps;
|
||||
if (!std::isfinite(time)) time = 0.0f;
|
||||
|
||||
if (time > (float)length - 1.0f)
|
||||
{
|
||||
if (isLoop)
|
||||
{
|
||||
time = std::fmod(time, (float)length);
|
||||
if (time < 0.0f) time += (float)length;
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
if (time >= (float)length)
|
||||
{
|
||||
time = (float)length - 1.0f;
|
||||
isPlaying = false;
|
||||
@@ -46,7 +44,6 @@ namespace anm2ed
|
||||
timing_reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Playback::decrement(int length)
|
||||
{
|
||||
|
||||
+3
-2
@@ -39,7 +39,6 @@ namespace anm2ed
|
||||
const std::vector<double>& frameDurations, AudioStream& audioStream, render::Type type, int fps)
|
||||
{
|
||||
if (framePaths.empty() || ffmpegPath.empty() || path.empty()) return false;
|
||||
(void)frameDurations;
|
||||
fps = std::max(fps, 1);
|
||||
|
||||
auto pathString = path::to_utf8(path);
|
||||
@@ -132,6 +131,7 @@ namespace anm2ed
|
||||
auto framePath = framePaths[index];
|
||||
auto framePathString = path::to_utf8(framePath);
|
||||
auto frameDuration = defaultFrameDuration;
|
||||
if (index < frameDurations.size() && frameDurations[index] > 0.0) frameDuration = frameDurations[index];
|
||||
framesListFile << "file '" << ffmpeg_concat_escape(framePathString) << "'\n";
|
||||
framesListFile << "duration " << std::format("{:.9f}", frameDuration) << "\n";
|
||||
}
|
||||
@@ -143,11 +143,12 @@ namespace anm2ed
|
||||
command = std::format("\"{0}\" -y -f concat -safe 0 -i \"{1}\"", ffmpegPathString, framesListPathString);
|
||||
|
||||
if (!audioInputArguments.empty()) command += " " + audioInputArguments;
|
||||
command += std::format(" -fps_mode cfr -r {}", fps);
|
||||
if (type != render::GIF) command += std::format(" -fps_mode cfr -r {}", fps);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case render::GIF:
|
||||
command += " -fps_mode vfr";
|
||||
command +=
|
||||
" -lavfi \"split[s0][s1];[s0]palettegen=stats_mode=full:reserve_transparent=1[p];"
|
||||
"[s1][p]paletteuse=dither=floyd_steinberg:alpha_threshold=128\""
|
||||
|
||||
@@ -606,6 +606,7 @@ namespace anm2ed
|
||||
X(TOOLTIP_PIVOTS, "Toggle the visibility of the animation's pivots.", "Alterna la visibilidad de los pivotes de la animacion.", "Переключить видимость точек поворота анимации.", "切换动画枢轴是否可见.", "애니메이션의 중심점을 표시하거나 숨깁니다.") \
|
||||
X(TOOLTIP_PLAYBACK_ALWAYS_LOOP, "Animations will always loop during playback, even if looping isn't set.", "Las animaciones siempre se loopearan durante la reproduccion, incluso si \"looping\" no esta activado.", "Анимации всегда будут циклично воспроизводиться во время воспроизведения, даже если цикличность не установлена.", "动画预放时也会循环, 即使循环并未被设置.", "반복 설정이 켜져 있지 않아도 애니메이션이 항상 반복 재생됩니다.") \
|
||||
X(TOOLTIP_PLAYBACK_CLAMP, "Operations will always be clamped to within the animation's bounds.\nFor example, dragging the playhead, or triggers.", "Las operaciones siempre se haran Clamp a las normas de la animacion.\nPor ejemplo: Arrastrar el Cabezal, o los triggers.", "Операции всегда будут ограничены границами анимации.\nНапример, перетаскивание воспроизводящей головки или триггеров.", "任何操作都会被限制在动画的区间内.\n比如, 拖拽播放指针或者触发器.", "작업이 항상 애니메이션 범위 내로 제한됩니다.\n예: 플레이헤드를 드래그하거나 트리거를 조작할 때.") \
|
||||
X(TOOLTIP_PLAYBACK_TICK_RATE, "Set how often animation playback updates while previewing.", "Ajusta con que frecuencia se actualiza la reproduccion de animacion durante la vista previa.", "Установить, как часто обновляется воспроизведение анимации в предпросмотре.", "设置动画预览播放时的更新频率。", "미리보기 중 애니메이션 재생이 업데이트되는 빈도를 설정합니다.") \
|
||||
X(TOOLTIP_PLAY_ANIMATION, "Play the animation.", "Reproduce la animacion.", "Возпроизвести анимацию.", "播放动画.", "애니메이션을 재생합니다.") \
|
||||
X(TOOLTIP_POSITION, "Change the position of the frame.", "Cambia la posicion del Frame.", "Изменить позицию кадра.", "更改此帧的位置.", "프레임의 위치를 변경합니다.") \
|
||||
X(TOOLTIP_PREVIEW_ZOOM, "Change the zoom of the preview.", "Cambia el zoom de la vista previa.", "Изменить масштаб предпросмотра.", "更改预览视图的缩放.", "미리보기의 줌을 변경합니다.") \
|
||||
|
||||
@@ -74,6 +74,7 @@ namespace anm2ed
|
||||
\
|
||||
X(PLAYBACK_IS_LOOP, playbackIsLoop, STRING_UNDEFINED, BOOL, true) \
|
||||
X(PLAYBACK_IS_CLAMP, playbackIsClamp, STRING_UNDEFINED, BOOL, true) \
|
||||
X(PLAYBACK_TICK_RATE, playbackTickRate, STRING_UNDEFINED, INT, 30) \
|
||||
\
|
||||
X(CHANGE_IS_CROP_X, changeIsCropX, STRING_UNDEFINED, BOOL, false) \
|
||||
X(CHANGE_IS_CROP_Y, changeIsCropY, STRING_UNDEFINED, BOOL, false) \
|
||||
|
||||
+4
-14
@@ -18,8 +18,6 @@ using namespace anm2ed::types;
|
||||
|
||||
namespace anm2ed
|
||||
{
|
||||
constexpr auto TICK_RATE = 30;
|
||||
constexpr auto TICK_INTERVAL = (1000 / TICK_RATE);
|
||||
constexpr auto UPDATE_RATE = 120;
|
||||
constexpr auto UPDATE_INTERVAL = (1000 / UPDATE_RATE);
|
||||
|
||||
@@ -33,7 +31,7 @@ namespace anm2ed
|
||||
manager.chords_set(settings);
|
||||
}
|
||||
|
||||
void State::tick(Settings& settings) { dockspace.tick(manager, settings); }
|
||||
void State::tick(Settings& settings, float deltaSeconds) { dockspace.tick(manager, settings, deltaSeconds); }
|
||||
|
||||
void State::update(SDL_Window*& window, Settings& settings)
|
||||
{
|
||||
@@ -160,16 +158,8 @@ namespace anm2ed
|
||||
auto currentTick = SDL_GetTicks();
|
||||
auto currentUpdate = SDL_GetTicks();
|
||||
auto isRecording = manager.isRecording;
|
||||
auto tickIntervalMs = (double)TICK_INTERVAL;
|
||||
|
||||
if (isRecording)
|
||||
{
|
||||
if (auto document = manager.get())
|
||||
{
|
||||
auto fps = std::max(document->anm2.info.fps, 1);
|
||||
tickIntervalMs = std::max(1.0, 1000.0 / (double)fps);
|
||||
}
|
||||
}
|
||||
auto tickRate = std::max(settings.playbackTickRate, 1);
|
||||
auto tickIntervalMs = 1000.0 / (double)tickRate;
|
||||
|
||||
if (isRecording != wasRecording)
|
||||
{
|
||||
@@ -194,7 +184,7 @@ namespace anm2ed
|
||||
|
||||
if (tickAccumulatorMs >= tickIntervalMs)
|
||||
{
|
||||
tick(settings);
|
||||
tick(settings, (float)(tickIntervalMs / 1000.0));
|
||||
tickAccumulatorMs -= tickIntervalMs;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ namespace anm2ed
|
||||
{
|
||||
class State
|
||||
{
|
||||
void tick(Settings&);
|
||||
void tick(Settings&, float);
|
||||
void update(SDL_Window*&, Settings&);
|
||||
void render(SDL_Window*&, Settings&);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user