Files
panopainter/src/legacy_node_stroke_preview_runtime_services.cpp

644 lines
23 KiB
C++

#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 <array>
#include <cstdint>
#include <stop_token>
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<float, 4> 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<float, 4> 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<float>(request.preview_rtt.getWidth()),
static_cast<float>(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<int>(request.size.x),
.height = static_cast<int>(request.size.y),
});
assert(copy_status.ok());
}
std::vector<LegacyNodeStrokePreviewFrame> plan_legacy_node_stroke_preview_stroke_frames(
const LegacyNodeStrokePreviewStrokeComputeRequest& request)
{
auto samples = const_cast<Stroke&>(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<vertex_t, 4>& 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<vertex_t, 4>&& shapes) -> LegacyNodeStrokePreviewFrame {
return LegacyNodeStrokePreviewFrame {
.col = color,
.flow = flow,
.opacity = opacity,
.shapes = std::move(shapes),
.mixer_rect = mixer_rect,
};
});
}
std::vector<LegacyNodeStrokePreviewFrame> 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<float>(request.preview_rtt.getWidth()),
static_cast<float>(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<float>(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<NodeStrokePreview*>(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<vertex_t, 4>& 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<std::shared_ptr<NodeStrokePreview>> 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<int>(m_size.x), static_cast<int>(m_size.y));
draw_stroke();
}
void NodeStrokePreview::clear_context()
{
NodeBorder::clear_context();
m_tex_preview.destroy();
}
Image NodeStrokePreview::render_to_image()
{
std::lock_guard<std::mutex> _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<std::mutex> 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<std::mutex> _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<NodeStrokePreview>(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<NodeStrokePreview>(shared_from_this()));
m_tex_preview.destroy();
}
}