Refactor, Vsync, FFmpeg fixes

This commit is contained in:
2025-08-15 12:18:57 -04:00
parent e8094a19c4
commit a665023626
15 changed files with 149 additions and 78 deletions

View File

@@ -23,10 +23,9 @@
#include <ranges> #include <ranges>
#include <string> #include <string>
#include <unordered_set> #include <unordered_set>
#include <variant>
#include <vector> #include <vector>
typedef uint8_t u8; typedef uint8_t u8;
typedef uint16_t u16; typedef uint16_t u16;
typedef uint32_t u32; typedef uint32_t u32;
@@ -49,10 +48,9 @@ using namespace glm;
#define FLOAT_TO_U8(x) (static_cast<u8>((x) * 255.0f)) #define FLOAT_TO_U8(x) (static_cast<u8>((x) * 255.0f))
#define U8_TO_FLOAT(x) ((x) / 255.0f) #define U8_TO_FLOAT(x) ((x) / 255.0f)
#define PERCENT_TO_UNIT(x) (x / 100.0f) #define PERCENT_TO_UNIT(x) (x / 100.0f)
#define TICK_DELAY 33.3f
#define TICK_CATCH_UP_MAX (33.3f * 5)
#define SECOND 1000.0f #define SECOND 1000.0f
#define TICK_RATE (SECOND / TICK_DELAY) #define TICK_DELAY (SECOND / 30.0)
#define UPDATE_DELAY (SECOND / 120.0)
#define ID_NONE -1 #define ID_NONE -1
#define INDEX_NONE -1 #define INDEX_NONE -1
#define TIME_NONE -1.0f #define TIME_NONE -1.0f
@@ -125,6 +123,11 @@ static inline bool string_to_bool(const std::string& string)
return lower == "true"; return lower == "true";
} }
static inline std::string string_quote(const std::string& string)
{
return "\"" + string + "\"";
}
static inline std::string path_canonical_resolve static inline std::string path_canonical_resolve
( (
const std::string& inputPath, const std::string& inputPath,

View File

@@ -1,7 +1,6 @@
#pragma once #pragma once
#include "anm2.h" #include "anm2.h"
#include <variant>
enum ClipboardItemType enum ClipboardItemType
{ {
@@ -34,4 +33,3 @@ void clipboard_copy(Clipboard* self);
void clipboard_cut(Clipboard* self); void clipboard_cut(Clipboard* self);
void clipboard_paste(Clipboard* self); void clipboard_paste(Clipboard* self);
void clipboard_init(Clipboard* self, Anm2* anm2); void clipboard_init(Clipboard* self, Anm2* anm2);

View File

@@ -30,44 +30,43 @@ void dialog_init(Dialog* self, SDL_Window* window)
void dialog_anm2_open(Dialog* self) void dialog_anm2_open(Dialog* self)
{ {
SDL_ShowOpenFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_ANM2, 1, nullptr, false); SDL_ShowOpenFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_ANM2, std::size(DIALOG_FILE_FILTER_ANM2), nullptr, false);
self->type = DIALOG_ANM2_OPEN; self->type = DIALOG_ANM2_OPEN;
} }
void dialog_anm2_save(Dialog* self) void dialog_anm2_save(Dialog* self)
{ {
SDL_ShowSaveFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_ANM2, 1, nullptr); SDL_ShowSaveFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_ANM2, std::size(DIALOG_FILE_FILTER_ANM2), nullptr);
self->type = DIALOG_ANM2_SAVE; self->type = DIALOG_ANM2_SAVE;
} }
void dialog_spritesheet_add(Dialog* self) void dialog_spritesheet_add(Dialog* self)
{ {
SDL_ShowOpenFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_PNG, 1, nullptr, false); SDL_ShowOpenFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_PNG, std::size(DIALOG_FILE_FILTER_PNG), nullptr, false);
self->type = DIALOG_SPRITESHEET_ADD; self->type = DIALOG_SPRITESHEET_ADD;
} }
void dialog_spritesheet_replace(Dialog* self, s32 id) void dialog_spritesheet_replace(Dialog* self, s32 id)
{ {
SDL_ShowOpenFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_PNG, 1, nullptr, false); SDL_ShowOpenFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_PNG, std::size(DIALOG_FILE_FILTER_PNG), nullptr, false);
self->replaceID = id; self->replaceID = id;
self->type = DIALOG_SPRITESHEET_REPLACE; self->type = DIALOG_SPRITESHEET_REPLACE;
} }
void dialog_render_path_set(Dialog* self) void dialog_render_path_set(Dialog* self, RenderType type)
{ {
SDL_ShowSaveFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_RENDER, 2, nullptr); SDL_DialogFileFilter filter = DIALOG_RENDER_FILE_FILTERS[type];
self->type = DIALOG_RENDER_PATH_SET;
}
void dialog_render_directory_set(Dialog* self) if (type == RENDER_PNG)
{
SDL_ShowOpenFolderDialog(_dialog_callback, self, self->window, nullptr, false); SDL_ShowOpenFolderDialog(_dialog_callback, self, self->window, nullptr, false);
self->type = DIALOG_RENDER_DIRECTORY_SET; else
SDL_ShowSaveFileDialog(_dialog_callback, self, self->window, &filter, 1, nullptr);
self->type = DIALOG_RENDER_PATH_SET;
} }
void dialog_ffmpeg_path_set(Dialog* self) void dialog_ffmpeg_path_set(Dialog* self)
{ {
SDL_ShowOpenFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_FFMPEG, 1, nullptr, false); SDL_ShowOpenFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_FFMPEG, std::size(DIALOG_FILE_FILTER_FFMPEG), nullptr, false);
self->type = DIALOG_FFMPEG_PATH_SET; self->type = DIALOG_FFMPEG_PATH_SET;
} }

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "render.h"
#include "window.h" #include "window.h"
const SDL_DialogFileFilter DIALOG_FILE_FILTER_ANM2[] = const SDL_DialogFileFilter DIALOG_FILE_FILTER_ANM2[] =
@@ -12,15 +13,17 @@ const SDL_DialogFileFilter DIALOG_FILE_FILTER_PNG[] =
{"PNG image", "png"} {"PNG image", "png"}
}; };
const SDL_DialogFileFilter DIALOG_FILE_FILTER_RENDER[] = const SDL_DialogFileFilter DIALOG_RENDER_FILE_FILTERS[] =
{ {
{"PNG image", "png"},
{"GIF image", "gif"}, {"GIF image", "gif"},
{"WebM video", "webm"} {"WebM video", "webm"},
{"MP4 video", "mp4"}
}; };
const SDL_DialogFileFilter DIALOG_FILE_FILTER_FFMPEG[] = const SDL_DialogFileFilter DIALOG_FILE_FILTER_FFMPEG[] =
{ {
{"Executable", ""} {"Executable", "exe"}
}; };
enum DialogType enum DialogType
@@ -31,7 +34,6 @@ enum DialogType
DIALOG_SPRITESHEET_ADD, DIALOG_SPRITESHEET_ADD,
DIALOG_SPRITESHEET_REPLACE, DIALOG_SPRITESHEET_REPLACE,
DIALOG_RENDER_PATH_SET, DIALOG_RENDER_PATH_SET,
DIALOG_RENDER_DIRECTORY_SET,
DIALOG_FFMPEG_PATH_SET DIALOG_FFMPEG_PATH_SET
}; };
@@ -50,7 +52,7 @@ void dialog_anm2_open(Dialog* self);
void dialog_spritesheet_add(Dialog* self); void dialog_spritesheet_add(Dialog* self);
void dialog_spritesheet_replace(Dialog* self, s32 id); void dialog_spritesheet_replace(Dialog* self, s32 id);
void dialog_anm2_save(Dialog* self); void dialog_anm2_save(Dialog* self);
void dialog_render_path_set(Dialog* self); void dialog_render_path_set(Dialog* self, RenderType type);
void dialog_render_directory_set(Dialog* self); void dialog_render_directory_set(Dialog* self);
void dialog_ffmpeg_path_set(Dialog* self); void dialog_ffmpeg_path_set(Dialog* self);
void dialog_reset(Dialog* self); void dialog_reset(Dialog* self);

