infinipaint/src/main.cpp

877 lines
33 KiB
C++
Raw Normal View History

#include "CustomEvents.hpp"
2025-07-06 20:43:53 +03:00
#include "Helpers/SCollision.hpp"
#include "Helpers/StringHelpers.hpp"
#include "Helpers/FileDownloader.hpp"
#include "VersionConstants.hpp"
2025-07-06 20:43:53 +03:00
#include "include/gpu/GpuTypes.h"
2026-02-24 17:21:41 +03:00
#include <SDL3/SDL_filesystem.h>
2025-07-06 20:43:53 +03:00
#include <SDL3/SDL_hints.h>
#include <SDL3/SDL_oldnames.h>
2025-09-29 17:05:53 +03:00
#include <chrono>
2025-07-06 20:43:53 +03:00
#include <filesystem>
#include <include/core/SkAlphaType.h>
#include <include/core/SkColorType.h>
#include <thread>
2025-07-06 20:43:53 +03:00
#ifdef USE_BACKEND_OPENGL
#ifndef __EMSCRIPTEN__
2025-08-09 10:45:39 +03:00
#ifdef USE_BACKEND_OPENGLES_3_0
#define GLAD_GLES2_IMPLEMENTATION
#include <glad/gles3_0.h>
#elif USE_BACKEND_OPENGL_2_1
#define GLAD_GL_IMPLEMENTATION
#include <glad/gl2_1.h>
2025-08-01 16:16:40 +03:00
#else
2025-08-09 10:45:39 +03:00
#define GLAD_GL_IMPLEMENTATION
#include <glad/gl3_3.h>
2025-08-01 16:16:40 +03:00
#endif
2025-07-06 20:43:53 +03:00
#endif
#endif
#include "cereal/archives/portable_binary.hpp"
#include <include/core/SkCanvas.h>
#include <include/core/SkSurface.h>
#include <include/core/SkSurfaceProps.h>
#ifdef USE_SKIA_BACKEND_GANESH
#include <include/gpu/ganesh/GrDirectContext.h>
#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include <include/gpu/ganesh/GrBackendSurface.h>
#elif USE_SKIA_BACKEND_GRAPHITE
#include <include/gpu/graphite/Surface.h>
#endif
#include <iostream>
#include <cereal/types/string.hpp>
#include "MainProgram.hpp"
#include <include/codec/SkPngDecoder.h>
#define SDL_MAIN_USE_CALLBACKS
#include <SDL3/SDL_main.h>
#include <SDL3/SDL_init.h>
#include <SDL3/SDL_pen.h>
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_keyboard.h>
#include <SDL3/SDL_mouse.h>
#include <SDL3/SDL_scancode.h>
#include <SDL3/SDL_video.h>
#include <SDL3/SDL.h>
2026-02-04 19:15:58 +03:00
#include "DrawingProgram/DrawingProgramCache.hpp"
2025-07-06 20:43:53 +03:00
#include <fstream>
#ifdef __EMSCRIPTEN__
#include <SDL3/SDL_opengl.h>
#include <include/gpu/ganesh/gl/GrGLDirectContext.h>
#include <include/gpu/ganesh/gl/GrGLInterface.h>
#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
#include <include/gpu/ganesh/gl/GrGLTypes.h>
#include <emscripten/html5.h>
#include <emscripten.h>
#include <emscripten/emscripten.h>
#elif USE_BACKEND_VULKAN
#ifdef USE_SKIA_BACKEND_GRAPHITE
#include "VulkanContext/GraphiteNativeVulkanWindowContext.h"
#elif USE_SKIA_BACKEND_GANESH
#include "VulkanContext/VulkanWindowContext.h"
#endif
#include <tools/window/DisplayParams.h>
#include <SDL3/SDL_vulkan.h>
#elif USE_BACKEND_OPENGL
#include <include/gpu/ganesh/gl/GrGLDirectContext.h>
#include <include/gpu/ganesh/gl/GrGLInterface.h>
#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
#include <include/gpu/ganesh/gl/GrGLTypes.h>
#endif
#ifdef __linux__
#include <unistd.h>
#include <pwd.h>
#elif _WIN32
#include <shlobj.h>
#endif
#include <Helpers/Logger.hpp>
#include "SwitchCWD.hpp"
#include "CustomEvents.hpp"
2025-07-06 20:43:53 +03:00
// Use dedicated graphics card on Windows
#ifdef _WIN32
extern "C"
{
__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;
__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
}
#endif
#include <unicode/udata.h>
std::string icudt; // Put this in global space so that it isn't deallocated
2025-07-06 20:43:53 +03:00
struct MainStruct {
#ifdef USE_BACKEND_VULKAN
#ifdef USE_SKIA_BACKEND_GANESH
std::unique_ptr<skwindow::internal::VulkanWindowContext> vulkanWindowContext;
#elif USE_SKIA_BACKEND_GRAPHITE
std::unique_ptr<skwindow::internal::GraphiteVulkanWindowContext> vulkanWindowContext;
#endif
#elif USE_BACKEND_OPENGL
unsigned kStencilBits = 8;
SDL_GLContext gl_context;
GLint defaultFBO = 0;
#endif
#ifdef USE_SKIA_BACKEND_GRAPHITE
#elif USE_SKIA_BACKEND_GANESH
sk_sp<GrDirectContext> ctx;
GrBackendRenderTarget target;
#endif
std::unique_ptr<MainProgram> m;
SDL_Window* window;
std::array<SDL_Cursor*, SDL_SYSTEM_CURSOR_COUNT> systemCursors;
unsigned currentCursor = 0;
SkCanvas* canvas;
sk_sp<SkSurface> surface;
SDL_Surface* iconSurface = nullptr;
std::string iconData;
SDL_Cursor* hiddenCursor = nullptr;
std::filesystem::path configPath;
std::filesystem::path homePath;
std::ofstream logFile;
2026-02-25 15:25:00 +03:00
std::chrono::steady_clock::time_point lastRenderTimePoint = std::chrono::steady_clock::now();
std::chrono::steady_clock::time_point lastUpdateTimePoint = std::chrono::steady_clock::now();
2026-02-25 15:25:00 +03:00
std::chrono::steady_clock::duration frameRefreshDuration = std::chrono::steady_clock::duration::zero();
2025-07-06 20:43:53 +03:00
} MainData;
#ifdef __EMSCRIPTEN__
bool emscriptenFilesystemReadReady = false;
bool emscriptenFilesystemLoadConfigDone = false;
#endif
bool load_file_to_string(std::string& toRet, std::string_view fileName) {
// https://nullptr.org/cpp-read-file-into-string/
std::ifstream file(fileName.data(), std::ios::in | std::ios::binary | std::ios::ate);
if(!file.is_open()) {
std::cout << "[load_file_to_string] Could not open file " << fileName << std::endl;
return false;
}
size_t fileSize;
auto tellgResult = file.tellg();
if(tellgResult == -1) {
std::cout << "[load_file_to_string] tellg failed for file " << fileName << std::endl;
return false;
}
fileSize = static_cast<size_t>(tellgResult);
file.seekg(0, std::ios_base::beg);
toRet.resize(fileSize);
file.read(toRet.data(), fileSize);
file.close();
return true;
}
2026-02-25 15:25:00 +03:00
void get_refresh_rate(MainStruct& mS) {
mS.frameRefreshDuration = std::chrono::seconds(0);
SDL_DisplayID displayID = SDL_GetDisplayForWindow(mS.window);
if(displayID == 0)
return;
const SDL_DisplayMode* displayMode = SDL_GetCurrentDisplayMode(displayID);
if(!displayMode)
return;
if(displayMode->refresh_rate != 0.0f)
mS.frameRefreshDuration = std::chrono::microseconds(static_cast<int>((1.0f / displayMode->refresh_rate) * 1000000.0f));
}
2025-07-06 20:43:53 +03:00
void initialize_sdl(MainStruct& mS, int wWidth, int wHeight) {
SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING, "InfiniPaint");
SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_VERSION_STRING, VersionConstants::CURRENT_VERSION_STRING.c_str());
SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_IDENTIFIER_STRING, "com.infinipaint.infinipaint");
SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_CREATOR_STRING, "Yousef Khadadeh (ErrorAtLine0)");
SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_COPYRIGHT_STRING, "Copyright (c) 2026 Yousef Khadadeh");
SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_URL_STRING, "https://infinipaint.com/");
SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_TYPE_STRING, "application");
2025-07-06 20:43:53 +03:00
SDL_SetHint(SDL_HINT_APP_NAME, "InfiniPaint");
SDL_SetHint(SDL_HINT_PEN_MOUSE_EVENTS, "0");
2026-02-27 16:16:51 +03:00
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
2025-07-06 20:43:53 +03:00
SDL_SetHint(SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT, "#canvas"); // Ensures that SDL only grabs input when browser is focused on canvas
2026-03-04 12:15:24 +03:00
if(!SDL_Init(SDL_INIT_VIDEO))
2025-07-06 20:43:53 +03:00
throw std::runtime_error("[SDL_Init] " + std::string(SDL_GetError()));
Uint32 window_flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_HIGH_PIXEL_DENSITY;
#ifdef USE_BACKEND_VULKAN
window_flags |= SDL_WINDOW_VULKAN;
#elif USE_BACKEND_OPENGL
window_flags |= SDL_WINDOW_OPENGL;
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
2025-08-09 10:45:39 +03:00
#if defined(__EMSCRIPTEN__) || defined(USE_BACKEND_OPENGLES_3_0)
2025-07-06 20:43:53 +03:00
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
2025-08-09 10:45:39 +03:00
#elif defined(USE_BACKEND_OPENGL_2_1)
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
2025-08-01 16:16:40 +03:00
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
2025-07-06 20:43:53 +03:00
#else
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
#endif
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, mS.kStencilBits);
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
#endif
mS.window = SDL_CreateWindow("InfiniPaint", wWidth, wHeight, window_flags);
if(mS.window == nullptr)
throw std::runtime_error("[SDL_CreateWindow] " + std::string(SDL_GetError()));
if(load_file_to_string(mS.iconData, "data/progicons/icon.png")) {
sk_sp<SkData> newData = SkData::MakeWithoutCopy(mS.iconData.c_str(), mS.iconData.size());
std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(newData, {SkPngDecoder::Decoder()});
if(codec) {
sk_sp<SkImage> newImage = std::get<0>(codec->getImage());
SkPixmap px;
newImage->peekPixels(&px);
mS.iconSurface = SDL_CreateSurfaceFrom(newImage->width(), newImage->height(), SDL_PIXELFORMAT_RGBA32, px.writable_addr(), px.rowBytes());
SDL_SetWindowIcon(mS.window, mS.iconSurface);
}
}
SDL_SetWindowPosition(mS.window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
#ifdef USE_BACKEND_OPENGL
mS.gl_context = SDL_GL_CreateContext(mS.window);
if(mS.gl_context == nullptr)
throw std::runtime_error("[SDL_GL_CreateContext] " + std::string(SDL_GetError()));
SDL_GL_MakeCurrent(mS.window, mS.gl_context);
#ifndef __EMSCRIPTEN__
2025-08-09 10:45:39 +03:00
#ifdef USE_BACKEND_OPENGLES_3_0
2025-08-01 16:16:40 +03:00
if(!gladLoadGLES2(SDL_GL_GetProcAddress))
2025-08-09 10:45:39 +03:00
throw std::runtime_error("[gladLoadGLES2] Failed to load GLAD OpenGLES 3.0 Loader");
#elif defined(USE_BACKEND_OPENGL_2_1)
if(!gladLoadGL(SDL_GL_GetProcAddress))
throw std::runtime_error("[gladLoadGL] Failed to load GLAD OpenGL 2.1 Loader");
2025-08-01 16:16:40 +03:00
#else
if(!gladLoadGL(SDL_GL_GetProcAddress))
throw std::runtime_error("[gladLoadGL] Failed to load GLAD OpenGL 3.3 Loader");
#endif
2025-08-09 10:45:39 +03:00
#ifndef USE_BACKEND_OPENGL_2_1
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &mS.defaultFBO);
#endif
2025-08-01 16:16:40 +03:00
Logger::get().log("INFO", "GL Version: " + std::string(reinterpret_cast<const char*>(glGetString(GL_VERSION))));
2025-07-06 20:43:53 +03:00
#endif
#endif
SDL_SetWindowSize(mS.window, wWidth, wHeight);
SDL_ShowWindow(mS.window);
for(unsigned i = 0; i < SDL_SYSTEM_CURSOR_COUNT; i++)
mS.systemCursors[i] = SDL_CreateSystemCursor((SDL_SystemCursor)i);
2026-02-25 15:25:00 +03:00
get_refresh_rate(mS);
2025-07-06 20:43:53 +03:00
}
void sdl_terminate(MainStruct& mS) {
for(unsigned i = 0; i < SDL_SYSTEM_CURSOR_COUNT; i++)
SDL_DestroyCursor(mS.systemCursors[i]);
if(mS.iconSurface)
SDL_DestroySurface(mS.iconSurface);
if(mS.hiddenCursor)
SDL_DestroyCursor(mS.hiddenCursor);
//SDL_GL_DestroyContext(mS.gl_context);
//SDL_DestroyWindow(mS.window);
//SDL_Quit();
}
void resize_window(MainStruct& mS) {
// Having intermediate surface allows for changing options more easily
mS.m->refresh_draw_surfaces();
2025-07-06 20:43:53 +03:00
#ifdef USE_BACKEND_VULKAN
mS.vulkanWindowContext->resize(mS.m->window.size.x(), mS.m->window.size.y());
#elif USE_BACKEND_OPENGL
GrGLFramebufferInfo info;
info.fFBOID = (GrGLuint)mS.defaultFBO;
info.fFormat = GL_RGBA8;
SkSurfaceProps props2;
mS.target = GrBackendRenderTargets::MakeGL(mS.m->window.size.x(), mS.m->window.size.y(), 0, mS.kStencilBits, info);
2025-11-20 12:38:28 +03:00
mS.surface = SkSurfaces::WrapBackendRenderTarget(mS.ctx.get(), mS.target, kBottomLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType, SkColorSpace::MakeSRGB(), &props2);
2025-07-06 20:43:53 +03:00
if(!mS.surface)
throw std::runtime_error("[resize_window] Could not make surface");
mS.canvas = mS.surface->getCanvas();
if(!mS.canvas)
throw std::runtime_error("[resize_window] No canvas made");
#endif
}
#ifdef __EMSCRIPTEN__
extern "C" {
EMSCRIPTEN_KEEPALIVE inline void emscripten_filesystem_loaded() {
emscriptenFilesystemReadReady = true;
}
}
const char* emscripten_before_unload(int eventType, const void *reserved, void *userData) {
MainStruct& mS = *((MainStruct*)userData);
mS.m->save_config();
return "";
}
#endif
void init_logs(MainStruct& mS) {
2026-02-24 17:21:41 +03:00
char* homePathSDL = SDL_GetCurrentDirectory();
mS.homePath = std::filesystem::path(homePathSDL);
SDL_free(homePathSDL);
2025-07-06 20:43:53 +03:00
#ifdef __EMSCRIPTEN__
EM_ASM(
FS.mkdir('/infinipaint');
FS.mount(FS.filesystems.IDBFS, {}, '/infinipaint');
FS.syncfs(true, (err) => {
if(err)
console.log("Error syncing IDBFS: ", err);
else {
console.log("Synced to IDBFS. Read Ready");
Module["ccall"]('emscripten_filesystem_loaded', null, [], []);
}
});
);
mS.homePath = std::filesystem::path("/");
mS.configPath = std::filesystem::path("/infinipaint");
#elif CONFIG_NEXT_TO_EXECUTABLE
std::string CONFIG_FOLDER_NAME = "config";
std::filesystem::create_directory(CONFIG_FOLDER_NAME);
mS.configPath = mS.homePath / CONFIG_FOLDER_NAME;
2025-07-06 20:43:53 +03:00
#else
2026-02-24 17:21:41 +03:00
char* configPathSDL = SDL_GetPrefPath("ErrorAtLine0", "infinipaint");
mS.configPath = std::filesystem::path(configPathSDL);
SDL_free(configPathSDL);
2025-07-06 20:43:53 +03:00
#endif
mS.logFile = std::ofstream(mS.configPath / "log.txt");
Logger::get().add_log("FATAL", [&, mS = &mS](const std::string& text) {
mS->logFile << "[FATAL] " << text << std::endl;
std::cerr << "[FATAL] " << text << std::endl;
mS->logFile.close();
});
Logger::get().add_log("INFO", [&, mS = &mS](const std::string& text) {
mS->logFile << "[INFO] " << text << std::endl;
2026-02-24 17:21:41 +03:00
Logger::get().cross_platform_println("[INFO] " + text);
2025-07-06 20:43:53 +03:00
});
Logger::get().log("INFO", "Home Path: " + mS.homePath.string());
Logger::get().log("INFO", "Config Path: " + mS.configPath.string());
}
SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
std::vector<std::filesystem::path> listOfFilesToOpenFromCommand;
for(int i = 1; i < argc; i++)
listOfFilesToOpenFromCommand.emplace_back(std::filesystem::canonical(std::filesystem::path(std::u8string_view(reinterpret_cast<char8_t*>(argv[i])))));
2025-07-06 20:43:53 +03:00
switch_cwd();
UErrorCode uerr = U_ZERO_ERROR;
2025-11-15 09:48:02 +03:00
icudt = read_file_to_string("data/icudt77l-small.dat");
udata_setCommonData((void *) icudt.data(), &uerr);
if (U_FAILURE(uerr)) {
char* errorName = const_cast<char*>(u_errorName(uerr));
2025-11-15 09:48:02 +03:00
std::cout << "Failed to load icudt77l-small.dat" << std::endl;
std::cout << errorName << std::endl;
}
2025-07-06 20:43:53 +03:00
MainStruct* mSPtr = new MainStruct;
*appstate = (void*)mSPtr;
MainStruct& mS = *mSPtr;
init_logs(mS);
FileDownloader::init();
2025-07-06 20:43:53 +03:00
#ifdef NDEBUG
try {
#endif
int initWidth = 1000;
int initHeight = 900;
#ifdef __EMSCRIPTEN__
Vector2d sizeD;
EMSCRIPTEN_RESULT result = emscripten_get_element_css_size("#canvas", &sizeD.x(), &sizeD.y());
if(result != EMSCRIPTEN_RESULT_SUCCESS)
std::cout << "Failed to get canvas size" << std::endl;
else {
initWidth = sizeD.x();
initHeight = sizeD.y();
std::cout << "Initial window size: " << initWidth << " " << initHeight << std::endl;
}
#endif
initialize_sdl(mS, initWidth, initHeight);
2026-04-05 20:26:03 +03:00
CustomEvents::init();
2025-07-06 20:43:53 +03:00
int32_t cursorData[2] = {0, 0};
mS.hiddenCursor = SDL_CreateCursor((Uint8 *)cursorData, (Uint8 *)cursorData, 8, 8, 4, 4);
mS.m = std::make_unique<MainProgram>();
mS.m->logFile = &mS.logFile;
mS.m->configPath = mS.configPath;
mS.m->homePath = mS.homePath;
2026-01-18 13:39:49 +03:00
#ifndef __EMSCRIPTEN__
2026-02-24 17:21:41 +03:00
const char* documentsPathSDL = SDL_GetUserFolder(SDL_FOLDER_DOCUMENTS);
if(!documentsPathSDL) {
documentsPathSDL = SDL_GetUserFolder(SDL_FOLDER_DESKTOP);
if(!documentsPathSDL)
2026-01-18 13:39:49 +03:00
mS.m->documentsPath = mS.m->configPath;
2026-02-24 17:21:41 +03:00
else
mS.m->documentsPath = std::filesystem::path(documentsPathSDL);
2026-01-18 13:39:49 +03:00
}
2026-02-24 17:21:41 +03:00
else
mS.m->documentsPath = std::filesystem::path(documentsPathSDL);
2026-01-18 13:39:49 +03:00
#endif
2025-07-06 20:43:53 +03:00
mS.m->window.sdlWindow = mS.window;
mS.m->update_scale_and_density();
2025-07-06 20:43:53 +03:00
#ifdef __EMSCRIPTEN__
emscripten_set_beforeunload_callback((void*)mSPtr, emscripten_before_unload);
#else
mS.m->load_config();
#endif
#ifdef USE_BACKEND_VULKAN
#ifdef USE_SKIA_BACKEND_GRAPHITE
std::unique_ptr<const skwindow::DisplayParams> displayParams = skwindow::DisplayParamsBuilder().build();
mS.vulkanWindowContext = std::make_unique<skwindow::internal::GraphiteVulkanWindowContext>(std::move(displayParams),
[window = mS.window](VkInstance instance) {
VkSurfaceKHR toRet;
if(!SDL_Vulkan_CreateSurface(window, instance, nullptr, &toRet))
throw std::runtime_error("Failed to create SDL vulkan surface");
else
std::cout << "Successfully created surface!" << std::endl;
return toRet;
},
SDL_Vulkan_GetPresentationSupport,
(PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr());
mS.surface = mS.vulkanWindowContext->getBackbufferSurface();
#elif USE_SKIA_BACKEND_GANESH
std::unique_ptr<const skwindow::DisplayParams> displayParams = skwindow::DisplayParamsBuilder().detach();
2025-07-06 20:43:53 +03:00
mS.vulkanWindowContext = std::make_unique<skwindow::internal::VulkanWindowContext>(std::move(displayParams),
[window = mS.window](VkInstance instance) {
VkSurfaceKHR toRet;
if(!SDL_Vulkan_CreateSurface(window, instance, nullptr, &toRet))
throw std::runtime_error("Failed to create SDL vulkan surface");
else
std::cout << "Successfully created surface!" << std::endl;
return toRet;
},
SDL_Vulkan_GetPresentationSupport,
(PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr());
mS.ctx = mS.vulkanWindowContext->fContext; // fContext is supposed to be private, but put it in public for convenience for this app
mS.surface = mS.vulkanWindowContext->getBackbufferSurface();
#endif
#elif USE_BACKEND_OPENGL
sk_sp<const GrGLInterface> iface = GrGLMakeNativeInterface();
GrContextOptions opts;
opts.fSuppressPrints = true;
opts.fDisableDriverCorrectnessWorkarounds = mS.m->conf.disableGraphicsDriverWorkarounds; // Driver workarounds causing glitches on intel drivers on windows, but are required in the Emscripten version. Good idea to have a toggle for it
2025-07-06 20:43:53 +03:00
mS.ctx = GrDirectContexts::MakeGL(iface, opts);
if(!mS.ctx)
throw std::runtime_error("[GrDirectContexts::MakeGL] Could not make context");
#endif
#ifdef USE_SKIA_BACKEND_GRAPHITE
mS.m->window.recorder = [&]() { return mS.vulkanWindowContext->graphiteRecorder(); };
#elif USE_SKIA_BACKEND_GANESH
mS.m->window.ctx = mS.ctx;
#endif
#ifdef __EMSCRIPTEN__
SDL_GetWindowPosition(mS.window, &mS.m->window.pos.x(), &mS.m->window.pos.y());
mS.m->window.writtenPos.x() = mS.m->window.pos.x();
mS.m->window.writtenPos.y() = mS.m->window.pos.y();
mS.m->window.size.x() = initWidth;
mS.m->window.size.y() = initHeight;
mS.m->window.writtenSize.x() = initWidth;
mS.m->window.writtenSize.y() = initHeight;
#else
if(mS.m->window.pos.x() >= 0 && mS.m->window.pos.y() >= 0 && mS.m->window.size.x() >= 0 && mS.m->window.size.y() >= 0) {
SDL_SetWindowSize(mS.window, mS.m->window.size.x(), mS.m->window.size.y());
SDL_SetWindowPosition(mS.window, mS.m->window.pos.x(), mS.m->window.pos.y());
}
else {
SDL_GetWindowPosition(mS.window, &mS.m->window.pos.x(), &mS.m->window.pos.y());
mS.m->window.writtenPos.x() = mS.m->window.pos.x();
mS.m->window.writtenPos.y() = mS.m->window.pos.y();
mS.m->window.size.x() = initWidth;
mS.m->window.size.y() = initHeight;
mS.m->window.writtenSize.x() = initWidth;
mS.m->window.writtenSize.y() = initHeight;
}
if(mS.m->window.maximized)
SDL_MaximizeWindow(mS.window);
if(mS.m->window.fullscreen)
SDL_SetWindowFullscreen(mS.window, true);
#endif
mS.m->window.canCreateSurfaces = true;
2025-07-06 20:43:53 +03:00
resize_window(mS);
if(listOfFilesToOpenFromCommand.empty()) {
2026-04-05 20:26:03 +03:00
mS.m->create_new_tab({
2026-01-01 22:46:02 +03:00
.isClient = false
2026-04-05 20:26:03 +03:00
});
2025-07-06 20:43:53 +03:00
}
else {
for(std::filesystem::path& f : listOfFilesToOpenFromCommand) {
2026-04-05 20:26:03 +03:00
mS.m->create_new_tab({
2026-01-01 22:46:02 +03:00
.isClient = false,
.filePathSource = f
2026-04-05 20:26:03 +03:00
});
2025-07-06 20:43:53 +03:00
}
}
2026-04-02 10:15:22 +03:00
mS.m->update(); // Run update once to make sure that callbacks dont crash
2025-07-06 20:43:53 +03:00
#ifdef NDEBUG
}
catch(const std::exception& e) {
Logger::get().log("FATAL", e.what());
return SDL_APP_FAILURE;
}
#endif
return SDL_APP_CONTINUE;
}
void cpu_save_sleep(MainStruct& mS) {
int swapInterval = 0;
constexpr std::chrono::duration MINIMUM_UPDATE_FRAMETIME = std::chrono::milliseconds(3);
SDL_GL_GetSwapInterval(&swapInterval);
if(swapInterval != 0) {
std::chrono::time_point point1 = mS.lastRenderTimePoint + mS.frameRefreshDuration;
std::chrono::time_point point2 = mS.lastUpdateTimePoint + MINIMUM_UPDATE_FRAMETIME;
std::this_thread::sleep_until(std::min(point1, point2));
}
}
2026-02-25 15:25:00 +03:00
void attempt_redraw_and_swap_buffers(MainStruct& mS) {
int swapInterval = 0;
SDL_GL_GetSwapInterval(&swapInterval);
if(std::chrono::steady_clock::now() - mS.lastRenderTimePoint > mS.frameRefreshDuration || swapInterval == 0) {
#ifdef USE_BACKEND_VULKAN
mS.vulkanWindowContext->swapBuffers();
#elif USE_BACKEND_OPENGL
SDL_GL_SwapWindow(mS.window);
#endif
mS.lastRenderTimePoint = std::chrono::steady_clock::now();
mS.m->window.intermediateCanvas->save();
mS.m->draw(mS.m->window.intermediateCanvas, mS.m->world, mS.m->world->drawData);
mS.m->window.intermediateCanvas->restore();
2026-02-25 15:25:00 +03:00
#ifdef USE_BACKEND_VULKAN
mS.vulkanWindowContext->getBackbufferSurface()->getCanvas()->drawImage(mS.intermediateSurface->makeTemporaryImage(), 0, 0);
mS.ctx->flushAndSubmit();
#elif USE_BACKEND_OPENGL
mS.canvas->drawImage(mS.m->window.intermediateSurface->makeTemporaryImage(), 0, 0);
2026-02-25 15:25:00 +03:00
mS.ctx->flushAndSubmit();
#endif
}
}
2026-02-27 16:16:51 +03:00
void regular_draw(MainStruct& mS) {
mS.lastRenderTimePoint = std::chrono::steady_clock::now();
mS.m->window.intermediateCanvas->save();
mS.m->draw(mS.m->window.intermediateCanvas, mS.m->world, mS.m->world->drawData);
mS.m->window.intermediateCanvas->restore();
2026-02-27 16:16:51 +03:00
#ifdef USE_BACKEND_VULKAN
mS.vulkanWindowContext->getBackbufferSurface()->getCanvas()->drawImage(mS.intermediateSurface->makeTemporaryImage(), 0, 0);
mS.ctx->flushAndSubmit();
mS.vulkanWindowContext->swapBuffers();
#elif USE_BACKEND_OPENGL
mS.canvas->drawImage(mS.m->window.intermediateSurface->makeTemporaryImage(), 0, 0);
2026-02-27 16:16:51 +03:00
mS.ctx->flushAndSubmit();
SDL_GL_SwapWindow(mS.window);
#endif
}
2025-07-06 20:43:53 +03:00
SDL_AppResult SDL_AppIterate(void *appstate) {
std::chrono::steady_clock::time_point frameTimeStart = std::chrono::steady_clock::now();
2025-07-06 20:43:53 +03:00
MainStruct& mS = *((MainStruct*)appstate);
mS.lastUpdateTimePoint = std::chrono::steady_clock::now();
2025-07-06 20:43:53 +03:00
#ifdef NDEBUG
try {
#endif
mS.m->update_scale_and_density();
2025-07-06 20:43:53 +03:00
#ifdef __EMSCRIPTEN__
if(emscriptenFilesystemReadReady && !emscriptenFilesystemLoadConfigDone) {
mS.m->load_config();
emscriptenFilesystemLoadConfigDone = true;
}
#endif
mS.m->update();
if(mS.m->setToQuit)
return SDL_APP_SUCCESS;
if(mS.m->input.toggleFullscreen) {
SDL_SetWindowFullscreen(mS.window, !mS.m->window.fullscreen);
mS.m->window.fullscreen = !mS.m->window.fullscreen;
}
if(mS.m->input.hideCursor) {
SDL_HideCursor();
SDL_SetCursor(mS.hiddenCursor); // Using this because SDL hidden cursor sometimes doesnt work
}
else {
SDL_ShowCursor();
SDL_SetCursor(mS.systemCursors[static_cast<unsigned>(mS.m->input.cursorIcon)]);
}
2026-02-27 16:16:51 +03:00
//cpu_save_sleep(mS);
//attempt_redraw_and_swap_buffers(mS);
regular_draw(mS);
2025-07-06 20:43:53 +03:00
mS.m->input.frame_reset(mS.m->window.size);
#ifdef NDEBUG
}
catch(const std::exception& e) {
Logger::get().log("FATAL", e.what());
return SDL_APP_FAILURE;
}
#endif
mS.m->window.lastFrameTime = std::chrono::steady_clock::now() - frameTimeStart;
2025-07-06 20:43:53 +03:00
return SDL_APP_CONTINUE;
}
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) {
MainStruct& mS = *((MainStruct*)appstate);
#ifdef NDEBUG
try {
#endif
switch(event->type) {
case SDL_EVENT_QUIT:
2026-01-18 13:39:49 +03:00
if(mS.m->app_close_requested())
2025-07-06 20:43:53 +03:00
return SDL_APP_SUCCESS;
break;
2026-01-18 13:39:49 +03:00
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
if(event->window.windowID == SDL_GetWindowID(mS.window)) {
if(mS.m->app_close_requested())
return SDL_APP_SUCCESS;
}
break;
2025-07-06 20:43:53 +03:00
case SDL_EVENT_WINDOW_MOVED:
mS.m->window.pos.x() = event->window.data1;
mS.m->window.pos.y() = event->window.data2;
if(!mS.m->window.fullscreen && !mS.m->window.maximized) {
mS.m->window.writtenPos.x() = event->window.data1;
mS.m->window.writtenPos.y() = event->window.data2;
}
2026-02-25 15:25:00 +03:00
get_refresh_rate(mS);
2025-07-06 20:43:53 +03:00
break;
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
mS.m->window.size.x() = event->window.data1;
mS.m->window.size.y() = event->window.data2;
if(!mS.m->window.fullscreen && !mS.m->window.maximized) {
mS.m->window.writtenSize.x() = event->window.data1;
mS.m->window.writtenSize.y() = event->window.data2;
}
resize_window(mS);
2026-02-25 15:25:00 +03:00
get_refresh_rate(mS);
2025-07-06 20:43:53 +03:00
break;
case SDL_EVENT_WINDOW_MAXIMIZED:
mS.m->window.maximized = true;
2026-02-25 15:25:00 +03:00
get_refresh_rate(mS);
2025-07-06 20:43:53 +03:00
break;
case SDL_EVENT_WINDOW_RESTORED:
mS.m->window.maximized = false;
2026-02-25 15:25:00 +03:00
get_refresh_rate(mS);
2025-07-06 20:43:53 +03:00
break;
case SDL_EVENT_MOUSE_MOTION:
mS.m->update_scale_and_density();
2025-07-08 15:12:35 +03:00
#ifdef _WIN32
if(!mS.m->toolbar.tabletOptions.ignoreMouseMovementWhenPenInProximity || !mS.m->input.pen.inProximity)
#endif
{
2026-03-01 17:28:43 +03:00
mS.m->input.backend_mouse_motion_update(event->motion);
2025-07-08 15:12:35 +03:00
}
2025-07-06 20:43:53 +03:00
break;
case SDL_EVENT_MOUSE_BUTTON_UP:
2026-03-01 17:28:43 +03:00
mS.m->update_scale_and_density();
mS.m->input.backend_mouse_button_up_update(event->button);
2025-07-06 20:43:53 +03:00
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
2026-03-01 17:28:43 +03:00
mS.m->update_scale_and_density();
mS.m->input.backend_mouse_button_down_update(event->button);
2025-07-06 20:43:53 +03:00
break;
case SDL_EVENT_MOUSE_WHEEL:
2026-03-02 23:09:48 +03:00
mS.m->update_scale_and_density();
2026-03-01 17:28:43 +03:00
mS.m->input.backend_mouse_wheel_update(event->wheel);
2025-07-06 20:43:53 +03:00
break;
case SDL_EVENT_KEY_DOWN:
mS.m->input.backend_key_down_update(event->key);
break;
case SDL_EVENT_KEY_UP:
mS.m->input.backend_key_up_update(event->key);
break;
case SDL_EVENT_TEXT_INPUT:
mS.m->input.backend_input_text_event(event->text.text);
2025-07-06 20:43:53 +03:00
break;
case SDL_EVENT_DROP_FILE:
mS.m->update_scale_and_density();
mS.m->input.backend_drop_file_event(event->drop);
2025-07-06 20:43:53 +03:00
break;
case SDL_EVENT_DROP_TEXT:
mS.m->update_scale_and_density();
mS.m->input.backend_drop_text_event(event->drop);
2025-07-06 20:43:53 +03:00
break;
case SDL_EVENT_PEN_PROXIMITY_IN: {
mS.m->input.pen.inProximity = true;
break;
}
case SDL_EVENT_PEN_PROXIMITY_OUT: {
mS.m->input.pen.inProximity = false;
break;
}
case SDL_EVENT_PEN_MOTION: {
mS.m->update_scale_and_density();
2026-03-03 14:59:18 +03:00
mS.m->input.backend_pen_motion_update(event->pmotion);
//mS.m->input.pen.isEraser = event->pmotion.pen_state & SDL_PEN_INPUT_ERASER_TIP;
//mS.m->input.mouse.set_pos({event->pmotion.x * mS.m->window.density, event->pmotion.y * mS.m->window.density});
2025-07-06 20:43:53 +03:00
break;
}
case SDL_EVENT_PEN_AXIS: {
2026-03-03 22:31:48 +03:00
mS.m->update_scale_and_density();
mS.m->input.backend_pen_axis_update(event->paxis);
2025-07-06 20:43:53 +03:00
break;
}
2026-03-03 14:59:18 +03:00
case SDL_EVENT_PEN_DOWN: {
mS.m->update_scale_and_density();
mS.m->input.backend_pen_touch_down_update(event->ptouch);
break;
}
2025-07-06 20:43:53 +03:00
case SDL_EVENT_PEN_UP: {
2026-03-03 14:59:18 +03:00
mS.m->update_scale_and_density();
mS.m->input.backend_pen_touch_up_update(event->ptouch);
2025-07-06 20:43:53 +03:00
break;
}
case SDL_EVENT_PEN_BUTTON_UP: {
2026-03-03 14:59:18 +03:00
mS.m->update_scale_and_density();
mS.m->input.backend_pen_button_up_update(event->pbutton);
2025-07-06 20:43:53 +03:00
break;
}
case SDL_EVENT_PEN_BUTTON_DOWN: {
2026-03-03 14:59:18 +03:00
mS.m->update_scale_and_density();
mS.m->input.backend_pen_button_down_update(event->pbutton);
2025-07-06 20:43:53 +03:00
break;
}
2026-02-27 16:16:51 +03:00
case SDL_EVENT_FINGER_DOWN: {
mS.m->update_scale_and_density();
mS.m->input.backend_touch_finger_down_update(event->tfinger);
2026-02-27 16:16:51 +03:00
break;
}
case SDL_EVENT_FINGER_UP: {
mS.m->update_scale_and_density();
mS.m->input.backend_touch_finger_up_update(event->tfinger);
2026-02-27 16:16:51 +03:00
break;
}
case SDL_EVENT_FINGER_MOTION: {
mS.m->update_scale_and_density();
mS.m->input.backend_touch_finger_motion_update(event->tfinger);
2026-02-27 16:16:51 +03:00
break;
}
2026-02-25 15:25:00 +03:00
case SDL_EVENT_DISPLAY_ORIENTATION:
case SDL_EVENT_DISPLAY_ADDED:
case SDL_EVENT_DISPLAY_REMOVED:
case SDL_EVENT_DISPLAY_MOVED:
case SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED:
case SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED:
case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED:
case SDL_EVENT_DISPLAY_USABLE_BOUNDS_CHANGED: {
get_refresh_rate(mS);
break;
}
2026-03-22 20:16:15 +03:00
case SDL_EVENT_SCREEN_KEYBOARD_SHOWN:
2026-03-07 13:00:10 +03:00
break;
2026-03-22 20:16:15 +03:00
case SDL_EVENT_SCREEN_KEYBOARD_HIDDEN:
2026-03-07 13:00:10 +03:00
break;
default: {
if(event->type == CustomEvents::PASTE_EVENT)
mS.m->input.backend_paste_event();
2026-04-05 20:26:03 +03:00
else if(event->type == CustomEvents::OPEN_INFINIPAINT_FILE_EVENT)
mS.m->input.backend_open_infinipaint_file_event();
2025-07-06 20:43:53 +03:00
break;
}
2025-07-06 20:43:53 +03:00
}
#ifdef NDEBUG
}
catch(const std::exception& e) {
Logger::get().log("FATAL", e.what());
return SDL_APP_FAILURE;
}
#endif
return SDL_APP_CONTINUE;
}
void SDL_AppQuit(void *appstate, SDL_AppResult result) {
2026-02-04 19:19:29 +03:00
DrawingProgramCache::delete_all_draw_cache();
2026-02-04 19:15:58 +03:00
2025-07-06 20:43:53 +03:00
MainStruct& mS = *((MainStruct*)appstate);
try {
mS.m->save_config();
mS.m->early_destroy();
sdl_terminate(mS);
}
catch(const std::exception& e) {
Logger::get().log("FATAL", e.what());
}
delete (&mS);
SDL_Quit();
FileDownloader::cleanup();
2025-07-06 20:43:53 +03:00
}