#include "pch.h" #include "log.h" #include "app.h" #include "node_icon.h" #include "node_dialog_open.h" #include "node_progress_bar.h" #ifdef __APPLE__ #include #include "objc_utils.h" #endif #include "settings.h" #ifdef __ANDROID__ void android_async_lock(); void android_async_swap(); void android_async_unlock(); void android_attach_jni(); void android_detach_jni(); #elif _WIN32 bool async_lock_try(); void async_lock(); void win32_async_swap(); void async_unlock(); void destroy_window(); #elif __LINUX__ std::string linux_home_path(); int mkpath(const std::string& dir, mode_t mode = DEFFILEMODE); #endif App* App::I = nullptr; // singleton std::deque App::render_tasklist; std::mutex App::render_task_mutex; std::condition_variable App::render_cv; std::thread App::render_thread; std::thread::id App::render_thread_id; bool App::render_running = false; std::deque App::ui_tasklist; std::mutex App::ui_task_mutex; std::condition_variable App::ui_cv; std::thread App::ui_thread; std::thread::id App::ui_thread_id; bool App::ui_running = false; void App::create() { width = 1920/2; height = 1080/2; } void App::open_document(std::string path) { std::regex r(R"((.*)[\\/]([^\\/]+)\.(\w+)$)"); std::smatch m; if (!std::regex_search(path, m, r)) return; std::string base = m[1].str(); std::string name = m[2].str(); std::string ext = m[3].str(); if (str_iequals(ext, "abr")) { auto mb = message_box("Import ABR", "Would you like to import the brushes?", true); mb->on_submit = [this, path] (Node* target) { std::thread(&NodePanelBrushPreset::import_abr, presets, path).detach(); target->destroy(); }; } else if (str_iequals(ext, "ppbr")) { auto mb = message_box("Import PPBR", "Would you like to import the brushes?", true); mb->on_submit = [this, path] (Node* target) { std::thread(&NodePanelBrushPreset::import_ppbr, presets, path).detach(); target->destroy(); }; } else { auto open_action = [this, path, base, name] { doc_name = name; doc_dir = base; doc_path = path; canvas->reset_camera(); layers->clear(); canvas->m_canvas->project_open(path, [this](bool success){ // on complete if (success) { title_update(); for (int layer_index = 0; layer_index < canvas->m_canvas->m_layers.size(); layer_index++) { auto l = layers->add_layer(canvas->m_canvas->m_layers[layer_index]->m_name.c_str(), false); l->m_visibility->set_value(canvas->m_canvas->m_layers[layer_index]->m_visible); } } else { message_box("Open Document Error", "There was an error opening the document.\n" "It may be inaccessible or corrupted."); } }); ActionManager::clear(); }; if (!Canvas::I->m_unsaved) { open_action(); } else { auto mb = message_box("Unsaved document", "Do you want to close the unsaved document before opening the file?", true); mb->on_submit = [this, open_action] (Node* target) { open_action(); target->destroy(); }; } } } bool App::request_close() { static bool dialog_already_opened = false; if (!Canvas::I->m_unsaved) return true; if (!dialog_already_opened) { auto* m = layout[main_id]->add_child(); m->m_title->set_text("Unsaved document"); m->m_message->set_text("Do you want to close without saving?"); m->btn_ok->m_text->set_text("Yes"); m->btn_ok->on_click = [this](Node*) { #ifdef _WIN32 destroy_window(); //PostQuitMessage(0); #elif __OSX__ [osx_view close]; #elif __LINUX__ glfwSetWindowShouldClose(glfw_window, GLFW_TRUE); #endif Canvas::I->m_unsaved = false; }; m->btn_cancel->m_text->set_text("No"); m->btn_cancel->on_click = [this,m](Node*) { m->destroy(); dialog_already_opened = false; }; dialog_already_opened = true; } return false; } void App::clear() { glClearColor(.1f, .1f, .1f, 1.f); glClear(GL_COLOR_BUFFER_BIT); } void App::initAssets() { LOG("initializing assets"); FontManager::init(); LOG("initializing assets loading fonts"); FontManager::load(kFont::Arial_11, "data/fonts/Roboto-Regular.ttf", 17, display_density * zoom); FontManager::load(kFont::Arial_30, "data/fonts/Roboto-Regular.ttf", 30, display_density * zoom); LOG("initializing assets create sampler"); sampler.create(GL_NEAREST); sampler_stencil.create(GL_LINEAR, GL_REPEAT); sampler_linear.create(GL_LINEAR); m_face_plane.create<1>(2, 2); sphere.create<8, 8>(1); LOG("initializing assets load uvs texture"); LOG("initializing assets completed"); } void App::initLog() { #if defined(__IOS__) [ios_view init_dirs]; #elif defined(__OSX__) [osx_app init_dirs]; #elif defined(_WIN32) //CHAR my_documents[MAX_PATH]; //HRESULT result = SHGetFolderPathA(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, my_documents); //HMODULE hModule = GetModuleHandle(NULL); //CHAR path[MAX_PATH]; //GetModuleFileNameA(hModule, path, MAX_PATH); //CHAR out_drive[MAX_PATH]; //CHAR out_path[MAX_PATH]; //_splitpath(path, out_drive, out_path, nullptr, nullptr); //sprintf_s(path, "%s%s", out_drive, out_path); //data_path = path; CHAR my_documents[MAX_PATH]; HRESULT result = SHGetFolderPathA(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, my_documents); if (SUCCEEDED(result)) { std::string path = std::string(my_documents) + "\\PanoPainter"; if (!PathFileExistsA(path.c_str())) CreateDirectoryA(path.c_str(), NULL); data_path = path; } else { CHAR path[MAX_PATH]; GetCurrentDirectoryA(sizeof(path), path); data_path = path; } rec_path = data_path + "\\frames"; if (!PathFileExistsA(rec_path.c_str())) CreateDirectoryA(rec_path.c_str(), NULL); if (!PathFileExistsA((data_path + "\\brushes").c_str())) CreateDirectoryA((data_path + "\\brushes").c_str(), NULL); if (!PathFileExistsA((data_path + "\\brushes\\thumbs").c_str())) CreateDirectoryA((data_path + "\\brushes\\thumbs").c_str(), NULL); if (!PathFileExistsA((data_path + "\\patterns").c_str())) CreateDirectoryA((data_path + "\\patterns").c_str(), NULL); if (!PathFileExistsA((data_path + "\\patterns\\thumbs").c_str())) CreateDirectoryA((data_path + "\\patterns\\thumbs").c_str(), NULL); if (!PathFileExistsA((data_path + "\\settings").c_str())) CreateDirectoryA((data_path + "\\settings").c_str(), NULL); #elif __LINUX__ data_path = linux_home_path() + "/PanoPainter"; mkpath(data_path + "/brushes"); mkpath(data_path + "/brushes/thumbs"); mkpath(data_path + "/patterns"); mkpath(data_path + "/patterns/thumbs"); mkpath(data_path + "/settings"); mkpath(data_path + "/frames"); #endif // TODO: save this path somewhere in the settings, don't overwrite every start work_path = data_path; //LogRemote::I.start(); LogRemote::I.file_init(); LOG("%s", g_version); LOG("load preferences"); if (!Settings::load()) LOG("load preferences failed"); } int progress_callback_download(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { std::function progress = *(std::function*)clientp; progress((float)dlnow / (float)dltotal); return 0; } int progress_callback_upload(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { std::function progress = *(std::function*)clientp; progress((float)ulnow / (float)ultotal); return 0; } void App::download(std::string url, std::string dest_filepath, std::function progress) { CURL *curl = curl_easy_init(); if (curl) { FILE* fp = fopen(dest_filepath.c_str(), "wb"); LOG("download %s to %s", url.c_str(), dest_filepath.c_str()); curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_data_write); #ifdef __ANDROID__ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); #endif if (progress) { curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback_download); curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &progress); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); } auto err = curl_easy_perform(curl); curl_easy_cleanup(curl); fclose(fp); } } bool App::check_license() { return true; // TODO: for distribuiton only CURL *curl = curl_easy_init(); if (curl) { std::string url = "https://panopainter.com/license/7565D057-ACBE-4721-9A4E-F342D3DDB7D8.php"; //std::string url = "https://panopainter.com/license/E8EDC2FE-E1BD-4AB1-91BD-FCCD926739BD.php"; // wacom //std::string url = "https://panopainter.com/license/A744FBA9-BC6C-43C8-BD24-0CCE24B3D985.php"; // others std::string ret; curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ret); //curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_data_handler); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 2L); #ifdef __ANDROID__ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); #endif auto err = curl_easy_perform(curl); curl_easy_cleanup(curl); LOG("License check: %s", ret.c_str()); if (err == CURLcode::CURLE_OK && ret == "success") return true; } return false; } void App::upload(std::string filename, std::string name, std::function progress) { CURL *curl; struct curl_httppost *formpost = NULL; struct curl_httppost *lastptr = NULL; //curl_global_init(CURL_GLOBAL_ALL); curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "fileToUpload", CURLFORM_FILE, filename.c_str(), CURLFORM_END); curl = curl_easy_init(); std::string res; if (curl) { std::string url = "https://panopainter.com/cloud/cloud-upl.php?name=" + name; curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &res); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_data_handler); #ifdef __ANDROID__ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); #endif if (progress) { curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback_upload); curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &progress); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); } auto err = curl_easy_perform(curl); std::cout << "\n\nUPLOAD RESULT\n" << res << "\n\n\n"; curl_easy_cleanup(curl); } } #ifdef _WIN32 static CONSOLE_SCREEN_BUFFER_INFO info; void handle_gl_callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) { static std::map colors = { { GL_DEBUG_SEVERITY_NOTIFICATION, 8 }, { GL_DEBUG_SEVERITY_LOW, 8 }, { GL_DEBUG_SEVERITY_MEDIUM, FOREGROUND_GREEN | FOREGROUND_INTENSITY }, { GL_DEBUG_SEVERITY_HIGH, FOREGROUND_RED | FOREGROUND_INTENSITY }, }; if (severity == GL_DEBUG_SEVERITY_HIGH || severity == GL_DEBUG_SEVERITY_MEDIUM || severity == GL_DEBUG_SEVERITY_LOW) { SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), colors[severity]); LOG("OPENGL: %.*s", length, message); FlushConsoleInputBuffer(GetStdHandle(STD_OUTPUT_HANDLE)); SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), info.wAttributes); #ifdef _DEBUG if (severity == GL_DEBUG_SEVERITY_HIGH) __debugbreak(); #endif } } #endif void App::init() { #ifdef _WIN32 if (glDebugMessageCallback) { // colors: http://stackoverflow.com/questions/4053837/colorizing-text-in-the-console-with-c GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info); render_task([] { glDebugMessageCallback(handle_gl_callback, nullptr); glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); }); } #endif LOG("Screen Resolution: %dx%d", (int)width, (int)height); render_task([] { LOG("GL version: %s", glGetString(GL_VERSION)); LOG("GL vendor: %s", glGetString(GL_VENDOR)); LOG("GL renderer: %s", glGetString(GL_RENDERER)); //GLint n_exts; //glGetIntegerv(GL_NUM_EXTENSIONS, &n_exts); //for (int i = 0; i < n_exts; i++) //{ // std::string ext = (const char*)glGetStringi(GL_EXTENSIONS, i); // //if (ext.find("debug") != std::string::npos) // { // LOG("%s", glGetStringi(GL_EXTENSIONS, i)); // } //} glDisable(GL_DEPTH_TEST); #if defined(_WIN32) || defined(__OSX__) glEnable(GL_PROGRAM_POINT_SIZE); glEnable(GL_LINE_SMOOTH); #endif glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendEquationSeparate(GL_FUNC_ADD, GL_MAX); }); int run_counter = Settings::value("run_counter") + 1; Settings::set("run_counter", Serializer::Integer(run_counter)); LOG("run_counter %d", run_counter); if (!Settings::save()) LOG("save preferences failed"); initShaders(); initAssets(); initLayout(); title_update(); uirtt.create(width, height, -1, GL_RGBA8, true); if (!check_license()) { message_box("License", "Could not validate this license, running in demo mode."); } } void App::async_start() { #if __OSX__ [osx_view async_lock]; #elif __IOS__ [ios_view async_lock]; #elif __ANDROID__ android_async_lock(); #elif _WIN32 async_lock(); glBindFramebuffer(GL_FRAMEBUFFER, 0); #elif __LINUX__ glfwMakeContextCurrent(glfw_window); #endif } void App::async_redraw() { redraw = true; ui_cv.notify_all(); } void App::async_end() { #if __OSX__ [osx_view async_unlock]; #elif __IOS__ [ios_view async_unlock]; #elif __ANDROID__ android_async_unlock(); #elif _WIN32 async_unlock(); #endif } void App::async_swap() { #if __OSX__ [osx_view async_swap]; #elif __IOS__ [ios_view async_swap]; #elif __ANDROID__ android_async_swap(); #elif _WIN32 win32_async_swap(); #elif __LINUX__ glfwSwapBuffers(glfw_window); #endif } bool App::update_ui_observer(Node *n) { if (n && n->m_display) { auto box = n->m_clip_uncut; Node* p = n->m_parent; while (p) { float pt = YGNodeLayoutGetPadding(p->y_node, YGEdgeTop); float pr = YGNodeLayoutGetPadding(p->y_node, YGEdgeRight); float pb = YGNodeLayoutGetPadding(p->y_node, YGEdgeBottom); float pl = YGNodeLayoutGetPadding(p->y_node, YGEdgeLeft); glm::vec2 off_p(pl, pt); glm::vec2 off_s(pr, pb); glm::vec4 pclip = { xy(p->m_clip_uncut) + off_p, zw(p->m_clip_uncut) - off_s - off_p }; box = rect_intersection(box, pclip); p = p->m_parent; } //auto box = n->m_clip; //glm::ivec4 c = glm::vec4((int)box.x - 1, (int)(height / zoom - box.y - box.w) - 1, (int)box.z + 2, (int)box.w + 2) * zoom; if (box.z <= 0.f || box.w <= 0.f) { if (n->m_on_screen) { if (dynamic_cast(n)) p = p; n->handle_on_screen(true, false); n->m_on_screen = false; } return false; } if (!n->m_on_screen) { n->handle_on_screen(false, true); n->m_on_screen = true; } glm::ivec4 c = glm::vec4(box.x - 1, (height / zoom - box.y - box.w - 1), box.z + 2, box.w + 2) * zoom; glScissor(floorf(c.x + off_x), floorf(c.y + off_y), ceilf(c.z), ceilf(c.w)); n->draw(); return true; } return false; } void App::draw(float dt) { // update offscreen stuff if (canvas && canvas->m_canvas) canvas->m_canvas->stroke_draw(); auto observer = std::bind(&App::update_ui_observer, this, std::placeholders::_1); if (vr_active && ui_visible) { uirtt.bindFramebuffer(); uirtt.clear(); glViewport(0, 0, uirtt.getWidth(), uirtt.getHeight()); glEnable(GL_SCISSOR_TEST); for (int i = 1; i < layout[main_id]->m_children.size(); i++) layout[main_id]->m_children[i]->watch(observer); for (int i = 0; layout_designer.get(main_id) && i < layout_designer[main_id]->m_children.size(); i++) layout_designer[main_id]->m_children[i]->watch(observer); //msgbox->watch(observer); glDisable(GL_SCISSOR_TEST); uirtt.unbindFramebuffer(); } if (!vr_only) { #if __IOS__ [ios_view->glview bindDrawable]; #else glBindFramebuffer(GL_FRAMEBUFFER, 0); #endif glViewport(off_x, off_y, (GLsizei)width, (GLsizei)height); glEnable(GL_SCISSOR_TEST); for (int i = 0; i < layout[main_id]->m_children.size(); i++) layout[main_id]->m_children[i]->watch(observer); for (int i = 0; layout_designer.get(main_id) && i < layout_designer[main_id]->m_children.size(); i++) layout_designer[main_id]->m_children[i]->watch(observer); //msgbox->watch(observer); glDisable(GL_SCISSOR_TEST); } if (rec_running) { static float rec_timer = 0.f; rec_timer += dt; if (rec_timer > 1.f && canvas->m_canvas->m_dirty_stroke) { canvas->m_canvas->m_dirty_stroke = false; LOG("rec tick"); rec_timer = 0.f; auto data = new uint8_t[(int)width * (int)height * 4]; #if __IOS__ [ios_view->glview bindDrawable]; #else glBindFramebuffer(GL_FRAMEBUFFER, 0); #endif GLint dfbo, rfbo; glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &rfbo); glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &dfbo); if (dfbo != rfbo) LOG("DIFFERENT FB"); glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data); { std::lock_guard lock(rec_mutex); rec_frames.emplace_back(data); rec_cv.notify_all(); } update_rec_frames(); } } redraw = false; } void App::update(float dt) { static std::mutex mutex; // avoid multiple threads to update the scene //std::lock_guard lock(mutex); if (!(redraw || animate)) return; if (auto* main = layout[main_id]) main->update(width, height, zoom); if (auto* main = layout_designer[main_id]) main->update(width, height, zoom); { static glm::vec4 color_button_normal{ .1, .1, .1, 1 }; static glm::vec4 color_button_hlight{ 1, .0, .0, 1 }; auto mode = Canvas::I->m_current_mode; CanvasModePen* pm = (CanvasModePen*)canvas->m_canvas->modes[(int)kCanvasMode::Draw][0]; layout[main_id]->find("btn-pick")->set_active(mode == kCanvasMode::Draw && pm->m_picking); layout[main_id]->find("btn-touchlock")->set_active(canvas->m_canvas->m_touch_lock); layout[main_id]->find("btn-pen")->set_active(mode == kCanvasMode::Draw); layout[main_id]->find("btn-erase")->set_active(mode == kCanvasMode::Erase); layout[main_id]->find("btn-cam")->set_active(mode == kCanvasMode::Camera); layout[main_id]->find("btn-line")->set_active(mode == kCanvasMode::Line); layout[main_id]->find("btn-grid")->set_active(mode == kCanvasMode::Grid); layout[main_id]->find("btn-copy")->set_active(mode == kCanvasMode::Copy); layout[main_id]->find("btn-cut")->set_active(mode == kCanvasMode::Cut); layout[main_id]->find("btn-mask-free")->set_active(mode == kCanvasMode::MaskFree); layout[main_id]->find("btn-mask-line")->set_active(mode == kCanvasMode::MaskLine); layout[main_id]->find("btn-bucket")->set_active(mode == kCanvasMode::FloodFill); } } void App::terminate() { LOG("App::terminate"); ui_save(); NodeStrokePreview::terminate_renderer(); rec_stop(); TextureManager::invalidate(); ShaderManager::invalidate(); layout.unload(); layout_designer.unload(); uirtt.destroy(); m_face_plane.destroy(); layers.reset(); color.reset(); stroke.reset(); grid.reset(); presets.reset(); floating_presets.reset(); floating_color.reset(); floating_layers.reset(); floating_picker.reset(); quick_mode_state.clear(); } void App::update_memory_usage(size_t bytes) { if (auto txt = layout[main_id]->find("txt-memory")) { static char buffer[128]; sprintf(buffer, "History memory: %.2f Mb", bytes / 1024.f / 1024.f); txt->set_text(buffer); } } void App::update_rec_frames() { if (auto txt = layout[main_id]->find("txt-rec")) { if (rec_running) { static char buffer[128]; sprintf(buffer, "Recorder %d frames", rec_count); txt->set_text(buffer); } else { txt->set_text(""); } } } int App::res_from_index(int i) { return res_map[i]; } int App::res_to_index(int res) { return (int)std::distance(res_map.begin(), std::find(res_map.begin(), res_map.end(), res)); } std::string App::res_to_string(int res) { return res_map_str[res_to_index(res)]; } void App::rec_clear() { rec_stop(); #if defined(__IOS__) || defined(__OSX__) delete_all_files_in_path(rec_path); #endif rec_count = 0; update_rec_frames(); } void App::rec_start() { if (!rec_running) { #if defined(__IOS__) || defined(__OSX__) NSString* path = [NSString stringWithUTF8String:rec_path.c_str()]; NSArray *dirFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil]; NSArray *jpgFiles = [dirFiles filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"self ENDSWITH '.jpg'"]]; rec_count = (int)[jpgFiles count]; update_rec_frames(); rec_thread = std::thread(&App::rec_loop, this); #else rec_count = Asset::list_files(rec_path, ".*\\.jpg").size(); update_rec_frames(); rec_thread = std::thread(&App::rec_loop, this); #endif } } void App::rec_stop() { if (rec_running) { rec_running = false; rec_cv.notify_all(); if (rec_thread.joinable()) rec_thread.join(); update_rec_frames(); } } void App::rec_export(std::string path) { int progress = 0; int tot = rec_count; auto pb = layout[main_id]->add_child(); pb->m_progress->SetWidthP(0); pb->m_title->set_text("Exporting MP4 movie"); #if defined(__IOS__) || defined(__OSX__) export_mp4(rec_path, width, height, rec_count, ^(float) { pb->m_progress->SetWidthP((float)progress / tot * 100.f); }); #endif pb->destroy(); } void App::rec_loop() { BT_SetTerminate(); rec_running = true; while(rec_running) { std::unique_lock lock(rec_mutex); rec_cv.wait(lock); if (!rec_running) break; if (!rec_frames.empty()) { if (rec_frames.front()) { auto inverted = std::make_unique(width*height*4); for (int y = height - 1, y1 = 0; y >= 0; y--, y1++) { uint8_t* dst = &inverted[y * width * 4]; uint8_t* src = &rec_frames.front()[y1 * width * 4]; std::copy_n(src, (int)width * 4, dst); } char path[256]; snprintf(path, sizeof(path), "%s/%04d.jpg", rec_path.c_str(), rec_count); LOG("writing %s", path); jpge::params params; params.m_quality = 75; bool saved = jpge::compress_image_to_jpeg_file(path, width, height, 4, inverted.get(), params); if (!saved) { LOG("error writing the frame"); rec_running = false; } else { rec_count++; redraw = true; } } rec_frames.pop_front(); } } } void App::render_thread_main() { BT_SetTerminate(); uint32_t count = 0; render_thread_id = std::this_thread::get_id(); render_running = true; while (render_running) { std::deque working_list; // move the task list locally to free the queue for other threads { std::unique_lock lock(render_task_mutex); render_cv.wait(lock, [this] { return render_tasklist.empty() && render_running ? false : true; }); working_list = std::move(render_tasklist); } // execute the tasks if (!working_list.empty()) { async_start(); while (!working_list.empty()) { //LOG("render task %d", count); //LOG("task %s", working_list.front().name.c_str()); count++; working_list.front()(); working_list.pop_front(); } async_end(); } } } void App::ui_thread_main() { BT_SetTerminate(); uint32_t count = 0; ui_thread_id = std::this_thread::get_id(); ui_running = true; #if __ANDROID__ android_attach_jni(); #endif LOG("ui thread init()"); init(); auto t_start = std::chrono::high_resolution_clock::now(); float t_frame = 0; float t_fps_counter = 0; float t_reloader = 0; int rendered_frames = 0; while (ui_running) { std::deque working_list; // move the task list locally to free the queue for other threads { std::unique_lock lock(ui_task_mutex); ui_cv.wait_for(lock, std::chrono::milliseconds(100), [this] { return ui_tasklist.empty() && ui_running ? false : true; }); working_list = std::move(ui_tasklist); } // execute the tasks if (!working_list.empty()) { while (!working_list.empty()) { //LOG("ui task %d", count); count++; working_list.front()(); working_list.pop_front(); } } auto t_now = std::chrono::high_resolution_clock::now(); float dt = std::chrono::duration(t_now - t_start).count(); t_start = t_now; #ifdef _WIN32 extern void win32_update_stylus(float dt); win32_update_stylus(dt); #endif // increment timers t_frame += dt; t_fps_counter += dt; if (t_fps_counter > 1.f) { #ifdef _WIN32 extern void win32_update_fps(int frames); win32_update_fps(rendered_frames); #elif __LINUX__ extern void linux_update_fps(int frames); linux_update_fps(rendered_frames); #endif t_fps_counter = 0; rendered_frames = 0; } #if /*_DEBUG &&*/ (_WIN32 || __OSX__) t_reloader += dt; if (t_reloader > 1.0) { t_reloader = 0; if (ShaderManager::reload()) { stroke->update_controls(); redraw = true; } if (layout.reload()) redraw = true; if (layout_designer.reload()) redraw = true; } #endif tick(dt); if (redraw) { update(t_frame); render_task([this, t_frame] { glBindFramebuffer(GL_FRAMEBUFFER, 0); clear(); draw(t_frame); async_swap(); }); t_frame = 0; rendered_frames++; } } #if __ANDROID__ android_detach_jni(); #endif } void App::render_thread_start() { render_thread = std::thread(&App::render_thread_main, this); render_running = true; } void App::render_thread_stop() { render_running = false; render_cv.notify_all(); if (render_thread.joinable()) render_thread.join(); } void App::ui_thread_start() { ui_thread = std::thread(&App::ui_thread_main, this); ui_running = true; } void App::ui_thread_stop() { ui_running = false; ui_cv.notify_all(); if (ui_thread.joinable()) ui_thread.join(); }