Refactor, Vsync, FFmpeg fixes
This commit is contained in:
		
							
								
								
									
										13
									
								
								src/COMMON.h
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								src/COMMON.h
									
									
									
									
									
								
							@@ -23,10 +23,9 @@
 | 
			
		||||
#include <ranges>                      
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <unordered_set>                      
 | 
			
		||||
#include <variant>                  
 | 
			
		||||
#include <vector>                  
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
typedef uint8_t u8;
 | 
			
		||||
typedef uint16_t u16;
 | 
			
		||||
typedef uint32_t u32;
 | 
			
		||||
@@ -49,10 +48,9 @@ using namespace glm;
 | 
			
		||||
#define FLOAT_TO_U8(x) (static_cast<u8>((x) * 255.0f))
 | 
			
		||||
#define U8_TO_FLOAT(x) ((x) / 255.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 TICK_RATE (SECOND / TICK_DELAY)
 | 
			
		||||
#define TICK_DELAY (SECOND / 30.0)
 | 
			
		||||
#define UPDATE_DELAY (SECOND / 120.0)
 | 
			
		||||
#define ID_NONE -1
 | 
			
		||||
#define INDEX_NONE -1
 | 
			
		||||
#define TIME_NONE -1.0f
 | 
			
		||||
@@ -125,6 +123,11 @@ static inline bool string_to_bool(const std::string& string)
 | 
			
		||||
    return lower == "true";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline std::string string_quote(const std::string& string) 
 | 
			
		||||
{
 | 
			
		||||
    return "\"" + string + "\"";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline std::string path_canonical_resolve
 | 
			
		||||
(
 | 
			
		||||
    const std::string& inputPath,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "anm2.h"
 | 
			
		||||
#include <variant>
 | 
			
		||||
 | 
			
		||||
enum ClipboardItemType
 | 
			
		||||
{
 | 
			
		||||
@@ -34,4 +33,3 @@ void clipboard_copy(Clipboard* self);
 | 
			
		||||
void clipboard_cut(Clipboard* self);
 | 
			
		||||
void clipboard_paste(Clipboard* self);
 | 
			
		||||
void clipboard_init(Clipboard* self, Anm2* anm2);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -30,44 +30,43 @@ void dialog_init(Dialog* self, SDL_Window* window)
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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->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);
 | 
			
		||||
	self->type = DIALOG_RENDER_PATH_SET;
 | 
			
		||||
}
 | 
			
		||||
	SDL_DialogFileFilter filter = DIALOG_RENDER_FILE_FILTERS[type];
 | 
			
		||||
 | 
			
		||||
void dialog_render_directory_set(Dialog* self)
 | 
			
		||||
{
 | 
			
		||||
	if (type == RENDER_PNG)
 | 
			
		||||
		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)
 | 
			
		||||
{
 | 
			
		||||
	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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								src/dialog.h
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								src/dialog.h
									
									
									
									
									
								
							@@ -1,5 +1,6 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "render.h"
 | 
			
		||||
#include "window.h"
 | 
			
		||||
 | 
			
		||||
const SDL_DialogFileFilter DIALOG_FILE_FILTER_ANM2[] =
 | 
			
		||||
@@ -12,15 +13,17 @@ const SDL_DialogFileFilter DIALOG_FILE_FILTER_PNG[] =
 | 
			
		||||
    {"PNG image", "png"}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const SDL_DialogFileFilter DIALOG_FILE_FILTER_RENDER[] =
 | 
			
		||||
const SDL_DialogFileFilter DIALOG_RENDER_FILE_FILTERS[] =
 | 
			
		||||
{
 | 
			
		||||
    {"PNG image", "png"},
 | 
			
		||||
    {"GIF image", "gif"}, 
 | 
			
		||||
    {"WebM video", "webm"}
 | 
			
		||||
    {"WebM video", "webm"},
 | 
			
		||||
    {"MP4 video", "mp4"}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const SDL_DialogFileFilter DIALOG_FILE_FILTER_FFMPEG[] =
 | 
			
		||||
{
 | 
			
		||||
    {"Executable", ""}
 | 
			
		||||
    {"Executable", "exe"}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum DialogType 
 | 
			
		||||
@@ -31,7 +34,6 @@ enum DialogType
 | 
			
		||||
    DIALOG_SPRITESHEET_ADD,
 | 
			
		||||
    DIALOG_SPRITESHEET_REPLACE,
 | 
			
		||||
    DIALOG_RENDER_PATH_SET,
 | 
			
		||||
    DIALOG_RENDER_DIRECTORY_SET,
 | 
			
		||||
    DIALOG_FFMPEG_PATH_SET
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -50,7 +52,7 @@ void dialog_anm2_open(Dialog* self);
 | 
			
		||||
void dialog_spritesheet_add(Dialog* self);
 | 
			
		||||
void dialog_spritesheet_replace(Dialog* self, s32 id);
 | 
			
		||||
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_ffmpeg_path_set(Dialog* self);
 | 
			
		||||
void dialog_reset(Dialog* self);
 | 
			
		||||
 
 | 
			
		||||
@@ -23,10 +23,17 @@ ffmpeg_render
 | 
			
		||||
        case RENDER_WEBM:
 | 
			
		||||
            command = std::format(FFMPEG_WEBM_FORMAT, ffmpegPath, size.x, size.y, fps, outputPath);
 | 
			
		||||
            break;
 | 
			
		||||
        case RENDER_MP4:
 | 
			
		||||
            command = std::format(FFMPEG_MP4_FORMAT, ffmpegPath, size.x, size.y, fps, outputPath);
 | 
			
		||||
            break;
 | 
			
		||||
        default:
 | 
			
		||||
            return false;
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#if _WIN32
 | 
			
		||||
    command = string_quote(command)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    FILE* fp = POPEN(command.c_str(), PWRITE_MODE);
 | 
			
		||||
 | 
			
		||||
    if (!fp) 
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								src/ffmpeg.h
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								src/ffmpeg.h
									
									
									
									
									
								
							@@ -6,19 +6,25 @@
 | 
			
		||||
#define FFMPEG_POPEN_ERROR "popen() (for FFmpeg) failed!\n{}"
 | 
			
		||||
 | 
			
		||||
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 "
 | 
			
		||||
"-lavfi \"split[s0][s1];"
 | 
			
		||||
"[s0]palettegen=stats_mode=full[p];"
 | 
			
		||||
"[s1][p]paletteuse=dither=floyd_steinberg\" "
 | 
			
		||||
"-loop 0 \"{4}\"";
 | 
			
		||||
"-loop 0 {4}";
 | 
			
		||||
 | 
			
		||||
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 "
 | 
			
		||||
"-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 
 | 
			
		||||
ffmpeg_render
 | 
			
		||||
 
 | 
			
		||||
@@ -354,10 +354,11 @@ static bool NAME(const ImguiItem& self, Imgui* imgui, bool& boolValue)
 | 
			
		||||
	ImguiItem checkboxItem = self.copy																\
 | 
			
		||||
	({.label = std::format(IMGUI_INVISIBLE_FORMAT, self.label), .isMnemonicDisabled = true});		\
 | 
			
		||||
	checkboxItem.isDisabled = false;																\
 | 
			
		||||
	_imgui_checkbox(checkboxItem, imgui, boolValue);												\
 | 
			
		||||
	bool isCheckboxActivated = _imgui_checkbox(checkboxItem, imgui, boolValue);						\
 | 
			
		||||
	ImGui::SameLine();			                                                                    \
 | 
			
		||||
    bool isActivated = ([&] { return FUNCTION; })();                                                \
 | 
			
		||||
	if (isActivated) boolValue = !boolValue;											            \
 | 
			
		||||
	if (isCheckboxActivated) isActivated = true;													\
 | 
			
		||||
	return isActivated;																				\
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -367,9 +368,10 @@ static bool NAME(const ImguiItem& self, Imgui* imgui, VALUE& value, bool& boolVa
 | 
			
		||||
	ImguiItem checkboxItem = self.copy																\
 | 
			
		||||
	({.label = std::format(IMGUI_INVISIBLE_FORMAT, self.label), .isMnemonicDisabled = true});		\
 | 
			
		||||
	checkboxItem.isDisabled = false;																\
 | 
			
		||||
	_imgui_checkbox(checkboxItem, imgui, boolValue);												\
 | 
			
		||||
	bool isCheckboxActivated = _imgui_checkbox(checkboxItem, imgui, boolValue);						\
 | 
			
		||||
	ImGui::SameLine();																				\
 | 
			
		||||
    bool isActivated = ([&](VALUE& value) { return FUNCTION; })(value);								\
 | 
			
		||||
	if (isCheckboxActivated) isActivated = true;													\
 | 
			
		||||
	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))
 | 
			
		||||
	{
 | 
			
		||||
		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);
 | 
			
		||||
 | 
			
		||||
		if (_imgui_atlas_button(IMGUI_RENDER_ANIMATION_LOCATION_BROWSE, self))
 | 
			
		||||
		{
 | 
			
		||||
			switch (self->settings->renderType)
 | 
			
		||||
			{
 | 
			
		||||
				case RENDER_PNG: dialog_render_directory_set(self->dialog); break;
 | 
			
		||||
				default: 
 | 
			
		||||
					dialog_render_path_set(self->dialog); 
 | 
			
		||||
					break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
			dialog_render_path_set(self->dialog, (RenderType)type);
 | 
			
		||||
 | 
			
		||||
		if 
 | 
			
		||||
		(
 | 
			
		||||
			self->dialog->isSelected && 
 | 
			
		||||
			(self->dialog->type == DIALOG_RENDER_PATH_SET || self->dialog->type == DIALOG_RENDER_DIRECTORY_SET)
 | 
			
		||||
		)
 | 
			
		||||
		if (dialogIsSelected && (dialogType == DIALOG_RENDER_PATH_SET))
 | 
			
		||||
		{
 | 
			
		||||
			self->settings->renderPath = self->dialog->path;
 | 
			
		||||
			path = path_extension_change(dialogPath, RENDER_EXTENSIONS[type]);
 | 
			
		||||
			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)) 
 | 
			
		||||
			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);
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		_imgui_input_text(IMGUI_RENDER_ANIMATION_FFMPEG_PATH, self, self->settings->ffmpegPath);
 | 
			
		||||
		_imgui_input_text(IMGUI_RENDER_ANIMATION_FORMAT, self, self->settings->renderFormat);
 | 
			
		||||
		_imgui_combo(IMGUI_RENDER_ANIMATION_OUTPUT, self, &self->settings->renderType);
 | 
			
		||||
		_imgui_input_text(IMGUI_RENDER_ANIMATION_FFMPEG_PATH, self, ffmpegPath);
 | 
			
		||||
		_imgui_input_text(IMGUI_RENDER_ANIMATION_FORMAT, self, format);
 | 
			
		||||
		_imgui_combo(IMGUI_RENDER_ANIMATION_OUTPUT, self, &type);
 | 
			
		||||
 | 
			
		||||
		if (_imgui_button(IMGUI_RENDER_ANIMATION_CONFIRM, self))
 | 
			
		||||
		{
 | 
			
		||||
			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);
 | 
			
		||||
				isRenderStart = false;
 | 
			
		||||
@@ -1466,7 +1464,7 @@ static void _imgui_taskbar(Imgui* self)
 | 
			
		||||
				switch (self->settings->renderType)
 | 
			
		||||
				{
 | 
			
		||||
					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);
 | 
			
		||||
							isRenderStart = false;
 | 
			
		||||
@@ -1474,7 +1472,8 @@ static void _imgui_taskbar(Imgui* self)
 | 
			
		||||
						break;
 | 
			
		||||
					case RENDER_GIF:
 | 
			
		||||
					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);
 | 
			
		||||
							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))
 | 
			
		||||
	{
 | 
			
		||||
		static s32& type = self->settings->renderType;
 | 
			
		||||
		static std::string& format = self->settings->renderFormat;
 | 
			
		||||
 | 
			
		||||
		auto rendering_end = [&]()
 | 
			
		||||
		{
 | 
			
		||||
			preview_render_end(self->preview);
 | 
			
		||||
@@ -1537,7 +1539,7 @@ static void _imgui_taskbar(Imgui* self)
 | 
			
		||||
		
 | 
			
		||||
		if (self->preview->isRenderFinished)
 | 
			
		||||
		{
 | 
			
		||||
			switch (self->settings->renderType)
 | 
			
		||||
			switch (type)
 | 
			
		||||
			{
 | 
			
		||||
				case RENDER_PNG:
 | 
			
		||||
				{
 | 
			
		||||
@@ -1546,10 +1548,9 @@ static void _imgui_taskbar(Imgui* self)
 | 
			
		||||
 | 
			
		||||
					for (auto [i, frame] : std::views::enumerate(frames))
 | 
			
		||||
					{
 | 
			
		||||
						std::string framePath = std::vformat(self->settings->renderFormat, std::make_format_args(i));
 | 
			
		||||
						framePath = path_extension_change(framePath, RENDER_EXTENSIONS[self->settings->renderType]);
 | 
			
		||||
						if (!frame.isInvalid)
 | 
			
		||||
							texture_from_gl_write(&frame, framePath);
 | 
			
		||||
						std::string framePath = std::vformat(format, std::make_format_args(i));
 | 
			
		||||
						framePath = path_extension_change(framePath, RENDER_EXTENSIONS[type]);
 | 
			
		||||
						if (!frame.isInvalid) texture_from_gl_write(&frame, framePath);
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					std::filesystem::current_path(workingPath);
 | 
			
		||||
@@ -1558,11 +1559,12 @@ static void _imgui_taskbar(Imgui* self)
 | 
			
		||||
				}
 | 
			
		||||
				case RENDER_GIF:
 | 
			
		||||
				case RENDER_WEBM:
 | 
			
		||||
				case RENDER_MP4:
 | 
			
		||||
				{
 | 
			
		||||
					std::string ffmpegPath = std::string(self->settings->ffmpegPath.c_str());
 | 
			
		||||
					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));
 | 
			
		||||
					else
 | 
			
		||||
						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_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();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								src/imgui.h
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								src/imgui.h
									
									
									
									
									
								
							@@ -975,8 +975,8 @@ IMGUI_ITEM(IMGUI_PLAYBACK,
 | 
			
		||||
    self.tooltip = "Opens the playback menu, for configuring playback settings.",
 | 
			
		||||
    self.popup = "## Playback Popup",
 | 
			
		||||
    self.popupType = IMGUI_POPUP_BY_ITEM,
 | 
			
		||||
    self.popupSize = {150, 0},
 | 
			
		||||
    self.isSizeToText = true
 | 
			
		||||
    self.isSizeToText = true,
 | 
			
		||||
    self.isSameLine = true
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
IMGUI_ITEM(IMGUI_ALWAYS_LOOP,
 | 
			
		||||
@@ -991,6 +991,20 @@ IMGUI_ITEM(IMGUI_CLAMP_PLAYHEAD,
 | 
			
		||||
    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, 
 | 
			
		||||
    self.label = "Animations",
 | 
			
		||||
    self.flags = ImGuiWindowFlags_NoScrollbar       |
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,7 @@ void preview_tick(Preview* self)
 | 
			
		||||
                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)
 | 
			
		||||
            {
 | 
			
		||||
@@ -72,10 +72,7 @@ void preview_tick(Preview* self)
 | 
			
		||||
			time = std::clamp(time, 0.0f, std::max(0.0f, (f32)animation->frameNum - 1));
 | 
			
		||||
		else
 | 
			
		||||
			time = std::max(time, 0.0f);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void preview_draw(Preview* self)
 | 
			
		||||
 
 | 
			
		||||
@@ -7,19 +7,23 @@ enum RenderType
 | 
			
		||||
    RENDER_PNG,
 | 
			
		||||
    RENDER_GIF,
 | 
			
		||||
    RENDER_WEBM,
 | 
			
		||||
    RENDER_COUNT
 | 
			
		||||
    RENDER_MP4
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
constexpr inline s32 RENDER_COUNT = RENDER_MP4 + 1;
 | 
			
		||||
 | 
			
		||||
const inline std::string RENDER_TYPE_STRINGS[] = 
 | 
			
		||||
{
 | 
			
		||||
    "PNG Images",
 | 
			
		||||
    "GIF image",
 | 
			
		||||
    "WebM video",
 | 
			
		||||
    "MP4 video"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const inline std::string RENDER_EXTENSIONS[RENDER_COUNT] =
 | 
			
		||||
{
 | 
			
		||||
    ".png",
 | 
			
		||||
    ".gif",
 | 
			
		||||
    ".webm"
 | 
			
		||||
    ".webm",
 | 
			
		||||
    ".mp4"
 | 
			
		||||
};
 | 
			
		||||
@@ -32,6 +32,7 @@ struct SettingsEntry
 | 
			
		||||
struct Settings
 | 
			
		||||
{
 | 
			
		||||
    ivec2 windowSize = {1080, 720};
 | 
			
		||||
    bool isVsync = true;
 | 
			
		||||
    bool playbackIsLoop = true;
 | 
			
		||||
    bool playbackIsClampPlayhead = true;
 | 
			
		||||
    bool changeIsCrop = false;
 | 
			
		||||
@@ -106,6 +107,7 @@ struct Settings
 | 
			
		||||
const SettingsEntry SETTINGS_ENTRIES[] =
 | 
			
		||||
{
 | 
			
		||||
    {"window", TYPE_IVEC2, offsetof(Settings, windowSize)},
 | 
			
		||||
    {"isVsync", TYPE_BOOL, offsetof(Settings, isVsync)},
 | 
			
		||||
    {"playbackIsLoop", TYPE_BOOL, offsetof(Settings, playbackIsLoop)},
 | 
			
		||||
    {"playbackIsClampPlayhead", TYPE_BOOL, offsetof(Settings, playbackIsClampPlayhead)},
 | 
			
		||||
    {"changeIsCrop", TYPE_BOOL, offsetof(Settings, changeIsCrop)},
 | 
			
		||||
@@ -182,6 +184,7 @@ const std::string SETTINGS_DEFAULT = R"(
 | 
			
		||||
[Settings]
 | 
			
		||||
windowX=1920
 | 
			
		||||
windowY=1080
 | 
			
		||||
isVsync=true
 | 
			
		||||
playbackIsLoop=true
 | 
			
		||||
playbackIsClampPlayhead=false
 | 
			
		||||
changeIsCrop=false
 | 
			
		||||
 
 | 
			
		||||
@@ -79,6 +79,8 @@ void init(State* self)
 | 
			
		||||
 | 
			
		||||
	self->glContext = SDL_GL_CreateContext(self->window);
 | 
			
		||||
 | 
			
		||||
	window_vsync_set(self->settings.isVsync);
 | 
			
		||||
	
 | 
			
		||||
	if (!self->glContext)
 | 
			
		||||
	{
 | 
			
		||||
		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_LINE_SMOOTH);
 | 
			
		||||
	
 | 
			
		||||
	SDL_GL_SetSwapInterval(1);
 | 
			
		||||
 | 
			
		||||
	resources_init(&self->resources);
 | 
			
		||||
	dialog_init(&self->dialog, self->window);
 | 
			
		||||
	clipboard_init(&self->clipboard, &self->anm2);
 | 
			
		||||
@@ -135,6 +135,7 @@ void init(State* self)
 | 
			
		||||
void loop(State* self)
 | 
			
		||||
{
 | 
			
		||||
	self->tick = SDL_GetTicks();
 | 
			
		||||
	self->update = self->tick;
 | 
			
		||||
 | 
			
		||||
	while (self->tick > self->lastTick + TICK_DELAY)
 | 
			
		||||
	{
 | 
			
		||||
@@ -148,10 +149,26 @@ void loop(State* self)
 | 
			
		||||
		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);
 | 
			
		||||
			_draw(self);
 | 
			
		||||
 | 
			
		||||
	SDL_Delay(STATE_DELAY_MIN);
 | 
			
		||||
			self->lastUpdate = self->update;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void quit(State* self)
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,8 @@ struct State
 | 
			
		||||
	std::string lastAction{};
 | 
			
		||||
	u64 lastTick{};
 | 
			
		||||
	u64 tick{};
 | 
			
		||||
	u64 update{};
 | 
			
		||||
	u64 lastUpdate{};
 | 
			
		||||
	bool isRunning = true;
 | 
			
		||||
}; 
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,3 +7,11 @@ void window_title_from_path_set(SDL_Window* self, const std::string& path)
 | 
			
		||||
    else
 | 
			
		||||
        SDL_SetWindowTitle(self, WINDOW_TITLE);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void window_vsync_set(bool isVsync)
 | 
			
		||||
{
 | 
			
		||||
    if (isVsync)
 | 
			
		||||
        SDL_GL_SetSwapInterval(1);
 | 
			
		||||
    else
 | 
			
		||||
        SDL_GL_SetSwapInterval(0);
 | 
			
		||||
}
 | 
			
		||||
@@ -8,3 +8,4 @@
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
void window_vsync_set(bool isVsync);
 | 
			
		||||
		Reference in New Issue
	
	Block a user