1086 lines
32 KiB
C++
1086 lines
32 KiB
C++
#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_status.h"
|
|
#include "app_core/canvas_tool_ui.h"
|
|
#include "app_core/document_recording.h"
|
|
#include "app_core/document_route.h"
|
|
#include "app_core/document_session.h"
|
|
#include "platform_api/platform_services.h"
|
|
#include "renderer_gl/opengl_capabilities.h"
|
|
|
|
#ifdef __APPLE__
|
|
#include <Foundation/Foundation.h>
|
|
#include "objc_utils.h"
|
|
#endif
|
|
#include "settings.h"
|
|
|
|
App* App::I = nullptr; // singleton
|
|
|
|
std::deque<AppTask> 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<const char*>(glGetString(static_cast<GLenum>(name)));
|
|
}
|
|
|
|
void enable_opengl_state(std::uint32_t state) noexcept
|
|
{
|
|
glEnable(static_cast<GLenum>(state));
|
|
}
|
|
|
|
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 disable_opengl_state(std::uint32_t state) noexcept
|
|
{
|
|
glDisable(static_cast<GLenum>(state));
|
|
}
|
|
|
|
void set_opengl_blend_func(std::uint32_t source_factor, std::uint32_t destination_factor) noexcept
|
|
{
|
|
glBlendFunc(static_cast<GLenum>(source_factor), static_cast<GLenum>(destination_factor));
|
|
}
|
|
|
|
void set_opengl_blend_equation_separate(std::uint32_t color_equation, std::uint32_t alpha_equation) noexcept
|
|
{
|
|
glBlendEquationSeparate(static_cast<GLenum>(color_equation), static_cast<GLenum>(alpha_equation));
|
|
}
|
|
|
|
void clear_opengl_color(float r, float g, float b, float a) noexcept
|
|
{
|
|
glClearColor(r, g, b, a);
|
|
}
|
|
|
|
void clear_opengl_buffers(std::uint32_t mask) noexcept
|
|
{
|
|
glClear(static_cast<GLbitfield>(mask));
|
|
}
|
|
|
|
void set_opengl_viewport(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height) noexcept
|
|
{
|
|
glViewport(static_cast<GLint>(x), static_cast<GLint>(y), static_cast<GLsizei>(width), static_cast<GLsizei>(height));
|
|
}
|
|
|
|
void set_opengl_scissor(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height) noexcept
|
|
{
|
|
glScissor(static_cast<GLint>(x), static_cast<GLint>(y), static_cast<GLsizei>(width), static_cast<GLsizei>(height));
|
|
}
|
|
|
|
void apply_app_viewport(pp::renderer::gl::OpenGlViewportRect viewport)
|
|
{
|
|
const auto status = pp::renderer::gl::apply_opengl_viewport(
|
|
viewport,
|
|
pp::renderer::gl::OpenGlViewportDispatch {
|
|
.viewport = 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 = enable_opengl_state,
|
|
.disable = disable_opengl_state,
|
|
.scissor = 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 = enable_opengl_state,
|
|
.disable = disable_opengl_state,
|
|
});
|
|
if (!status.ok())
|
|
LOG("OpenGL scissor test failed: %s", status.message);
|
|
}
|
|
|
|
[[nodiscard]] GLint rgba8_internal_format() noexcept
|
|
{
|
|
return static_cast<GLint>(pp::renderer::gl::rgba8_internal_format());
|
|
}
|
|
|
|
[[nodiscard]] GLenum linear_texture_filter() noexcept
|
|
{
|
|
return static_cast<GLenum>(pp::renderer::gl::linear_texture_filter());
|
|
}
|
|
|
|
[[nodiscard]] GLenum nearest_texture_filter() noexcept
|
|
{
|
|
return static_cast<GLenum>(pp::renderer::gl::nearest_texture_filter());
|
|
}
|
|
|
|
[[nodiscard]] GLenum repeat_texture_wrap() noexcept
|
|
{
|
|
return static_cast<GLenum>(pp::renderer::gl::repeat_texture_wrap());
|
|
}
|
|
|
|
[[nodiscard]] GLenum framebuffer_target() noexcept
|
|
{
|
|
return static_cast<GLenum>(pp::renderer::gl::framebuffer_target());
|
|
}
|
|
|
|
[[nodiscard]] GLuint default_framebuffer_id() noexcept
|
|
{
|
|
return static_cast<GLuint>(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<AppTask> 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)
|
|
{
|
|
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);
|
|
if (open_plan == pp::app::DocumentOpenPlanAction::prompt_import_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 (open_plan == pp::app::DocumentOpenPlanAction::prompt_import_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
|
|
{
|
|
const std::string base = route.value().directory;
|
|
const std::string name = route.value().name;
|
|
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 (open_plan == pp::app::DocumentOpenPlanAction::open_project_now)
|
|
{
|
|
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;
|
|
const auto close_decision = pp::app::plan_close_request(
|
|
Canvas::I->m_unsaved,
|
|
dialog_already_opened);
|
|
if (close_decision == pp::app::CloseRequestDecision::close_now)
|
|
return true;
|
|
if (close_decision == pp::app::CloseRequestDecision::show_unsaved_prompt)
|
|
{
|
|
auto* m = layout[main_id]->add_child<NodeMessageBox>();
|
|
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*) {
|
|
request_app_close();
|
|
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()
|
|
{
|
|
const auto status = pp::renderer::gl::clear_panopainter_default_target(
|
|
pp::renderer::gl::OpenGlClearDispatch {
|
|
.clear_color = clear_opengl_color,
|
|
.clear = clear_opengl_buffers,
|
|
});
|
|
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)
|
|
{
|
|
std::function<void(float)> progress = *(std::function<void(float)>*)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<void(float)> progress = *(std::function<void(float)>*)clientp;
|
|
progress((float)ulnow / (float)ultotal);
|
|
return 0;
|
|
}
|
|
#endif //CURL
|
|
|
|
void App::download(std::string url, std::string dest_filepath, std::function<void(float)> progress)
|
|
{
|
|
#if WITH_CURL
|
|
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);
|
|
}
|
|
#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);
|
|
#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;
|
|
}
|
|
#endif //CURL
|
|
return false;
|
|
}
|
|
|
|
void App::upload(std::string filename, std::string name, std::function<void(float)> progress)
|
|
{
|
|
#if WITH_CURL
|
|
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);
|
|
}
|
|
#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 = enable_opengl_state,
|
|
.disable = disable_opengl_state,
|
|
.blend_func = set_opengl_blend_func,
|
|
.blend_equation_separate = set_opengl_blend_equation_separate,
|
|
});
|
|
if (!startup_state_status.ok())
|
|
LOG("OpenGL startup state failed: %s", startup_state_status.message);
|
|
});
|
|
|
|
int run_counter = Settings::value<Serializer::Integer>("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, rgba8_internal_format(), true);
|
|
|
|
if (Settings::value_or<Serializer::Boolean>("auto-timelapse", true))
|
|
rec_start();
|
|
Settings::value<Serializer::Boolean>("vr-controllers-enabled", vr_controllers_enabled);
|
|
|
|
if (!check_license())
|
|
{
|
|
message_box("License", "Could not validate this license, running in demo mode.");
|
|
}
|
|
}
|
|
|
|
void App::async_start()
|
|
{
|
|
acquire_render_context();
|
|
}
|
|
|
|
void App::async_redraw()
|
|
{
|
|
redraw = true;
|
|
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)
|
|
{
|
|
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::vec2 parent_offset = p->m_parent ? -p->m_parent->m_pos_offset_childred : glm::vec2(0.f);
|
|
glm::vec4 pclip = { xy(p->m_clip_uncut) + off_p, zw(p->m_clip_uncut) - off_s - off_p/* + parent_offset*/ };
|
|
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;
|
|
glm::vec2 parent_offset = n->m_parent ? n->m_parent->m_pos_offset_childred : glm::vec2(0.f);
|
|
if (box.z <= 0 || box.w <= 0)
|
|
{
|
|
if (n->m_on_screen)
|
|
{
|
|
if (dynamic_cast<NodeStrokePreview*>(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;
|
|
apply_app_scissor(pp::renderer::gl::OpenGlScissorRect {
|
|
.enabled = 1U,
|
|
.x = static_cast<std::int32_t>(floorf(c.x + off_x)),
|
|
.y = static_cast<std::int32_t>(floorf(c.y + off_y)),
|
|
.width = static_cast<std::int32_t>(ceilf(c.z)),
|
|
.height = static_cast<std::int32_t>(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();
|
|
apply_app_viewport(pp::renderer::gl::OpenGlViewportRect {
|
|
.width = static_cast<std::int32_t>(uirtt.getWidth()),
|
|
.height = static_cast<std::int32_t>(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 (!vr_only)
|
|
{
|
|
bind_main_render_target();
|
|
apply_app_viewport(pp::renderer::gl::OpenGlViewportRect {
|
|
.x = static_cast<std::int32_t>(off_x),
|
|
.y = static_cast<std::int32_t>(off_y),
|
|
.width = static_cast<std::int32_t>(width),
|
|
.height = static_cast<std::int32_t>(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);
|
|
}
|
|
|
|
redraw = false;
|
|
}
|
|
|
|
void App::update(float dt)
|
|
{
|
|
static std::mutex mutex;
|
|
|
|
// avoid multiple threads to update the scene
|
|
//std::lock_guard<std::mutex> 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);
|
|
|
|
{
|
|
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<NodeButtonCustom>("btn-pick")->set_active(toolbar.pick_active);
|
|
layout[main_id]->find<NodeButtonCustom>("btn-touchlock")->set_active(toolbar.touch_lock_active);
|
|
|
|
layout[main_id]->find<NodeButtonCustom>("btn-pen")->set_active(toolbar.pen_active);
|
|
layout[main_id]->find<NodeButtonCustom>("btn-erase")->set_active(toolbar.erase_active);
|
|
layout[main_id]->find<NodeButton>("btn-cam")->set_active(toolbar.camera_active);
|
|
layout[main_id]->find<NodeButtonCustom>("btn-line")->set_active(toolbar.line_active);
|
|
layout[main_id]->find<NodeButton>("btn-grid")->set_active(toolbar.grid_active);
|
|
layout[main_id]->find<NodeButton>("btn-copy")->set_active(toolbar.copy_active);
|
|
layout[main_id]->find<NodeButton>("btn-cut")->set_active(toolbar.cut_active);
|
|
layout[main_id]->find<NodeButtonCustom>("btn-mask-free")->set_active(toolbar.mask_free_active);
|
|
layout[main_id]->find<NodeButtonCustom>("btn-mask-line")->set_active(toolbar.mask_line_active);
|
|
layout[main_id]->find<NodeButtonCustom>("btn-bucket")->set_active(toolbar.flood_fill_active);
|
|
}
|
|
}
|
|
|
|
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<NodeText>("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<NodeText>("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<int>(index.value()) : static_cast<int>(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()
|
|
);
|
|
if (plan.stop_running_recording)
|
|
rec_stop();
|
|
if (plan.delete_recorded_files)
|
|
clear_platform_recorded_files(rec_path);
|
|
rec_count = plan.frame_count_after_clear;
|
|
update_rec_frames();
|
|
}
|
|
|
|
void App::rec_start()
|
|
{
|
|
const auto plan = pp::app::plan_recording_start(rec_running);
|
|
switch (plan)
|
|
{
|
|
case pp::app::RecordingStartAction::start_thread:
|
|
break;
|
|
case pp::app::RecordingStartAction::no_op_already_running:
|
|
return;
|
|
}
|
|
|
|
update_rec_frames();
|
|
rec_thread = std::thread(&App::rec_loop, this);
|
|
}
|
|
|
|
void App::rec_stop()
|
|
{
|
|
const auto plan = pp::app::plan_recording_stop(rec_running);
|
|
switch (plan)
|
|
{
|
|
case pp::app::RecordingStopAction::stop_thread:
|
|
break;
|
|
case pp::app::RecordingStopAction::no_op_not_running:
|
|
return;
|
|
}
|
|
|
|
rec_running = false;
|
|
rec_cv.notify_all();
|
|
if (rec_thread.joinable())
|
|
rec_thread.join();
|
|
update_rec_frames();
|
|
}
|
|
|
|
void App::rec_export(std::string path)
|
|
{
|
|
const auto plan = pp::app::plan_recording_export(static_cast<std::size_t>(rec_count));
|
|
auto pb = layout[main_id]->add_child<NodeProgressBar>();
|
|
pb->m_progress->SetWidthP(0);
|
|
pb->m_title->set_text("Exporting MP4 movie");
|
|
pb->m_total = plan.progress_total;
|
|
pb->m_count = 0;
|
|
|
|
/*
|
|
#if defined(__IOS__) || defined(__OSX__)
|
|
export_mp4(rec_path, width, height, rec_count, ^(float) {
|
|
pb->increment();
|
|
});
|
|
#endif
|
|
*/
|
|
Canvas::I->m_encoder->write_mp4(path);
|
|
|
|
pb->destroy();
|
|
}
|
|
|
|
void App::rec_loop()
|
|
{
|
|
BT_SetTerminate();
|
|
rec_running = true;
|
|
while(rec_running)
|
|
{
|
|
std::unique_lock<std::mutex> lock(rec_mutex);
|
|
rec_cv.wait(lock/*, [this] { return !(rec_frames.empty() && rec_running); }*/);
|
|
if (!rec_running)
|
|
break;
|
|
|
|
if (Canvas::I->m_encoder)
|
|
{
|
|
canvas->m_canvas->m_dirty_stroke = false;
|
|
PBO equirect = Canvas::I->m_layers_merge.gen_equirect_pbo(
|
|
Canvas::I->m_encoder->frame_size());
|
|
std::this_thread::yield();
|
|
ImageRef img;
|
|
img.create(equirect.width, equirect.height, equirect.map());
|
|
Canvas::I->m_encoder->encode(img);
|
|
equirect.unmap();
|
|
LOG("rec frame encoded");
|
|
update_rec_frames();
|
|
}
|
|
}
|
|
}
|
|
|
|
void App::render_thread_tick()
|
|
{
|
|
static uint32_t count = 0;
|
|
render_thread_id = std::this_thread::get_id();
|
|
render_running = true;
|
|
std::deque<AppTask> working_list;
|
|
|
|
// move the task list locally to free the queue for other threads
|
|
{
|
|
std::unique_lock<std::mutex> lock(render_task_mutex);
|
|
if (render_tasklist.empty())
|
|
return;
|
|
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::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<AppTask> working_list;
|
|
|
|
// move the task list locally to free the queue for other threads
|
|
{
|
|
std::unique_lock<std::mutex> 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_tick()
|
|
{
|
|
ui_thread_id = std::this_thread::get_id();
|
|
ui_running = true;
|
|
|
|
std::deque<AppTask> working_list;
|
|
|
|
// move the task list locally to free the queue for other threads
|
|
{
|
|
std::unique_lock<std::mutex> lock(ui_task_mutex);
|
|
working_list = std::move(ui_tasklist);
|
|
}
|
|
|
|
// execute the tasks
|
|
if (!working_list.empty())
|
|
{
|
|
while (!working_list.empty())
|
|
{
|
|
//LOG("ui task %d", count);
|
|
working_list.front()();
|
|
working_list.pop_front();
|
|
}
|
|
}
|
|
|
|
tick(0);
|
|
|
|
if (redraw)
|
|
{
|
|
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 = true;
|
|
|
|
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<AppTask> working_list;
|
|
|
|
// move the task list locally to free the queue for other threads
|
|
{
|
|
std::unique_lock<std::mutex> 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<float>(t_now - t_start).count();
|
|
t_start = t_now;
|
|
|
|
update_platform_frame(dt);
|
|
|
|
// increment timers
|
|
t_frame += dt;
|
|
t_fps_counter += dt;
|
|
|
|
if (t_fps_counter > 1.f)
|
|
{
|
|
report_rendered_frames(rendered_frames);
|
|
t_fps_counter = 0;
|
|
rendered_frames = 0;
|
|
}
|
|
|
|
if (platform_enables_live_asset_reloading())
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
tick(dt);
|
|
|
|
if (redraw)
|
|
{
|
|
update(t_frame);
|
|
render_task([this, t_frame]
|
|
{
|
|
bind_default_render_target();
|
|
clear();
|
|
draw(t_frame);
|
|
async_swap();
|
|
});
|
|
t_frame = 0;
|
|
rendered_frames++;
|
|
}
|
|
}
|
|
detach_ui_thread();
|
|
}
|
|
|
|
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();
|
|
}
|