#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" #include "legacy_canvas_draw_merge_services.h" #include "legacy_canvas_stroke_composite_services.h" #include "legacy_canvas_stroke_execution_services.h" #include "legacy_canvas_stroke_preview_services.h" #include "legacy_canvas_stroke_shader_services.h" #include "legacy_canvas_stroke_services.h" #include "legacy_node_stroke_preview_execution_services.h" #include "legacy_ui_gl_dispatch.h" #include "paint_renderer/compositor.h" #include "renderer_gl/opengl_capabilities.h" #include "util.h" #include #include namespace { pp::renderer::RenderDeviceFeatures stroke_preview_render_device_features() noexcept { return ShaderManager::render_device_features(); } void set_active_texture_unit(std::uint32_t unit_index) { pp::legacy::ui_gl::activate_texture_unit(unit_index, "NodeStrokePreview"); } void unbind_texture_2d() { pp::legacy::ui_gl::unbind_texture_2d("NodeStrokePreview"); } void apply_stroke_preview_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, "NodeStrokePreview"); } pp::renderer::gl::OpenGlViewportRect query_stroke_preview_viewport() { return pp::legacy::ui_gl::query_viewport_rect("NodeStrokePreview"); } std::array query_stroke_preview_clear_color() { return pp::legacy::ui_gl::query_clear_color("NodeStrokePreview"); } void apply_stroke_preview_clear_color(std::array color) { pp::legacy::ui_gl::set_clear_color(color, "NodeStrokePreview"); } void apply_stroke_preview_scissor(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height) { pp::legacy::ui_gl::apply_scissor_rect(x, y, width, height, "NodeStrokePreview"); } void apply_stroke_preview_capability(std::uint32_t state, bool enabled) { pp::legacy::ui_gl::set_capability(state, enabled, "NodeStrokePreview"); } namespace stroke_preview_composite_slots { constexpr std::uint32_t kBackground = 0U; constexpr std::uint32_t kStroke = 1U; constexpr std::uint32_t kDual = 3U; constexpr std::uint32_t kPattern = 4U; } namespace stroke_preview_live_slots { constexpr std::uint32_t kTip = 0U; constexpr std::uint32_t kDestination = 1U; constexpr std::uint32_t kPattern = 2U; constexpr std::uint32_t kMixer = 3U; constexpr std::uint32_t kReservedLinear = 4U; } struct StrokePreviewCompositePassInputs { glm::vec2 resolution; glm::vec2 pattern_scale; const Brush& brush; const pp::paint_renderer::CanvasStrokeCompositePassPlan& composite_pass; Texture2D& background_texture; Texture2D& stroke_texture; Texture2D& dual_texture; Sampler& linear_sampler; Sampler& repeat_sampler; std::function draw_composite; }; pp::panopainter::LegacyNodeStrokePreviewMixPassRequest make_stroke_preview_mix_pass_request( const Brush& brush, glm::vec2 resolution) noexcept { return { .resolution = resolution, .pattern_scale = brush.m_pattern_scale, .pattern_flipx = brush.m_pattern_flipx, .pattern_flipy = brush.m_pattern_flipy, .pattern_invert = brush.m_pattern_invert, .pattern_brightness = brush.m_pattern_brightness, .pattern_contrast = brush.m_pattern_contrast, .pattern_depth = brush.m_pattern_depth, .pattern_rand_offset = brush.m_pattern_rand_offset, .pattern_enabled = brush.m_pattern_enabled, .pattern_eachsample = brush.m_pattern_eachsample, .tip_wet = brush.m_tip_wet, .tip_mix = brush.m_tip_mix, .tip_noise = brush.m_tip_noise, .dual_enabled = brush.m_dual_enabled, .dual_blend_mode = brush.m_dual_blend_mode, .pattern_blend_mode = brush.m_pattern_blend_mode, .dual_opacity = brush.m_dual_opacity, .blend_mode = brush.m_blend_mode, }; } pp::panopainter::LegacyStrokeCompositeUniforms make_stroke_preview_mix_composite_uniforms( const pp::panopainter::LegacyNodeStrokePreviewMixPassPlan::ShaderPlan& shader_plan) noexcept { return { .resolution = shader_plan.resolution, .pattern = { .scale = shader_plan.pattern_scale, .invert = shader_plan.pattern_invert, .brightness = shader_plan.pattern_brightness, .contrast = shader_plan.pattern_contrast, .depth = shader_plan.pattern_depth, .blend_mode = shader_plan.pattern_blend_mode, .offset = shader_plan.pattern_offset, }, .mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f), .layer_alpha = 1.0f, .alpha_lock = false, .mask_enabled = false, .use_fragcoord = false, .blend_mode = shader_plan.blend_mode, .use_dual = shader_plan.use_dual, .dual_blend_mode = shader_plan.dual_blend_mode, .dual_alpha = shader_plan.dual_alpha, .use_pattern = shader_plan.use_pattern, }; } void execute_stroke_preview_final_composite_pass(const StrokePreviewCompositePassInputs& inputs) { pp::panopainter::execute_legacy_stroke_preview_final_composite( [&] { pp::panopainter::setup_legacy_stroke_composite_shader( pp::panopainter::LegacyStrokeCompositeUniforms { .resolution = inputs.resolution, .pattern = { .scale = inputs.pattern_scale, .invert = static_cast(inputs.brush.m_pattern_invert), .brightness = inputs.brush.m_pattern_brightness, .contrast = inputs.brush.m_pattern_contrast, .depth = inputs.brush.m_pattern_depth, .blend_mode = inputs.composite_pass.pattern_blend_mode, .offset = glm::vec2(inputs.brush.m_pattern_rand_offset ? 0.5f : 0.0f), }, .mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f), .layer_alpha = 1.0f, .alpha_lock = false, .mask_enabled = false, .use_fragcoord = false, .blend_mode = inputs.brush.m_blend_mode, .use_dual = inputs.composite_pass.use_dual, .dual_blend_mode = inputs.composite_pass.dual_blend_mode, .dual_alpha = inputs.composite_pass.dual_alpha, .use_pattern = inputs.composite_pass.use_pattern, }); }, [&] { inputs.linear_sampler.bind(stroke_preview_composite_slots::kBackground); inputs.linear_sampler.bind(stroke_preview_composite_slots::kStroke); inputs.linear_sampler.bind(2U); inputs.linear_sampler.bind(stroke_preview_composite_slots::kDual); inputs.repeat_sampler.bind(stroke_preview_composite_slots::kPattern); }, [&] { set_active_texture_unit(stroke_preview_composite_slots::kBackground); inputs.background_texture.bind(); set_active_texture_unit(stroke_preview_composite_slots::kStroke); inputs.stroke_texture.bind(); set_active_texture_unit(stroke_preview_composite_slots::kDual); inputs.dual_texture.bind(); set_active_texture_unit(stroke_preview_composite_slots::kPattern); inputs.brush.m_pattern_texture ? inputs.brush.m_pattern_texture->bind() : unbind_texture_2d(); }, [&] { inputs.draw_composite(); }); } void copy_stroke_preview_framebuffer_to_texture( Texture2D& texture, glm::vec2 size, std::uint32_t texture_unit) { set_active_texture_unit(texture_unit); texture.bind(); copy_framebuffer_to_texture_2d( 0, 0, 0, 0, static_cast(size.x), static_cast(size.y)); } void bind_stroke_preview_live_samplers( Sampler& mipmap_sampler, Sampler& linear_sampler, Sampler& repeat_sampler) { mipmap_sampler.bind(stroke_preview_live_slots::kTip); linear_sampler.bind(stroke_preview_live_slots::kDestination); repeat_sampler.bind(stroke_preview_live_slots::kPattern); linear_sampler.bind(stroke_preview_live_slots::kMixer); linear_sampler.bind(stroke_preview_live_slots::kReservedLinear); } void bind_stroke_preview_dual_pass_textures(const Brush& dual_brush) { set_active_texture_unit(stroke_preview_live_slots::kTip); dual_brush.m_tip_texture ? dual_brush.m_tip_texture->bind() : unbind_texture_2d(); } void bind_stroke_preview_main_pass_textures( const Brush& brush, Texture2D& stroke_destination_texture, RTT& mixer_rtt, bool copy_stroke_destination, bool uses_mixer) { set_active_texture_unit(stroke_preview_live_slots::kTip); brush.m_tip_texture ? brush.m_tip_texture->bind() : unbind_texture_2d(); if (copy_stroke_destination) { set_active_texture_unit(stroke_preview_live_slots::kDestination); stroke_destination_texture.bind(); } set_active_texture_unit(stroke_preview_live_slots::kPattern); brush.m_pattern_texture ? brush.m_pattern_texture->bind() : unbind_texture_2d(); set_active_texture_unit(stroke_preview_live_slots::kMixer); uses_mixer ? mixer_rtt.bindTexture() : unbind_texture_2d(); } void bind_stroke_preview_destination_texture(Texture2D& texture) { set_active_texture_unit(stroke_preview_live_slots::kDestination); texture.bind(); } void unbind_stroke_preview_destination_texture(Texture2D& texture) { set_active_texture_unit(stroke_preview_live_slots::kDestination); texture.unbind(); } void copy_stroke_preview_destination_texture_region( int src_x, int src_y, int dst_x, int dst_y, int width, int height) { copy_framebuffer_to_texture_2d(src_x, src_y, dst_x, dst_y, width, height); } std::array make_stroke_preview_sample_points( const std::array& vertices) { return { pp::paint_renderer::CanvasStrokePoint { .x = vertices[0].pos.x, .y = vertices[0].pos.y }, pp::paint_renderer::CanvasStrokePoint { .x = vertices[1].pos.x, .y = vertices[1].pos.y }, pp::paint_renderer::CanvasStrokePoint { .x = vertices[2].pos.x, .y = vertices[2].pos.y }, pp::paint_renderer::CanvasStrokePoint { .x = vertices[3].pos.x, .y = vertices[3].pos.y }, }; } void upload_stroke_preview_brush_vertices(DynamicShape& brush_shape, std::span vertices) { brush_shape.update_vertices( const_cast(vertices.data()), static_cast(vertices.size())); } glm::vec4 execute_stroke_preview_sample_pass( std::array& vertices, glm::vec2 target_size, Texture2D& blend_texture, DynamicShape& brush_shape, bool copy_stroke_destination) { const auto sample_points = make_stroke_preview_sample_points(vertices); const auto result = pp::panopainter::execute_legacy_canvas_stroke_sample( pp::panopainter::LegacyStrokeSampleExecutionRequest { .context = "NodeStrokePreview::stroke_draw_samples", .target_size = target_size, .vertices = vertices, .sample_points = sample_points, .copy_stroke_destination = copy_stroke_destination, .bind_destination_texture = [&] { bind_stroke_preview_destination_texture(blend_texture); }, .copy_framebuffer_to_destination_texture = []( int src_x, int src_y, int dst_x, int dst_y, int width, int height) { copy_stroke_preview_destination_texture_region( src_x, src_y, dst_x, dst_y, width, height); }, .unbind_destination_texture = [&] { unbind_stroke_preview_destination_texture(blend_texture); }, .upload_brush_vertices = [&](std::span brush_vertices) { upload_stroke_preview_brush_vertices(brush_shape, brush_vertices); }, .draw_brush_shape = [&] { brush_shape.draw_fill(); }, }); return result.dirty_bounds; } void execute_stroke_preview_background_capture_pass( glm::vec2 size, bool colorize, Texture2D& background_texture, const std::function& draw_checkerboard) { pp::panopainter::execute_legacy_stroke_preview_background_capture( [&] { const float aspect = size.x / size.y; pp::panopainter::setup_legacy_canvas_draw_merge_checkerboard_shader( pp::panopainter::LegacyCanvasDrawMergeCheckerboardUniforms { .mvp = glm::ortho(-.5f, .5f, -.5f / aspect, .5f / aspect, -1.f, 1.f), .colorize = colorize, }); }, [&] { draw_checkerboard(); }, [&] { background_texture.bind(); }, []( int src_x, int src_y, int dst_x, int dst_y, int width, int height) { copy_framebuffer_to_texture_2d(src_x, src_y, dst_x, dst_y, width, height); }, pp::panopainter::LegacyStrokePreviewCopySize { .width = static_cast(size.x), .height = static_cast(size.y), }); } void copy_stroke_preview_result_to_texture(Texture2D& preview_texture, glm::vec2 size) { pp::panopainter::copy_legacy_stroke_preview_texture( [&] { preview_texture.bind(); }, []( int src_x, int src_y, int dst_x, int dst_y, int width, int height) { copy_framebuffer_to_texture_2d(src_x, src_y, dst_x, dst_y, width, height); }, pp::panopainter::LegacyStrokePreviewCopySize { .width = static_cast(size.x), .height = static_cast(size.y), }); } } 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(static_cast(m_size.x), static_cast(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) { const auto& b = m_brush; const auto mix_pass = pp::panopainter::plan_legacy_node_stroke_preview_mix_pass( make_stroke_preview_mix_pass_request(*b, m_size)); gl_state gl; [[maybe_unused]] const bool mix_ok = pp::panopainter::execute_legacy_node_stroke_preview_mix_pass( pp::panopainter::LegacyNodeStrokePreviewMixExecutionRequest { .shader = mix_pass.shader, .mixer_width = m_rtt_mixer.getWidth(), .mixer_height = m_rtt_mixer.getHeight(), .scissor_x = static_cast(bb_min.x), .scissor_y = static_cast(bb_min.y), .scissor_width = static_cast(bb_sz.x), .scissor_height = static_cast(bb_sz.y), .save_state = [&] { gl.save(); }, .setup_mix_shader = [&](const pp::panopainter::LegacyNodeStrokePreviewMixPassPlan::ShaderPlan& shader_plan) { pp::panopainter::setup_legacy_stroke_composite_shader( make_stroke_preview_mix_composite_uniforms(shader_plan)); }, .bind_mixer_framebuffer = [&] { m_rtt_mixer.bindFramebuffer(); }, .configure_mix_target_state = [&]( int mixer_width, int mixer_height, int scissor_x, int scissor_y, int scissor_width, int scissor_height) { apply_stroke_preview_viewport(0, 0, mixer_width, mixer_height); apply_stroke_preview_capability(pp::renderer::gl::depth_test_state(), false); apply_stroke_preview_capability(pp::renderer::gl::scissor_test_state(), true); apply_stroke_preview_capability(pp::renderer::gl::blend_state(), false); apply_stroke_preview_scissor( scissor_x, scissor_y, scissor_width, scissor_height); }, .bind_mix_inputs = [&] { m_sampler_linear.bind(stroke_preview_composite_slots::kBackground); set_active_texture_unit(stroke_preview_composite_slots::kBackground); m_tex_background.bind(); set_active_texture_unit(stroke_preview_composite_slots::kStroke); m_rtt.bindTexture(); set_active_texture_unit(stroke_preview_composite_slots::kDual); m_tex_dual.bind(); set_active_texture_unit(stroke_preview_composite_slots::kPattern); b->m_pattern_texture ? b->m_pattern_texture->bind() : unbind_texture_2d(); }, .draw_mix = [&] { m_plane.draw_fill(); }, .unbind_mixer_framebuffer = [&] { m_rtt_mixer.unbindFramebuffer(); }, .restore_state = [&] { gl.restore(); }, }); assert(mix_ok); } glm::vec4 NodeStrokePreview::stroke_draw_samples( std::array& P, Texture2D& blend_tex, bool copy_stroke_destination) { const glm::vec2 size = { m_rtt.getWidth(), m_rtt.getHeight() }; return execute_stroke_preview_sample_pass( P, size, blend_tex, m_brush_shape, copy_stroke_destination); } std::vector NodeStrokePreview::stroke_draw_compute(Stroke& stroke, float zoom) const { auto samples = stroke.compute_samples(); StrokeSample previous_sample = stroke.m_prev_sample; previous_sample.size *= zoom; for (auto& sample : samples) { sample.size *= zoom; } return pp::panopainter::plan_legacy_canvas_stroke_frames( pp::panopainter::LegacyCanvasStrokeComputeRequest { .previous_sample = previous_sample, .samples = samples, .zoom = 1.0f, .mixer_size = glm::vec2(m_rtt.getWidth(), m_rtt.getHeight()), }, []( std::array& brush_quad, bool /*project_3d*/, glm::mat4 /*model_view*/) { return brush_quad; }, []( glm::vec4 mixer_rect, glm::vec4 color, float flow, float opacity, std::array&& shapes) { return StrokeFrame { .col = color, .flow = flow, .opacity = opacity, .shapes = std::move(shapes), .m_mixer_rect = mixer_rect, }; }); } void NodeStrokePreview::draw_stroke_immediate() { if (m_size.x == 0 || m_size.y == 0) return; const auto vp = query_stroke_preview_viewport(); const auto cc = query_stroke_preview_clear_color(); float zoom = root()->m_zoom; glm::vec2 size = { m_rtt.getWidth(), m_rtt.getHeight() }; const auto stroke_setup = pp::panopainter::plan_legacy_node_stroke_preview_stroke_setup( pp::panopainter::LegacyNodeStrokePreviewStrokeSetupRequest { .preview_size = m_size, .zoom = zoom, .brush_tip_size = m_brush->m_tip_size, .stroke_max_size_override = m_max_size, .pad_override = m_pad_override, .tip_size_pressure = m_brush->m_tip_size_pressure, .dual_enabled = m_brush->m_dual_enabled, .dual_size = m_brush->m_dual_size, .pattern_scale = m_brush->m_pattern_scale, .pattern_flipx = m_brush->m_pattern_flipx, .pattern_flipy = m_brush->m_pattern_flipy, }); glm::mat4 ortho_proj = glm::ortho(0, size.x, 0, size.y, -1, 1); apply_stroke_preview_viewport(0, 0, m_rtt.getWidth(), m_rtt.getHeight()); m_rtt.bindFramebuffer(); m_rtt.clear(); bind_stroke_preview_live_samplers( m_sampler_mipmap, m_sampler_linear, m_sampler_linear_repeat); const auto& b = m_brush; Stroke m_stroke; Stroke m_dual_stroke; m_stroke.m_filter_points = false; m_stroke.m_max_size = stroke_setup.stroke_max_size; 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 (stroke_setup.dual_enabled) { m_dual_stroke.m_filter_points = false; m_dual_stroke.m_max_size = stroke_setup.dual_stroke_max_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); } for (const auto& point : stroke_setup.points) { m_stroke.add_point(point.position, point.pressure); if (stroke_setup.dual_enabled) m_dual_stroke.add_point(point.position, point.pressure); } const glm::vec2 patt_scale = stroke_setup.pattern_scale; apply_stroke_preview_capability(pp::renderer::gl::blend_state(), false); const auto pass_orchestration = pp::panopainter::plan_legacy_node_stroke_preview_pass_orchestration( pp::panopainter::LegacyNodeStrokePreviewPassOrchestrationRequest { .features = stroke_preview_render_device_features(), .preview_size = size, .pattern_scale = b->m_pattern_scale, .pattern_flipx = b->m_pattern_flipx, .pattern_flipy = b->m_pattern_flipy, .pattern_invert = b->m_pattern_invert, .pattern_brightness = b->m_pattern_brightness, .pattern_contrast = b->m_pattern_contrast, .pattern_depth = b->m_pattern_depth, .pattern_rand_offset = b->m_pattern_rand_offset, .pattern_enabled = b->m_pattern_enabled, .pattern_eachsample = b->m_pattern_eachsample, .tip_mix = b->m_tip_mix, .tip_wet = b->m_tip_wet, .tip_noise = b->m_tip_noise, .dual_enabled = b->m_dual_enabled, .dual_blend_mode = b->m_dual_blend_mode, .dual_opacity = b->m_dual_opacity, .pattern_blend_mode = b->m_pattern_blend_mode, .blend_mode = b->m_blend_mode, .mvp = ortho_proj, }); const bool copy_stroke_destination = pass_orchestration.copy_stroke_destination; const auto& material = pass_orchestration.material; pp::panopainter::setup_legacy_stroke_shader(pass_orchestration.stroke_shader); const bool sequence_ok = pp::panopainter::execute_legacy_node_stroke_preview_pass_sequence( pp::panopainter::LegacyNodeStrokePreviewPassSequenceRequest { .dual_pass_enabled = material.dual_pass.enabled, .prepare_dual_pass = [&] { pp::panopainter::setup_legacy_stroke_dual_shader(material.dual_pass.uses_pattern); bind_stroke_preview_dual_pass_textures(*dual_brush); }, .execute_dual_pass = [&] { pp::panopainter::execute_legacy_stroke_preview_live_pass( [&] { m_rtt.clear(); }, [&] { return stroke_draw_compute(m_dual_stroke, zoom); }, [](auto& frame) { frame.col = { 0, 0, 0, 1 }; }, [&](auto& frame) { pp::panopainter::use_legacy_stroke_shader(); pp::panopainter::apply_legacy_stroke_sample_uniforms( pp::panopainter::LegacyStrokeSampleUniforms { .color = frame.col, .alpha = frame.flow, .opacity = frame.opacity, }); }, [&](auto& frame) { /*auto rect =*/ stroke_draw_samples(frame.shapes, m_tex_dual, copy_stroke_destination); }, [&] { copy_stroke_preview_framebuffer_to_texture( m_tex_dual, size, stroke_preview_composite_slots::kStroke); }); }, .capture_background = [&] { execute_stroke_preview_background_capture_pass( size, pass_orchestration.background_colorize, m_tex_background, [&] { m_plane.draw_fill(); }); }, .prepare_main_pass = [&] { pp::panopainter::use_legacy_stroke_shader(); pp::panopainter::apply_legacy_stroke_blend_uniforms( material.stroke_pass.uses_pattern, b->m_tip_mix, b->m_tip_wet, b->m_tip_noise); bind_stroke_preview_main_pass_textures( *b, m_tex, m_rtt_mixer, copy_stroke_destination, pass_orchestration.composite.uses_mixer); }, .execute_main_pass = [&] { pp::panopainter::execute_legacy_stroke_preview_live_pass( [&] { m_rtt.clear(); }, [&] { return stroke_draw_compute(m_stroke, zoom); }, [&](auto& frame) { if (b->m_tip_mix > 0.f) { stroke_draw_mix(xy(frame.m_mixer_rect), zw(frame.m_mixer_rect)); } frame.col = b->m_blend_mode != 0 || b->m_tip_mix > 0.f ? glm::vec4 { .7, .4, .1, 1 } : glm::vec4 { 0, 0, 0, 1 }; frame.flow = glm::max(frame.flow, m_min_flow); }, [&](auto& frame) { pp::panopainter::use_legacy_stroke_shader(); pp::panopainter::apply_legacy_stroke_sample_uniforms( pp::panopainter::LegacyStrokeSampleUniforms { .color = frame.col, .alpha = frame.flow, .opacity = frame.opacity, }); }, [&](auto& frame) { /*auto rect =*/ stroke_draw_samples(frame.shapes, m_tex, copy_stroke_destination); }, [&] { copy_stroke_preview_framebuffer_to_texture( m_tex, size, stroke_preview_composite_slots::kStroke); }); }, .finish_main_pass = [&] { set_active_texture_unit(stroke_preview_live_slots::kMixer); m_rtt_mixer.unbindTexture(); }, .execute_final_composite = [&] { execute_stroke_preview_final_composite_pass( StrokePreviewCompositePassInputs { .resolution = size, .pattern_scale = patt_scale, .brush = *b, .composite_pass = material.composite_pass, .background_texture = m_tex_background, .stroke_texture = m_tex, .dual_texture = m_tex_dual, .linear_sampler = m_sampler_linear, .repeat_sampler = m_sampler_linear_repeat, .draw_composite = [&] { m_plane.draw_fill(); }, }); }, .copy_preview_result = [&] { copy_stroke_preview_result_to_texture(m_tex_preview, size); }, }); assert(sequence_ok); m_rtt.unbindFramebuffer(); apply_stroke_preview_viewport(vp.x, vp.y, vp.width, vp.height); apply_stroke_preview_clear_color(cc); } 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( pp::renderer::gl::linear_texture_filter(), pp::renderer::gl::repeat_texture_wrap()); m_sampler_mipmap.create(); m_sampler_mipmap.set_filter( pp::renderer::gl::linear_mipmap_linear_texture_filter(), pp::renderer::gl::linear_texture_filter()); 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() { pp::panopainter::setup_legacy_canvas_draw_merge_texture_shader( pp::panopainter::LegacyCanvasDrawMergeTextureUniforms { .mvp = m_mvp, .texture_slot = 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(); } }