#include "pch.h" #include "legacy_node_stroke_preview_runtime_services.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_node_stroke_preview_sample_services.h" #include "legacy_ui_gl_dispatch.h" #include "paint_renderer/compositor.h" #include "renderer_gl/opengl_capabilities.h" #include "util.h" #include #include #include namespace pp::panopainter { pp::renderer::RenderDeviceFeatures stroke_preview_render_device_features() noexcept { return ShaderManager::render_device_features(); } void apply_legacy_node_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_legacy_node_stroke_preview_viewport() { return pp::legacy::ui_gl::query_viewport_rect("NodeStrokePreview"); } std::array query_legacy_node_stroke_preview_clear_color() { return pp::legacy::ui_gl::query_clear_color("NodeStrokePreview"); } void apply_legacy_node_stroke_preview_clear_color(std::array color) { pp::legacy::ui_gl::set_clear_color(color, "NodeStrokePreview"); } void apply_legacy_node_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_legacy_node_stroke_preview_capability(std::uint32_t state, bool enabled) { pp::legacy::ui_gl::set_capability(state, enabled, "NodeStrokePreview"); } namespace { 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; } 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"); } } // namespace void bind_legacy_node_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_legacy_node_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_legacy_node_stroke_preview_pattern_texture(const Brush& brush) { set_active_texture_unit(stroke_preview_live_slots::kPattern); brush.m_pattern_texture ? brush.m_pattern_texture->bind() : unbind_texture_2d(); } void bind_legacy_node_stroke_preview_destination_texture(Texture2D& texture) { set_active_texture_unit(stroke_preview_live_slots::kDestination); texture.bind(); } void unbind_legacy_node_stroke_preview_destination_texture(Texture2D& texture) { set_active_texture_unit(stroke_preview_live_slots::kDestination); texture.unbind(); } void unbind_legacy_node_stroke_preview_mixer_texture(RTT& mixer_rtt) { set_active_texture_unit(stroke_preview_live_slots::kMixer); mixer_rtt.unbindTexture(); } void copy_legacy_node_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); } bool execute_legacy_node_stroke_preview_immediate_draw( const LegacyNodeStrokePreviewImmediateDrawRequest& request) { if (!request.brush || !request.prepare_render_target || !request.finish_render_target || !request.compute_frames || !request.draw_samples || !request.draw_mix || !request.draw_checkerboard || !request.draw_composite) { return false; } if (!ensure_legacy_node_stroke_preview_render_targets( LegacyNodeStrokePreviewRenderTargetSetup { .preview_rtt = request.preview_rtt, .preview_rtt_mixer = request.preview_rtt_mixer, .preview_stroke_texture = request.preview_stroke_texture, .preview_dual_texture = request.preview_dual_texture, .preview_background_texture = request.preview_background_texture, .preview_image_texture = request.preview_image_texture, .size = request.preview_size, })) { return false; } const auto vp = query_legacy_node_stroke_preview_viewport(); const auto cc = query_legacy_node_stroke_preview_clear_color(); const glm::vec2 size = { static_cast(request.preview_rtt.getWidth()), static_cast(request.preview_rtt.getHeight()), }; const bool sequence_ok = execute_legacy_node_stroke_preview_immediate_runtime( LegacyNodeStrokePreviewImmediateRuntimeRequest { .brush = request.brush, .preview_size = request.preview_size, .zoom = request.zoom, .min_flow = request.min_flow, .stroke_max_size_override = request.stroke_max_size_override, .pad_override = request.pad_override, .camera_fov = request.camera_fov, .camera_rot = request.camera_rot, .render_device_features = request.render_device_features, .preview_rtt = request.preview_rtt, .preview_rtt_mixer = request.preview_rtt_mixer, .preview_stroke_texture = request.preview_stroke_texture, .preview_dual_texture = request.preview_dual_texture, .preview_background_texture = request.preview_background_texture, .preview_image_texture = request.preview_image_texture, .linear_sampler = request.linear_sampler, .repeat_sampler = request.repeat_sampler, .prepare_render_target = request.prepare_render_target, .finish_render_target = request.finish_render_target, .set_blend_enabled = [](bool enabled) { apply_legacy_node_stroke_preview_capability(pp::renderer::gl::blend_state(), enabled); }, .setup_stroke_shader = [](const LegacyStrokeShaderSetupUniforms& uniforms) { setup_legacy_stroke_shader(uniforms); }, .bind_dual_pass_textures = [](const Brush& dual_brush) { bind_legacy_node_stroke_preview_dual_pass_textures(dual_brush); }, .capture_background = [&](bool colorize) { execute_legacy_node_stroke_preview_background_capture_pass( LegacyNodeStrokePreviewBackgroundCaptureRequest { .size = size, .colorize = colorize, .background_texture = request.preview_background_texture, .draw_checkerboard = [&] { request.draw_checkerboard(); }, }); }, .compute_frames = request.compute_frames, .draw_samples = request.draw_samples, .draw_mix = request.draw_mix, .unbind_mixer_texture = [&] { unbind_legacy_node_stroke_preview_mixer_texture(request.preview_rtt_mixer); }, .bind_pattern_texture = [&] { bind_legacy_node_stroke_preview_pattern_texture(*request.brush); }, .draw_composite = request.draw_composite, }); apply_legacy_node_stroke_preview_viewport(vp.x, vp.y, vp.width, vp.height); apply_legacy_node_stroke_preview_clear_color(cc); return sequence_ok; } void execute_legacy_node_stroke_preview_background_capture_pass( const LegacyNodeStrokePreviewBackgroundCaptureRequest& request) { if (!request.draw_checkerboard) { return; } const float aspect = request.size.x / request.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 = request.colorize, }); request.draw_checkerboard(); const auto copy_status = pp::paint_renderer::copy_stroke_preview_result_to_texture( [&] { request.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::paint_renderer::StrokePreviewCopySize { .width = static_cast(request.size.x), .height = static_cast(request.size.y), }); assert(copy_status.ok()); } std::vector plan_legacy_node_stroke_preview_stroke_frames( const LegacyNodeStrokePreviewStrokeComputeRequest& request) { auto samples = const_cast(request.stroke).compute_samples(); StrokeSample previous_sample = request.stroke.m_prev_sample; previous_sample.size *= request.zoom; for (auto& sample : samples) { sample.size *= request.zoom; } return pp::panopainter::plan_legacy_canvas_stroke_frames( pp::panopainter::LegacyCanvasStrokeComputeRequest { .previous_sample = previous_sample, .samples = samples, .zoom = 1.0f, .mixer_size = request.mixer_size, }, []( 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) -> LegacyNodeStrokePreviewFrame { return LegacyNodeStrokePreviewFrame { .col = color, .flow = flow, .opacity = opacity, .shapes = std::move(shapes), .mixer_rect = mixer_rect, }; }); } std::vector plan_legacy_node_stroke_preview_stroke_frames( const Stroke& stroke, float zoom, glm::vec2 mixer_size) { return plan_legacy_node_stroke_preview_stroke_frames( LegacyNodeStrokePreviewStrokeComputeRequest { .stroke = stroke, .zoom = zoom, .mixer_size = mixer_size, }); } bool execute_legacy_node_stroke_preview_immediate_runtime( const LegacyNodeStrokePreviewImmediateRuntimeRequest& request) { if (!request.brush || !request.prepare_render_target || !request.finish_render_target || !request.set_blend_enabled || !request.setup_stroke_shader) { return false; } const glm::vec2 render_target_size = { static_cast(request.preview_rtt.getWidth()), static_cast(request.preview_rtt.getHeight()), }; const auto stroke_setup = plan_legacy_node_stroke_preview_stroke_setup( LegacyNodeStrokePreviewStrokeSetupRequest { .preview_size = request.preview_size, .zoom = request.zoom, .brush_tip_size = request.brush->m_tip_size, .stroke_max_size_override = request.stroke_max_size_override, .pad_override = request.pad_override, .tip_size_pressure = request.brush->m_tip_size_pressure, .dual_enabled = request.brush->m_dual_enabled, .dual_size = request.brush->m_dual_size, .pattern_scale = request.brush->m_pattern_scale, .pattern_flipx = request.brush->m_pattern_flipx, .pattern_flipy = request.brush->m_pattern_flipy, }); const auto prepared_strokes = prepare_legacy_node_stroke_preview_strokes( request.brush, stroke_setup, request.camera_fov, request.camera_rot); const glm::mat4 ortho_proj = glm::ortho(0, render_target_size.x, 0, render_target_size.y, -1, 1); request.prepare_render_target(); request.set_blend_enabled(false); const auto pass_orchestration = plan_legacy_node_stroke_preview_pass_orchestration( make_legacy_node_stroke_preview_pass_orchestration_request( request.render_device_features, render_target_size, *request.brush, ortho_proj)); request.setup_stroke_shader(pass_orchestration.stroke_shader); const bool sequence_ok = execute_legacy_node_stroke_preview_live_render_passes( LegacyNodeStrokePreviewLiveRenderRequest { .brush = *request.brush, .pass_orchestration = pass_orchestration, .prepared_strokes = prepared_strokes, .stroke_texture = request.preview_stroke_texture, .mixer_rtt = request.preview_rtt_mixer, .render_target = request.preview_rtt, .background_texture = request.preview_background_texture, .dual_texture = request.preview_dual_texture, .preview_texture = request.preview_image_texture, .linear_sampler = request.linear_sampler, .repeat_sampler = request.repeat_sampler, .zoom = request.zoom, .min_flow = request.min_flow, .copy_stroke_destination = pass_orchestration.copy_stroke_destination, .size = render_target_size, .bind_dual_pass_textures = request.bind_dual_pass_textures, .capture_background = request.capture_background, .compute_frames = request.compute_frames, .draw_samples = request.draw_samples, .draw_mix = request.draw_mix, .unbind_mixer_texture = request.unbind_mixer_texture, .bind_pattern_texture = request.bind_pattern_texture, .draw_composite = request.draw_composite, }); request.finish_render_target(); return sequence_ok; } void initialize_legacy_node_stroke_preview_clone(Node* dest) { static_cast(dest)->init_controls(); } bool execute_legacy_node_stroke_preview_immediate_draw(NodeStrokePreview& preview) { if (preview.m_size.x == 0 || preview.m_size.y == 0) { return false; } const bool sequence_ok = pp::panopainter::execute_legacy_node_stroke_preview_immediate_draw( pp::panopainter::LegacyNodeStrokePreviewImmediateDrawRequest { .brush = preview.m_brush, .preview_size = preview.m_size, .zoom = preview.root()->m_zoom, .min_flow = preview.m_min_flow, .stroke_max_size_override = preview.m_max_size, .pad_override = preview.m_pad_override, .camera_fov = Canvas::I->m_cam_fov, .camera_rot = Canvas::I->m_cam_rot, .render_device_features = pp::panopainter::stroke_preview_render_device_features(), .preview_rtt = preview.m_rtt, .preview_rtt_mixer = preview.m_rtt_mixer, .preview_stroke_texture = preview.m_tex, .preview_dual_texture = preview.m_tex_dual, .preview_background_texture = preview.m_tex_background, .preview_image_texture = preview.m_tex_preview, .linear_sampler = preview.m_sampler_linear, .repeat_sampler = preview.m_sampler_linear_repeat, .prepare_render_target = [&] { pp::panopainter::apply_legacy_node_stroke_preview_viewport( 0, 0, preview.m_rtt.getWidth(), preview.m_rtt.getHeight()); preview.m_rtt.bindFramebuffer(); preview.m_rtt.clear(); pp::panopainter::bind_legacy_node_stroke_preview_live_samplers( preview.m_sampler_mipmap, preview.m_sampler_linear, preview.m_sampler_linear_repeat); }, .finish_render_target = [&] { preview.m_rtt.unbindFramebuffer(); }, .compute_frames = [&](const Stroke& stroke, float frame_zoom) { return plan_legacy_node_stroke_preview_stroke_frames( stroke, frame_zoom, glm::vec2(preview.m_rtt.getWidth(), preview.m_rtt.getHeight())); }, .draw_samples = [&](std::array& shapes, Texture2D& texture, bool copy_stroke_destination) { return pp::panopainter::execute_legacy_node_stroke_preview_sample_pass( preview.m_rtt, shapes, preview.m_brush_shape, texture, copy_stroke_destination); }, .draw_mix = [&](const glm::vec2& bb_min, const glm::vec2& bb_sz) { pp::panopainter::execute_legacy_node_stroke_preview_mix_pass( *preview.m_brush, preview.m_size, preview.m_rtt_mixer, bb_min, bb_sz, preview.m_sampler_linear, preview.m_tex_background, preview.m_tex, preview.m_tex_dual, [&] { preview.m_plane.draw_fill(); }); }, .draw_checkerboard = [&] { preview.m_plane.draw_fill(); }, .draw_composite = [&] { preview.m_plane.draw_fill(); }, }); assert(sequence_ok); return sequence_ok; } } // namespace pp::panopainter std::atomic_int NodeStrokePreview::s_instances{ 0 }; std::atomic_bool NodeStrokePreview::s_running{ false }; std::mutex NodeStrokePreview::s_render_mutex; 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; std::jthread NodeStrokePreview::s_renderer; void NodeStrokePreview::terminate_renderer() { if (!s_renderer.joinable()) return; s_running = false; s_renderer.request_stop(); s_queue.UnlockGetters(); s_renderer.join(); } void NodeStrokePreview::empty_queue() { s_queue.q.clear(); } 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(); } Image NodeStrokePreview::render_to_image() { std::lock_guard _lock(s_render_mutex); App::I->render_task([this] { draw_stroke_immediate(); }); return m_tex_preview.get_image(); } void NodeStrokePreview::draw_stroke() { if (m_size.x == 0 || m_size.y == 0) return; std::unique_lock queue_lock(s_queue.mutex); if (!s_renderer.joinable()) { s_running = true; s_renderer = std::jthread([](std::stop_token stop_token) { 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 && !stop_token.stop_requested()) { 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(); 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_running = false; }); } queue_lock.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(); } }