View File

@@ -23,10 +23,17 @@ ffmpeg_render
case RENDER_WEBM: case RENDER_WEBM:
command = std::format(FFMPEG_WEBM_FORMAT, ffmpegPath, size.x, size.y, fps, outputPath); command = std::format(FFMPEG_WEBM_FORMAT, ffmpegPath, size.x, size.y, fps, outputPath);
break; break;
case RENDER_MP4:
command = std::format(FFMPEG_MP4_FORMAT, ffmpegPath, size.x, size.y, fps, outputPath);
break;
default: default:
return false; break;
} }
#if _WIN32
command = string_quote(command)
#endif
FILE* fp = POPEN(command.c_str(), PWRITE_MODE); FILE* fp = POPEN(command.c_str(), PWRITE_MODE);
if (!fp) if (!fp)

View File

@@ -6,19 +6,25 @@
#define FFMPEG_POPEN_ERROR "popen() (for FFmpeg) failed!\n{}" #define FFMPEG_POPEN_ERROR "popen() (for FFmpeg) failed!\n{}"
static constexpr const char* FFMPEG_GIF_FORMAT = static constexpr const char* FFMPEG_GIF_FORMAT =
"\"{0}\" -y " "{0} -y "
"-f rawvideo -pix_fmt rgba -s {1}x{2} -r {3} -i pipe:0 " "-f rawvideo -pix_fmt rgba -s {1}x{2} -r {3} -i pipe:0 "
"-lavfi \"split[s0][s1];" "-lavfi \"split[s0][s1];"
"[s0]palettegen=stats_mode=full[p];" "[s0]palettegen=stats_mode=full[p];"
"[s1][p]paletteuse=dither=floyd_steinberg\" " "[s1][p]paletteuse=dither=floyd_steinberg\" "
"-loop 0 \"{4}\""; "-loop 0 {4}";
static constexpr const char* FFMPEG_WEBM_FORMAT = static constexpr const char* FFMPEG_WEBM_FORMAT =
"\"{0}\" -y " "{0} -y "
"-f rawvideo -pix_fmt rgba -s {1}x{2} -r {3} -i pipe:0 " "-f rawvideo -pix_fmt rgba -s {1}x{2} -r {3} -i pipe:0 "
"-c:v libvpx-vp9 -crf 30 -b:v 0 -pix_fmt yuva420p -row-mt 1 -threads 0 -speed 2 " "-c:v libvpx-vp9 -crf 30 -b:v 0 -pix_fmt yuva420p -row-mt 1 -threads 0 -speed 2 "
"-auto-alt-ref 0 -an \"{4}\""; "-auto-alt-ref 0 -an {4}";
static constexpr const char* FFMPEG_MP4_FORMAT =
"{0} -y "
"-f rawvideo -pix_fmt rgba -s {1}x{2} -r {3} -i pipe:0 "
"-vf \"format=yuv420p,scale=trunc(iw/2)*2:trunc(ih/2)*2\" "
"-c:v libx265 -crf 20 -preset slow "
"-tag:v hvc1 -movflags +faststart -an {4}";
bool bool
ffmpeg_render ffmpeg_render

