955 lines
37 KiB
C++
955 lines
37 KiB
C++
#include "pch.h"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
#include "app_core/canvas_hotkey.h"
|
|
#include "app_core/canvas_tool_ui.h"
|
|
#include "app_core/canvas_view.h"
|
|
#include "app_core/document_animation.h"
|
|
#include "app.h"
|
|
#include "node_panel_grid.h"
|
|
#include "legacy_canvas_draw_merge_services.h"
|
|
#include "legacy_canvas_stroke_composite_services.h"
|
|
#include "legacy_canvas_stroke_erase_services.h"
|
|
#include "legacy_preference_storage.h"
|
|
#include "legacy_ui_gl_dispatch.h"
|
|
#include "legacy_ui_overlay_services.h"
|
|
#include "legacy_canvas_tool_services.h"
|
|
#include "legacy_history_services.h"
|
|
#include "log.h"
|
|
#include "node_canvas.h"
|
|
#include "node_image_texture.h"
|
|
#include "paint_renderer/compositor.h"
|
|
#include "renderer_gl/opengl_capabilities.h"
|
|
#include "util.h"
|
|
|
|
namespace {
|
|
|
|
void set_active_texture_unit(std::uint32_t unit_index)
|
|
{
|
|
pp::legacy::ui_gl::activate_texture_unit(unit_index, "NodeCanvas");
|
|
}
|
|
|
|
void unbind_texture_2d()
|
|
{
|
|
pp::legacy::ui_gl::unbind_texture_2d("NodeCanvas");
|
|
}
|
|
|
|
void apply_node_canvas_viewport(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height)
|
|
{
|
|
pp::legacy::ui_gl::apply_viewport(x, y, width, height, "NodeCanvas");
|
|
}
|
|
|
|
pp::renderer::gl::OpenGlViewportRect query_node_canvas_viewport()
|
|
{
|
|
return pp::legacy::ui_gl::query_viewport_rect("NodeCanvas");
|
|
}
|
|
|
|
std::array<float, 4> query_node_canvas_clear_color()
|
|
{
|
|
return pp::legacy::ui_gl::query_clear_color("NodeCanvas");
|
|
}
|
|
|
|
void apply_node_canvas_clear_color(std::array<float, 4> color)
|
|
{
|
|
pp::legacy::ui_gl::set_clear_color(color, "NodeCanvas");
|
|
}
|
|
|
|
void clear_node_canvas_color_buffer(std::array<float, 4> color)
|
|
{
|
|
pp::legacy::ui_gl::clear_color_buffer(color, "NodeCanvas");
|
|
}
|
|
|
|
void apply_node_canvas_capability(std::uint32_t state, bool enabled)
|
|
{
|
|
pp::legacy::ui_gl::set_capability(state, enabled, "NodeCanvas");
|
|
}
|
|
|
|
bool query_node_canvas_capability(std::uint32_t state)
|
|
{
|
|
return pp::legacy::ui_gl::query_capability(state, "NodeCanvas");
|
|
}
|
|
|
|
pp::renderer::RenderDeviceFeatures node_canvas_stroke_composite_features() noexcept
|
|
{
|
|
return ShaderManager::render_device_features();
|
|
}
|
|
|
|
pp::paint_renderer::CanvasBlendGatePlan node_canvas_blend_gate_plan(
|
|
int width,
|
|
int height,
|
|
const std::vector<std::shared_ptr<Layer>>& layers,
|
|
const Brush* brush) noexcept
|
|
{
|
|
std::vector<int> layer_blend_modes;
|
|
layer_blend_modes.reserve(layers.size());
|
|
for (const auto& layer : layers) {
|
|
if (!layer) {
|
|
continue;
|
|
}
|
|
layer_blend_modes.push_back(layer->m_blend_mode);
|
|
}
|
|
|
|
const auto plan = pp::paint_renderer::plan_canvas_blend_gate(
|
|
node_canvas_stroke_composite_features(),
|
|
pp::paint_renderer::CanvasBlendGateRequest {
|
|
.extent = pp::renderer::Extent2D {
|
|
.width = static_cast<std::uint32_t>(std::max(width, 0)),
|
|
.height = static_cast<std::uint32_t>(std::max(height, 0)),
|
|
},
|
|
.layer_blend_modes = layer_blend_modes,
|
|
.has_stroke_blend_mode = brush != nullptr,
|
|
.stroke_blend_mode = brush ? brush->m_blend_mode : 0,
|
|
});
|
|
if (plan) {
|
|
return plan.value();
|
|
}
|
|
|
|
pp::paint_renderer::CanvasBlendGatePlan fallback;
|
|
fallback.shader_blend = true;
|
|
fallback.complex_blend = true;
|
|
fallback.compatibility_fallback = true;
|
|
return fallback;
|
|
}
|
|
|
|
pp::app::CanvasHotkeyKey canvas_hotkey_key(kKey key) noexcept
|
|
{
|
|
switch (key) {
|
|
case kKey::AndroidBack:
|
|
return pp::app::CanvasHotkeyKey::android_back;
|
|
case kKey::KeyAlt:
|
|
return pp::app::CanvasHotkeyKey::alt;
|
|
case kKey::KeyE:
|
|
return pp::app::CanvasHotkeyKey::e;
|
|
case kKey::KeyS:
|
|
return pp::app::CanvasHotkeyKey::s;
|
|
case kKey::KeyTab:
|
|
return pp::app::CanvasHotkeyKey::tab;
|
|
case kKey::KeyZ:
|
|
return pp::app::CanvasHotkeyKey::z;
|
|
case kKey::KeyBracketLeft:
|
|
return pp::app::CanvasHotkeyKey::bracket_left;
|
|
case kKey::KeyBracketRight:
|
|
return pp::app::CanvasHotkeyKey::bracket_right;
|
|
default:
|
|
return pp::app::CanvasHotkeyKey::other;
|
|
}
|
|
}
|
|
|
|
pp::app::CanvasToolMode canvas_tool_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;
|
|
case kCanvasMode::COUNT:
|
|
return pp::app::CanvasToolMode::draw;
|
|
}
|
|
|
|
return pp::app::CanvasToolMode::draw;
|
|
}
|
|
|
|
pp::app::CanvasCursorVisibilityMode canvas_cursor_visibility_mode(NodeCanvas::kCursorVisibility mode) noexcept
|
|
{
|
|
switch (mode) {
|
|
case NodeCanvas::kCursorVisibility::Never:
|
|
return pp::app::CanvasCursorVisibilityMode::never;
|
|
case NodeCanvas::kCursorVisibility::SmallBrush:
|
|
return pp::app::CanvasCursorVisibilityMode::small_brush;
|
|
case NodeCanvas::kCursorVisibility::NotPainting:
|
|
return pp::app::CanvasCursorVisibilityMode::not_painting;
|
|
case NodeCanvas::kCursorVisibility::Always:
|
|
return pp::app::CanvasCursorVisibilityMode::always;
|
|
}
|
|
|
|
return pp::app::CanvasCursorVisibilityMode::never;
|
|
}
|
|
|
|
pp::app::CanvasHotkeyState canvas_hotkey_state(bool mouse_focused, int touch_finger_count = 0) noexcept
|
|
{
|
|
pp::app::CanvasHotkeyState state;
|
|
state.ctrl_down = App::I && App::I->keys[(int)kKey::KeyCtrl];
|
|
state.shift_down = App::I && App::I->keys[(int)kKey::KeyShift];
|
|
state.mouse_focused = mouse_focused;
|
|
const auto history = pp::panopainter::legacy_history_snapshot();
|
|
state.undo_count = history.undo_count;
|
|
state.redo_count = history.redo_count;
|
|
state.touch_finger_count = touch_finger_count;
|
|
return state;
|
|
}
|
|
|
|
void execute_canvas_hotkey_plan(const pp::app::CanvasHotkeyPlan& plan)
|
|
{
|
|
const auto status = pp::panopainter::execute_legacy_canvas_hotkey_plan(plan);
|
|
if (!status.ok())
|
|
LOG("Canvas hotkey action failed: %s", status.message);
|
|
}
|
|
|
|
void run_canvas_hotkey(
|
|
pp::app::CanvasHotkeyEvent event,
|
|
kKey key,
|
|
bool mouse_focused,
|
|
int touch_finger_count = 0)
|
|
{
|
|
const auto plan = pp::app::plan_canvas_hotkey(
|
|
event,
|
|
canvas_hotkey_key(key),
|
|
canvas_hotkey_state(mouse_focused, touch_finger_count));
|
|
if (plan)
|
|
execute_canvas_hotkey_plan(plan.value());
|
|
else
|
|
LOG("Canvas hotkey planning failed: %s", plan.status().message);
|
|
}
|
|
|
|
void run_canvas_tool_mode(pp::app::CanvasToolMode mode)
|
|
{
|
|
const auto plan = pp::app::plan_canvas_tool_select(mode);
|
|
const auto status = pp::panopainter::execute_legacy_canvas_input_tool_plan(plan);
|
|
if (!status.ok())
|
|
LOG("Canvas input tool action failed: %s", status.message);
|
|
}
|
|
|
|
}
|
|
|
|
Node* NodeCanvas::clone_instantiate() const
|
|
{
|
|
return new NodeCanvas();
|
|
}
|
|
|
|
void NodeCanvas::init()
|
|
{
|
|
const auto preferences = pp::panopainter::read_legacy_canvas_preferences();
|
|
m_density = preferences.viewport_density;
|
|
m_cursor_visibility = (kCursorVisibility)preferences.cursor_mode;
|
|
|
|
m_mouse_ignore = false;
|
|
m_canvas = std::make_unique<Canvas>();
|
|
const int canvas_resolution = App::I->default_canvas_resolution();
|
|
m_canvas->create(canvas_resolution, canvas_resolution);
|
|
m_canvas->m_unsaved = false;
|
|
m_canvas->m_node = this;
|
|
|
|
m_sampler.create();
|
|
//m_sampler.set_filter(pp::renderer::gl::linear_texture_filter(), pp::renderer::gl::nearest_texture_filter());
|
|
|
|
m_sampler_nearest.create(pp::renderer::gl::nearest_texture_filter());
|
|
|
|
m_sampler_linear.create(pp::renderer::gl::linear_texture_filter());
|
|
m_sampler_stencil.create(
|
|
pp::renderer::gl::linear_texture_filter(),
|
|
pp::renderer::gl::repeat_texture_wrap());
|
|
m_face_plane.create<1>(2, 2);
|
|
m_line.create();
|
|
CanvasMode::node = this;
|
|
for (int i = 0; i < (int)kCanvasMode::COUNT; i++)
|
|
for (auto m : Canvas::modes[i])
|
|
m->init();
|
|
|
|
m_grid.create(1, 1, m_grid_divs);
|
|
}
|
|
|
|
void NodeCanvas::restore_context()
|
|
{
|
|
Node::restore_context();
|
|
const int canvas_resolution = App::I->default_canvas_resolution();
|
|
m_canvas->create(canvas_resolution, canvas_resolution);
|
|
|
|
m_sampler.create();
|
|
|
|
m_sampler.set_filter(
|
|
pp::renderer::gl::linear_texture_filter(),
|
|
pp::renderer::gl::nearest_texture_filter());
|
|
m_face_plane.create<1>(2, 2);
|
|
m_canvas->snapshot_restore();
|
|
CanvasMode::node = this;
|
|
for (int i = 0; i < (int)kCanvasMode::COUNT; i++)
|
|
for (auto m : Canvas::modes[i])
|
|
m->init();
|
|
}
|
|
|
|
void NodeCanvas::clear_context()
|
|
{
|
|
Node::clear_context();
|
|
m_canvas->snapshot_save();
|
|
m_canvas->clear_context();
|
|
// TODO: clear CanvasMode objects
|
|
}
|
|
|
|
void NodeCanvas::draw()
|
|
{
|
|
// sanity checks
|
|
float zoom = root()->m_zoom;
|
|
if (zoom == 0.f)
|
|
zoom = 1.f;
|
|
auto box = m_clip * zoom;
|
|
if (box.z == 0 || box.w == 0)
|
|
return;
|
|
|
|
const auto vp = query_node_canvas_viewport();
|
|
const auto cc = query_node_canvas_clear_color();
|
|
const auto blend = query_node_canvas_capability(pp::renderer::gl::blend_state());
|
|
const auto depth = query_node_canvas_capability(pp::renderer::gl::depth_test_state());
|
|
const auto scissor = query_node_canvas_capability(pp::renderer::gl::scissor_test_state());
|
|
|
|
apply_node_canvas_capability(pp::renderer::gl::scissor_test_state(), false);
|
|
|
|
glm::ivec4 c = (glm::ivec4)glm::vec4(box.x, (int)(vp.height - box.y - box.w), box.z, box.w);
|
|
|
|
//m_canvas->m_cam_rot = m_pan * 0.003f;
|
|
|
|
glm::mat4 ortho_proj = glm::ortho(0.f, box.z, 0.f, box.w, -1000.f, 1000.f);
|
|
glm::mat4 proj = glm::perspective(glm::radians(m_canvas->m_cam_fov), box.z / box.w, 0.001f, 1000.f);
|
|
glm::mat4 camera = glm::translate(m_canvas->m_cam_pos) * m_canvas->m_cam_rot;
|
|
|
|
m_canvas->m_mv = camera;
|
|
m_canvas->m_proj = proj;
|
|
m_canvas->m_box = box;
|
|
m_canvas->m_vp = c;
|
|
|
|
float pitch = 0;
|
|
if (auto slider = root()->find<NodeSliderH>("pitch-slider"))
|
|
pitch = (slider->get_value() - 0.5) * glm::half_pi<float>();
|
|
float yaw = 0;
|
|
if (auto slider = root()->find<NodeSliderH>("yaw-slider"))
|
|
yaw = (slider->get_value() - 0.5) * glm::half_pi<float>();
|
|
float roll = 0;
|
|
if (auto slider = root()->find<NodeSliderH>("roll-slider"))
|
|
roll = (slider->get_value() - 0.5) * glm::half_pi<float>();
|
|
|
|
// pre computed helpers
|
|
for (int plane_index = 0; plane_index < 6; plane_index++)
|
|
{
|
|
//glm::mat4 plane_camera = glm::lookAt(m_canvas->m_plane_origin[plane_index], m_canvas->m_plane_normal[plane_index], m_canvas->m_plane_tangent[plane_index]);
|
|
m_canvas->m_plane_unproject[plane_index] = glm::inverse(m_canvas->m_proj * m_canvas->m_mv * m_canvas->m_plane_transform[plane_index]);
|
|
m_canvas->m_plane_dir[plane_index] = -(m_canvas->m_plane_transform[plane_index] * glm::vec4(m_canvas->m_plane_origin[plane_index], 1));
|
|
// face is the 2d shape of the cube plane i projected onto the window space
|
|
m_canvas->m_plane_shape[plane_index] = m_canvas->face_to_shape2D(plane_index);
|
|
}
|
|
|
|
|
|
if (m_density != 1.f)
|
|
{
|
|
m_rtt.bindFramebuffer();
|
|
clear_node_canvas_color_buffer({ 1.f, 1.f, 0.f, 0.f });
|
|
apply_node_canvas_viewport(0, 0, m_rtt.getWidth(), m_rtt.getHeight());
|
|
}
|
|
else
|
|
{
|
|
clear_node_canvas_color_buffer({ 1.f, 1.f, 1.f, 0.f });
|
|
apply_node_canvas_viewport(c.x + App::I->off_x, c.y + App::I->off_y, c.z, c.w);
|
|
}
|
|
|
|
// NOTE: draw_merge has been disabled for worst performance
|
|
bool draw_merged = !(m_canvas->m_current_mode == kCanvasMode::Camera);
|
|
draw_merged = false;
|
|
|
|
if (draw_merged)
|
|
{
|
|
apply_node_canvas_capability(pp::renderer::gl::blend_state(), false);
|
|
// draw the grid
|
|
for (int plane_index = 0; plane_index < 6; plane_index++)
|
|
{
|
|
auto plane_mvp = proj * camera *
|
|
glm::scale(glm::vec3(m_canvas->m_layers.size() + 500)) *
|
|
m_canvas->m_plane_transform[plane_index] *
|
|
glm::translate(glm::vec3(0, 0, -1));
|
|
|
|
pp::panopainter::setup_legacy_canvas_draw_merge_checkerboard_shader(
|
|
pp::panopainter::LegacyCanvasDrawMergeCheckerboardUniforms {
|
|
.mvp = plane_mvp,
|
|
.colorize = false,
|
|
});
|
|
m_face_plane.draw_fill();
|
|
|
|
int z = 1;
|
|
auto plane_mvp_z = proj * camera *
|
|
//glm::scale(glm::vec3(z + 1)) *
|
|
//glm::eulerAngleYXZ(yaw, pitch, roll) *
|
|
m_canvas->m_plane_transform[plane_index] *
|
|
glm::translate(glm::vec3(0, 0, -1));
|
|
|
|
m_sampler.bind(0);
|
|
ShaderManager::use(kShader::TextureAlpha);
|
|
ShaderManager::u_int(kShaderUniform::Tex, 0);
|
|
ShaderManager::u_float(kShaderUniform::Alpha, 1.f);
|
|
ShaderManager::u_int(kShaderUniform::Highlight, false);
|
|
ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp_z);
|
|
|
|
set_active_texture_unit(0);
|
|
m_canvas->m_layers_merge.rtt(plane_index).bindTexture();
|
|
m_face_plane.draw_fill();
|
|
m_canvas->m_layers_merge.rtt(plane_index).unbindTexture();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const auto blend_gate = node_canvas_blend_gate_plan(
|
|
m_cache_rtt.getWidth(),
|
|
m_cache_rtt.getHeight(),
|
|
m_canvas->m_layers,
|
|
m_canvas->m_current_stroke ? m_canvas->m_current_stroke->m_brush.get() : nullptr);
|
|
const bool use_blend = blend_gate.shader_blend;
|
|
const bool copy_blend_destination = use_blend && !blend_gate.reads_destination_color;
|
|
|
|
if (use_blend)
|
|
{
|
|
apply_node_canvas_viewport(0, 0, m_cache_rtt.getWidth(), m_cache_rtt.getHeight());
|
|
m_cache_rtt.bindFramebuffer();
|
|
m_cache_rtt.clear({ 1, 1, 1, 0 });
|
|
}
|
|
else
|
|
{
|
|
// draw the grid
|
|
for (int plane_index = 0; plane_index < 6; plane_index++)
|
|
{
|
|
auto plane_mvp = proj * camera *
|
|
glm::scale(glm::vec3(m_canvas->m_layers.size() + 500)) *
|
|
m_canvas->m_plane_transform[plane_index] *
|
|
glm::translate(glm::vec3(0, 0, -1));
|
|
|
|
pp::panopainter::setup_legacy_canvas_draw_merge_checkerboard_shader(
|
|
pp::panopainter::LegacyCanvasDrawMergeCheckerboardUniforms {
|
|
.mvp = plane_mvp,
|
|
.colorize = false,
|
|
});
|
|
m_face_plane.draw_fill();
|
|
}
|
|
}
|
|
|
|
// if not using shader blend, use gl rasterizer blend
|
|
use_blend ? apply_node_canvas_capability(pp::renderer::gl::blend_state(), false) : apply_node_canvas_capability(pp::renderer::gl::blend_state(), true);
|
|
apply_node_canvas_capability(pp::renderer::gl::depth_test_state(), false);
|
|
|
|
const auto& b = m_canvas->m_current_stroke->m_brush;
|
|
|
|
for (size_t i = 0; i < m_canvas->m_layers.size(); i++)
|
|
{
|
|
auto layer_index = i;
|
|
for (int plane_index = 0; plane_index < 6; plane_index++)
|
|
{
|
|
const auto onion_range_result = pp::app::plan_animation_onion_frame_range(
|
|
m_canvas->m_layers[layer_index]->frames_count(),
|
|
m_canvas->m_layers[layer_index]->m_frame_index,
|
|
App::I->animation->get_onion_size());
|
|
if (!onion_range_result) {
|
|
LOG("NodeCanvas onion frame range failed: %s", onion_range_result.status().message);
|
|
continue;
|
|
}
|
|
const auto onion_range = onion_range_result.value();
|
|
bool faces = false;
|
|
for (int frame = onion_range.first_frame; frame <= onion_range.last_frame; frame++)
|
|
faces |= m_canvas->m_layers[layer_index]->face(plane_index, frame);
|
|
if (!(m_canvas->m_show_tmp && m_canvas->m_current_layer_idx == layer_index) &&
|
|
(!m_canvas->m_layers[layer_index]->m_visible ||
|
|
m_canvas->m_layers[layer_index]->m_opacity == .0f || !faces))
|
|
continue;
|
|
|
|
if (use_blend)
|
|
{
|
|
m_blender_rtt.bindFramebuffer();
|
|
m_blender_rtt.clear();
|
|
}
|
|
|
|
int z = (int)(m_canvas->m_layers.size() - i);
|
|
auto plane_mvp_z = proj * camera *
|
|
glm::scale(glm::vec3(z + 1)) *
|
|
glm::eulerAngleYXZ(yaw, pitch, roll) *
|
|
m_canvas->m_plane_transform[plane_index] *
|
|
glm::translate(glm::vec3(0, 0, -1));
|
|
|
|
if (m_canvas->m_current_stroke && m_canvas->m_current_mode == kCanvasMode::Erase && m_canvas->m_show_tmp && m_canvas->m_current_layer_idx == layer_index)
|
|
{
|
|
m_sampler.bind(0);
|
|
m_sampler.bind(1);
|
|
m_sampler.bind(2);
|
|
|
|
//ShaderManager::u_vec2(kShaderUniform::Resolution, zw(m_canvas->m_box) / zoom);
|
|
//ShaderManager::u_int(kShaderUniform::Lock, m_canvas->m_layers[layer_index]->m_alpha_locked);
|
|
pp::panopainter::setup_legacy_stroke_erase_shader(
|
|
pp::panopainter::LegacyStrokeEraseUniforms {
|
|
.mvp = plane_mvp_z,
|
|
.texture_slot = 0,
|
|
.stroke_texture_slot = 1,
|
|
.mask_texture_slot = 2,
|
|
.mask_enabled = m_canvas->m_smask_active,
|
|
});
|
|
set_active_texture_unit(1);
|
|
m_canvas->m_tmp[plane_index].bindTexture();
|
|
set_active_texture_unit(2);
|
|
m_canvas->m_smask.rtt(plane_index).bindTexture();
|
|
for (int frame = onion_range.first_frame; frame <= onion_range.last_frame; frame++)
|
|
{
|
|
const float onion_alpha = pp::app::animation_onion_frame_alpha(onion_range, frame);
|
|
ShaderManager::u_float(kShaderUniform::Alpha, m_canvas->m_layers[layer_index]->m_opacity* onion_alpha);
|
|
set_active_texture_unit(0);
|
|
m_canvas->m_layers[layer_index]->rtt(plane_index, frame).bindTexture();
|
|
m_face_plane.draw_fill();
|
|
set_active_texture_unit(0);
|
|
m_canvas->m_layers[layer_index]->rtt(plane_index, frame).unbindTexture();
|
|
}
|
|
set_active_texture_unit(2);
|
|
m_canvas->m_smask.rtt(plane_index).unbindTexture();
|
|
set_active_texture_unit(1);
|
|
m_canvas->m_tmp[plane_index].unbindTexture();
|
|
}
|
|
else if(m_canvas->m_current_stroke && m_canvas->m_show_tmp && m_canvas->m_current_layer_idx == layer_index)
|
|
{
|
|
m_sampler.bind(0);
|
|
m_sampler.bind(1);
|
|
m_sampler.bind(2);
|
|
m_sampler.bind(3);
|
|
m_sampler_stencil.bind(4);
|
|
|
|
glm::vec2 patt_scale = glm::vec2(b->m_pattern_scale);
|
|
if (b->m_pattern_flipx) patt_scale.x *= -1.f;
|
|
if (b->m_pattern_flipy) patt_scale.y *= -1.f;
|
|
|
|
pp::panopainter::setup_legacy_stroke_composite_shader(
|
|
pp::panopainter::LegacyStrokeCompositeUniforms {
|
|
.resolution = Canvas::I->m_size,
|
|
.pattern = {
|
|
.scale = patt_scale,
|
|
.invert = static_cast<float>(b->m_pattern_invert),
|
|
.brightness = b->m_pattern_brightness,
|
|
.contrast = b->m_pattern_contrast,
|
|
.depth = b->m_pattern_depth,
|
|
.blend_mode = b->m_pattern_blend_mode,
|
|
.offset = Canvas::I->m_pattern_offset,
|
|
},
|
|
.mvp = plane_mvp_z,
|
|
.layer_alpha = 1.0f,
|
|
.alpha_lock = m_canvas->m_layers[layer_index]->m_alpha_locked,
|
|
.mask_enabled = m_canvas->m_smask_active,
|
|
.use_fragcoord = false,
|
|
.blend_mode = b->m_blend_mode,
|
|
.use_dual = b->m_dual_enabled,
|
|
.dual_blend_mode = b->m_dual_blend_mode,
|
|
.dual_alpha = b->m_dual_opacity,
|
|
.use_pattern = b->m_pattern_enabled && !b->m_pattern_eachsample,
|
|
});
|
|
|
|
set_active_texture_unit(1);
|
|
m_canvas->m_tmp[plane_index].bindTexture();
|
|
set_active_texture_unit(2);
|
|
m_canvas->m_smask.rtt(plane_index).bindTexture();
|
|
set_active_texture_unit(3);
|
|
if (b->m_dual_enabled)
|
|
m_canvas->m_tmp_dual[plane_index].bindTexture();
|
|
set_active_texture_unit(4);
|
|
b->m_pattern_texture ?
|
|
b->m_pattern_texture->bind() :
|
|
unbind_texture_2d();
|
|
for (int frame = onion_range.first_frame; frame <= onion_range.last_frame; frame++)
|
|
{
|
|
const float onion_alpha = pp::app::animation_onion_frame_alpha(onion_range, frame);
|
|
ShaderManager::u_float(kShaderUniform::Alpha, m_canvas->m_layers[layer_index]->m_opacity * onion_alpha);
|
|
set_active_texture_unit(0);
|
|
m_canvas->m_layers[layer_index]->rtt(plane_index, frame).bindTexture();
|
|
m_face_plane.draw_fill();
|
|
set_active_texture_unit(0);
|
|
m_canvas->m_layers[layer_index]->rtt(plane_index, frame).unbindTexture();
|
|
}
|
|
set_active_texture_unit(3);
|
|
if (b->m_dual_enabled)
|
|
m_canvas->m_tmp_dual[plane_index].unbindTexture();
|
|
set_active_texture_unit(2);
|
|
m_canvas->m_smask.rtt(plane_index).unbindTexture();
|
|
set_active_texture_unit(1);
|
|
m_canvas->m_tmp[plane_index].unbindTexture();
|
|
}
|
|
else
|
|
{
|
|
m_canvas->m_cam_fov < 20.f ? m_sampler_nearest.bind(0) : m_sampler.bind(0);
|
|
ShaderManager::use(kShader::TextureAlpha);
|
|
ShaderManager::u_int(kShaderUniform::Tex, 0);
|
|
ShaderManager::u_int(kShaderUniform::Highlight, m_canvas->m_layers[layer_index]->m_hightlight);
|
|
ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp_z);
|
|
|
|
for (int frame = onion_range.first_frame; frame <= onion_range.last_frame; frame++)
|
|
{
|
|
const float onion_alpha = pp::app::animation_onion_frame_alpha(onion_range, frame);
|
|
ShaderManager::u_float(kShaderUniform::Alpha, m_canvas->m_layers[layer_index]->m_opacity * onion_alpha);
|
|
set_active_texture_unit(0);
|
|
m_canvas->m_layers[layer_index]->rtt(plane_index, frame).bindTexture();
|
|
m_face_plane.draw_fill();
|
|
m_canvas->m_layers[layer_index]->rtt(plane_index, frame).unbindTexture();
|
|
}
|
|
}
|
|
|
|
if (use_blend)
|
|
{
|
|
m_blender_rtt.unbindFramebuffer();
|
|
}
|
|
|
|
// draw the blended
|
|
if (use_blend)
|
|
{
|
|
m_sampler.bind(0);
|
|
m_sampler.bind(2);
|
|
|
|
pp::panopainter::setup_legacy_canvas_draw_merge_texture_blend_shader(
|
|
pp::panopainter::LegacyCanvasDrawMergeTextureBlendUniforms {
|
|
.mvp = glm::ortho(-1, 1, -1, 1),
|
|
.texture_slot = 0,
|
|
.destination_texture_slot = 2,
|
|
.use_destination_texture = copy_blend_destination,
|
|
.blend_mode = m_canvas->m_layers[layer_index]->m_blend_mode,
|
|
.alpha = 1.f,
|
|
});
|
|
|
|
set_active_texture_unit(0);
|
|
m_blender_rtt.bindTexture();
|
|
if (copy_blend_destination)
|
|
{
|
|
set_active_texture_unit(2);
|
|
m_blender_bg.bind();
|
|
copy_framebuffer_to_texture_2d(
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
m_blender_bg.size().x,
|
|
m_blender_bg.size().y);
|
|
}
|
|
|
|
m_face_plane.draw_fill();
|
|
|
|
if (copy_blend_destination)
|
|
{
|
|
set_active_texture_unit(2);
|
|
m_blender_bg.unbind();
|
|
}
|
|
set_active_texture_unit(0);
|
|
m_blender_rtt.unbindTexture();
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
// draw dirty area
|
|
{
|
|
auto bb = m_canvas->m_layers[layer_index]->box(plane_index) / (float)m_canvas->m_layers[layer_index]->w;
|
|
glm::vec2 bbmin = xy(bb);
|
|
glm::vec2 bbsz = zw(bb) - xy(bb);
|
|
ShaderManager::use(kShader::Color);
|
|
ShaderManager::u_vec4(kShaderUniform::Col, { 1, 0, 0, 1 });
|
|
ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp_z
|
|
* glm::translate(glm::vec3(bbmin * 2.f, 0))
|
|
* glm::translate(glm::vec3(-1, -1, 0))
|
|
* glm::scale(glm::vec3(bbsz, 1))
|
|
* glm::translate(glm::vec3(1, 1, 0))
|
|
);
|
|
m_face_plane.draw_stroke();
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
if (use_blend)
|
|
{
|
|
m_cache_rtt.unbindFramebuffer();
|
|
if (m_density != 1.f)
|
|
apply_node_canvas_viewport(0, 0, m_rtt.getWidth(), m_rtt.getHeight());
|
|
else
|
|
apply_node_canvas_viewport(c.x + App::I->off_x, c.y + App::I->off_y, c.z, c.w);
|
|
}
|
|
|
|
// draw the grid behind the layers using a temporary copy
|
|
if (use_blend)
|
|
{
|
|
apply_node_canvas_capability(pp::renderer::gl::blend_state(), true);
|
|
|
|
//draw the grid
|
|
for (int plane_index = 0; plane_index < 6; plane_index++)
|
|
{
|
|
auto plane_mvp = proj * camera *
|
|
glm::scale(glm::vec3(m_canvas->m_layers.size() + 500.f)) *
|
|
m_canvas->m_plane_transform[plane_index] *
|
|
glm::translate(glm::vec3(0, 0, -1.f));
|
|
|
|
pp::panopainter::setup_legacy_canvas_draw_merge_checkerboard_shader(
|
|
pp::panopainter::LegacyCanvasDrawMergeCheckerboardUniforms {
|
|
.mvp = plane_mvp,
|
|
.colorize = false,
|
|
});
|
|
m_face_plane.draw_fill();
|
|
}
|
|
|
|
// draw the layers
|
|
m_sampler.bind(0);
|
|
set_active_texture_unit(0);
|
|
m_cache_rtt.bindTexture();
|
|
pp::panopainter::setup_legacy_canvas_draw_merge_texture_shader(
|
|
pp::panopainter::LegacyCanvasDrawMergeTextureUniforms {
|
|
.mvp = glm::ortho<float>(-1, 1, -1, 1),
|
|
.texture_slot = 0,
|
|
});
|
|
m_face_plane.draw_fill();
|
|
m_cache_rtt.unbindTexture();
|
|
}
|
|
}
|
|
|
|
apply_node_canvas_capability(pp::renderer::gl::depth_test_state(), false);
|
|
|
|
if (m_canvas->m_smask_active || m_canvas->m_current_mode == kCanvasMode::Copy || m_canvas->m_current_mode == kCanvasMode::Cut)
|
|
{
|
|
if (m_canvas->m_smask_mode == 1)
|
|
m_canvas->modes[(int)kCanvasMode::MaskFree][0]->on_Draw(ortho_proj, proj, camera);
|
|
else if (m_canvas->m_smask_mode == 2)
|
|
m_canvas->modes[(int)kCanvasMode::MaskLine][0]->on_Draw(ortho_proj, proj, camera);
|
|
}
|
|
|
|
if (m_canvas->m_smask_active)
|
|
{
|
|
ShaderManager::use(kShader::TextureMask);
|
|
ShaderManager::u_int(kShaderUniform::Tex, 0);
|
|
ShaderManager::u_vec2(kShaderUniform::PatternOffset, m_outline_pan);
|
|
set_active_texture_unit(0);
|
|
apply_node_canvas_capability(pp::renderer::gl::blend_state(), true);
|
|
|
|
//draw the cube faces
|
|
for (int plane_index = 0; plane_index < 6; plane_index++)
|
|
{
|
|
auto plane_mvp = proj * camera *
|
|
glm::scale(glm::vec3(m_canvas->m_layers.size() + 500.f)) *
|
|
m_canvas->m_plane_transform[plane_index] *
|
|
glm::translate(glm::vec3(0, 0, -1.f));
|
|
|
|
ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp);
|
|
m_canvas->m_smask.rtt(plane_index).bindTexture();
|
|
m_face_plane.draw_fill();
|
|
m_canvas->m_smask.rtt(plane_index).unbindTexture();
|
|
}
|
|
|
|
|
|
}
|
|
|
|
// keep drawing the grids
|
|
if (m_canvas->m_current_mode != kCanvasMode::Grid)
|
|
for (auto& mode : Canvas::modes[(int)kCanvasMode::Grid])
|
|
mode->on_Draw(ortho_proj, proj, camera);
|
|
|
|
App::I->grid->draw_heightmap(proj, camera, false);
|
|
|
|
for (auto& mode : *m_canvas->m_mode)
|
|
mode->on_Draw(ortho_proj, proj, camera);
|
|
|
|
if (m_density != 1.f)
|
|
{
|
|
m_rtt.unbindFramebuffer();
|
|
|
|
clear_node_canvas_color_buffer({ 1.f, 1.f, 1.f, 0.f });
|
|
apply_node_canvas_viewport(c.x + App::I->off_x, c.y + App::I->off_y, c.z, c.w);
|
|
|
|
// draw the canvas
|
|
m_sampler_nearest.bind(0);
|
|
set_active_texture_unit(0);
|
|
m_rtt.bindTexture();
|
|
ShaderManager::use(kShader::Texture);
|
|
ShaderManager::u_int(kShaderUniform::Tex, 0);
|
|
ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho<float>(-1, 1, -1, 1));
|
|
m_face_plane.draw_fill();
|
|
m_rtt.unbindTexture();
|
|
}
|
|
|
|
scissor ? apply_node_canvas_capability(pp::renderer::gl::scissor_test_state(), true) : apply_node_canvas_capability(pp::renderer::gl::scissor_test_state(), false);
|
|
blend ? apply_node_canvas_capability(pp::renderer::gl::blend_state(), true) : apply_node_canvas_capability(pp::renderer::gl::blend_state(), false);
|
|
depth ? apply_node_canvas_capability(pp::renderer::gl::depth_test_state(), true) : apply_node_canvas_capability(pp::renderer::gl::depth_test_state(), false);
|
|
apply_node_canvas_viewport(vp.x, vp.y, vp.width, vp.height);
|
|
apply_node_canvas_clear_color(cc);
|
|
}
|
|
|
|
void NodeCanvas::handle_resize(glm::vec2 old_size, glm::vec2 new_size, float zoom)
|
|
{
|
|
if (new_size.x != m_canvas->m_width || new_size.y != m_canvas->m_height)
|
|
{
|
|
new_size = new_size * m_density;
|
|
create_buffers();
|
|
}
|
|
}
|
|
|
|
kEventResult NodeCanvas::handle_event(Event* e)
|
|
{
|
|
static bool stylus_eraser = false;
|
|
Node::handle_event(e);
|
|
MouseEvent* me = static_cast<MouseEvent*>(e);
|
|
KeyEvent* ke = static_cast<KeyEvent*>(e);
|
|
GestureEvent* ge = static_cast<GestureEvent*>(e);
|
|
TouchEvent* te = static_cast<TouchEvent*>(e);
|
|
auto loc = (me->m_pos - m_pos) * root()->m_zoom;
|
|
|
|
switch (e->m_type)
|
|
{
|
|
case kEventType::MouseMove:
|
|
if (stylus_eraser != me->m_eraser)
|
|
{
|
|
run_canvas_tool_mode(me->m_eraser ?
|
|
pp::app::CanvasToolMode::erase :
|
|
pp::app::CanvasToolMode::draw);
|
|
stylus_eraser = me->m_eraser;
|
|
}
|
|
case kEventType::MouseScroll:
|
|
case kEventType::MouseDownL:
|
|
case kEventType::MouseUpL:
|
|
case kEventType::MouseDownR:
|
|
case kEventType::MouseUpR:
|
|
case kEventType::MouseCancel:
|
|
m_canvas->m_cur_pos = loc;
|
|
update_cursor();
|
|
for (auto& mode : *m_canvas->m_mode)
|
|
mode->on_MouseEvent(me, loc);
|
|
break;
|
|
case kEventType::MouseUnfocus:
|
|
(*m_canvas->m_mode)[0]->m_draw_tip = false;
|
|
App::I->show_cursor();
|
|
break;
|
|
case kEventType::MouseFocus:
|
|
update_cursor();
|
|
break;
|
|
case kEventType::KeyDown:
|
|
run_canvas_hotkey(
|
|
pp::app::CanvasHotkeyEvent::key_down,
|
|
ke->m_key,
|
|
m_mouse_focus);
|
|
for (auto& mode : *m_canvas->m_mode)
|
|
mode->on_KeyEvent(ke);
|
|
break;
|
|
case kEventType::KeyUp:
|
|
update_cursor();
|
|
run_canvas_hotkey(
|
|
pp::app::CanvasHotkeyEvent::key_up,
|
|
ke->m_key,
|
|
m_mouse_focus);
|
|
for (auto& mode : *m_canvas->m_mode)
|
|
mode->on_KeyEvent(ke);
|
|
break;
|
|
case kEventType::GestureStart:
|
|
mouse_capture();
|
|
for (auto& mode : *m_canvas->m_mode)
|
|
mode->on_GestureEvent(ge);
|
|
break;
|
|
case kEventType::GestureMove:
|
|
for (auto& mode : *m_canvas->m_mode)
|
|
mode->on_GestureEvent(ge);
|
|
break;
|
|
case kEventType::GestureEnd:
|
|
pp::panopainter::release_legacy_mouse_capture(*this);
|
|
for (auto& mode : *m_canvas->m_mode)
|
|
mode->on_GestureEvent(ge);
|
|
break;
|
|
case kEventType::TouchTap:
|
|
run_canvas_hotkey(
|
|
pp::app::CanvasHotkeyEvent::touch_tap,
|
|
kKey::Unknown,
|
|
m_mouse_focus,
|
|
te->m_finger_count);
|
|
break;
|
|
default:
|
|
return kEventResult::Available;
|
|
break;
|
|
}
|
|
return kEventResult::Consumed;
|
|
}
|
|
|
|
void NodeCanvas::reset_camera()
|
|
{
|
|
const auto state = pp::app::plan_canvas_camera_reset();
|
|
m_canvas->m_cam_rot = glm::mat4(
|
|
state.rotation[0], state.rotation[1], state.rotation[2], state.rotation[3],
|
|
state.rotation[4], state.rotation[5], state.rotation[6], state.rotation[7],
|
|
state.rotation[8], state.rotation[9], state.rotation[10], state.rotation[11],
|
|
state.rotation[12], state.rotation[13], state.rotation[14], state.rotation[15]);
|
|
m_canvas->m_cam_pos = {
|
|
state.position[0],
|
|
state.position[1],
|
|
state.position[2],
|
|
};
|
|
m_canvas->m_cam_fov = state.field_of_view_degrees;
|
|
m_canvas->m_pan = {
|
|
state.pan[0],
|
|
state.pan[1],
|
|
};
|
|
}
|
|
|
|
void NodeCanvas::create_buffers()
|
|
{
|
|
auto new_size = GetSize() * m_density;
|
|
LOG("NodeCanvas::create_buffers size: %d x %d density %f", (int)new_size.x, (int)new_size.y, m_density);
|
|
m_canvas->m_mixer.create((int)new_size.x * m_canvas->m_mixer_scale,
|
|
(int)new_size.y * m_canvas->m_mixer_scale, -1, pp::renderer::gl::rgba8_internal_format());
|
|
m_blender_rtt.create((int)new_size.x, (int)new_size.y, -1, pp::renderer::gl::rgba8_internal_format());
|
|
m_cache_rtt.create((int)new_size.x, (int)new_size.y, -1, pp::renderer::gl::rgba8_internal_format());
|
|
m_rtt.create((int)new_size.x, (int)new_size.y, -1, pp::renderer::gl::rgba8_internal_format(), true);
|
|
m_blender_bg.create((int)new_size.x, (int)new_size.y, pp::renderer::gl::rgba8_internal_format());
|
|
}
|
|
|
|
void NodeCanvas::set_density(float d)
|
|
{
|
|
m_density = d;
|
|
create_buffers();
|
|
}
|
|
|
|
void NodeCanvas::set_cursor_visibility(kCursorVisibility mode)
|
|
{
|
|
m_cursor_visibility = mode;
|
|
}
|
|
|
|
void NodeCanvas::update_cursor()
|
|
{
|
|
auto* pen_mode = m_canvas->get_mode<CanvasModePen>();
|
|
const auto plan = pp::app::plan_canvas_cursor_visibility(pp::app::CanvasCursorVisibilityInput {
|
|
.mode = canvas_tool_mode(m_canvas->m_current_mode),
|
|
.visibility_mode = canvas_cursor_visibility_mode(m_cursor_visibility),
|
|
.has_current_brush = m_canvas->m_current_brush != nullptr,
|
|
.brush_tip_size = m_canvas->m_current_brush ? m_canvas->m_current_brush->m_tip_size : 0.0F,
|
|
.pen_is_drawing = pen_mode && pen_mode->m_drawing,
|
|
.alt_down = App::I && App::I->keys[(int)kKey::KeyAlt],
|
|
.pen_is_resizing = pen_mode && pen_mode->m_resizing,
|
|
.pen_is_picking = pen_mode && pen_mode->m_picking,
|
|
});
|
|
if (!plan) {
|
|
LOG("Canvas cursor visibility planning failed: %s", plan.status().message);
|
|
App::I->show_cursor();
|
|
return;
|
|
}
|
|
|
|
plan.value().visible ? App::I->show_cursor() : App::I->hide_cursor();
|
|
}
|
|
|
|
void NodeCanvas::on_tick(float dt)
|
|
{
|
|
m_outline_pan = glm::fract(m_outline_pan + dt * 0.01f);
|
|
}
|
|
|
|
void NodeCanvas::destroy()
|
|
{
|
|
m_blender_rtt.destroy();
|
|
m_cache_rtt.destroy();
|
|
m_rtt.destroy();
|
|
m_blender_bg.destroy();
|
|
m_face_plane.destroy();
|
|
m_line.destroy();
|
|
m_grid.destroy();
|
|
Node::destroy();
|
|
}
|