diff --git a/src/COMMON.h b/src/COMMON.h index a9f079a..75653e3 100644 --- a/src/COMMON.h +++ b/src/COMMON.h @@ -23,10 +23,9 @@ #include #include #include +#include #include - - 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((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, diff --git a/src/clipboard.h b/src/clipboard.h index 2f874ca..092cf97 100644 --- a/src/clipboard.h +++ b/src/clipboard.h @@ -1,7 +1,6 @@ #pragma once #include "anm2.h" -#include enum ClipboardItemType { @@ -33,5 +32,4 @@ struct Clipboard void clipboard_copy(Clipboard* self); void clipboard_cut(Clipboard* self); void clipboard_paste(Clipboard* self); -void clipboard_init(Clipboard* self, Anm2* anm2); - +void clipboard_init(Clipboard* self, Anm2* anm2); \ No newline at end of file diff --git a/src/dialog.cpp b/src/dialog.cpp index 81abdd9..f2634be 100644 --- a/src/dialog.cpp +++ b/src/dialog.cpp @@ -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) -{ - SDL_ShowOpenFolderDialog(_dialog_callback, self, self->window, nullptr, false); - self->type = DIALOG_RENDER_DIRECTORY_SET; + if (type == RENDER_PNG) + SDL_ShowOpenFolderDialog(_dialog_callback, self, self->window, nullptr, false); + 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; } diff --git a/src/dialog.h b/src/dialog.h index b65927c..6c2896b 100644 --- a/src/dialog.h +++ b/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); diff --git a/src/ffmpeg.cpp b/src/ffmpeg.cpp index 281bab9..12d68a1 100644 --- a/src/ffmpeg.cpp +++ b/src/ffmpeg.cpp @@ -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) diff --git a/src/ffmpeg.h b/src/ffmpeg.h index 031ff27..007943c 100644 --- a/src/ffmpeg.h +++ b/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 diff --git a/src/imgui.cpp b/src/imgui.cpp index 01396da..7be2ab6 100644 --- a/src/imgui.cpp +++ b/src/imgui.cpp @@ -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)); @@ -1586,6 +1588,14 @@ static void _imgui_taskbar(Imgui* self) _imgui_checkbox_selectable(IMGUI_CLAMP_PLAYHEAD, self, self->settings->playbackIsClampPlayhead); 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(); } diff --git a/src/imgui.h b/src/imgui.h index 3231c14..6f2998f 100644 --- a/src/imgui.h +++ b/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 | diff --git a/src/preview.cpp b/src/preview.cpp index 9617229..47e2dfc 100644 --- a/src/preview.cpp +++ b/src/preview.cpp @@ -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) diff --git a/src/render.h b/src/render.h index 748201c..2664b3c 100644 --- a/src/render.h +++ b/src/render.h @@ -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" }; \ No newline at end of file diff --git a/src/settings.h b/src/settings.h index 3290028..b1f0fd2 100644 --- a/src/settings.h +++ b/src/settings.h @@ -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 diff --git a/src/state.cpp b/src/state.cpp index af70ee2..5ba283d 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -78,6 +78,8 @@ void init(State* self) SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); self->glContext = SDL_GL_CreateContext(self->window); + + window_vsync_set(self->settings.isVsync); if (!self->glContext) { @@ -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,7 +135,8 @@ void init(State* self) void loop(State* self) { self->tick = SDL_GetTicks(); - + self->update = self->tick; + while (self->tick > self->lastTick + TICK_DELAY) { self->tick = SDL_GetTicks(); @@ -148,10 +149,26 @@ void loop(State* self) self->lastTick = self->tick; } - _update(self); - _draw(self); + 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)); - SDL_Delay(STATE_DELAY_MIN); + _update(self); + _draw(self); + + self->lastUpdate = self->update; + } + } } void quit(State* self) diff --git a/src/state.h b/src/state.h index 4e023c2..ff94666 100644 --- a/src/state.h +++ b/src/state.h @@ -43,6 +43,8 @@ struct State std::string lastAction{}; u64 lastTick{}; u64 tick{}; + u64 update{}; + u64 lastUpdate{}; bool isRunning = true; }; diff --git a/src/window.cpp b/src/window.cpp index bf9243f..553d0f4 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -6,4 +6,12 @@ void window_title_from_path_set(SDL_Window* self, const std::string& path) SDL_SetWindowTitle(self, std::format(WINDOW_TITLE_FORMAT, path).c_str()); else SDL_SetWindowTitle(self, WINDOW_TITLE); +} + +void window_vsync_set(bool isVsync) +{ + if (isVsync) + SDL_GL_SetSwapInterval(1); + else + SDL_GL_SetSwapInterval(0); } \ No newline at end of file diff --git a/src/window.h b/src/window.h index 91adb8b..6b9ffdc 100644 --- a/src/window.h +++ b/src/window.h @@ -7,4 +7,5 @@ #define WINDOW_FLAGS SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL 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); \ No newline at end of file +bool window_color_from_position_get(SDL_Window* self, vec2 position, vec4* color); +void window_vsync_set(bool isVsync); \ No newline at end of file