View File

@@ -354,10 +354,11 @@ static bool NAME(const ImguiItem& self, Imgui* imgui, bool& boolValue)
ImguiItem checkboxItem = self.copy \ ImguiItem checkboxItem = self.copy \
({.label = std::format(IMGUI_INVISIBLE_FORMAT, self.label), .isMnemonicDisabled = true}); \ ({.label = std::format(IMGUI_INVISIBLE_FORMAT, self.label), .isMnemonicDisabled = true}); \
checkboxItem.isDisabled = false; \ checkboxItem.isDisabled = false; \
_imgui_checkbox(checkboxItem, imgui, boolValue); \ bool isCheckboxActivated = _imgui_checkbox(checkboxItem, imgui, boolValue); \
ImGui::SameLine(); \ ImGui::SameLine(); \
bool isActivated = ([&] { return FUNCTION; })(); \ bool isActivated = ([&] { return FUNCTION; })(); \
if (isActivated) boolValue = !boolValue; \ if (isActivated) boolValue = !boolValue; \
if (isCheckboxActivated) isActivated = true; \
return isActivated; \ return isActivated; \
} }
@@ -367,9 +368,10 @@ static bool NAME(const ImguiItem& self, Imgui* imgui, VALUE& value, bool& boolVa
ImguiItem checkboxItem = self.copy \ ImguiItem checkboxItem = self.copy \
({.label = std::format(IMGUI_INVISIBLE_FORMAT, self.label), .isMnemonicDisabled = true}); \ ({.label = std::format(IMGUI_INVISIBLE_FORMAT, self.label), .isMnemonicDisabled = true}); \
checkboxItem.isDisabled = false; \ checkboxItem.isDisabled = false; \
_imgui_checkbox(checkboxItem, imgui, boolValue); \ bool isCheckboxActivated = _imgui_checkbox(checkboxItem, imgui, boolValue); \
ImGui::SameLine(); \ ImGui::SameLine(); \
bool isActivated = ([&](VALUE& value) { return FUNCTION; })(value); \ bool isActivated = ([&](VALUE& value) { return FUNCTION; })(value); \
if (isCheckboxActivated) isActivated = true; \
return isActivated; \ return isActivated; \
} }
@@ -1413,49 +1415,45 @@ static void _imgui_taskbar(Imgui* self)
if (imgui_begin_popup_modal(IMGUI_RENDER_ANIMATION.popup, self, IMGUI_RENDER_ANIMATION.popupSize)) if (imgui_begin_popup_modal(IMGUI_RENDER_ANIMATION.popup, self, IMGUI_RENDER_ANIMATION.popupSize))
{ {
static DialogType& dialogType = self->dialog->type;
static bool& dialogIsSelected = self->dialog->isSelected;
static s32& type = self->settings->renderType;
static std::string& dialogPath = self->dialog->path;
static std::string& ffmpegPath = self->settings->ffmpegPath;
static std::string& format = self->settings->renderFormat;
static std::string& path = self->settings->renderPath;
_imgui_begin_child(IMGUI_RENDER_ANIMATION_CHILD, self); _imgui_begin_child(IMGUI_RENDER_ANIMATION_CHILD, self);
if (_imgui_atlas_button(IMGUI_RENDER_ANIMATION_LOCATION_BROWSE, self)) if (_imgui_atlas_button(IMGUI_RENDER_ANIMATION_LOCATION_BROWSE, self))
{ dialog_render_path_set(self->dialog, (RenderType)type);
switch (self->settings->renderType)
{
case RENDER_PNG: dialog_render_directory_set(self->dialog); break;
default:
dialog_render_path_set(self->dialog);
break;
}
}
if if (dialogIsSelected && (dialogType == DIALOG_RENDER_PATH_SET))
(
self->dialog->isSelected &&
(self->dialog->type == DIALOG_RENDER_PATH_SET || self->dialog->type == DIALOG_RENDER_DIRECTORY_SET)
)
{ {
self->settings->renderPath = self->dialog->path; path = path_extension_change(dialogPath, RENDER_EXTENSIONS[type]);
dialog_reset(self->dialog); dialog_reset(self->dialog);
} }
_imgui_input_text(IMGUI_RENDER_ANIMATION_LOCATION, self, self->settings->renderPath); _imgui_input_text(IMGUI_RENDER_ANIMATION_LOCATION, self, path);
if (_imgui_atlas_button(IMGUI_RENDER_ANIMATION_FFMPEG_BROWSE, self)) if (_imgui_atlas_button(IMGUI_RENDER_ANIMATION_FFMPEG_BROWSE, self))
dialog_ffmpeg_path_set(self->dialog); dialog_ffmpeg_path_set(self->dialog);
if (self->dialog->isSelected && self->dialog->type == DIALOG_FFMPEG_PATH_SET) if (dialogIsSelected && dialogType == DIALOG_FFMPEG_PATH_SET)
{ {
self->settings->ffmpegPath = self->dialog->path; ffmpegPath = self->dialog->path;
dialog_reset(self->dialog); dialog_reset(self->dialog);
} }
_imgui_input_text(IMGUI_RENDER_ANIMATION_FFMPEG_PATH, self, self->settings->ffmpegPath); _imgui_input_text(IMGUI_RENDER_ANIMATION_FFMPEG_PATH, self, ffmpegPath);
_imgui_input_text(IMGUI_RENDER_ANIMATION_FORMAT, self, self->settings->renderFormat); _imgui_input_text(IMGUI_RENDER_ANIMATION_FORMAT, self, format);
_imgui_combo(IMGUI_RENDER_ANIMATION_OUTPUT, self, &self->settings->renderType); _imgui_combo(IMGUI_RENDER_ANIMATION_OUTPUT, self, &type);
if (_imgui_button(IMGUI_RENDER_ANIMATION_CONFIRM, self)) if (_imgui_button(IMGUI_RENDER_ANIMATION_CONFIRM, self))
{ {
bool isRenderStart = true; bool isRenderStart = true;
if (!std::filesystem::exists(self->settings->ffmpegPath)) if (!std::filesystem::exists(ffmpegPath))
{ {
imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_FFMPEG_PATH_ERROR); imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_FFMPEG_PATH_ERROR);
isRenderStart = false; isRenderStart = false;
@@ -1466,7 +1464,7 @@ static void _imgui_taskbar(Imgui* self)
switch (self->settings->renderType) switch (self->settings->renderType)
{ {
case RENDER_PNG: case RENDER_PNG:
if (!std::filesystem::is_directory(self->settings->renderPath)) if (!std::filesystem::is_directory(path))
{ {
imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_DIRECTORY_ERROR); imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_DIRECTORY_ERROR);
isRenderStart = false; isRenderStart = false;
@@ -1474,7 +1472,8 @@ static void _imgui_taskbar(Imgui* self)
break; break;
case RENDER_GIF: case RENDER_GIF:
case RENDER_WEBM: case RENDER_WEBM:
if (!path_is_valid(self->settings->renderPath)) case RENDER_MP4:
if (!path_is_valid(path))
{ {
imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_PATH_ERROR); imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_PATH_ERROR);
isRenderStart = false; isRenderStart = false;
@@ -1502,6 +1501,9 @@ static void _imgui_taskbar(Imgui* self)
if (imgui_begin_popup_modal(IMGUI_RENDER_ANIMATION_CONFIRM.popup, self, IMGUI_RENDER_ANIMATION_CONFIRM.popupSize)) if (imgui_begin_popup_modal(IMGUI_RENDER_ANIMATION_CONFIRM.popup, self, IMGUI_RENDER_ANIMATION_CONFIRM.popupSize))
{ {
static s32& type = self->settings->renderType;
static std::string& format = self->settings->renderFormat;
auto rendering_end = [&]() auto rendering_end = [&]()
{ {
preview_render_end(self->preview); preview_render_end(self->preview);
@@ -1537,7 +1539,7 @@ static void _imgui_taskbar(Imgui* self)
if (self->preview->isRenderFinished) if (self->preview->isRenderFinished)
{ {
switch (self->settings->renderType) switch (type)
{ {
case RENDER_PNG: case RENDER_PNG:
{ {
@@ -1546,10 +1548,9 @@ static void _imgui_taskbar(Imgui* self)
for (auto [i, frame] : std::views::enumerate(frames)) for (auto [i, frame] : std::views::enumerate(frames))
{ {
std::string framePath = std::vformat(self->settings->renderFormat, std::make_format_args(i)); std::string framePath = std::vformat(format, std::make_format_args(i));
framePath = path_extension_change(framePath, RENDER_EXTENSIONS[self->settings->renderType]); framePath = path_extension_change(framePath, RENDER_EXTENSIONS[type]);
if (!frame.isInvalid) if (!frame.isInvalid) texture_from_gl_write(&frame, framePath);
texture_from_gl_write(&frame, framePath);
} }
std::filesystem::current_path(workingPath); std::filesystem::current_path(workingPath);
@@ -1558,11 +1559,12 @@ static void _imgui_taskbar(Imgui* self)
} }
case RENDER_GIF: case RENDER_GIF:
case RENDER_WEBM: case RENDER_WEBM:
case RENDER_MP4:
{ {
std::string ffmpegPath = std::string(self->settings->ffmpegPath.c_str()); std::string ffmpegPath = std::string(self->settings->ffmpegPath.c_str());
path = path_extension_change(path, RENDER_EXTENSIONS[self->settings->renderType]); path = path_extension_change(path, RENDER_EXTENSIONS[self->settings->renderType]);
if (ffmpeg_render(ffmpegPath, path, frames, self->preview->canvas.size, self->anm2->fps, (RenderType)self->settings->renderType)) if (ffmpeg_render(ffmpegPath, path, frames, self->preview->canvas.size, self->anm2->fps, (RenderType)type))
imgui_log_push(self, std::format(IMGUI_LOG_RENDER_ANIMATION_SAVE_FORMAT, path)); imgui_log_push(self, std::format(IMGUI_LOG_RENDER_ANIMATION_SAVE_FORMAT, path));
else else
imgui_log_push(self, std::format(IMGUI_LOG_RENDER_ANIMATION_FFMPEG_ERROR, path)); imgui_log_push(self, std::format(IMGUI_LOG_RENDER_ANIMATION_FFMPEG_ERROR, path));
@@ -1587,6 +1589,14 @@ static void _imgui_taskbar(Imgui* self)
imgui_end_popup(self); imgui_end_popup(self);
} }
_imgui_selectable(IMGUI_SETTINGS.copy({}), self);
if (imgui_begin_popup(IMGUI_SETTINGS.popup, self, IMGUI_SETTINGS.popupSize))
{
if (_imgui_checkbox_selectable(IMGUI_VSYNC, self, self->settings->isVsync)) window_vsync_set(self->settings->isVsync);
imgui_end_popup(self);
}
_imgui_end(); _imgui_end();
} }

View File

@@ -975,8 +975,8 @@ IMGUI_ITEM(IMGUI_PLAYBACK,
self.tooltip = "Opens the playback menu, for configuring playback settings.", self.tooltip = "Opens the playback menu, for configuring playback settings.",
self.popup = "## Playback Popup", self.popup = "## Playback Popup",
self.popupType = IMGUI_POPUP_BY_ITEM, self.popupType = IMGUI_POPUP_BY_ITEM,
self.popupSize = {150, 0}, self.isSizeToText = true,
self.isSizeToText = true self.isSameLine = true
); );
IMGUI_ITEM(IMGUI_ALWAYS_LOOP, IMGUI_ITEM(IMGUI_ALWAYS_LOOP,
@@ -991,6 +991,20 @@ IMGUI_ITEM(IMGUI_CLAMP_PLAYHEAD,
self.isSizeToText = true self.isSizeToText = true
); );
IMGUI_ITEM(IMGUI_SETTINGS,
self.label = "&Settings",
self.tooltip = "Opens the setting menu, for configuring general program settings.",
self.popup = "## Settings Popup",
self.popupType = IMGUI_POPUP_BY_ITEM,
self.isSizeToText = true
);
IMGUI_ITEM(IMGUI_VSYNC,
self.label = "&Vsync",
self.tooltip = "Toggle vertical sync; synchronizes program framerate with your monitor's refresh rate.",
self.isSizeToText = true
);
IMGUI_ITEM(IMGUI_ANIMATIONS, IMGUI_ITEM(IMGUI_ANIMATIONS,
self.label = "Animations", self.label = "Animations",
self.flags = ImGuiWindowFlags_NoScrollbar | self.flags = ImGuiWindowFlags_NoScrollbar |

View File

@@ -44,7 +44,7 @@ void preview_tick(Preview* self)
self->renderFrames.push_back(frameTexture); self->renderFrames.push_back(frameTexture);
} }
time += (f32)self->anm2->fps / TICK_RATE; time += (f32)self->anm2->fps / TICK_DELAY;
if (time >= (f32)animation->frameNum - 1) if (time >= (f32)animation->frameNum - 1)
{ {
@@ -72,10 +72,7 @@ void preview_tick(Preview* self)
time = std::clamp(time, 0.0f, std::max(0.0f, (f32)animation->frameNum - 1)); time = std::clamp(time, 0.0f, std::max(0.0f, (f32)animation->frameNum - 1));
else else
time = std::max(time, 0.0f); time = std::max(time, 0.0f);
} }
} }
void preview_draw(Preview* self) void preview_draw(Preview* self)

View File

@@ -7,19 +7,23 @@ enum RenderType
RENDER_PNG, RENDER_PNG,
RENDER_GIF, RENDER_GIF,
RENDER_WEBM, RENDER_WEBM,
RENDER_COUNT RENDER_MP4
}; };
constexpr inline s32 RENDER_COUNT = RENDER_MP4 + 1;
const inline std::string RENDER_TYPE_STRINGS[] = const inline std::string RENDER_TYPE_STRINGS[] =
{ {
"PNG Images", "PNG Images",
"GIF image", "GIF image",
"WebM video", "WebM video",
"MP4 video"
}; };
const inline std::string RENDER_EXTENSIONS[RENDER_COUNT] = const inline std::string RENDER_EXTENSIONS[RENDER_COUNT] =
{ {
".png", ".png",
".gif", ".gif",
".webm" ".webm",
".mp4"
}; };

View File

@@ -32,6 +32,7 @@ struct SettingsEntry
struct Settings struct Settings
{ {
ivec2 windowSize = {1080, 720}; ivec2 windowSize = {1080, 720};
bool isVsync = true;
bool playbackIsLoop = true; bool playbackIsLoop = true;
bool playbackIsClampPlayhead = true; bool playbackIsClampPlayhead = true;
bool changeIsCrop = false; bool changeIsCrop = false;
@@ -106,6 +107,7 @@ struct Settings
const SettingsEntry SETTINGS_ENTRIES[] = const SettingsEntry SETTINGS_ENTRIES[] =
{ {
{"window", TYPE_IVEC2, offsetof(Settings, windowSize)}, {"window", TYPE_IVEC2, offsetof(Settings, windowSize)},
{"isVsync", TYPE_BOOL, offsetof(Settings, isVsync)},
{"playbackIsLoop", TYPE_BOOL, offsetof(Settings, playbackIsLoop)}, {"playbackIsLoop", TYPE_BOOL, offsetof(Settings, playbackIsLoop)},
{"playbackIsClampPlayhead", TYPE_BOOL, offsetof(Settings, playbackIsClampPlayhead)}, {"playbackIsClampPlayhead", TYPE_BOOL, offsetof(Settings, playbackIsClampPlayhead)},
{"changeIsCrop", TYPE_BOOL, offsetof(Settings, changeIsCrop)}, {"changeIsCrop", TYPE_BOOL, offsetof(Settings, changeIsCrop)},
@@ -182,6 +184,7 @@ const std::string SETTINGS_DEFAULT = R"(
[Settings] [Settings]
windowX=1920 windowX=1920
windowY=1080 windowY=1080
isVsync=true
playbackIsLoop=true playbackIsLoop=true
playbackIsClampPlayhead=false playbackIsClampPlayhead=false
changeIsCrop=false changeIsCrop=false

View File

@@ -79,6 +79,8 @@ void init(State* self)
self->glContext = SDL_GL_CreateContext(self->window); self->glContext = SDL_GL_CreateContext(self->window);
window_vsync_set(self->settings.isVsync);
if (!self->glContext) if (!self->glContext)
{ {
log_error(std::format(STATE_GL_CONTEXT_INIT_ERROR, SDL_GetError())); log_error(std::format(STATE_GL_CONTEXT_INIT_ERROR, SDL_GetError()));
@@ -96,8 +98,6 @@ void init(State* self)
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);
glDisable(GL_LINE_SMOOTH); glDisable(GL_LINE_SMOOTH);
SDL_GL_SetSwapInterval(1);
resources_init(&self->resources); resources_init(&self->resources);
dialog_init(&self->dialog, self->window); dialog_init(&self->dialog, self->window);
clipboard_init(&self->clipboard, &self->anm2); clipboard_init(&self->clipboard, &self->anm2);
@@ -135,6 +135,7 @@ void init(State* self)
void loop(State* self) void loop(State* self)
{ {
self->tick = SDL_GetTicks(); self->tick = SDL_GetTicks();
self->update = self->tick;
while (self->tick > self->lastTick + TICK_DELAY) while (self->tick > self->lastTick + TICK_DELAY)
{ {
@@ -148,10 +149,26 @@ void loop(State* self)
self->lastTick = self->tick; self->lastTick = self->tick;
} }
if (self->settings.isVsync)
{
_update(self);
_draw(self);
}
else
{
while (self->update > self->lastUpdate + UPDATE_DELAY)
{
self->update = SDL_GetTicks();
if (self->update - self->lastUpdate < UPDATE_DELAY)
SDL_Delay(UPDATE_DELAY - (self->update - self->lastUpdate));
_update(self); _update(self);
_draw(self); _draw(self);
SDL_Delay(STATE_DELAY_MIN); self->lastUpdate = self->update;
}
}
} }
void quit(State* self) void quit(State* self)

View File

@@ -43,6 +43,8 @@ struct State
std::string lastAction{}; std::string lastAction{};
u64 lastTick{}; u64 lastTick{};
u64 tick{}; u64 tick{};
u64 update{};
u64 lastUpdate{};
bool isRunning = true; bool isRunning = true;
}; };

View File

@@ -7,3 +7,11 @@ void window_title_from_path_set(SDL_Window* self, const std::string& path)
else else
SDL_SetWindowTitle(self, WINDOW_TITLE); SDL_SetWindowTitle(self, WINDOW_TITLE);
} }
void window_vsync_set(bool isVsync)
{
if (isVsync)
SDL_GL_SetSwapInterval(1);
else
SDL_GL_SetSwapInterval(0);
}

View File

@@ -8,3 +8,4 @@
void window_title_from_path_set(SDL_Window* self, const std::string& path); void window_title_from_path_set(SDL_Window* self, const std::string& path);
bool window_color_from_position_get(SDL_Window* self, vec2 position, vec4* color); bool window_color_from_position_get(SDL_Window* self, vec2 position, vec4* color);
void window_vsync_set(bool isVsync);