#include "pch.h" #include "log.h" #include "node_stroke_preview.h" #include "texture.h" #include "shader.h" #include "bezier.h" #include "canvas.h" #include "app.h" std::atomic_int NodeStrokePreview::s_instances{ 0 }; std::atomic_bool NodeStrokePreview::s_running{ false }; std::mutex NodeStrokePreview::s_render_mutex; std::thread NodeStrokePreview::s_renderer; BlockingQueue> NodeStrokePreview::s_queue; RTT NodeStrokePreview::m_rtt; RTT NodeStrokePreview::m_rtt_mixer; Texture2D NodeStrokePreview::m_tex; // blending tmp texture Texture2D NodeStrokePreview::m_tex_dual; Texture2D NodeStrokePreview::m_tex_background; Sampler NodeStrokePreview::m_sampler_linear; Sampler NodeStrokePreview::m_sampler_linear_repeat; Sampler NodeStrokePreview::m_sampler_mipmap; DynamicShape NodeStrokePreview::m_brush_shape; void NodeStrokePreview::terminate_renderer() { if (s_running && s_renderer.joinable()) { s_running = false; s_queue.UnlockGetters(); s_renderer.join(); } } void NodeStrokePreview::empty_queue() { s_queue.q.clear(); } Node* NodeStrokePreview::clone_instantiate() const { return new NodeStrokePreview(); } void NodeStrokePreview::clone_copy(Node* dest) const { NodeBorder::clone_copy(dest); } void NodeStrokePreview::clone_children(Node* dest) const { // stop children cloning } void NodeStrokePreview::clone_finalize(Node* dest) const { NodeStrokePreview* n = (NodeStrokePreview*)dest; n->init_controls(); } void NodeStrokePreview::init_controls() { // TextureManager::load("data/thumbs/Round-Hard.png"); // Canvas::I->m_current_brush.m_tex_id = const_hash("data/thumbs/Round-Hard.png"); } void NodeStrokePreview::restore_context() { NodeBorder::restore_context(); init_controls(); if (m_size.x > 0 && m_size.y > 0) m_tex_preview.create(m_size.x, m_size.y); draw_stroke(); } void NodeStrokePreview::clear_context() { NodeBorder::clear_context(); m_tex_preview.destroy(); } void NodeStrokePreview::stroke_draw_mix(const glm::vec2& bb_min, const glm::vec2& bb_sz) { gl_state gl; gl.save(); m_rtt_mixer.bindFramebuffer(); glViewport(0, 0, m_rtt_mixer.getWidth(), m_rtt_mixer.getHeight()); glDisable(GL_DEPTH_TEST); glEnable(GL_SCISSOR_TEST); glDisable(GL_BLEND); glScissor(bb_min.x, bb_min.y, bb_sz.x, bb_sz.y); const auto& b = m_brush; 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; ShaderManager::use(kShader::CompDraw); ShaderManager::u_int(kShaderUniform::Tex, 0); ShaderManager::u_int(kShaderUniform::TexStroke, 1); ShaderManager::u_int(kShaderUniform::TexMask, 2); ShaderManager::u_int(kShaderUniform::TexDual, 3); ShaderManager::u_int(kShaderUniform::TexPattern, 4); ShaderManager::u_vec2(kShaderUniform::Resolution, m_size); ShaderManager::u_float(kShaderUniform::Alpha, 1); ShaderManager::u_int(kShaderUniform::Lock, false); ShaderManager::u_int(kShaderUniform::Mask, false); ShaderManager::u_int(kShaderUniform::UseFragcoord, false); ShaderManager::u_int(kShaderUniform::BlendMode, b->m_blend_mode); ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f)); ShaderManager::u_int(kShaderUniform::UseDual, b->m_dual_enabled); ShaderManager::u_int(kShaderUniform::UsePattern, b->m_pattern_enabled && !b->m_pattern_eachsample); ShaderManager::u_int(kShaderUniform::DualBlendMode, b->m_dual_blend_mode); ShaderManager::u_vec2(kShaderUniform::PatternScale, patt_scale); ShaderManager::u_float(kShaderUniform::PatternInvert, b->m_pattern_invert); ShaderManager::u_float(kShaderUniform::PatternBright, b->m_pattern_brightness); ShaderManager::u_float(kShaderUniform::PatternContrast, b->m_pattern_contrast); ShaderManager::u_float(kShaderUniform::PatternDepth, b->m_pattern_depth); ShaderManager::u_int(kShaderUniform::PatternBlendMode, b->m_pattern_blend_mode); ShaderManager::u_vec2(kShaderUniform::PatternOffset, glm::vec2(b->m_pattern_rand_offset ? 0.5f : 0.0f)); ShaderManager::u_float(kShaderUniform::DualAlpha, b->m_dual_opacity); m_sampler_linear.bind(0); glActiveTexture(GL_TEXTURE0); m_tex_background.bind(); glActiveTexture(GL_TEXTURE1); m_tex.bind(); glActiveTexture(GL_TEXTURE3); m_tex_dual.bind(); glActiveTexture(GL_TEXTURE4); b->m_pattern_texture ? b->m_pattern_texture->bind() : glBindTexture(GL_TEXTURE_2D, 0); m_plane.draw_fill(); m_rtt_mixer.unbindFramebuffer(); gl.restore(); } glm::vec4 NodeStrokePreview::stroke_draw_samples(std::array& P, Texture2D& blend_tex) { if (!ShaderManager::ext_framebuffer_fetch) { glActiveTexture(GL_TEXTURE1); blend_tex.bind(); // bg, copy of framebuffer (copied before drawing) } glm::vec2 size = { m_rtt.getWidth(), m_rtt.getHeight() }; glm::vec2 bb_min(size); glm::vec2 bb_max(0, 0); for (int j = 0; j < P.size(); j++) { bb_min = glm::max({ 0, 0 }, glm::min(bb_min, xy(P[j].pos))); bb_max = glm::min(size, glm::max(bb_max, xy(P[j].pos))); } auto bb_sz = bb_max - bb_min; glm::vec2 pad(1); glm::ivec2 tex_pos = glm::clamp(glm::floor(bb_min) - pad, { 0, 0 }, size); glm::ivec2 tex_sz = glm::clamp(glm::ceil(bb_sz) + pad * 2.f, { 0, 0 }, (glm::vec2)(glm::ivec2(size) - tex_pos)); if (!ShaderManager::ext_framebuffer_fetch) { glCopyTexSubImage2D(GL_TEXTURE_2D, 0, tex_pos.x, tex_pos.y, tex_pos.x, tex_pos.y, tex_sz.x, tex_sz.y); } if (P.size() == 4) { static vertex_t rect[6]; rect[0] = P[0]; rect[1] = P[1]; rect[2] = P[2]; rect[3] = P[0]; rect[4] = P[2]; rect[5] = P[3]; m_brush_shape.update_vertices(rect, 6); } m_brush_shape.draw_fill(); if (!ShaderManager::ext_framebuffer_fetch) { glActiveTexture(GL_TEXTURE1); blend_tex.unbind(); } return glm::vec4(tex_pos, tex_pos + tex_sz); } std::vector NodeStrokePreview::stroke_draw_compute(Stroke& stroke, float zoom) const { std::vector ret; StrokeSample prev = stroke.m_prev_sample; auto samples = stroke.compute_samples(); std::array B = { vertex_t{ {0, 0, 1, 1}, {0, 0}, {0, 0} }, vertex_t{ {0, 0, 1, 1}, {0, 1}, {0, 1} }, vertex_t{ {0, 0, 1, 1}, {1, 1}, {1, 1} }, vertex_t{ {0, 0, 1, 1}, {1, 0}, {1, 0} }, }; for (const auto& s : samples) { if (!s.valid()) continue; ret.emplace_back(); auto& f = ret.back(); glm::vec2 dx_mix(prev.size * 0.5f, 0), dy_mix(0, prev.size * 0.5f); glm::vec2 off_mix[4] = { -dx_mix - dy_mix, // A - bottom-left -dx_mix + dy_mix, // B - top-left +dx_mix + dy_mix, // C - top-right +dx_mix - dy_mix, // D - bottom-right }; // P is the initial square centered at the cursor location glm::vec2 dx(s.size * 0.5f, 0), dy(0, s.size * 0.5f); glm::vec2 off[4] = { -dx - dy, // A - bottom-left -dx + dy, // B - top-left +dx + dy, // C - top-right +dx - dy, // D - bottom-right }; glm::vec2 mixer_sz(m_rtt.getWidth(), m_rtt.getHeight()); glm::vec2 mixer_bb_min(mixer_sz); glm::vec2 mixer_bb_max(0, 0); for (int j = 0; j < 4; j++) { auto p = (xy(prev.pos) + zoom * s.scale * off_mix[j] * glm::orientate2(-s.angle)); mixer_bb_min = glm::max({ 0, 0 }, glm::min(mixer_bb_min, p)); mixer_bb_max = glm::min(mixer_sz, glm::max(mixer_bb_max, p)); B[j].pos = glm::vec4(xy(s.pos) + zoom * s.scale * off[j] * glm::orientate2(-s.angle) - glm::vec2(0, 1), 1, 1); B[j].uvs2 = p / mixer_sz; } f.m_mixer_rect = { glm::floor(mixer_bb_min), glm::ceil(mixer_bb_max - mixer_bb_min) }; f.col = glm::vec4(s.col, 1); f.flow = s.flow; f.opacity = s.opacity; f.shapes = B; prev = s; } return ret; } void NodeStrokePreview::draw_stroke_immediate() { if (m_size.x == 0 || m_size.y == 0) return; GLint vp[4]; GLfloat cc[4]; glGetIntegerv(GL_VIEWPORT, vp); glGetFloatv(GL_COLOR_CLEAR_VALUE, cc); float zoom = root()->m_zoom; glm::vec2 size = { m_rtt.getWidth(), m_rtt.getHeight() }; glm::mat4 ortho_proj = glm::ortho(0, size.x, 0, size.y, -1, 1); glViewport(0, 0, m_rtt.getWidth(), m_rtt.getHeight()); m_rtt.bindFramebuffer(); m_rtt.clear(); m_sampler_mipmap.bind(0); m_sampler_linear.bind(1); m_sampler_linear_repeat.bind(2); m_sampler_linear.bind(3); m_sampler_linear.bind(4); const auto& b = m_brush; Stroke m_stroke; Stroke m_dual_stroke; m_stroke.m_filter_points = false; m_stroke.m_max_size = m_max_size > 0 ? m_max_size : m_size.y * .75f; m_stroke.m_camera.fov = Canvas::I->m_cam_fov; m_stroke.m_camera.rot = Canvas::I->m_cam_rot; m_stroke.reset(true); m_stroke.start(b); auto dual_brush = std::make_shared(); dual_brush->m_tip_scale = b->m_dual_scale; dual_brush->m_tip_angle = b->m_dual_angle; dual_brush->m_tip_flow = b->m_dual_flow; dual_brush->m_tip_opacity = b->m_dual_opacity; dual_brush->m_tip_flipx = b->m_dual_flipx; dual_brush->m_tip_flipy = b->m_dual_flipy; dual_brush->m_tip_invert = b->m_dual_invert; dual_brush->m_blend_mode = b->m_dual_blend_mode; dual_brush->m_tip_randflipx = b->m_dual_randflip; dual_brush->m_tip_randflipy = b->m_dual_randflip; dual_brush->m_tip_size = b->m_dual_size * b->m_tip_size; dual_brush->m_tip_spacing = b->m_dual_spacing; dual_brush->m_jitter_scatter = b->m_dual_scatter; dual_brush->m_jitter_scatter_bothaxis = b->m_dual_scatter_bothaxis; dual_brush->m_jitter_angle = b->m_dual_rotate; dual_brush->m_tip_texture = b->m_dual_texture; dual_brush->m_tip_aspect = b->m_dual_aspect; if (b->m_dual_enabled) { m_dual_stroke.m_filter_points = false; m_dual_stroke.m_max_size = m_stroke.m_max_size * b->m_dual_size; m_dual_stroke.m_camera.fov = Canvas::I->m_cam_fov; m_dual_stroke.m_camera.rot = Canvas::I->m_cam_rot; m_dual_stroke.reset(true); m_dual_stroke.start(dual_brush); } { float min_pad = size.x * 0.05f; float pad = (5.f + glm::max(glm::min(m_stroke.m_max_size, m_brush->m_tip_size) / 2.f, min_pad)) * zoom; if (b->m_tip_size_pressure) pad = min_pad * zoom; if (!glm::isnan(m_pad_override)) pad = m_pad_override; float w = m_size.x * zoom; float h = m_size.y * zoom; std::vector kp = { { pad, h / 2.f }, { w / 2.f, 0 }, { w / 2.f, h }, { w - pad, h / 2.f }, }; for (int i = 0; i < 100; i++) { float t = (float)i / 100.f; float p = glm::clamp((1.f - glm::abs(t * 2.f - 1.f)) * 1.1f, 0.f, 1.f); m_stroke.add_point(glm::vec3(BezierCurve::Bezier2D(kp, t), 0), p); if (b->m_dual_enabled) m_dual_stroke.add_point(glm::vec3(BezierCurve::Bezier2D(kp, t), 0), p); } } 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; glDisable(GL_BLEND); ShaderManager::use(kShader::Stroke); ShaderManager::u_int(kShaderUniform::Tex, 0); // brush if (!ShaderManager::ext_framebuffer_fetch) ShaderManager::u_int(kShaderUniform::TexBG, 1); // bg ShaderManager::u_int(kShaderUniform::TexPattern, 2); // pattern ShaderManager::u_int(kShaderUniform::TexMix, 3); // mixer //ShaderManager::u_int(kShaderUniform::TexMixA, 4); // mixer ShaderManager::u_vec2(kShaderUniform::Resolution, size); ShaderManager::u_vec2(kShaderUniform::PatternScale, patt_scale); ShaderManager::u_float(kShaderUniform::PatternInvert, b->m_pattern_invert); ShaderManager::u_float(kShaderUniform::PatternBright, b->m_pattern_brightness); ShaderManager::u_float(kShaderUniform::PatternContrast, b->m_pattern_contrast); ShaderManager::u_float(kShaderUniform::PatternDepth, b->m_pattern_depth); ShaderManager::u_int(kShaderUniform::PatternBlendMode, b->m_pattern_blend_mode); ShaderManager::u_vec2(kShaderUniform::PatternOffset, glm::vec2(b->m_pattern_rand_offset ? 0.5f : 0.0f)); ShaderManager::u_mat4(kShaderUniform::MVP, ortho_proj); // DRAW DUAL BRUSH if (b->m_dual_enabled) { m_rtt.clear(); ShaderManager::use(kShader::Stroke); ShaderManager::u_int(kShaderUniform::UsePattern, false); ShaderManager::u_float(kShaderUniform::MixAlpha, 0); ShaderManager::u_float(kShaderUniform::Wet, 0); ShaderManager::u_float(kShaderUniform::Noise, 0); glActiveTexture(GL_TEXTURE0); dual_brush->m_tip_texture ? dual_brush->m_tip_texture->bind() : glBindTexture(GL_TEXTURE_2D, 0); auto frames_dual = stroke_draw_compute(m_dual_stroke, zoom); for (auto& f : frames_dual) { ShaderManager::u_vec4(kShaderUniform::Col, { 0, 0, 0, 1 }); ShaderManager::u_float(kShaderUniform::Alpha, f.flow); ShaderManager::u_float(kShaderUniform::Opacity, f.opacity); /*auto rect =*/ stroke_draw_samples(f.shapes, m_tex_dual); } // copy raw stroke to tex glActiveTexture(GL_TEXTURE1); m_tex_dual.bind(); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, size.x, size.y); } // DRAW MAIN BRUSH ShaderManager::use(kShader::Stroke); ShaderManager::u_int(kShaderUniform::UsePattern, b->m_pattern_enabled && b->m_pattern_eachsample); ShaderManager::u_float(kShaderUniform::MixAlpha, b->m_tip_mix); ShaderManager::u_float(kShaderUniform::Wet, b->m_tip_wet); ShaderManager::u_float(kShaderUniform::Noise, b->m_tip_noise); glActiveTexture(GL_TEXTURE0); b->m_tip_texture->bind(); glActiveTexture(GL_TEXTURE1); m_tex.bind(); // tmp swap for blending glActiveTexture(GL_TEXTURE2); b->m_pattern_texture ? b->m_pattern_texture->bind() : glBindTexture(GL_TEXTURE_2D, 0); glActiveTexture(GL_TEXTURE3); b->m_tip_mix > 0.f ? m_rtt_mixer.bindTexture() : glBindTexture(GL_TEXTURE_2D, 0); auto frames = stroke_draw_compute(m_stroke, zoom); m_rtt.clear(); for (auto& f : frames) { if (b->m_tip_mix > 0.f) { stroke_draw_mix(xy(f.m_mixer_rect), zw(f.m_mixer_rect)); } ShaderManager::use(kShader::Stroke); if (b->m_blend_mode != 0) ShaderManager::u_vec4(kShaderUniform::Col, { .7, .4, .1, 1 } /*f.col*/); else ShaderManager::u_vec4(kShaderUniform::Col, { 0, 0, 0, 1 } /*f.col*/); ShaderManager::u_float(kShaderUniform::Alpha, glm::max(f.flow, m_min_flow)); ShaderManager::u_float(kShaderUniform::Opacity, f.opacity); /*auto rect =*/ stroke_draw_samples(f.shapes, m_tex); } glActiveTexture(GL_TEXTURE3); m_rtt_mixer.unbindTexture(); // copy raw stroke to tex glActiveTexture(GL_TEXTURE1); m_tex.bind(); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, size.x, size.y); // CHEKCERBOARD // copy background color to tex2 ShaderManager::use(kShader::Checkerboard); ShaderManager::u_int(kShaderUniform::Colorize, b->m_tip_mix > 0.f || b->m_blend_mode != 0); float aspect = size.x / size.y; ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-.5f, .5f, -.5f / aspect, .5f / aspect, -1.f, 1.f)); m_plane.draw_fill(); //m_rtt.clear({ .3f, .3f, .3f, 1.f }); m_tex_background.bind(); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, size.x, size.y); // COMPOSITE ShaderManager::use(kShader::CompDraw); ShaderManager::u_int(kShaderUniform::Tex, 0); ShaderManager::u_int(kShaderUniform::TexStroke, 1); ShaderManager::u_int(kShaderUniform::TexMask, 2); ShaderManager::u_int(kShaderUniform::TexDual, 3); ShaderManager::u_int(kShaderUniform::TexPattern, 4); ShaderManager::u_vec2(kShaderUniform::Resolution, size); ShaderManager::u_float(kShaderUniform::Alpha, 1); ShaderManager::u_int(kShaderUniform::UseFragcoord, false); ShaderManager::u_int(kShaderUniform::Mask, false); ShaderManager::u_int(kShaderUniform::BlendMode, b->m_blend_mode); ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f)); ShaderManager::u_int(kShaderUniform::UseDual, b->m_dual_enabled); ShaderManager::u_int(kShaderUniform::UsePattern, b->m_pattern_enabled && !b->m_pattern_eachsample); ShaderManager::u_int(kShaderUniform::DualBlendMode, b->m_dual_blend_mode); ShaderManager::u_vec2(kShaderUniform::PatternScale, patt_scale); ShaderManager::u_float(kShaderUniform::PatternInvert, b->m_pattern_invert); ShaderManager::u_float(kShaderUniform::PatternBright, b->m_pattern_brightness); ShaderManager::u_float(kShaderUniform::PatternContrast, b->m_pattern_contrast); ShaderManager::u_float(kShaderUniform::PatternDepth, b->m_pattern_depth); ShaderManager::u_int(kShaderUniform::PatternBlendMode, b->m_pattern_blend_mode); ShaderManager::u_vec2(kShaderUniform::PatternOffset, glm::vec2(b->m_pattern_rand_offset ? 0.5f : 0.0f)); ShaderManager::u_float(kShaderUniform::DualAlpha, b->m_dual_opacity); m_sampler_linear.bind(0); m_sampler_linear.bind(1); m_sampler_linear.bind(2); m_sampler_linear.bind(3); m_sampler_linear_repeat.bind(4); glActiveTexture(GL_TEXTURE0); m_tex_background.bind(); glActiveTexture(GL_TEXTURE1); m_tex.bind(); glActiveTexture(GL_TEXTURE3); m_tex_dual.bind(); glActiveTexture(GL_TEXTURE4); b->m_pattern_texture ? b->m_pattern_texture->bind() : glBindTexture(GL_TEXTURE_2D, 0); m_plane.draw_fill(); // copy the result to the actual preview m_tex_preview.bind(); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, size.x, size.y); m_rtt.unbindFramebuffer(); glViewport(vp[0], vp[1], vp[2], vp[3]); glClearColor(cc[0], cc[1], cc[2], cc[3]); } Image NodeStrokePreview::render_to_image() { std::lock_guard _lock(s_render_mutex); App::I->render_task([this] { auto new_size = m_preview_size; if (!m_tex_preview.ready() || m_tex_preview.size() != new_size) m_tex_preview.create((int)new_size.x, (int)new_size.y); if (m_tex.size() != new_size) { m_rtt.create((int)new_size.x, (int)new_size.y); m_rtt_mixer.create((int)new_size.x, (int)new_size.y); m_tex.create((int)new_size.x, (int)new_size.y); m_tex_dual.create((int)new_size.x, (int)new_size.y); m_tex_background.create((int)new_size.x, (int)new_size.y); } draw_stroke_immediate(); }); return m_tex_preview.get_image(); } void NodeStrokePreview::draw_stroke() { if (m_size.x == 0 || m_size.y == 0) return; s_queue.mutex.lock(); if (!s_running) { s_running = true; s_renderer = std::thread([] { BT_SetTerminate(); m_sampler_linear.create(); m_sampler_linear_repeat.create(GL_LINEAR, GL_REPEAT); m_sampler_mipmap.create(); m_sampler_mipmap.set_filter(GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR); m_brush_shape.create(); while (s_running) { auto node = s_queue.Get(); if (node) { std::lock_guard _lock(s_render_mutex); // if the brush is not already loaded, load it and then destroy it bool to_unload = (node->m_brush->m_tip_texture == nullptr); node->m_brush->preload(); App::I->render_task([node, to_unload] { gl_state gl; gl.save(); auto new_size = node->m_preview_size; if (!node->m_tex_preview.ready() || node->m_tex_preview.size() != new_size) node->m_tex_preview.create((int)new_size.x, (int)new_size.y); if (m_tex.size() != new_size) { m_rtt.create((int)new_size.x, (int)new_size.y); m_rtt_mixer.create((int)new_size.x, (int)new_size.y); m_tex.create((int)new_size.x, (int)new_size.y); m_tex_dual.create((int)new_size.x, (int)new_size.y); m_tex_background.create((int)new_size.x, (int)new_size.y); } node->m_brush->load(); node->draw_stroke_immediate(); if (to_unload) node->m_brush->unload(); gl.restore(); }); node->app_redraw(); //std::this_thread::sleep_for(std::chrono::milliseconds(30)); std::this_thread::yield(); } } m_rtt.destroy(); m_rtt_mixer.destroy(); m_tex.destroy(); m_tex_dual.destroy(); m_tex_background.destroy(); m_brush_shape.destroy(); }); } s_queue.mutex.unlock(); s_queue.PostUnique(std::static_pointer_cast(shared_from_this()), m_draw_first); } void NodeStrokePreview::draw() { ShaderManager::use(kShader::Texture); ShaderManager::u_mat4(kShaderUniform::MVP, m_mvp); ShaderManager::u_int(kShaderUniform::Tex, 0); m_tex_preview.bind(); m_sampler_linear.bind(0); m_plane.draw_fill(); m_sampler_linear.unbind(); m_tex_preview.unbind(); } void NodeStrokePreview::handle_resize(glm::vec2 old_size, glm::vec2 new_size, float zoom) { if (m_preview_size == (new_size * root()->m_zoom) || !m_brush) return; m_preview_size = new_size * root()->m_zoom; if (m_on_screen) draw_stroke(); } void NodeStrokePreview::destroy() { m_tex_preview.destroy(); Node::destroy(); } void NodeStrokePreview::handle_on_screen(bool old_visibility, bool new_visibility) { parent::handle_on_screen(old_visibility, new_visibility); if (new_visibility) { draw_stroke(); } else { s_queue.Remove(std::static_pointer_cast(shared_from_this())); m_tex_preview.destroy(); } }