Thin retained app and render runtime seams

This commit is contained in:
2026-06-17 18:33:36 +02:00
parent 04a1c5d0b1
commit dd638e5af4
12 changed files with 344 additions and 132 deletions

View File

@@ -88,6 +88,7 @@ set(PP_LEGACY_APP_SOURCES
src/legacy_canvas_mode_fill.cpp
src/legacy_canvas_mode_pen_line.cpp
src/legacy_canvas_mode_helpers.cpp
src/legacy_document_image_import_services.cpp
src/legacy_canvas_mode_helpers.h
src/legacy_canvas_mode_mask.cpp
src/legacy_canvas_mode_transform.cpp
@@ -101,6 +102,7 @@ set(PP_LEGACY_APP_SOURCES
src/legacy_preference_storage.h
src/legacy_document_canvas_services.cpp
src/legacy_document_canvas_services.h
src/legacy_document_image_import_services.h
src/legacy_document_layer_services.cpp
src/legacy_document_layer_services.h
src/legacy_history_services.cpp

View File

@@ -18,6 +18,24 @@ agent or engineer to remove them without reconstructing context from chat.
## Reductions
- 2026-06-17: `DEBT-0036` was narrowed again. Retained `Shape`,
`src/shader.cpp`, and `src/font.cpp` now queue render-thread create/update/
destroy work through `src/renderer_gl/render_runtime_dispatch.h` instead of
calling `App::I` directly, and `Shader` render-thread entry points now assert
that explicit runtime contract while shader-program and mesh ownership remain
retained.
- 2026-06-17: `DEBT-0036` was narrowed again. `NodeStrokePreview` no longer
owns static worker-thread, queue, or render-mutex state directly;
`src/legacy_node_stroke_preview_runtime_services.*` now owns the retained
preview worker lifecycle and queue helpers, leaving the node as a thinner
adapter while the retained preview runtime still reaches app/render helpers
internally.
- 2026-06-17: `DEBT-0029` was narrowed again.
`LegacyFileMenuServices::pick_image_for_import()` in
`src/legacy_app_shell_services.cpp` now delegates retained import execution
to `src/legacy_document_image_import_services.*`, so the File-menu adapter no
longer defines the live `DocumentImageImportServices` implementation inline;
legacy `Image` loading and retained canvas/import-mode execution still remain.
- 2026-06-17: `DEBT-0003` was narrowed again. `src/app_runtime.cpp` and
`src/app_runtime.h` now synchronize render/UI worker running state, reject
cross-thread work once those workers stop, and drain queued work through

View File

@@ -53,8 +53,9 @@ Key facts:
- `Canvas::I` still appears hundreds of times in retained canvas modes, panels,
and workflow bridges.
- Raw `Node*` and callback captures remain a dominant UI lifetime risk.
- `RTT`, `Texture2D`, `Shape`, `Shader`, `Font`, and `CanvasLayer` still route
render work through `App::I` queues.
- `CanvasLayer` and retained stroke-preview/runtime draw paths still depend on
legacy render/runtime helpers, but `RTT`, `Texture2D`, `Shape`, `Shader`,
and `TextMesh` no longer call `App::I` directly for queueing.
- `AppRuntime` now owns synchronized running flags plus explicit post/reject,
same-thread execution, and queue-drain behavior, but broader singleton reach
and app-shell ownership remain.

View File

@@ -3,6 +3,7 @@
#include "font.h"
#include "legacy_gl_mesh_dispatch.h"
#include "legacy_ui_gl_dispatch.h"
#include "renderer_gl/render_runtime_dispatch.h"
#include "shader.h"
#include "asset.h"
#include "util.h"
@@ -202,7 +203,7 @@ std::vector<TextMesh::Token> TextMesh::tokenize(const std::string& s, const Font
bool TextMesh::create()
{
App::I->render_task([this]
pp::renderer::gl::render_runtime_dispatch().render_task([this]
{
const auto mesh = pp::legacy::gl_mesh::create_mesh_objects(
pp::renderer::gl::OpenGlMeshUpload {
@@ -305,7 +306,7 @@ void TextMesh::update(const std::string& text, const std::string& font, int size
cur_box -= glm::vec4(xy(f.bounds), 0, 0);
bb = bbmax - bbmin;
font_array_count = (int)idx.size();
App::I->render_task([&]
pp::renderer::gl::render_runtime_dispatch().render_task([&]
{
(void)pp::legacy::gl_mesh::upload_buffer_data(
pp::renderer::gl::OpenGlBufferUpload {

View File

@@ -6,6 +6,7 @@
#include "app_core/document_import.h"
#include "legacy_app_dialog_services.h"
#include "legacy_canvas_view_services.h"
#include "legacy_document_image_import_services.h"
#include "legacy_document_canvas_services.h"
#include "legacy_history_services.h"
#include "legacy_ui_overlay_services.h"
@@ -51,50 +52,23 @@ public:
void pick_image_for_import() override
{
auto* app_ptr = &app_;
app_.pick_image([app_ptr](std::string path) {
app_.pick_image([this](std::string path) {
Image img;
img.load_file(path);
const auto import_plan = pp::app::plan_document_image_import(img.width, img.height);
if (!import_plan)
return;
class LegacyDocumentImageImportServices final : public pp::app::DocumentImageImportServices {
public:
LegacyDocumentImageImportServices(App& app, Image& image) noexcept
: app_(app)
, image_(image)
{
}
void import_equirectangular(std::string_view import_path) override
{
if (Canvas::I)
Canvas::I->import_equirectangular(std::string(import_path));
}
void enter_transform_import(std::string_view) override
{
if (!app_.canvas || !app_.canvas->m_canvas)
return;
auto* mode = static_cast<CanvasModeTransform*>(
app_.canvas->m_canvas->modes[(int)kCanvasMode::Import][0]);
mode->m_action = CanvasModeTransform::ActionType::Import;
mode->m_source_image = std::move(image_);
Canvas::set_mode(kCanvasMode::Import);
}
private:
App& app_;
Image& image_;
};
LegacyDocumentImageImportServices services(*app_ptr, img);
const auto status = pp::app::execute_document_image_import_plan(
auto* canvas = app_.canvas ? app_.canvas->m_canvas.get() : nullptr;
auto* import_mode = canvas
? static_cast<CanvasModeTransform*>(canvas->modes[(int)kCanvasMode::Import][0])
: nullptr;
const auto status = execute_legacy_document_image_import_plan(
import_plan.value(),
path,
services);
canvas,
import_mode,
std::move(img));
if (!status.ok())
LOG("Image import failed: %s", status.message);
});

View File

@@ -0,0 +1,57 @@
#include "pch.h"
#include "legacy_document_image_import_services.h"
#include "canvas.h"
#include "canvas_modes.h"
#include <utility>
namespace pp::panopainter {
namespace {
class LegacyDocumentImageImportServices final : public pp::app::DocumentImageImportServices {
public:
LegacyDocumentImageImportServices(Canvas* canvas, CanvasModeTransform* import_mode, Image image) noexcept
: canvas_(canvas)
, import_mode_(import_mode)
, image_(std::move(image))
{
}
void import_equirectangular(std::string_view import_path) override
{
if (canvas_)
canvas_->import_equirectangular(std::string(import_path));
}
void enter_transform_import(std::string_view) override
{
if (!canvas_ || !import_mode_)
return;
import_mode_->m_action = CanvasModeTransform::ActionType::Import;
import_mode_->m_source_image = std::move(image_);
Canvas::set_mode(kCanvasMode::Import);
}
private:
Canvas* canvas_ = nullptr;
CanvasModeTransform* import_mode_ = nullptr;
Image image_;
};
} // namespace
pp::foundation::Status execute_legacy_document_image_import_plan(
const pp::app::DocumentImageImportPlan& plan,
std::string_view path,
Canvas* canvas,
CanvasModeTransform* import_mode,
Image image)
{
LegacyDocumentImageImportServices services(canvas, import_mode, std::move(image));
return pp::app::execute_document_image_import_plan(plan, path, services);
}
} // namespace pp::panopainter

View File

@@ -0,0 +1,20 @@
#pragma once
#include "app_core/document_import.h"
#include "image.h"
#include <string_view>
class Canvas;
class CanvasModeTransform;
namespace pp::panopainter {
[[nodiscard]] pp::foundation::Status execute_legacy_document_image_import_plan(
const pp::app::DocumentImageImportPlan& plan,
std::string_view path,
Canvas* canvas,
CanvasModeTransform* import_mode,
Image image);
} // namespace pp::panopainter

View File

@@ -19,6 +19,7 @@
#include "paint_renderer/compositor.h"
#include "renderer_gl/opengl_capabilities.h"
#include "util.h"
#include <algorithm>
#include <array>
#include <cstdint>
#include <stop_token>
@@ -477,11 +478,141 @@ bool execute_legacy_node_stroke_preview_immediate_draw(NodeStrokePreview& previe
} // 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;
namespace pp::panopainter {
class NodeStrokePreviewRuntime
{
public:
std::mutex& render_mutex() noexcept
{
return render_mutex_;
}
void start()
{
std::lock_guard<std::mutex> lock(lifecycle_mutex_);
if (worker_.joinable())
return;
worker_ = std::jthread([this](std::stop_token stop_token) {
run(stop_token);
});
}
void stop()
{
std::jthread worker;
{
std::lock_guard<std::mutex> lock(lifecycle_mutex_);
if (!worker_.joinable())
return;
worker = std::move(worker_);
}
worker.request_stop();
queue_.UnlockGetters();
if (worker.joinable())
worker.join();
}
void clear_queue()
{
std::lock_guard<std::mutex> lock(queue_.mutex);
queue_.q.clear();
}
void enqueue(const std::shared_ptr<NodeStrokePreview>& preview, bool draw_first)
{
if (!preview)
return;
start();
queue_.PostUnique(preview, draw_first);
}
void remove(const std::shared_ptr<NodeStrokePreview>& preview)
{
if (!preview)
return;
std::lock_guard<std::mutex> lock(queue_.mutex);
auto it = std::find_if(queue_.q.begin(), queue_.q.end(), [&preview](const auto& queued) {
return queued.first == preview;
});
if (it != queue_.q.end())
queue_.q.erase(it);
}
private:
void run(std::stop_token stop_token)
{
BT_SetTerminate();
NodeStrokePreview::m_sampler_linear.create();
NodeStrokePreview::m_sampler_linear_repeat.create(
pp::renderer::gl::linear_texture_filter(),
pp::renderer::gl::repeat_texture_wrap());
NodeStrokePreview::m_sampler_mipmap.create();
NodeStrokePreview::m_sampler_mipmap.set_filter(
pp::renderer::gl::linear_mipmap_linear_texture_filter(),
pp::renderer::gl::linear_texture_filter());
NodeStrokePreview::m_brush_shape.create();
while (!stop_token.stop_requested())
{
auto node = queue_.Get();
if (!node) {
if (stop_token.stop_requested())
break;
continue;
}
std::lock_guard<std::mutex> render_lock(render_mutex_);
// if the brush is not already loaded, load it and then destroy it
const bool to_unload = (node->m_brush->m_tip_texture == nullptr);
node->m_brush->preload();
pp::panopainter::execute_legacy_node_stroke_preview_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::yield();
}
NodeStrokePreview::m_rtt.destroy();
NodeStrokePreview::m_rtt_mixer.destroy();
NodeStrokePreview::m_tex.destroy();
NodeStrokePreview::m_tex_dual.destroy();
NodeStrokePreview::m_tex_background.destroy();
NodeStrokePreview::m_brush_shape.destroy();
}
std::mutex lifecycle_mutex_;
std::mutex render_mutex_;
BlockingQueue<std::shared_ptr<NodeStrokePreview>> queue_;
std::jthread worker_;
};
NodeStrokePreviewRuntime& legacy_node_stroke_preview_runtime()
{
static NodeStrokePreviewRuntime runtime;
return runtime;
}
} // namespace pp::panopainter
std::atomic_int NodeStrokePreview::s_instances{ 0 };
RTT NodeStrokePreview::m_rtt;
RTT NodeStrokePreview::m_rtt_mixer;
Texture2D NodeStrokePreview::m_tex; // blending tmp texture
@@ -491,22 +622,60 @@ 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;
namespace pp::panopainter {
std::mutex& legacy_node_stroke_preview_render_mutex()
{
return legacy_node_stroke_preview_runtime().render_mutex();
}
void execute_legacy_node_stroke_preview_render_task(std::function<void()> task)
{
if (!task)
return;
App::I->render_task(std::move(task));
}
void start_legacy_node_stroke_preview_worker()
{
legacy_node_stroke_preview_runtime().start();
}
void stop_legacy_node_stroke_preview_worker()
{
legacy_node_stroke_preview_runtime().stop();
}
void clear_legacy_node_stroke_preview_worker_queue()
{
legacy_node_stroke_preview_runtime().clear_queue();
}
void enqueue_legacy_node_stroke_preview_worker(
const std::shared_ptr<NodeStrokePreview>& preview,
bool draw_first)
{
legacy_node_stroke_preview_runtime().enqueue(preview, draw_first);
}
void remove_legacy_node_stroke_preview_worker(
const std::shared_ptr<NodeStrokePreview>& preview)
{
legacy_node_stroke_preview_runtime().remove(preview);
}
} // namespace pp::panopainter
void NodeStrokePreview::terminate_renderer()
{
if (!s_renderer.joinable())
return;
s_running = false;
s_renderer.request_stop();
s_queue.UnlockGetters();
s_renderer.join();
pp::panopainter::stop_legacy_node_stroke_preview_worker();
}
void NodeStrokePreview::empty_queue()
{
s_queue.q.clear();
pp::panopainter::clear_legacy_node_stroke_preview_worker_queue();
}
void NodeStrokePreview::restore_context()
@@ -526,9 +695,9 @@ void NodeStrokePreview::clear_context()
Image NodeStrokePreview::render_to_image()
{
std::lock_guard<std::mutex> _lock(s_render_mutex);
std::lock_guard<std::mutex> _lock(pp::panopainter::legacy_node_stroke_preview_render_mutex());
App::I->render_task([this] {
pp::panopainter::execute_legacy_node_stroke_preview_render_task([this] {
draw_stroke_immediate();
});
return m_tex_preview.get_image();
@@ -538,63 +707,9 @@ 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);
pp::panopainter::enqueue_legacy_node_stroke_preview_worker(
std::static_pointer_cast<NodeStrokePreview>(shared_from_this()),
m_draw_first);
}
void NodeStrokePreview::draw()
@@ -637,7 +752,7 @@ void NodeStrokePreview::handle_on_screen(bool old_visibility, bool new_visibilit
}
else
{
s_queue.Remove(std::static_pointer_cast<NodeStrokePreview>(shared_from_this()));
pp::panopainter::remove_legacy_node_stroke_preview_worker(std::static_pointer_cast<NodeStrokePreview>(shared_from_this()));
m_tex_preview.destroy();
}
}

View File

@@ -14,6 +14,7 @@
#include <cstdint>
#include <functional>
#include <memory>
#include <mutex>
#include <vector>
class Node;
@@ -136,6 +137,17 @@ struct LegacyNodeStrokePreviewImmediateDrawRequest {
[[nodiscard]] bool execute_legacy_node_stroke_preview_immediate_runtime(
const LegacyNodeStrokePreviewImmediateRuntimeRequest& request);
[[nodiscard]] std::mutex& legacy_node_stroke_preview_render_mutex();
void execute_legacy_node_stroke_preview_render_task(std::function<void()> task);
void start_legacy_node_stroke_preview_worker();
void stop_legacy_node_stroke_preview_worker();
void clear_legacy_node_stroke_preview_worker_queue();
void enqueue_legacy_node_stroke_preview_worker(
const std::shared_ptr<NodeStrokePreview>& preview,
bool draw_first);
void remove_legacy_node_stroke_preview_worker(
const std::shared_ptr<NodeStrokePreview>& preview);
void initialize_legacy_node_stroke_preview_clone(Node* dest);
[[nodiscard]] bool execute_legacy_node_stroke_preview_immediate_draw(NodeStrokePreview& preview);

View File

@@ -1,6 +1,5 @@
#pragma once
#include <atomic>
#include <thread>
#include "node_border.h"
#include "rtt.h"
#include "brush.h"
@@ -9,10 +8,15 @@
#include "legacy_node_stroke_preview_execution_services.h"
#include "legacy_node_stroke_preview_runtime_services.h"
namespace pp::panopainter {
class NodeStrokePreviewRuntime;
}
class NodeStrokePreview : public NodeBorder
{
using StrokeFrame = pp::panopainter::LegacyNodeStrokePreviewFrame;
friend class pp::panopainter::NodeStrokePreviewRuntime;
friend bool pp::panopainter::execute_legacy_node_stroke_preview_immediate_draw(NodeStrokePreview& preview);
static RTT m_rtt;
@@ -28,10 +32,6 @@ class NodeStrokePreview : public NodeBorder
public:
using parent = NodeBorder;
static std::atomic_int s_instances;
static std::atomic_bool s_running;
static std::mutex s_render_mutex;
static std::jthread s_renderer;
static BlockingQueue<std::shared_ptr<NodeStrokePreview>> s_queue;
static void terminate_renderer();
static void empty_queue();
std::shared_ptr<Brush> m_brush;

View File

@@ -2,9 +2,9 @@
#include "log.h"
#include "shader.h"
#include "asset.h"
#include "app.h"
#include "legacy_gl_shader_dispatch.h"
#include "renderer_gl/opengl_capabilities.h"
#include "renderer_gl/render_runtime_dispatch.h"
#include "renderer_gl/shader_bindings.h"
#include <cstdint>
@@ -165,7 +165,7 @@ bool Shader::reload()
bool Shader::create(const std::string& vertex, const std::string& fragment)
{
bool ret = true;
App::I->render_task([this, &ret, vertex, fragment]
pp::renderer::gl::render_runtime_dispatch().render_task([this, &ret, vertex, fragment]
{
static char infolog[4096];
const auto vertex_shader = pp::renderer::gl::compile_opengl_shader_source(
@@ -337,7 +337,7 @@ void Shader::destroy()
{
if (prog)
{
App::I->render_task_async([prog=prog]
pp::renderer::gl::render_runtime_dispatch().render_task_async([prog=prog]
{
const auto status = pp::renderer::gl::delete_opengl_program(
static_cast<std::uint32_t>(prog),
@@ -352,6 +352,7 @@ void Shader::destroy()
void Shader::use()
{
assert(pp::renderer::gl::render_runtime_dispatch().is_render_thread());
const auto status = pp::renderer::gl::use_opengl_program(
static_cast<std::uint32_t>(prog),
pp::legacy::gl_shader::program_use_dispatch());
@@ -360,6 +361,7 @@ void Shader::use()
}
void Shader::u_vec4(kShaderUniform id, const glm::vec4& v)
{
assert(pp::renderer::gl::render_runtime_dispatch().is_render_thread());
if (m_umap.count(id) == 0)
LOG("UNIFORM vec4 %d NOT FOUND in shader %d", (int)id, (int)name)
else
@@ -374,6 +376,7 @@ void Shader::u_vec4(kShaderUniform id, const glm::vec4& v)
}
void Shader::u_vec3(kShaderUniform id, const glm::vec3& v)
{
assert(pp::renderer::gl::render_runtime_dispatch().is_render_thread());
if (m_umap.count(id) == 0)
LOG("UNIFORM vec3 %d NOT FOUND in shader %d", (int)id, (int)name)
else
@@ -389,6 +392,7 @@ void Shader::u_vec3(kShaderUniform id, const glm::vec3& v)
void Shader::u_vec2(kShaderUniform id, const glm::vec2& v)
{
assert(pp::renderer::gl::render_runtime_dispatch().is_render_thread());
if (m_umap.count(id) == 0)
LOG("UNIFORM vec2 %d NOT FOUND in shader %d", (int)id, (int)name)
else
@@ -404,6 +408,7 @@ void Shader::u_vec2(kShaderUniform id, const glm::vec2& v)
void Shader::u_mat4(kShaderUniform id, const glm::mat4& m)
{
assert(pp::renderer::gl::render_runtime_dispatch().is_render_thread());
if (m_umap.count(id) == 0)
LOG("UNIFORM mat4 %d NOT FOUND in shader %d", (int)id, (int)name)
else
@@ -418,6 +423,7 @@ void Shader::u_mat4(kShaderUniform id, const glm::mat4& m)
}
void Shader::u_int(kShaderUniform id, int i)
{
assert(pp::renderer::gl::render_runtime_dispatch().is_render_thread());
if (m_umap.count(id) == 0)
LOG("UNIFORM int %d NOT FOUND in shader %d", (int)id, (int)name)
else
@@ -432,6 +438,7 @@ void Shader::u_int(kShaderUniform id, int i)
}
void Shader::u_int(const char* uniform_name, int i)
{
assert(pp::renderer::gl::render_runtime_dispatch().is_render_thread());
const auto location = pp::renderer::gl::get_opengl_attribute_location(
static_cast<std::uint32_t>(prog),
uniform_name,
@@ -450,6 +457,7 @@ void Shader::u_int(const char* uniform_name, int i)
}
void Shader::u_float(kShaderUniform id, float f)
{
assert(pp::renderer::gl::render_runtime_dispatch().is_render_thread());
if (m_umap.count(id) == 0)
LOG("UNIFORM float %d NOT FOUND in shader %d", (int)id, (int)name)
else
@@ -464,6 +472,7 @@ void Shader::u_float(kShaderUniform id, float f)
}
GLint Shader::GetAttribLocation(const char* attribute_name)
{
assert(pp::renderer::gl::render_runtime_dispatch().is_render_thread());
const auto location = pp::renderer::gl::get_opengl_attribute_location(
static_cast<std::uint32_t>(prog),
attribute_name,

View File

@@ -2,8 +2,8 @@
#include "log.h"
#include "legacy_gl_mesh_dispatch.h"
#include "shape.h"
#include "app.h"
#include "renderer_gl/opengl_capabilities.h"
#include "renderer_gl/render_runtime_dispatch.h"
#include <array>
#include <cstddef>
@@ -71,7 +71,7 @@ bool Shape::create_buffers_imp(GLvoid* idx, GLvoid* vertices, int isize, int vsi
use_idx = true;
bool ret = false;
App::I->render_task([&]
pp::renderer::gl::render_runtime_dispatch().render_task([&]
{
destroy();
@@ -101,7 +101,7 @@ bool Shape::create_buffers(GLvoid* vertices, int vsize)
use_idx = false;
bool ret = false;
App::I->render_task([&]
pp::renderer::gl::render_runtime_dispatch().render_task([&]
{
destroy();
@@ -129,7 +129,7 @@ void Shape::draw_fill() const
if (count[0] == 0) return;
const auto type = static_cast<GLenum>(pp::renderer::gl::primitive_mode_for_fill_count(count[0]));
App::I->render_task([=]
pp::renderer::gl::render_runtime_dispatch().render_task([=]
{
(void)pp::legacy::gl_mesh::draw_mesh(
pp::renderer::gl::OpenGlMeshDraw {
@@ -148,7 +148,7 @@ void Shape::draw_stroke() const
if (count[0] == 0) return;
const auto type = static_cast<GLenum>(pp::renderer::gl::primitive_mode_for_stroke_count(count[1]));
App::I->render_task([=]
pp::renderer::gl::render_runtime_dispatch().render_task([=]
{
(void)pp::legacy::gl_mesh::draw_mesh(
pp::renderer::gl::OpenGlMeshDraw {
@@ -165,10 +165,13 @@ void Shape::draw_stroke() const
void Shape::destroy()
{
if (App::I) App::I->render_task_async([b1=buffers[0],b2=buffers[1],a1=arrays[0],a2=arrays[1]]
if (buffers[0] || buffers[1] || arrays[0] || arrays[1])
{
pp::renderer::gl::render_runtime_dispatch().render_task_async([b1=buffers[0],b2=buffers[1],a1=arrays[0],a2=arrays[1]]
{
(void)pp::legacy::gl_mesh::delete_mesh_objects({ b1, b2 }, { a1, a2 }, "Shape::destroy");
});
}
buffers[0] = buffers[1] = 0;
arrays[0] = arrays[1] = 0;
}
@@ -451,7 +454,7 @@ void Plane::update_vertices(const glm::vec4* data, const glm::vec2* uvs, const g
vertices[i].pos.z = q;
}
App::I->render_task([this]
pp::renderer::gl::render_runtime_dispatch().render_task([this]
{
(void)pp::legacy::gl_mesh::upload_buffer_data(
pp::renderer::gl::OpenGlBufferUpload {
@@ -732,7 +735,7 @@ void Sphere::create_impl(int rings, int sectors, float radius,
}
void LineSegment::update_vertices(const glm::vec4 data[2])
{
App::I->render_task([&]
pp::renderer::gl::render_runtime_dispatch().render_task([&]
{
static vertex_t vertices[2];
vertices[0] = { data[0], { 0, 0 } }; // A
@@ -750,7 +753,7 @@ void LineSegment::update_vertices(const glm::vec4 data[2])
}
void DynamicShape::update_vertices(vertex_t* vertices, int vcount)
{
App::I->render_task([&]
pp::renderer::gl::render_runtime_dispatch().render_task([&]
{
count[0] = vcount;
count[1] = vcount;