#include "pch.h" #include "log.h" #include "app.h" #include "node_icon.h" #include "node_dialog_open.h" #include "node_progress_bar.h" #include "mp4enc.h" #include "app_core/app_frame.h" #include "app_core/app_shutdown.h" #include "app_core/app_status.h" #include "app_core/app_startup.h" #include "app_core/app_thread.h" #include "app_core/canvas_tool_ui.h" #include "app_core/document_cloud.h" #include "app_core/document_recording.h" #include "app_core/document_route.h" #include "app_core/document_session.h" #include "legacy_app_startup_services.h" #include "legacy_document_open_services.h" #include "legacy_document_session_services.h" #include "legacy_recording_services.h" #include "legacy_ui_gl_dispatch.h" #include "platform_api/platform_services.h" #include "renderer_gl/opengl_capabilities.h" #ifdef __APPLE__ #include #include "objc_utils.h" #endif #include "settings.h" App* App::I = nullptr; // singleton std::deque App::render_tasklist; std::mutex App::render_task_mutex; std::condition_variable App::render_cv; namespace { [[nodiscard]] const char* query_opengl_string(std::uint32_t name) noexcept { return reinterpret_cast(glGetString(static_cast(name))); } pp::app::CanvasToolMode canvas_tool_mode_from_canvas_mode(kCanvasMode mode) noexcept { switch (mode) { case kCanvasMode::Draw: return pp::app::CanvasToolMode::draw; case kCanvasMode::Erase: return pp::app::CanvasToolMode::erase; case kCanvasMode::Line: return pp::app::CanvasToolMode::line; case kCanvasMode::Camera: return pp::app::CanvasToolMode::camera; case kCanvasMode::Grid: return pp::app::CanvasToolMode::grid; case kCanvasMode::Copy: return pp::app::CanvasToolMode::copy; case kCanvasMode::Cut: return pp::app::CanvasToolMode::cut; case kCanvasMode::Fill: return pp::app::CanvasToolMode::fill; case kCanvasMode::MaskFree: return pp::app::CanvasToolMode::mask_free; case kCanvasMode::MaskLine: return pp::app::CanvasToolMode::mask_line; case kCanvasMode::FloodFill: return pp::app::CanvasToolMode::flood_fill; default: return pp::app::CanvasToolMode::draw; } } void apply_app_viewport(pp::renderer::gl::OpenGlViewportRect viewport) { const auto status = pp::renderer::gl::apply_opengl_viewport( viewport, pp::renderer::gl::OpenGlViewportDispatch { .viewport = pp::legacy::ui_gl::set_opengl_viewport, }); if (!status.ok()) LOG("OpenGL viewport failed: %s", status.message); } void apply_app_scissor(pp::renderer::gl::OpenGlScissorRect scissor) { const auto status = pp::renderer::gl::apply_opengl_scissor_rect( scissor, pp::renderer::gl::OpenGlScissorDispatch { .enable = pp::legacy::ui_gl::enable_opengl_state, .disable = pp::legacy::ui_gl::disable_opengl_state, .scissor = pp::legacy::ui_gl::set_opengl_scissor, }); if (!status.ok()) LOG("OpenGL scissor failed: %s", status.message); } void apply_app_scissor_test(bool enabled) { const auto status = pp::renderer::gl::apply_opengl_scissor_test( enabled, pp::renderer::gl::OpenGlScissorTestDispatch { .enable = pp::legacy::ui_gl::enable_opengl_state, .disable = pp::legacy::ui_gl::disable_opengl_state, }); if (!status.ok()) LOG("OpenGL scissor test failed: %s", status.message); } [[nodiscard]] GLenum linear_texture_filter() noexcept { return static_cast(pp::renderer::gl::linear_texture_filter()); } [[nodiscard]] GLenum nearest_texture_filter() noexcept { return static_cast(pp::renderer::gl::nearest_texture_filter()); } [[nodiscard]] GLenum repeat_texture_wrap() noexcept { return static_cast(pp::renderer::gl::repeat_texture_wrap()); } [[nodiscard]] GLenum framebuffer_target() noexcept { return static_cast(pp::renderer::gl::framebuffer_target()); } [[nodiscard]] GLuint default_framebuffer_id() noexcept { return static_cast(pp::renderer::gl::default_framebuffer_id()); } } 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() { const auto initial_surface = pp::app::plan_app_initial_surface(); width = initial_surface.width; height = initial_surface.height; } void App::open_document(std::string path) { const auto route = pp::app::classify_document_open_path(path); if (!route) return; const bool has_unsaved_project = route.value().kind == pp::app::DocumentOpenKind::open_project && Canvas::I->m_unsaved; const auto open_plan = pp::app::plan_document_open(route.value().kind, has_unsaved_project); const auto status = pp::panopainter::execute_legacy_document_open_plan(*this, open_plan, route.value()); if (!status.ok()) LOG("Document open action failed: %s", status.message); } bool App::request_close() { static bool dialog_already_opened = false; const auto close_decision = pp::app::plan_close_request( Canvas::I->m_unsaved, dialog_already_opened); const auto status = pp::panopainter::execute_legacy_close_request_decision( *this, close_decision, dialog_already_opened); if (!status.ok()) LOG("Close request action failed: %s", status.message); return close_decision == pp::app::CloseRequestDecision::close_now; } void App::clear() { const auto status = pp::renderer::gl::clear_panopainter_default_target( pp::renderer::gl::OpenGlClearDispatch { .clear_color = pp::legacy::ui_gl::set_opengl_clear_color, .clear = pp::legacy::ui_gl::clear_opengl_buffer, }); if (!status.ok()) LOG("OpenGL clear failed: %s", status.message); } void App::initAssets() { LOG("initializing assets"); FontManager::init(); LOG("initializing assets create sampler"); sampler.create(nearest_texture_filter()); sampler_stencil.create(linear_texture_filter(), repeat_texture_wrap()); sampler_linear.create(linear_texture_filter()); 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() { const auto paths = prepare_storage_paths(); if (!paths.data_path.empty()) data_path = paths.data_path; if (!paths.recording_path.empty()) rec_path = paths.recording_path; if (!paths.temporary_path.empty()) tmp_path = paths.temporary_path; // TODO: save this path somewhere in the settings, don't overwrite every start work_path = paths.work_path.empty() ? data_path : paths.work_path; //LogRemote::I.start(); LogRemote::I.file_init(); LOG("%s", g_version); LOG("load preferences"); if (!Settings::load()) LOG("load preferences failed"); } #if WITH_CURL int progress_callback_download(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { (void)ultotal; (void)ulnow; auto* progress = static_cast*>(clientp); const auto plan = pp::app::plan_cloud_transfer_progress( static_cast(dltotal), static_cast(dlnow)); if (progress != nullptr && *progress && plan.notify) (*progress)(plan.fraction); return 0; } int progress_callback_upload(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { (void)dltotal; (void)dlnow; auto* progress = static_cast*>(clientp); const auto plan = pp::app::plan_cloud_transfer_progress( static_cast(ultotal), static_cast(ulnow)); if (progress != nullptr && *progress && plan.notify) (*progress)(plan.fraction); return 0; } #endif //CURL void App::download(std::string url, std::string dest_filepath, std::function progress) { #if WITH_CURL const auto plan = pp::app::plan_cloud_download_transfer( url, dest_filepath, progress != nullptr, disables_network_tls_verification()); if (plan.action != pp::app::CloudTransferAction::start_transfer) { LOG("download skipped: invalid transfer request"); return; } CURL *curl = curl_easy_init(); if (curl) { FILE* fp = fopen(dest_filepath.c_str(), "wb"); if (fp == nullptr) { LOG("download failed to open destination %s", dest_filepath.c_str()); curl_easy_cleanup(curl); return; } 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); if (plan.disable_tls_verification) curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); if (plan.enable_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); } #endif //CURL } bool App::check_license() { return true; // TODO: for distribuiton only #if WITH_CURL 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); if (disables_network_tls_verification()) curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); 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; } #endif //CURL return false; } void App::upload(std::string filename, std::string name, std::function progress) { #if WITH_CURL const auto plan = pp::app::plan_cloud_upload_transfer( filename, progress != nullptr, disables_network_tls_verification()); if (plan.action != pp::app::CloudTransferAction::start_transfer) { LOG("upload skipped: invalid transfer request"); return; } 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); if (plan.disable_tls_verification) curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); if (plan.enable_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); } #endif //CURL } void App::init() { LOG("Screen Resolution: %dx%d", (int)width, (int)height); render_task([] { App::I->install_render_debug_callback(); const auto runtime_info_result = pp::renderer::gl::query_opengl_runtime_info( pp::renderer::gl::OpenGlRuntimeInfoDispatch { .get_string = query_opengl_string, }); if (runtime_info_result.ok()) { const auto& runtime_info = runtime_info_result.value(); LOG("GL version: %s", runtime_info.version); LOG("GL vendor: %s", runtime_info.vendor); LOG("GL renderer: %s", runtime_info.renderer); LOG("GLSL version: %s", runtime_info.shading_language_version); } else { LOG("OpenGL runtime info failed: %s", runtime_info_result.status().message); } //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)); // } //} App::I->apply_render_platform_hints(); const auto startup_state_status = pp::renderer::gl::apply_panopainter_initial_state( pp::renderer::gl::OpenGlStateDispatch { .enable = pp::legacy::ui_gl::enable_opengl_state, .disable = pp::legacy::ui_gl::disable_opengl_state, .blend_func = pp::legacy::ui_gl::set_opengl_blend_func, .blend_equation_separate = pp::legacy::ui_gl::set_opengl_blend_equation_separate, }); if (!startup_state_status.ok()) LOG("OpenGL startup state failed: %s", startup_state_status.message); }); const auto startup_plan = pp::app::plan_app_startup( Settings::value("run_counter"), Settings::value_or("auto-timelapse", true), Settings::value_or("vr-controllers-enabled", vr_controllers_enabled), check_license()); if (!startup_plan) { LOG("App startup plan failed: %s", startup_plan.status().message); } else { const auto persistence_status = pp::panopainter::execute_legacy_app_startup_persistence_plan( *this, startup_plan.value()); if (!persistence_status.ok()) LOG("App startup persistence failed: %s", persistence_status.message); } const auto startup_resources = pp::app::plan_app_startup_resources(width, height); if (!startup_resources) { LOG("App startup resource plan failed: %s", startup_resources.status().message); } else { const auto resource_status = pp::panopainter::execute_legacy_app_startup_resources( *this, startup_resources.value()); if (!resource_status.ok()) LOG("App startup resources failed: %s", resource_status.message); } if (startup_plan) { const auto startup_status = pp::panopainter::execute_legacy_app_startup_runtime_plan( *this, startup_plan.value()); if (!startup_status.ok()) LOG("App startup runtime execution failed: %s", startup_status.message); } } void App::async_start() { acquire_render_context(); } void App::async_redraw() { const auto plan = pp::app::plan_app_async_redraw(); if (plan.set_redraw) redraw = true; if (plan.notify_ui) ui_cv.notify_all(); } void App::async_end() { release_render_context(); } void App::async_swap() { present_render_context(); } bool App::update_ui_observer(Node *n) { std::vector parent_clips; if (n) { for (Node* p = n->m_parent; p; p = p->m_parent) { parent_clips.push_back(pp::app::AppUiObserverParentClip { .clip = pp::app::AppUiObserverRect { .x = p->m_clip_uncut.x, .y = p->m_clip_uncut.y, .width = p->m_clip_uncut.z, .height = p->m_clip_uncut.w, }, .padding_top = YGNodeLayoutGetPadding(p->y_node, YGEdgeTop), .padding_right = YGNodeLayoutGetPadding(p->y_node, YGEdgeRight), .padding_bottom = YGNodeLayoutGetPadding(p->y_node, YGEdgeBottom), .padding_left = YGNodeLayoutGetPadding(p->y_node, YGEdgeLeft), }); } } const auto plan = pp::app::plan_app_ui_observer( n != nullptr, n && n->m_display, n && n->m_on_screen, n ? pp::app::AppUiObserverRect { .x = n->m_clip_uncut.x, .y = n->m_clip_uncut.y, .width = n->m_clip_uncut.z, .height = n->m_clip_uncut.w, } : pp::app::AppUiObserverRect {}, parent_clips, height, zoom, off_x, off_y); if (!plan) { LOG("UI observer plan failed: %s", plan.status().message); return false; } if (!n) return false; if (plan.value().notify_leave_screen) n->handle_on_screen(true, false); if (plan.value().notify_enter_screen) n->handle_on_screen(false, true); n->m_on_screen = plan.value().next_on_screen; if (!plan.value().draw_node) return false; apply_app_scissor(pp::renderer::gl::OpenGlScissorRect { .enabled = 1U, .x = plan.value().scissor_x, .y = plan.value().scissor_y, .width = plan.value().scissor_width, .height = plan.value().scissor_height, }); n->draw(); return true; } void App::draw(float dt) { const auto draw_plan = pp::app::plan_app_frame_draw( canvas != nullptr, canvas && canvas->m_canvas, vr_active, ui_visible, vr_only); // update offscreen stuff if (draw_plan.draw_canvas_stroke) canvas->m_canvas->stroke_draw(); auto observer = std::bind(&App::update_ui_observer, this, std::placeholders::_1); if (draw_plan.draw_vr_ui) { uirtt.bindFramebuffer(); uirtt.clear(); apply_app_viewport(pp::renderer::gl::OpenGlViewportRect { .width = static_cast(uirtt.getWidth()), .height = static_cast(uirtt.getHeight()), }); apply_app_scissor_test(true); 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); apply_app_scissor_test(false); uirtt.unbindFramebuffer(); } if (draw_plan.draw_main_ui) { bind_main_render_target(); apply_app_viewport(pp::renderer::gl::OpenGlViewportRect { .x = static_cast(off_x), .y = static_cast(off_y), .width = static_cast(width), .height = static_cast(height), }); apply_app_scissor_test(true); 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); apply_app_scissor_test(false); } if (draw_plan.reset_redraw) redraw = false; } void App::update(float dt) { static std::mutex mutex; // avoid multiple threads to update the scene //std::lock_guard lock(mutex); const auto update_plan = pp::app::plan_app_frame_update(redraw, animate); if (!update_plan.update_frame) return; if (auto* main = layout[main_id]; update_plan.update_layouts && main) main->update(width, height, zoom); if (auto* main = layout_designer[main_id]; update_plan.update_layouts && main) main->update(width, height, zoom); if (!update_plan.refresh_canvas_toolbar) return; { auto mode = Canvas::I->m_current_mode; CanvasModePen* pm = (CanvasModePen*)canvas->m_canvas->modes[(int)kCanvasMode::Draw][0]; const auto toolbar = pp::app::plan_canvas_tool_button_state( canvas_tool_mode_from_canvas_mode(mode), pm && pm->m_picking, canvas->m_canvas->m_touch_lock); layout[main_id]->find("btn-pick")->set_active(toolbar.pick_active); layout[main_id]->find("btn-touchlock")->set_active(toolbar.touch_lock_active); layout[main_id]->find("btn-pen")->set_active(toolbar.pen_active); layout[main_id]->find("btn-erase")->set_active(toolbar.erase_active); layout[main_id]->find("btn-cam")->set_active(toolbar.camera_active); layout[main_id]->find("btn-line")->set_active(toolbar.line_active); layout[main_id]->find("btn-grid")->set_active(toolbar.grid_active); layout[main_id]->find("btn-copy")->set_active(toolbar.copy_active); layout[main_id]->find("btn-cut")->set_active(toolbar.cut_active); layout[main_id]->find("btn-mask-free")->set_active(toolbar.mask_free_active); layout[main_id]->find("btn-mask-line")->set_active(toolbar.mask_line_active); layout[main_id]->find("btn-bucket")->set_active(toolbar.flood_fill_active); } } void App::terminate() { LOG("App::terminate"); const auto shutdown_plan = pp::app::plan_app_shutdown(); if (shutdown_plan.save_ui_state) ui_save(); if (shutdown_plan.terminate_stroke_preview_renderer) NodeStrokePreview::terminate_renderer(); if (shutdown_plan.stop_recording) rec_stop(); if (shutdown_plan.invalidate_textures) TextureManager::invalidate(); if (shutdown_plan.invalidate_shaders) ShaderManager::invalidate(); if (shutdown_plan.unload_layouts) { layout.unload(); layout_designer.unload(); } if (shutdown_plan.destroy_ui_render_target) uirtt.destroy(); if (shutdown_plan.destroy_face_plane) m_face_plane.destroy(); if (shutdown_plan.release_panel_nodes) { layers.reset(); color.reset(); stroke.reset(); grid.reset(); presets.reset(); floating_presets.reset(); floating_color.reset(); floating_layers.reset(); floating_picker.reset(); } if (shutdown_plan.clear_quick_mode_state) quick_mode_state.clear(); } void App::update_memory_usage(size_t bytes) { if (auto txt = layout[main_id]->find("txt-memory")) { const auto label = pp::app::make_history_memory_label(bytes); txt->set_text(label.c_str()); } } void App::update_rec_frames() { if (auto txt = layout[main_id]->find("txt-rec")) { const auto label = pp::app::make_recording_frame_label( rec_running, Canvas::I->m_encoder != nullptr, Canvas::I->m_encoder ? Canvas::I->m_encoder->frames_count() : 0); txt->set_text(label.text.c_str()); } } int App::res_from_index(int i) { const auto resolution = pp::app::display_resolution_from_index(i); return resolution ? resolution.value() : pp::app::document_resolution_values.front(); } int App::res_to_index(int res) { const auto index = pp::app::document_resolution_to_index(res); return index ? static_cast(index.value()) : static_cast(pp::app::document_resolution_values.size()); } std::string App::res_to_string(int res) { const auto label = pp::app::document_resolution_label(res); return label ? std::string(label.value()) : std::string("unknown"); } void App::renderdoc_frame_start() { begin_render_capture_frame(); } void App::renderdoc_frame_end() { end_render_capture_frame(); } void App::rec_clear() { const auto plan = pp::app::plan_recording_clear( rec_running, platform_deletes_recorded_files_on_clear() ); const auto status = pp::panopainter::execute_legacy_recording_clear_plan(*this, plan); if (!status.ok()) LOG("Recording clear action failed: %s", status.message); } void App::rec_start() { const auto plan = pp::app::plan_recording_start(rec_running); const auto status = pp::panopainter::execute_legacy_recording_start_action(*this, plan); if (!status.ok()) LOG("Recording start action failed: %s", status.message); } void App::rec_stop() { const auto plan = pp::app::plan_recording_stop(rec_running); const auto status = pp::panopainter::execute_legacy_recording_stop_action(*this, plan); if (!status.ok()) LOG("Recording stop action failed: %s", status.message); } void App::rec_export(std::string path) { const auto plan = pp::app::plan_recording_export(static_cast(rec_count)); /* #if defined(__IOS__) || defined(__OSX__) export_mp4(rec_path, width, height, rec_count, ^(float) { pb->increment(); }); #endif */ const auto status = pp::panopainter::execute_legacy_recording_export_plan(*this, plan, path); if (!status.ok()) LOG("Recording export action failed: %s", status.message); } void App::rec_loop() { BT_SetTerminate(); rec_running = true; while(rec_running) { std::unique_lock lock(rec_mutex); rec_cv.wait(lock/*, [this] { return !(rec_frames.empty() && rec_running); }*/); auto* legacy_canvas = Canvas::I; auto* canvas_document = canvas ? canvas->m_canvas.get() : nullptr; auto* encoder = legacy_canvas ? legacy_canvas->m_encoder.get() : nullptr; const auto plan = pp::app::plan_recording_worker_iteration( rec_running, encoder != nullptr, legacy_canvas != nullptr && canvas_document != nullptr); if (!plan.continue_running) break; if (plan.encode_frame && legacy_canvas && canvas_document && encoder) { if (plan.clear_dirty_stroke) canvas_document->m_dirty_stroke = false; PBO equirect = legacy_canvas->m_layers_merge.gen_equirect_pbo( encoder->frame_size()); std::this_thread::yield(); ImageRef img; img.create(equirect.width, equirect.height, equirect.map()); encoder->encode(img); equirect.unmap(); LOG("rec frame encoded"); if (plan.update_frame_label) update_rec_frames(); } } } void App::render_thread_tick() { static uint32_t count = 0; render_thread_id = std::this_thread::get_id(); std::deque working_list; pp::app::AppQueueDrainPlan drain_plan; // move the task list locally to free the queue for other threads { std::unique_lock lock(render_task_mutex); drain_plan = pp::app::plan_app_render_queue_drain(render_tasklist.size()); render_running = drain_plan.mark_running; if (!drain_plan.drain_tasks) return; working_list = std::move(render_tasklist); } // execute the tasks if (drain_plan.wrap_in_render_context) { 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::render_thread_main() { BT_SetTerminate(); uint32_t count = 0; render_thread_id = std::this_thread::get_id(); render_running = pp::app::plan_app_thread_start().mark_running; while (render_running) { std::deque working_list; pp::app::AppQueueDrainPlan drain_plan; // 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; }); drain_plan = pp::app::plan_app_render_queue_drain(render_tasklist.size()); working_list = std::move(render_tasklist); } // execute the tasks if (drain_plan.wrap_in_render_context) { 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_tick() { ui_thread_id = std::this_thread::get_id(); std::deque working_list; pp::app::AppUiTickPlan tick_plan; // move the task list locally to free the queue for other threads { std::unique_lock lock(ui_task_mutex); tick_plan = pp::app::plan_app_ui_thread_tick(ui_tasklist.size(), redraw); ui_running = tick_plan.mark_running; working_list = std::move(ui_tasklist); } // execute the tasks if (tick_plan.execute_tasks) { while (!working_list.empty()) { //LOG("ui task %d", count); working_list.front()(); working_list.pop_front(); } } if (tick_plan.tick_app) tick(0); const auto redraw_plan = pp::app::plan_app_ui_loop_redraw(redraw, 0); if (redraw_plan.enqueue_render_frame) { if (redraw_plan.update_before_render) update(0); render_task([this] { bind_default_render_target(); clear(); draw(0); async_swap(); }); } } void App::ui_thread_main() { BT_SetTerminate(); uint32_t count = 0; ui_thread_id = std::this_thread::get_id(); ui_running = pp::app::plan_app_thread_start().mark_running; attach_ui_thread(); 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(idle_ms), [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; const auto timer_plan = pp::app::plan_app_ui_loop_timers( dt, t_frame, t_fps_counter, t_reloader, rendered_frames, platform_enables_live_asset_reloading()); if (timer_plan) { if (timer_plan.value().update_platform_frame) update_platform_frame(dt); t_frame = timer_plan.value().frame_accumulator; t_fps_counter = timer_plan.value().fps_accumulator; t_reloader = timer_plan.value().reload_accumulator; rendered_frames = timer_plan.value().rendered_frames_after_report; if (timer_plan.value().report_rendered_frames) report_rendered_frames(timer_plan.value().reported_frame_count); if (timer_plan.value().check_live_asset_reload) { if (ShaderManager::reload()) { stroke->update_controls(); redraw = true; } if (layout.reload()) redraw = true; if (layout_designer.reload()) redraw = true; } } const auto redraw_plan = pp::app::plan_app_ui_loop_redraw(redraw, rendered_frames); if (redraw_plan.tick_app) tick(dt); if (redraw_plan.enqueue_render_frame) { if (redraw_plan.update_before_render) update(t_frame); render_task([this, t_frame] { bind_default_render_target(); clear(); draw(t_frame); async_swap(); }); if (redraw_plan.reset_frame_accumulator) t_frame = 0; rendered_frames = redraw_plan.rendered_frames; } } detach_ui_thread(); } void App::render_thread_start() { const auto plan = pp::app::plan_app_thread_start(); if (plan.start_thread) render_thread = std::thread(&App::render_thread_main, this); render_running = plan.mark_running; } void App::render_thread_stop() { const auto plan = pp::app::plan_app_thread_stop(render_thread.joinable()); if (plan.mark_not_running) render_running = false; if (plan.notify_worker) render_cv.notify_all(); if (plan.join_thread) render_thread.join(); } void App::ui_thread_start() { const auto plan = pp::app::plan_app_thread_start(); if (plan.start_thread) ui_thread = std::thread(&App::ui_thread_main, this); ui_running = plan.mark_running; } void App::ui_thread_stop() { const auto plan = pp::app::plan_app_thread_stop(ui_thread.joinable()); if (plan.mark_not_running) ui_running = false; if (plan.notify_worker) ui_cv.notify_all(); if (plan.join_thread) ui_thread.join(); }