diff --git a/src/desktop/dialogs/playbackdialog.cpp b/src/desktop/dialogs/playbackdialog.cpp index 2b561ed547..c46b4b1663 100644 --- a/src/desktop/dialogs/playbackdialog.cpp +++ b/src/desktop/dialogs/playbackdialog.cpp @@ -354,7 +354,10 @@ void PlaybackDialog::exportFrame(int count) Q_ASSERT(m_exporter); count = qMax(1, count); - const QImage image = m_paintengine->getPixmap().toImage(); + QImage image; + m_paintengine->withPixmap([&image](const QPixmap &pixmap) { + image = pixmap.toImage(); + }); if(image.isNull()) { qWarning("exportFrame: image is null!"); onExporterReady(); diff --git a/src/desktop/docks/navigator.cpp b/src/desktop/docks/navigator.cpp index c209c09fda..3bd686202e 100644 --- a/src/desktop/docks/navigator.cpp +++ b/src/desktop/docks/navigator.cpp @@ -71,9 +71,10 @@ NavigatorView::NavigatorView(QWidget *parent) void NavigatorView::setCanvasModel(canvas::CanvasModel *model) { m_model = model; - connect(m_model->paintEngine(), &canvas::PaintEngine::areaChanged, this, &NavigatorView::onChange); - connect(m_model->paintEngine(), &canvas::PaintEngine::resized, this, &NavigatorView::onResize); + connect(m_model->paintEngine(), &canvas::PaintEngine::areaChanged, this, &NavigatorView::onChange, Qt::QueuedConnection); + connect(m_model->paintEngine(), &canvas::PaintEngine::resized, this, &NavigatorView::onResize, Qt::QueuedConnection); connect(m_model->paintEngine(), &canvas::PaintEngine::cursorMoved, this, &NavigatorView::onCursorMove); + m_model->paintEngine()->setRenderOutsideView(isVisible()); refreshCache(); } @@ -132,6 +133,22 @@ void NavigatorView::wheelEvent(QWheelEvent *event) } } +void NavigatorView::showEvent(QShowEvent *event) +{ + QWidget::showEvent(event); + if(m_model) { + m_model->paintEngine()->setRenderOutsideView(true); + } +} + +void NavigatorView::hideEvent(QHideEvent *event) +{ + QWidget::hideEvent(event); + if(m_model) { + m_model->paintEngine()->setRenderOutsideView(false); + } +} + /** * The focus rectangle represents the visible area in the * main viewport. @@ -157,22 +174,22 @@ void NavigatorView::onResize() void NavigatorView::refreshCache() { - if(!m_model) - return; - - const QPixmap &canvas = m_model->paintEngine()->getPixmap(); - if(canvas.isNull()) + if(!m_model) { return; - - const QSize size = this->size(); - if(size != m_cachedSize) { - m_cachedSize = size; - const QSize pixmapSize = canvas.size().scaled(size, Qt::KeepAspectRatio); - m_cache = QPixmap(pixmapSize); } - QPainter painter(&m_cache); - painter.drawPixmap(m_cache.rect(), canvas); + m_model->paintEngine()->withPixmap([this](const QPixmap &pixmap) { + if(!pixmap.isNull()) { + QSize navigatorSize = size(); + if(navigatorSize != m_cachedSize) { + m_cachedSize = navigatorSize; + m_cache = QPixmap{ + pixmap.size().scaled(navigatorSize, Qt::KeepAspectRatio)}; + } + QPainter painter(&m_cache); + painter.drawPixmap(m_cache.rect(), pixmap); + } + }); update(); } diff --git a/src/desktop/docks/navigator.h b/src/desktop/docks/navigator.h index a330e30b66..f20b9b2b96 100644 --- a/src/desktop/docks/navigator.h +++ b/src/desktop/docks/navigator.h @@ -41,6 +41,8 @@ public slots: void mouseMoveEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void wheelEvent(QWheelEvent *event) override; + void showEvent(QShowEvent *event) override; + void hideEvent(QHideEvent *event) override; private slots: void onChange(); diff --git a/src/desktop/mainwindow.cpp b/src/desktop/mainwindow.cpp index f7075e804c..96f2458101 100644 --- a/src/desktop/mainwindow.cpp +++ b/src/desktop/mainwindow.cpp @@ -2509,7 +2509,9 @@ void MainWindow::resizeCanvas() const QSize size = m_doc->canvas()->size(); dialogs::ResizeDialog *dlg = new dialogs::ResizeDialog(size, this); - dlg->setPreviewImage(m_doc->canvas()->paintEngine()->getPixmap().scaled(300, 300, Qt::KeepAspectRatio).toImage()); + m_doc->canvas()->paintEngine()->withPixmap([dlg](const QPixmap &pixmap) { + dlg->setPreviewImage(pixmap.scaled(300, 300, Qt::KeepAspectRatio).toImage()); + }); dlg->setAttribute(Qt::WA_DeleteOnClose); // Preset crop from selection if one exists diff --git a/src/desktop/scene/canvasitem.cpp b/src/desktop/scene/canvasitem.cpp index 020bfb193c..b9dbb68bde 100644 --- a/src/desktop/scene/canvasitem.cpp +++ b/src/desktop/scene/canvasitem.cpp @@ -27,8 +27,9 @@ void CanvasItem::setPaintEngine(canvas::PaintEngine *pe) { m_image = pe; if(m_image) { - connect(m_image, &canvas::PaintEngine::areaChanged, this, &CanvasItem::refreshImage); - connect(m_image, &canvas::PaintEngine::resized, this, &CanvasItem::canvasResize); + connect(m_image, &canvas::PaintEngine::areaChanged, this, &CanvasItem::refreshImage, Qt::QueuedConnection); + connect(m_image, &canvas::PaintEngine::resized, this, &CanvasItem::canvasResize, Qt::QueuedConnection); + m_image->setCanvasViewArea(m_visibleArea); } canvasResize(); } @@ -74,7 +75,9 @@ void CanvasItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option { if(m_image) { QRect exposed = option->exposedRect.toAlignedRect(); - painter->drawPixmap(exposed, m_image->getPixmapView(m_visibleArea), exposed); + m_image->withPixmap([&](const QPixmap &pixmap) { + painter->drawPixmap(exposed, pixmap, exposed); + }); if(m_pixelGrid) { QPen pen(QColor(160, 160, 160)); pen.setCosmetic(true); @@ -92,6 +95,9 @@ void CanvasItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option void CanvasItem::updateVisibleArea() { m_visibleArea = m_viewportBounds.intersected(m_boundingRect).toAlignedRect(); + if(m_image) { + m_image->setCanvasViewArea(m_visibleArea); + } } } diff --git a/src/desktop/scene/canvasscene.cpp b/src/desktop/scene/canvasscene.cpp index b7caeaaad0..d9c9de1c81 100644 --- a/src/desktop/scene/canvasscene.cpp +++ b/src/desktop/scene/canvasscene.cpp @@ -67,7 +67,7 @@ void CanvasScene::initCanvas(canvas::CanvasModel *model) connect( m_model->paintEngine(), &canvas::PaintEngine::resized, this, - &CanvasScene::handleCanvasResize); + &CanvasScene::handleCanvasResize, Qt::QueuedConnection); connect( m_model->paintEngine(), &canvas::PaintEngine::annotationsChanged, this, &CanvasScene::annotationsChanged); diff --git a/src/drawdance/libcommon/dpcommon/common.h b/src/drawdance/libcommon/dpcommon/common.h index a7c957b36f..0e8f9e8bf7 100644 --- a/src/drawdance/libcommon/dpcommon/common.h +++ b/src/drawdance/libcommon/dpcommon/common.h @@ -268,9 +268,11 @@ DP_INLINE size_t DP_flex_size(size_t type_size, size_t flex_offset, * member. Takes potential trailing padding being used as part of the flexible * array member into account. */ +// NOLINTBEGIN(bugprone-sizeof-expression) #define DP_FLEX_SIZEOF(TYPE, FIELD, COUNT) \ DP_flex_size(sizeof(TYPE), offsetof(TYPE, FIELD), \ sizeof(((TYPE *)NULL)->FIELD[0]), COUNT) +// NOLINTEND(bugprone-sizeof-expression) void *DP_malloc(size_t size) DP_MALLOC_ATTR; diff --git a/src/drawdance/libcommon/dpcommon/queue.c b/src/drawdance/libcommon/dpcommon/queue.c index b83115a46c..94c823ae95 100644 --- a/src/drawdance/libcommon/dpcommon/queue.c +++ b/src/drawdance/libcommon/dpcommon/queue.c @@ -178,3 +178,21 @@ bool DP_queue_all(DP_Queue *queue, size_t element_size, } return true; } + +size_t DP_queue_search_index(DP_Queue *queue, size_t element_size, + bool (*predicate)(void *element, void *user), + void *user) +{ + DP_ASSERT(queue); + DP_ASSERT(predicate); + size_t capacity = queue->capacity; + size_t used = queue->used; + size_t head = queue->head; + for (size_t i = 0; i < used; ++i) { + void *element = element_at(queue, (head + i) % capacity, element_size); + if (predicate(element, user)) { + return i; + } + } + return used; +} diff --git a/src/drawdance/libcommon/dpcommon/queue.h b/src/drawdance/libcommon/dpcommon/queue.h index 94720e52ef..e725b34d0d 100644 --- a/src/drawdance/libcommon/dpcommon/queue.h +++ b/src/drawdance/libcommon/dpcommon/queue.h @@ -67,5 +67,9 @@ void DP_queue_each(DP_Queue *queue, size_t element_size, bool DP_queue_all(DP_Queue *queue, size_t element_size, bool (*predicate)(void *element, void *user), void *user); +size_t DP_queue_search_index(DP_Queue *queue, size_t element_size, + bool (*predicate)(void *element, void *user), + void *user); + #endif diff --git a/src/drawdance/libengine/CMakeLists.txt b/src/drawdance/libengine/CMakeLists.txt index 37b35b2da8..33a2c2ac76 100644 --- a/src/drawdance/libengine/CMakeLists.txt +++ b/src/drawdance/libengine/CMakeLists.txt @@ -32,6 +32,7 @@ dp_target_sources(dpengine dpengine/pixels.c dpengine/player.c dpengine/recorder.c + dpengine/renderer.c dpengine/snapshots.c dpengine/text.c dpengine/tile.c @@ -72,6 +73,7 @@ dp_target_sources(dpengine dpengine/pixels.h dpengine/player.h dpengine/recorder.h + dpengine/renderer.h dpengine/snapshots.h dpengine/text.h dpengine/tile.h diff --git a/src/drawdance/libengine/dpengine/canvas_diff.c b/src/drawdance/libengine/dpengine/canvas_diff.c index 8b528fd1a9..9c8d58f066 100644 --- a/src/drawdance/libengine/dpengine/canvas_diff.c +++ b/src/drawdance/libengine/dpengine/canvas_diff.c @@ -49,6 +49,18 @@ void DP_canvas_diff_free(DP_CanvasDiff *diff) } } +int DP_canvas_diff_xtiles(DP_CanvasDiff *diff) +{ + DP_ASSERT(diff); + return diff->xtiles; +} + +int DP_canvas_diff_ytiles(DP_CanvasDiff *diff) +{ + DP_ASSERT(diff); + return diff->ytiles; +} + void DP_canvas_diff_begin(DP_CanvasDiff *diff, int old_width, int old_height, int current_width, int current_height, @@ -170,6 +182,25 @@ void DP_canvas_diff_each_pos_reset(DP_CanvasDiff *diff, } } +void DP_canvas_diff_bounds_clamp(DP_CanvasDiff *diff, int tile_left, + int tile_top, int tile_right, int tile_bottom, + int *out_left, int *out_top, int *out_right, + int *out_bottom, int *out_xtiles) +{ + DP_ASSERT(diff); + DP_ASSERT(out_left); + DP_ASSERT(out_top); + DP_ASSERT(out_right); + DP_ASSERT(out_bottom); + DP_ASSERT(out_xtiles); + int xtiles = diff->xtiles; + *out_left = DP_max_int(0, tile_left); + *out_top = DP_max_int(0, tile_top); + *out_right = DP_min_int(xtiles - 1, tile_right); + *out_bottom = DP_min_int(diff->ytiles - 1, tile_bottom); + *out_xtiles = xtiles; +} + void DP_canvas_diff_each_pos_tile_bounds_reset(DP_CanvasDiff *diff, int tile_left, int tile_top, int tile_right, int tile_bottom, @@ -178,11 +209,10 @@ void DP_canvas_diff_each_pos_tile_bounds_reset(DP_CanvasDiff *diff, { DP_ASSERT(diff); DP_ASSERT(fn); - int xtiles = diff->xtiles; - int left = DP_max_int(0, tile_left); - int top = DP_max_int(0, tile_top); - int right = DP_min_int(xtiles - 1, tile_right); - int bottom = DP_min_int(diff->ytiles - 1, tile_bottom); + int left, top, right, bottom, xtiles; + DP_canvas_diff_bounds_clamp(diff, tile_left, tile_top, tile_right, + tile_bottom, &left, &top, &right, &bottom, + &xtiles); bool *tile_changes = diff->tile_changes; for (int y = top; y <= bottom; ++y) { for (int x = left; x <= right; ++x) { diff --git a/src/drawdance/libengine/dpengine/canvas_diff.h b/src/drawdance/libengine/dpengine/canvas_diff.h index 8ac94ea1d5..ec5be94221 100644 --- a/src/drawdance/libengine/dpengine/canvas_diff.h +++ b/src/drawdance/libengine/dpengine/canvas_diff.h @@ -54,6 +54,11 @@ void DP_canvas_diff_each_pos(DP_CanvasDiff *diff, DP_CanvasDiffEachPosFn fn, void DP_canvas_diff_each_pos_reset(DP_CanvasDiff *diff, DP_CanvasDiffEachPosFn fn, void *data); +void DP_canvas_diff_bounds_clamp(DP_CanvasDiff *diff, int tile_left, + int tile_top, int tile_right, int tile_bottom, + int *out_left, int *out_top, int *out_right, + int *out_bottom, int *out_xtiles); + void DP_canvas_diff_each_pos_tile_bounds_reset(DP_CanvasDiff *diff, int tile_left, int tile_top, int tile_right, int tile_bottom, diff --git a/src/drawdance/libengine/dpengine/canvas_state.h b/src/drawdance/libengine/dpengine/canvas_state.h index aaf8793e7a..31153358e5 100644 --- a/src/drawdance/libengine/dpengine/canvas_state.h +++ b/src/drawdance/libengine/dpengine/canvas_state.h @@ -22,6 +22,7 @@ #ifndef DPENGINE_CANVAS_STATE_H #define DPENGINE_CANVAS_STATE_H #include "annotation_list.h" +#include "pixels.h" #include typedef struct DP_AnnotationList DP_AnnotationList; diff --git a/src/drawdance/libengine/dpengine/paint_engine.c b/src/drawdance/libengine/dpengine/paint_engine.c index f017a844c4..d69e94d06e 100644 --- a/src/drawdance/libengine/dpengine/paint_engine.c +++ b/src/drawdance/libengine/dpengine/paint_engine.c @@ -36,6 +36,7 @@ #include "paint.h" #include "player.h" #include "recorder.h" +#include "renderer.h" #include "tile.h" #include "timeline.h" #include "track.h" @@ -138,11 +139,6 @@ typedef struct DP_PaintEngineDabsPreview { DP_Message *messages[]; } DP_PaintEngineDabsPreview; -typedef struct DP_PaintEngineRenderBuffer { - DP_ALIGNAS_SIMD DP_Pixel8 pixels[DP_TILE_LENGTH]; - DP_ViewModeBuffer *vmb; -} DP_PaintEngineRenderBuffer; - typedef struct DP_PaintEngineCursorChange { DP_MessageType type; unsigned int context_id; @@ -162,7 +158,6 @@ struct DP_PaintEngine { DP_AclState *acls; DP_CanvasHistory *ch; DP_CanvasDiff *diff; - DP_TransientLayerContent *tlc; DP_Tile *checker; DP_CanvasState *history_cs; DP_CanvasState *view_cs; @@ -197,6 +192,7 @@ struct DP_PaintEngine { bool catching_up; bool reset_locked; DP_Thread *paint_thread; + DP_Renderer *renderer; struct { uint8_t acl_change_flags; DP_Vector cursor_changes; @@ -218,25 +214,6 @@ struct DP_PaintEngine { DP_PaintEngineDumpPlaybackFn dump_fn; void *user; } playback; - struct { - DP_Worker *worker; - DP_Semaphore *tiles_done_sem; - int tiles_waiting; - DP_PaintEngineRenderBuffer *buffers; - } render; -}; - -struct DP_PaintEngineRenderParams { - DP_PaintEngine *pe; - int xtiles; - bool needs_checkers; - DP_PaintEngineRenderTileFn render_tile; - void *user; -}; - -struct DP_PaintEngineRenderJobParams { - struct DP_PaintEngineRenderParams *render_params; - int x, y; }; @@ -632,6 +609,7 @@ static void run_paint_engine(void *user) DP_PaintEngine *pe = user; DP_DrawContext *dc = pe->paint_dc; DP_Semaphore *sem = pe->queue_sem; + // NOLINTNEXTLINE(bugprone-sizeof-expression) DP_Message **msgs = DP_malloc(sizeof(*msgs) * MAX_MULTIDAB_MESSAGES); while (true) { DP_SEMAPHORE_MUST_WAIT(sem); @@ -646,49 +624,6 @@ static void run_paint_engine(void *user) } -static DP_TransientTile *flatten_tile(DP_PaintEngine *pe, - DP_ViewModeBuffer *vmb, - bool needs_checkers, int tile_index) -{ - DP_CanvasState *cs = pe->view_cs; - DP_TransientTile *tt = DP_transient_tile_new_nullable( - DP_canvas_state_background_tile_noinc(cs), 0); - DP_ViewModeFilter vmf = - DP_local_state_view_mode_filter_make(pe->local_state, vmb, cs); - DP_canvas_state_flatten_tile_to(cs, tile_index, tt, true, &vmf); - - if (needs_checkers) { - DP_transient_tile_merge(tt, pe->checker, DP_BIT15, - DP_BLEND_MODE_BEHIND); - } - DP_transient_layer_content_transient_tile_set_noinc(pe->tlc, tt, - tile_index); - return tt; -} - -static void render_job(void *user, int thread_index) -{ - struct DP_PaintEngineRenderJobParams *job_params = user; - struct DP_PaintEngineRenderParams *render_params = - job_params->render_params; - int x = job_params->x; - int y = job_params->y; - - DP_PaintEngine *pe = render_params->pe; - int tile_index = y * render_params->xtiles + x; - DP_TransientTile *tt = - flatten_tile(pe, pe->render.buffers[thread_index].vmb, - render_params->needs_checkers, tile_index); - - DP_Pixel8 *pixel_buffer = pe->render.buffers[thread_index].pixels; - DP_pixels15_to_8_tile(pixel_buffer, DP_transient_tile_pixels(tt)); - - render_params->render_tile(render_params->user, x, y, pixel_buffer, - thread_index); - - DP_SEMAPHORE_MUST_POST(pe->render.tiles_done_sem); -} - static void invalidate_local_view(DP_PaintEngine *pe, bool check_all) { DP_layer_props_list_decref_nullable(pe->local_view.layers.prev_lpl); @@ -723,11 +658,12 @@ static void local_view_invalidated(void *user, bool check_all, int layer_id) DP_PaintEngine *DP_paint_engine_new_inc( DP_DrawContext *paint_dc, DP_DrawContext *preview_dc, DP_AclState *acls, - DP_CanvasState *cs_or_null, DP_CanvasHistorySavePointFn save_point_fn, - void *save_point_user, bool want_canvas_history_dump, - const char *canvas_history_dump_dir, DP_RecorderGetTimeMsFn get_time_ms_fn, - void *get_time_ms_user, DP_Player *player_or_null, - DP_PaintEnginePlaybackFn playback_fn, + DP_CanvasState *cs_or_null, DP_RendererTileFn renderer_tile_fn, + DP_RendererResizeFn renderer_resize_fn, void *renderer_user, + DP_CanvasHistorySavePointFn save_point_fn, void *save_point_user, + bool want_canvas_history_dump, const char *canvas_history_dump_dir, + DP_RecorderGetTimeMsFn get_time_ms_fn, void *get_time_ms_user, + DP_Player *player_or_null, DP_PaintEnginePlaybackFn playback_fn, DP_PaintEngineDumpPlaybackFn dump_playback_fn, void *playback_user) { DP_PaintEngine *pe = DP_malloc(sizeof(*pe)); @@ -737,7 +673,6 @@ DP_PaintEngine *DP_paint_engine_new_inc( cs_or_null, save_point_fn, save_point_user, want_canvas_history_dump, canvas_history_dump_dir); pe->diff = DP_canvas_diff_new(); - pe->tlc = DP_transient_layer_content_new_init(0, 0, NULL); pe->checker = DP_tile_new_checker( 0, (DP_Pixel15){DP_BIT15 / 2, DP_BIT15 / 2, DP_BIT15 / 2, DP_BIT15}, (DP_Pixel15){DP_BIT15, DP_BIT15, DP_BIT15, DP_BIT15}); @@ -772,6 +707,8 @@ DP_PaintEngine *DP_paint_engine_new_inc( pe->catching_up = false; pe->reset_locked = false; pe->paint_thread = DP_thread_new(run_paint_engine, pe); + pe->renderer = DP_renderer_new(DP_thread_cpu_count(), renderer_tile_fn, + renderer_resize_fn, renderer_user); pe->meta.acl_change_flags = 0; DP_VECTOR_INIT_TYPE(&pe->meta.cursor_changes, DP_PaintEngineCursorChange, 8); @@ -787,17 +724,6 @@ DP_PaintEngine *DP_paint_engine_new_inc( pe->playback.fn = playback_fn; pe->playback.dump_fn = dump_playback_fn; pe->playback.user = playback_user; - int render_thread_count = DP_thread_cpu_count(); - pe->render.worker = - DP_worker_new(1024, sizeof(struct DP_PaintEngineRenderJobParams), - render_thread_count, render_job); - pe->render.tiles_done_sem = DP_semaphore_new(0); - pe->render.tiles_waiting = 0; - pe->render.buffers = DP_malloc_simd(sizeof(DP_PaintEngineRenderBuffer) - * DP_int_to_size(render_thread_count)); - for (int i = 0; i < render_thread_count; ++i) { - pe->render.buffers[i].vmb = DP_view_mode_buffer_new(); - } return pe; } @@ -806,18 +732,12 @@ void DP_paint_engine_free_join(DP_PaintEngine *pe) if (pe) { DP_paint_engine_recorder_stop(pe); DP_atomic_set(&pe->running, false); - DP_semaphore_free(pe->render.tiles_done_sem); - int render_thread_count = DP_paint_engine_render_thread_count(pe); - for (int i = 0; i < render_thread_count; ++i) { - DP_view_mode_buffer_free(pe->render.buffers[i].vmb); - } - DP_free_simd(pe->render.buffers); - DP_worker_free_join(pe->render.worker); DP_SEMAPHORE_MUST_POST(pe->queue_sem); DP_thread_free_join(pe->paint_thread); DP_player_free(pe->playback.player); DP_semaphore_free(pe->record.start_sem); DP_vector_dispose(&pe->meta.cursor_changes); + DP_renderer_free(pe->renderer); DP_mutex_free(pe->queue_mutex); DP_semaphore_free(pe->queue_sem); DP_message_queue_dispose(&pe->remote_queue); @@ -838,6 +758,7 @@ void DP_paint_engine_free_join(DP_PaintEngine *pe) DP_Message **msgs = DP_msg_internal_dump_command_messages(mi, &count); decref_messages(count, msgs); + break; } default: break; @@ -859,7 +780,6 @@ void DP_paint_engine_free_join(DP_PaintEngine *pe) DP_canvas_state_decref_nullable(pe->history_cs); DP_canvas_state_decref_nullable(pe->view_cs); DP_tile_decref(pe->checker); - DP_transient_layer_content_decref(pe->tlc); DP_canvas_diff_free(pe->diff); DP_canvas_history_free(pe->ch); DP_free(pe); @@ -869,14 +789,7 @@ void DP_paint_engine_free_join(DP_PaintEngine *pe) int DP_paint_engine_render_thread_count(DP_PaintEngine *pe) { DP_ASSERT(pe); - return DP_worker_thread_count(pe->render.worker); -} - -DP_TransientLayerContent * -DP_paint_engine_render_content_noinc(DP_PaintEngine *pe) -{ - DP_ASSERT(pe); - return pe->tlc; + return DP_renderer_thread_count(pe->renderer); } void DP_paint_engine_local_drawing_in_progress_set( @@ -2249,29 +2162,18 @@ static DP_CanvasState *apply_local_background_tile(DP_PaintEngine *pe, static void emit_changes(DP_PaintEngine *pe, DP_CanvasState *prev, DP_CanvasState *cs, - bool catching_up, bool catchup_done, - DP_PaintEngineResizedFn resized, - DP_CanvasDiffEachPosFn tile_changed, + bool catching_up, bool catchup_done, DP_Rect tile_bounds, + bool render_outside_tile_bounds, DP_PaintEngineLayerPropsChangedFn layer_props_changed, DP_PaintEngineAnnotationsChangedFn annotations_changed, DP_PaintEngineDocumentMetadataChangedFn document_metadata_changed, DP_PaintEngineTimelineChangedFn timeline_changed, DP_PaintEngineCursorMovedFn cursor_moved, void *user) { - int prev_width = DP_canvas_state_width(prev); - int prev_height = DP_canvas_state_height(prev); - int width = DP_canvas_state_width(cs); - int height = DP_canvas_state_height(cs); - if (prev_width != width || prev_height != height) { - resized(user, - DP_canvas_state_offset_x(prev) - DP_canvas_state_offset_x(cs), - DP_canvas_state_offset_y(prev) - DP_canvas_state_offset_y(cs), - prev_width, prev_height); - } - DP_CanvasDiff *diff = pe->diff; DP_canvas_state_diff(cs, prev, diff); - DP_canvas_diff_each_pos(diff, tile_changed, user); + DP_renderer_apply(pe->renderer, cs, diff, tile_bounds, + render_outside_tile_bounds, false); if (!catching_up) { if (DP_canvas_diff_layer_props_changed_reset(diff) || catchup_done) { @@ -2304,10 +2206,10 @@ emit_changes(DP_PaintEngine *pe, DP_CanvasState *prev, DP_CanvasState *cs, } void DP_paint_engine_tick( - DP_PaintEngine *pe, DP_PaintEngineCatchupFn catchup, + DP_PaintEngine *pe, DP_Rect tile_bounds, bool render_outside_tile_bounds, + DP_PaintEngineCatchupFn catchup, DP_PaintEngineResetLockChangedFn reset_lock_changed, DP_PaintEngineRecorderStateChangedFn recorder_state_changed, - DP_PaintEngineResizedFn resized, DP_CanvasDiffEachPosFn tile_changed, DP_PaintEngineLayerPropsChangedFn layer_props_changed, DP_PaintEngineAnnotationsChangedFn annotations_changed, DP_PaintEngineDocumentMetadataChangedFn document_metadata_changed, @@ -2318,8 +2220,6 @@ void DP_paint_engine_tick( { DP_ASSERT(pe); DP_ASSERT(catchup); - DP_ASSERT(resized); - DP_ASSERT(tile_changed); DP_ASSERT(layer_props_changed); DP_PERF_BEGIN(fn, "tick"); @@ -2408,9 +2308,10 @@ void DP_paint_engine_tick( } emit_changes(pe, prev_view_cs, next_view_cs, catching_up, catchup_done, - resized, tile_changed, layer_props_changed, - annotations_changed, document_metadata_changed, - timeline_changed, cursor_moved, user); + tile_bounds, render_outside_tile_bounds, + layer_props_changed, annotations_changed, + document_metadata_changed, timeline_changed, cursor_moved, + user); DP_canvas_state_decref(prev_view_cs); DP_PERF_END(changes); } @@ -2440,88 +2341,11 @@ void DP_paint_engine_tick( DP_PERF_END(fn); } -void DP_paint_engine_prepare_render(DP_PaintEngine *pe, - DP_PaintEngineRenderSizeFn render_size, - void *user) -{ - DP_ASSERT(pe); - DP_ASSERT(render_size); - DP_CanvasState *cs = pe->view_cs; - int width = DP_canvas_state_width(cs); - int height = DP_canvas_state_height(cs); - render_size(user, width, height); - - DP_TransientLayerContent *tlc = pe->tlc; - bool render_size_changed = width != DP_transient_layer_content_width(tlc) - || height != DP_transient_layer_content_height(tlc); - if (render_size_changed) { - DP_transient_layer_content_decref(tlc); - pe->tlc = DP_transient_layer_content_new_init(width, height, NULL); - } -} - -static void render_pos(void *user, int x, int y) +void DP_paint_engine_change_bounds(DP_PaintEngine *pe, DP_Rect tile_bounds, + bool render_outside_tile_bounds) { - struct DP_PaintEngineRenderParams *params = user; - DP_PaintEngine *pe = params->pe; - ++pe->render.tiles_waiting; - struct DP_PaintEngineRenderJobParams job_params = {params, x, y}; - DP_worker_push(pe->render.worker, &job_params); -} - -static void wait_for_render(DP_PaintEngine *pe) -{ - int n = pe->render.tiles_waiting; - if (n != 0) { - pe->render.tiles_waiting = 0; - DP_SEMAPHORE_MUST_WAIT_N(pe->render.tiles_done_sem, n); - } -} - -static struct DP_PaintEngineRenderParams -make_render_params(DP_PaintEngine *pe, DP_PaintEngineRenderTileFn render_tile, - void *user) -{ - // It's very rare in practice for the checkerboard background to actually be - // visible behind the canvas. Only if the canvas background is set to a - // non-opaque value or there's weird blend modes like Erase at top-level. - bool needs_checkers = !DP_canvas_state_background_opaque(pe->view_cs) - || pe->local_view.layers_can_decrease_opacity; - return (struct DP_PaintEngineRenderParams){ - pe, DP_tile_count_round(DP_canvas_state_width(pe->view_cs)), - needs_checkers, render_tile, user}; -} - -void DP_paint_engine_render_everything(DP_PaintEngine *pe, - DP_PaintEngineRenderTileFn render_tile, - void *user) -{ - DP_ASSERT(pe); - DP_ASSERT(render_tile); - DP_PERF_BEGIN(fn, "render:everything"); - struct DP_PaintEngineRenderParams params = - make_render_params(pe, render_tile, user); - DP_canvas_diff_each_pos_reset(pe->diff, render_pos, ¶ms); - wait_for_render(pe); - DP_PERF_END(fn); -} - -void DP_paint_engine_render_tile_bounds(DP_PaintEngine *pe, int tile_left, - int tile_top, int tile_right, - int tile_bottom, - DP_PaintEngineRenderTileFn render_tile, - void *user) -{ - DP_ASSERT(pe); - DP_ASSERT(render_tile); - DP_PERF_BEGIN(fn, "render:tile_bounds"); - struct DP_PaintEngineRenderParams params = - make_render_params(pe, render_tile, user); - DP_canvas_diff_each_pos_tile_bounds_reset(pe->diff, tile_left, tile_top, - tile_right, tile_bottom, - render_pos, ¶ms); - wait_for_render(pe); - DP_PERF_END(fn); + DP_renderer_apply(pe->renderer, pe->view_cs, pe->diff, tile_bounds, + render_outside_tile_bounds, true); } diff --git a/src/drawdance/libengine/dpengine/paint_engine.h b/src/drawdance/libengine/dpengine/paint_engine.h index edb0b54469..9faf9ea35d 100644 --- a/src/drawdance/libengine/dpengine/paint_engine.h +++ b/src/drawdance/libengine/dpengine/paint_engine.h @@ -21,13 +21,14 @@ */ #ifndef DPENGINE_PAINT_ENGINE #define DPENGINE_PAINT_ENGINE -#include "canvas_diff.h" #include "canvas_history.h" #include "local_state.h" #include "player.h" #include "recorder.h" +#include "renderer.h" #include "view_mode.h" #include +#include typedef struct DP_AclState DP_AclState; typedef struct DP_AnnotationList DP_AnnotationList; @@ -59,8 +60,6 @@ typedef void (*DP_PaintEngineUndoDepthLimitSetFn)(void *user, typedef void (*DP_PaintEngineCatchupFn)(void *user, int progress); typedef void (*DP_PaintEngineResetLockChangedFn)(void *user, bool locked); typedef void (*DP_PaintEngineRecorderStateChangedFn)(void *user, bool started); -typedef void (*DP_PaintEngineResizedFn)(void *user, int offset_x, int offset_y, - int prev_width, int prev_height); typedef void (*DP_PaintEngineLayerPropsChangedFn)(void *user, DP_LayerPropsList *lpl); typedef void (*DP_PaintEngineAnnotationsChangedFn)(void *user, @@ -71,9 +70,6 @@ typedef void (*DP_PaintEngineTimelineChangedFn)(void *user, DP_Timeline *tl); typedef void (*DP_PaintEngineCursorMovedFn)(void *user, unsigned int flags, unsigned int context_id, int layer_id, int x, int y); -typedef void (*DP_PaintEngineRenderSizeFn)(void *user, int width, int height); -typedef void (*DP_PaintEngineRenderTileFn)(void *user, int x, int y, - DP_Pixel8 *pixels, int thread_index); typedef void (*DP_PaintEnginePushMessageFn)(void *user, DP_Message *msg); typedef const DP_Pixel8 *(*DP_PaintEngineTransformGetPixelsFn)(void *user); typedef void (*DP_PaintEngineTransformDisposePixelsFn)(void *user); @@ -83,20 +79,18 @@ typedef struct DP_PaintEngine DP_PaintEngine; DP_PaintEngine *DP_paint_engine_new_inc( DP_DrawContext *paint_dc, DP_DrawContext *preview_dc, DP_AclState *acls, - DP_CanvasState *cs_or_null, DP_CanvasHistorySavePointFn save_point_fn, - void *save_point_user, bool want_canvas_history_dump, - const char *canvas_history_dump_dir, DP_RecorderGetTimeMsFn get_time_ms_fn, - void *get_time_ms_user, DP_Player *player_or_null, - DP_PaintEnginePlaybackFn playback_fn, + DP_CanvasState *cs_or_null, DP_RendererTileFn renderer_tile_fn, + DP_RendererResizeFn renderer_resize_fn, void *renderer_user, + DP_CanvasHistorySavePointFn save_point_fn, void *save_point_user, + bool want_canvas_history_dump, const char *canvas_history_dump_dir, + DP_RecorderGetTimeMsFn get_time_ms_fn, void *get_time_ms_user, + DP_Player *player_or_null, DP_PaintEnginePlaybackFn playback_fn, DP_PaintEngineDumpPlaybackFn dump_playback_fn, void *playback_user); void DP_paint_engine_free_join(DP_PaintEngine *pe); int DP_paint_engine_render_thread_count(DP_PaintEngine *pe); -DP_TransientLayerContent * -DP_paint_engine_render_content_noinc(DP_PaintEngine *pe); - void DP_paint_engine_local_drawing_in_progress_set( DP_PaintEngine *pe, bool local_drawing_in_progress); @@ -202,10 +196,10 @@ int DP_paint_engine_handle_inc(DP_PaintEngine *pe, bool local, void *user); void DP_paint_engine_tick( - DP_PaintEngine *pe, DP_PaintEngineCatchupFn catchup, + DP_PaintEngine *pe, DP_Rect tile_bounds, bool render_outside_tile_bounds, + DP_PaintEngineCatchupFn catchup, DP_PaintEngineResetLockChangedFn reset_lock_changed, DP_PaintEngineRecorderStateChangedFn recorder_state_changed, - DP_PaintEngineResizedFn resized, DP_CanvasDiffEachPosFn tile_changed, DP_PaintEngineLayerPropsChangedFn layer_props_changed, DP_PaintEngineAnnotationsChangedFn annotations_changed, DP_PaintEngineDocumentMetadataChangedFn document_metadata_changed, @@ -214,19 +208,8 @@ void DP_paint_engine_tick( DP_PaintEngineDefaultLayerSetFn default_layer_set, DP_PaintEngineUndoDepthLimitSetFn undo_depth_limit_set, void *user); -void DP_paint_engine_prepare_render(DP_PaintEngine *pe, - DP_PaintEngineRenderSizeFn render_size, - void *user); - -void DP_paint_engine_render_everything(DP_PaintEngine *pe, - DP_PaintEngineRenderTileFn render_tile, - void *user); - -void DP_paint_engine_render_tile_bounds(DP_PaintEngine *pe, int tile_left, - int tile_top, int tile_right, - int tile_bottom, - DP_PaintEngineRenderTileFn render_tile, - void *user); +void DP_paint_engine_change_bounds(DP_PaintEngine *pe, DP_Rect tile_bounds, + bool render_outside_tile_bounds); void DP_paint_engine_preview_cut(DP_PaintEngine *pe, int layer_id, int x, int y, int width, int height, diff --git a/src/drawdance/libengine/dpengine/renderer.c b/src/drawdance/libengine/dpengine/renderer.c new file mode 100644 index 0000000000..fc5a57ddda --- /dev/null +++ b/src/drawdance/libengine/dpengine/renderer.c @@ -0,0 +1,556 @@ +#include "renderer.h" +#include "canvas_diff.h" +#include "canvas_state.h" +#include "layer_content.h" +#include "pixels.h" +#include "tile.h" +#include "view_mode.h" +#include +#include +#include +#include +#include +#include + +#define TILE_QUEUE_INITIAL_CAPACITY 1024 +#define TILE_QUEUED_NONE 0 +#define TILE_QUEUED_HIGH 1 +#define TILE_QUEUED_LOW 2 + +typedef struct DP_RenderContext { + DP_ALIGNAS_SIMD DP_Pixel8 pixels[DP_TILE_LENGTH]; + DP_TransientTile *tt; + DP_ViewModeBuffer vmb; +} DP_RenderContext; + +typedef enum DP_RenderJobType { + DP_RENDER_JOB_TILE, + DP_RENDER_JOB_RESIZE, + DP_RENDER_JOB_WAIT, + DP_RENDER_JOB_INVALID, + DP_RENDER_JOB_QUIT, +} DP_RenderJobType; + +typedef struct DP_RendererTileCoords { + int tile_x, tile_y; +} DP_RendererTileCoords; + +typedef struct DP_RendererTileJob { + int tile_x, tile_y; + int tile_index; + DP_CanvasState *cs; +} DP_RendererTileJob; + +typedef struct DP_RendererResizeJob { + int width, height; + int prev_width, prev_height; + int offset_x, offset_y; +} DP_RendererResizeJob; + +typedef struct DP_RenderJob { + DP_RenderJobType type; + union { + DP_RendererTileJob tile; + DP_RendererResizeJob resize; + }; +} DP_RenderJob; + +struct DP_Renderer { + struct { + DP_RendererTileFn tile; + DP_RendererResizeFn resize; + void *user; + } fn; + DP_RenderContext *contexts; + DP_Queue resize_queue; + struct { + DP_Queue queue_high; + DP_Queue queue_low; + size_t map_capacity; + char *map; + } tile; + DP_Tile *checker; + DP_CanvasState *cs; + int xtiles; + DP_Mutex *queue_mutex; + DP_Semaphore *queue_sem; + DP_Semaphore *wait_ready_sem; + DP_Semaphore *wait_done_sem; + int thread_count; + DP_Thread *threads[]; +}; + + +static void handle_tile_job(DP_Renderer *renderer, DP_RenderContext *rc, + DP_RendererTileJob *job) +{ + DP_TransientTile *tt = rc->tt; + DP_CanvasState *cs = job->cs; + DP_Tile *background_tile = DP_canvas_state_background_tile_noinc(cs); + if (background_tile) { + DP_transient_tile_copy(tt, background_tile); + } + else { + DP_transient_tile_clear(tt); + } + + DP_ViewModeFilter vmf = // FIXME + DP_view_mode_filter_make(&rc->vmb, DP_VIEW_MODE_NORMAL, cs, 0, 0, NULL); + + DP_canvas_state_flatten_tile_to(cs, job->tile_index, tt, true, &vmf); + + bool needs_checkers = false; // FIXME + if (needs_checkers) { + DP_transient_tile_merge(tt, renderer->checker, DP_BIT15, + DP_BLEND_MODE_BEHIND); + } + + DP_Pixel8 *pixel_buffer = rc->pixels; + DP_pixels15_to_8_tile(pixel_buffer, DP_transient_tile_pixels(tt)); + renderer->fn.tile(renderer->fn.user, job->tile_x, job->tile_y, + pixel_buffer); + + DP_canvas_state_decref(cs); +} + + +static void handle_resize_job(DP_Renderer *renderer, DP_RendererResizeJob *job) +{ + // Wait for all threads to be in a safe state before proceeding. + int wait_thread_count = renderer->thread_count - 1; + DP_SEMAPHORE_MUST_WAIT_N(renderer->wait_ready_sem, wait_thread_count); + + renderer->fn.resize(renderer->fn.user, job->width, job->height, + job->prev_width, job->prev_height, job->offset_x, + job->offset_y); + + // Unlock the other threads. + DP_SEMAPHORE_MUST_POST_N(renderer->wait_done_sem, wait_thread_count); +} + + +static void handle_wait_job(DP_Renderer *renderer) +{ + DP_SEMAPHORE_MUST_POST(renderer->wait_ready_sem); + DP_SEMAPHORE_MUST_WAIT(renderer->wait_done_sem); +} + + +static bool dequeue_job_resize(DP_Renderer *renderer, DP_RenderJob *out_job) +{ + DP_Queue *resize_queue = &renderer->resize_queue; + DP_RenderJob *resize_job = DP_queue_peek(resize_queue, sizeof(*resize_job)); + if (resize_job) { + *out_job = *resize_job; + DP_queue_shift(resize_queue); + return true; + } + else { + return false; + } +} + +static bool dequeue_job_tile(DP_Renderer *renderer, DP_Queue *queue, + DP_RenderJob *out_job) +{ + DP_RendererTileCoords *coords = DP_queue_peek(queue, sizeof(*coords)); + if (coords) { + int tile_x = coords->tile_x; + if (tile_x >= 0) { + int tile_y = coords->tile_y; + int tile_index = tile_y * renderer->xtiles + tile_x; + renderer->tile.map[tile_index] = TILE_QUEUED_NONE; + out_job->type = DP_RENDER_JOB_TILE; + out_job->tile = + (DP_RendererTileJob){tile_x, tile_y, tile_index, + DP_canvas_state_incref(renderer->cs)}; + } + else { + out_job->type = DP_RENDER_JOB_INVALID; + } + DP_queue_shift(queue); + return true; + } + else { + return false; + } +} + +static DP_RenderJob dequeue_job(DP_Renderer *renderer) +{ + DP_RenderJob job; + bool job_found = + dequeue_job_resize(renderer, &job) + || dequeue_job_tile(renderer, &renderer->tile.queue_high, &job) + || dequeue_job_tile(renderer, &renderer->tile.queue_low, &job); + if (!job_found) { + job.type = DP_RENDER_JOB_QUIT; + } + return job; +} + +static void handle_jobs(DP_Renderer *renderer, int thread_index) +{ + DP_Mutex *queue_mutex = renderer->queue_mutex; + DP_Semaphore *queue_sem = renderer->queue_sem; + DP_RenderContext *rc = &renderer->contexts[thread_index]; + while (true) { + DP_SEMAPHORE_MUST_WAIT(queue_sem); + DP_MUTEX_MUST_LOCK(queue_mutex); + DP_RenderJob job = dequeue_job(renderer); + DP_MUTEX_MUST_UNLOCK(queue_mutex); + switch (job.type) { + case DP_RENDER_JOB_TILE: + handle_tile_job(renderer, rc, &job.tile); + break; + case DP_RENDER_JOB_RESIZE: + handle_resize_job(renderer, &job.resize); + break; + case DP_RENDER_JOB_WAIT: + handle_wait_job(renderer); + break; + case DP_RENDER_JOB_INVALID: + break; + case DP_RENDER_JOB_QUIT: + return; + default: + DP_UNREACHABLE(); + } + } +} + +struct DP_RenderWorkerParams { + DP_Renderer *renderer; + int thread_index; +}; + +static void run_worker_thread(void *user) +{ + struct DP_RenderWorkerParams *params = user; + DP_Renderer *renderer = params->renderer; + int thread_index = params->thread_index; + DP_free(params); + handle_jobs(renderer, thread_index); +} + + +DP_Renderer *DP_renderer_new(int thread_count, DP_RendererTileFn tile_fn, + DP_RendererResizeFn resize_fn, void *user) +{ + DP_ASSERT(thread_count > 0); + DP_ASSERT(tile_fn); + DP_ASSERT(resize_fn); + + size_t size_thread_count = DP_int_to_size(thread_count); + DP_Renderer *renderer = + DP_malloc(DP_FLEX_SIZEOF(DP_Renderer, threads, size_thread_count)); + renderer->fn.tile = tile_fn; + renderer->fn.resize = resize_fn; + renderer->fn.user = user; + renderer->thread_count = thread_count; + renderer->queue_mutex = NULL; + renderer->queue_sem = NULL; + renderer->wait_ready_sem = NULL; + renderer->wait_done_sem = NULL; + DP_queue_init(&renderer->resize_queue, size_thread_count * 2, + sizeof(DP_RenderJob)); + DP_queue_init(&renderer->tile.queue_high, TILE_QUEUE_INITIAL_CAPACITY, + sizeof(DP_RendererTileCoords)); + DP_queue_init(&renderer->tile.queue_low, TILE_QUEUE_INITIAL_CAPACITY, + sizeof(DP_RendererTileCoords)); + renderer->tile.map_capacity = 0; + renderer->tile.map = NULL; + renderer->checker = DP_tile_new_checker( + 0, (DP_Pixel15){DP_BIT15 / 2, DP_BIT15 / 2, DP_BIT15 / 2, DP_BIT15}, + (DP_Pixel15){DP_BIT15, DP_BIT15, DP_BIT15, DP_BIT15}); + renderer->cs = DP_canvas_state_new(); + renderer->xtiles = 0; + renderer->contexts = DP_malloc_simd(sizeof(*renderer->contexts) + * DP_int_to_size(thread_count)); + for (int i = 0; i < thread_count; ++i) { + renderer->contexts[i].tt = DP_transient_tile_new_blank(0); + DP_view_mode_buffer_init(&renderer->contexts[i].vmb); + renderer->threads[i] = NULL; + } + + bool ok = (renderer->queue_mutex = DP_mutex_new()) != NULL + && (renderer->queue_sem = DP_semaphore_new(0)) != NULL + && (renderer->wait_ready_sem = DP_semaphore_new(0)) != NULL + && (renderer->wait_done_sem = DP_semaphore_new(0)) != NULL; + if (!ok) { + DP_renderer_free(renderer); + return NULL; + } + + for (int i = 0; i < thread_count; ++i) { + struct DP_RenderWorkerParams *params = DP_malloc(sizeof(*params)); + *params = (struct DP_RenderWorkerParams){renderer, i}; + renderer->threads[i] = DP_thread_new(run_worker_thread, params); + if (!renderer->threads[i]) { + DP_free(params); + DP_renderer_free(renderer); + return NULL; + } + } + + return renderer; +} + +void DP_renderer_free(DP_Renderer *renderer) +{ + if (renderer) { + int thread_count = renderer->thread_count; + DP_MUTEX_MUST_LOCK(renderer->queue_mutex); + renderer->resize_queue.used = 0; + renderer->tile.queue_high.used = 0; + renderer->tile.queue_low.used = 0; + DP_SEMAPHORE_MUST_POST_N(renderer->queue_sem, thread_count); + DP_MUTEX_MUST_UNLOCK(renderer->queue_mutex); + + for (int i = 0; i < thread_count; ++i) { + DP_thread_free_join(renderer->threads[i]); + } + DP_semaphore_free(renderer->wait_done_sem); + DP_semaphore_free(renderer->wait_ready_sem); + DP_semaphore_free(renderer->queue_sem); + DP_mutex_free(renderer->queue_mutex); + DP_canvas_state_decref(renderer->cs); + DP_tile_decref(renderer->checker); + DP_free(renderer->tile.map); + DP_queue_dispose(&renderer->tile.queue_low); + DP_queue_dispose(&renderer->tile.queue_high); + DP_queue_dispose(&renderer->resize_queue); + for (int i = 0; i < thread_count; ++i) { + DP_view_mode_buffer_dispose(&renderer->contexts[i].vmb); + DP_transient_tile_decref(renderer->contexts[i].tt); + } + DP_free_simd(renderer->contexts); + DP_free(renderer); + } +} + +int DP_renderer_thread_count(DP_Renderer *renderer) +{ + DP_ASSERT(renderer); + return renderer->thread_count; +} + + +static void invalidate_tile_coords(void *element, DP_UNUSED void *user) +{ + DP_RendererTileCoords *coords = element; + coords->tile_x = -1; +} + +static int push_resize(DP_Renderer *renderer, int width, int height, + int prev_width, int prev_height, int offset_x, + int offset_y) +{ + DP_Queue *resize_queue = &renderer->resize_queue; + DP_Queue *tile_queue_high = &renderer->tile.queue_high; + DP_Queue *tile_queue_low = &renderer->tile.queue_low; + int thread_count = renderer->thread_count; + int wait_thread_count = renderer->thread_count - 1; + + // Resizing must be synchronized between all render threads, since all tile + // positions are invalidated by the operation. So we enqueue a wait job for + // all except one thread, plus the actual resize job for the last one. + for (int i = 0; i < wait_thread_count; ++i) { + DP_RenderJob *wait_job = DP_queue_push(resize_queue, sizeof(*wait_job)); + wait_job->type = DP_RENDER_JOB_WAIT; + } + + DP_RenderJob *resize_job = DP_queue_push(resize_queue, sizeof(*resize_job)); + resize_job->type = DP_RENDER_JOB_RESIZE; + resize_job->resize = (DP_RendererResizeJob){ + width, height, prev_width, prev_height, offset_x, offset_y}; + + // All current tile jobs are invalidated by the resize, so we turn those + // into tombstones to avoid any pointless processing thereof. + DP_queue_each(tile_queue_high, sizeof(DP_RendererTileCoords), + invalidate_tile_coords, NULL); + DP_queue_each(tile_queue_low, sizeof(DP_RendererTileCoords), + invalidate_tile_coords, NULL); + + // Resize the priority lookup map and clear it too. + size_t required_capacity = DP_int_to_size(width) * DP_int_to_size(height); + if (renderer->tile.map_capacity < required_capacity) { + renderer->tile.map = DP_realloc(renderer->tile.map, required_capacity); + renderer->tile.map_capacity = required_capacity; + } + memset(renderer->tile.map, TILE_QUEUED_NONE, required_capacity); + + // Update the horizontal tile count used to calculate tile indexes. + renderer->xtiles = DP_tile_count_round(width); + + return thread_count; +} + + +static void enqueue_tile(DP_Queue *queue, char *map, int tile_x, int tile_y, + int tile_index, char priority) +{ + DP_RendererTileCoords *coords = DP_queue_push(queue, sizeof(*coords)); + *coords = (DP_RendererTileCoords){tile_x, tile_y}; + map[tile_index] = priority; +} + +static bool should_remove_tile(void *element, void *user) +{ + DP_RendererTileCoords *a = element; + DP_RendererTileCoords *b = user; + return a->tile_x == b->tile_x && a->tile_y == b->tile_y; +} + +static void remove_tile(DP_Queue *queue, int tile_x, int tile_y) +{ + DP_RendererTileCoords coords = {tile_x, tile_y}; + size_t index = DP_queue_search_index(queue, sizeof(DP_RendererTileCoords), + should_remove_tile, &coords); + DP_ASSERT(index < queue->used); + // We don't actually care too much about the order in our queue, so instead + // of going through the effort of removing an element in the middle, we just + // replace it with the last element. If it's at the beginning or end, we can + // just shift or pop it respectively though, which is even easier. + if (index == 0) { + DP_queue_shift(queue); + } + else if (index == queue->used - 1) { + DP_queue_pop(queue); + } + else { + DP_RendererTileCoords *last = + DP_queue_peek_last(queue, sizeof(DP_RendererTileCoords)); + DP_RendererTileCoords *target = + DP_queue_at(queue, sizeof(DP_RendererTileCoords), index); + *target = *last; + DP_queue_pop(queue); + } +} + +static void upgrade_tile_priority(DP_Renderer *renderer, int tile_x, int tile_y, + int tile_index) +{ + enqueue_tile(&renderer->tile.queue_high, renderer->tile.map, tile_x, tile_y, + tile_index, TILE_QUEUED_HIGH); + remove_tile(&renderer->tile.queue_low, tile_x, tile_y); +} + +static void push_tile_high_priority(DP_Renderer *renderer, int tile_x, + int tile_y, int *out_pushed) +{ + int tile_index = tile_y * renderer->xtiles + tile_x; + char status = renderer->tile.map[tile_index]; + if (status == TILE_QUEUED_NONE) { + enqueue_tile(&renderer->tile.queue_high, renderer->tile.map, tile_x, + tile_y, tile_index, TILE_QUEUED_HIGH); + ++*out_pushed; + } + else if (status == TILE_QUEUED_LOW) { + upgrade_tile_priority(renderer, tile_x, tile_y, tile_index); + } +} + +struct DP_RendererPushTileParams { + DP_Renderer *renderer; + DP_Rect tile_bounds; + int pushed; +}; + +static void push_tile(void *user, int tile_x, int tile_y) +{ + struct DP_RendererPushTileParams *params = user; + DP_Renderer *renderer = params->renderer; + if (DP_rect_contains(params->tile_bounds, tile_x, tile_y)) { + push_tile_high_priority(renderer, tile_x, tile_y, ¶ms->pushed); + } + else { + int tile_index = tile_y * renderer->xtiles + tile_x; + char status = renderer->tile.map[tile_index]; + if (status == TILE_QUEUED_NONE) { + enqueue_tile(&renderer->tile.queue_low, renderer->tile.map, tile_x, + tile_y, tile_index, TILE_QUEUED_LOW); + ++params->pushed; + } + } +} + +struct DP_RendererPushTileInViewParams { + DP_Renderer *renderer; + int pushed; +}; + +static void push_tile_in_view(void *user, int tile_x, int tile_y) +{ + struct DP_RendererPushTileInViewParams *params = user; + DP_Renderer *renderer = params->renderer; + push_tile_high_priority(renderer, tile_x, tile_y, ¶ms->pushed); +} + +static void reprioritize_tiles(DP_Renderer *renderer, DP_CanvasDiff *diff, + DP_Rect tile_bounds) +{ + int left, top, right, bottom, xtiles; + DP_canvas_diff_bounds_clamp(diff, tile_bounds.x1, tile_bounds.y1, + tile_bounds.x2, tile_bounds.y2, &left, &top, + &right, &bottom, &xtiles); + char *tile_map = renderer->tile.map; + for (int tile_y = top; tile_y <= bottom; ++tile_y) { + for (int tile_x = left; tile_x <= right; ++tile_x) { + int tile_index = tile_y * xtiles + tile_x; + if (tile_map[tile_index] == TILE_QUEUED_LOW) { + upgrade_tile_priority(renderer, tile_x, tile_y, tile_index); + } + } + } +} + +void DP_renderer_apply(DP_Renderer *renderer, DP_CanvasState *cs, + DP_CanvasDiff *diff, DP_Rect tile_bounds, + bool render_outside_tile_bounds, + bool tile_bounds_changed) +{ + DP_ASSERT(renderer); + DP_ASSERT(cs); + DP_ASSERT(diff); + + DP_CanvasState *prev_cs = renderer->cs; + int prev_width = DP_canvas_state_width(prev_cs); + int prev_height = DP_canvas_state_height(prev_cs); + int width = DP_canvas_state_width(cs); + int height = DP_canvas_state_height(cs); + + DP_Mutex *queue_mutex = renderer->queue_mutex; + DP_MUTEX_MUST_LOCK(queue_mutex); + + renderer->cs = DP_canvas_state_incref(cs); + + int pushed = 0; + if (width != prev_width || height != prev_height) { + pushed += push_resize( + renderer, width, height, prev_width, prev_height, + DP_canvas_state_offset_x(prev_cs) - DP_canvas_state_offset_x(cs), + DP_canvas_state_offset_y(prev_cs) - DP_canvas_state_offset_y(cs)); + } + + DP_canvas_state_decref(prev_cs); + if (render_outside_tile_bounds) { + struct DP_RendererPushTileParams params = {renderer, tile_bounds, 0}; + DP_canvas_diff_each_pos_reset(diff, push_tile, ¶ms); + pushed += params.pushed; + } + else { + struct DP_RendererPushTileInViewParams params = {renderer, 0}; + DP_canvas_diff_each_pos_tile_bounds_reset( + diff, tile_bounds.x1, tile_bounds.y1, tile_bounds.x2, + tile_bounds.y2, push_tile_in_view, ¶ms); + pushed += params.pushed; + } + + if (tile_bounds_changed) { + reprioritize_tiles(renderer, diff, tile_bounds); + } + + DP_SEMAPHORE_MUST_POST_N(renderer->queue_sem, pushed); + DP_MUTEX_MUST_UNLOCK(queue_mutex); +} diff --git a/src/drawdance/libengine/dpengine/renderer.h b/src/drawdance/libengine/dpengine/renderer.h new file mode 100644 index 0000000000..4f5922eecc --- /dev/null +++ b/src/drawdance/libengine/dpengine/renderer.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#include +#include + +typedef struct DP_CanvasDiff DP_CanvasDiff; +typedef struct DP_CanvasState DP_CanvasState; +typedef union DP_Pixel8 DP_Pixel8; + + +typedef struct DP_Renderer DP_Renderer; +typedef void (*DP_RendererTileFn)(void *user, int x, int y, DP_Pixel8 *pixels); +typedef void (*DP_RendererResizeFn)(void *user, int width, int height, + int prev_width, int prev_height, + int offset_x, int offset_y); + +DP_Renderer *DP_renderer_new(int thread_count, DP_RendererTileFn tile_fn, + DP_RendererResizeFn resize_fn, void *user); + +void DP_renderer_free(DP_Renderer *renderer); + +int DP_renderer_thread_count(DP_Renderer *renderer); + +// Increments refcount on the given canvas state, resets the given diff. +void DP_renderer_apply(DP_Renderer *renderer, DP_CanvasState *cs, + DP_CanvasDiff *diff, DP_Rect tile_bounds, + bool render_outside_tile_bounds, + bool tile_bounds_changed); diff --git a/src/drawdance/libengine/dpengine/save.c b/src/drawdance/libengine/dpengine/save.c index 31c4623e72..d8e1534e47 100644 --- a/src/drawdance/libengine/dpengine/save.c +++ b/src/drawdance/libengine/dpengine/save.c @@ -793,7 +793,7 @@ static const char *get_path_separator(const char *path) struct DP_SaveFrameContext { DP_CanvasState *cs; - DP_ViewModeBuffer **vmbs; + DP_ViewModeBuffer *vmbs; int frame_count; const char *path; const char *separator; @@ -810,15 +810,6 @@ struct DP_SaveFrameJobParams { int frames[]; }; -static DP_ViewModeBuffer * -get_frame_view_mode_buffer(struct DP_SaveFrameContext *c, int thread_index) -{ - if (!c->vmbs[thread_index]) { - c->vmbs[thread_index] = DP_view_mode_buffer_new(); - } - return c->vmbs[thread_index]; -} - static void set_error_result(struct DP_SaveFrameContext *c, DP_SaveResult result) { @@ -880,7 +871,7 @@ static void save_frame_job(void *element, int thread_index) *(struct DP_SaveFrameJobParams **)element; struct DP_SaveFrameContext *c = params->c; if (DP_atomic_get(&c->result) == DP_SAVE_RESULT_SUCCESS) { - DP_ViewModeBuffer *vmb = get_frame_view_mode_buffer(c, thread_index); + DP_ViewModeBuffer *vmb = &c->vmbs[thread_index]; // Render and save the first frame given. int first_frame = params->frames[0]; char *path = save_frame(c, vmb, first_frame); @@ -929,7 +920,7 @@ save_animation_frames(DP_CanvasState *cs, const char *path, int thread_count = DP_worker_thread_count(worker); struct DP_SaveFrameContext c = { cs, - DP_malloc_zeroed(sizeof(*c.vmbs) * DP_int_to_size(thread_count)), + DP_malloc(sizeof(*c.vmbs) * DP_int_to_size(thread_count)), frame_count, path, get_path_separator(path), @@ -940,6 +931,10 @@ save_animation_frames(DP_CanvasState *cs, const char *path, 0, }; + for (int i = 0; i < thread_count; ++i) { + DP_view_mode_buffer_init(&c.vmbs[i]); + } + int *frames = DP_malloc(sizeof(*frames) * DP_int_to_size(frame_count)); for (int i = 0; i < frame_count; ++i) { frames[i] = i; @@ -978,7 +973,7 @@ save_animation_frames(DP_CanvasState *cs, const char *path, DP_mutex_free(progress_mutex); for (int i = 0; i < thread_count; ++i) { - DP_view_mode_buffer_free(c.vmbs[i]); + DP_view_mode_buffer_dispose(&c.vmbs[i]); } DP_free(c.vmbs); @@ -1054,7 +1049,8 @@ static DP_SaveResult save_animation_gif(DP_CanvasState *cs, const char *path, return DP_SAVE_RESULT_CANCEL; } - DP_ViewModeBuffer *vmb = DP_view_mode_buffer_new(); + DP_ViewModeBuffer vmb; + DP_view_mode_buffer_init(&vmb); double centiseconds_per_frame = get_gif_centiseconds_per_frame(cs); double delay_frac = 0.0; for (int i = 0; i < frame_count; ++i) { @@ -1066,7 +1062,7 @@ static DP_SaveResult save_animation_gif(DP_CanvasState *cs, const char *path, } DP_ViewModeFilter vmf = - DP_view_mode_filter_make_frame(vmb, cs, i, NULL); + DP_view_mode_filter_make_frame(&vmb, cs, i, NULL); DP_Image *img = DP_canvas_state_to_flat_image( cs, DP_FLAT_IMAGE_RENDER_FLAGS, NULL, &vmf); double delay = centiseconds_per_frame * DP_int_to_double(instances); @@ -1079,18 +1075,18 @@ static DP_SaveResult save_animation_gif(DP_CanvasState *cs, const char *path, if (!frame_ok) { jo_gifx_abort(gif); DP_output_free(output); - DP_view_mode_buffer_free(vmb); + DP_view_mode_buffer_dispose(&vmb); return DP_SAVE_RESULT_WRITE_ERROR; } if (!report_gif_progress(progress_fn, user, i + 1, frame_count)) { jo_gifx_abort(gif); DP_output_free(output); - DP_view_mode_buffer_free(vmb); + DP_view_mode_buffer_dispose(&vmb); return DP_SAVE_RESULT_CANCEL; } } - DP_view_mode_buffer_free(vmb); + DP_view_mode_buffer_dispose(&vmb); if (!jo_gifx_end(write_gif, output, gif) || !DP_output_flush(output)) { DP_output_free(output); diff --git a/src/drawdance/libengine/dpengine/tile.c b/src/drawdance/libengine/dpengine/tile.c index ea587c5735..1efcbb708f 100644 --- a/src/drawdance/libengine/dpengine/tile.c +++ b/src/drawdance/libengine/dpengine/tile.c @@ -651,6 +651,24 @@ void DP_transient_tile_pixel_at_put(DP_TransientTile *tt, int blend_mode, int x, blend_mode); } +void DP_transient_tile_clear(DP_TransientTile *tt) +{ + DP_ASSERT(tt); + DP_ASSERT(DP_atomic_get(&tt->refcount) > 0); + DP_ASSERT(tt->transient); + memset(tt->pixels, 0, DP_TILE_BYTES); +} + +void DP_transient_tile_copy(DP_TransientTile *tt, DP_Tile *t) +{ + DP_ASSERT(tt); + DP_ASSERT(DP_atomic_get(&tt->refcount) > 0); + DP_ASSERT(tt->transient); + DP_ASSERT(t); + DP_ASSERT(DP_atomic_get(&t->refcount) > 0); + memcpy(tt->pixels, t->pixels, DP_TILE_BYTES); +} + bool DP_transient_tile_blank(DP_TransientTile *tt) { DP_ASSERT(tt); diff --git a/src/drawdance/libengine/dpengine/tile.h b/src/drawdance/libengine/dpengine/tile.h index 34f76d194f..aebab8e3f1 100644 --- a/src/drawdance/libengine/dpengine/tile.h +++ b/src/drawdance/libengine/dpengine/tile.h @@ -189,6 +189,10 @@ void DP_transient_tile_pixel_at_set(DP_TransientTile *tt, int x, int y, void DP_transient_tile_pixel_at_put(DP_TransientTile *tt, int blend_mode, int x, int y, DP_Pixel15 pixel); +void DP_transient_tile_clear(DP_TransientTile *tt); + +void DP_transient_tile_copy(DP_TransientTile *tt, DP_Tile *t); + bool DP_transient_tile_blank(DP_TransientTile *tt); void DP_transient_tile_merge(DP_TransientTile *DP_RESTRICT tt, diff --git a/src/drawdance/libengine/dpengine/view_mode.c b/src/drawdance/libengine/dpengine/view_mode.c index 6beaedf076..4d44d6f375 100644 --- a/src/drawdance/libengine/dpengine/view_mode.c +++ b/src/drawdance/libengine/dpengine/view_mode.c @@ -49,12 +49,6 @@ typedef struct DP_ViewModeTrack { const DP_OnionSkin *onion_skin; } DP_ViewModeTrack; -struct DP_ViewModeBuffer { - int capacity; - int count; - DP_ViewModeTrack *tracks; -}; - struct DP_OnionSkins { int count_below; int count_above; @@ -62,14 +56,12 @@ struct DP_OnionSkins { }; -DP_ViewModeBuffer *DP_view_mode_buffer_new(void) +void DP_view_mode_buffer_init(DP_ViewModeBuffer *vmb) { - DP_ViewModeBuffer *vmb = DP_malloc(sizeof(*vmb)); *vmb = (DP_ViewModeBuffer){0, 0, NULL}; - return vmb; } -void DP_view_mode_buffer_free(DP_ViewModeBuffer *vmb) +void DP_view_mode_buffer_dispose(DP_ViewModeBuffer *vmb) { if (vmb) { int track_count = vmb->capacity; // Not count, we want to clean em all. @@ -77,7 +69,6 @@ void DP_view_mode_buffer_free(DP_ViewModeBuffer *vmb) DP_vector_dispose(&vmb->tracks[i].hidden_layer_ids); } DP_free(vmb->tracks); - DP_free(vmb); } } diff --git a/src/drawdance/libengine/dpengine/view_mode.h b/src/drawdance/libengine/dpengine/view_mode.h index 92e85ddf55..59bd5ea651 100644 --- a/src/drawdance/libengine/dpengine/view_mode.h +++ b/src/drawdance/libengine/dpengine/view_mode.h @@ -38,7 +38,13 @@ typedef enum DP_ViewMode { DP_VIEW_MODE_FRAME, } DP_ViewMode; -typedef struct DP_ViewModeBuffer DP_ViewModeBuffer; +typedef struct DP_ViewModeTrack DP_ViewModeTrack; + +typedef struct DP_ViewModeBuffer { + int capacity; + int count; + DP_ViewModeTrack *tracks; +} DP_ViewModeBuffer; typedef struct DP_ViewModeFilter { int internal_type; @@ -84,9 +90,9 @@ typedef struct DP_OnionSkins DP_OnionSkins; typedef void (*DP_AddVisibleLayerFn)(void *user, int layer_id, bool visible); -DP_ViewModeBuffer *DP_view_mode_buffer_new(void); +void DP_view_mode_buffer_init(DP_ViewModeBuffer *vmb); -void DP_view_mode_buffer_free(DP_ViewModeBuffer *vmb); +void DP_view_mode_buffer_dispose(DP_ViewModeBuffer *vmb); DP_ViewModeFilter DP_view_mode_filter_make_default(void); diff --git a/src/libclient/canvas/canvasmodel.cpp b/src/libclient/canvas/canvasmodel.cpp index 7f7a8e5b64..0a4b1af770 100644 --- a/src/libclient/canvas/canvasmodel.cpp +++ b/src/libclient/canvas/canvasmodel.cpp @@ -54,7 +54,7 @@ CanvasModel::CanvasModel(libclient::settings::Settings &settings, m_timeline->setAclState(m_aclstate); connect(m_layerlist, &LayerListModel::autoSelectRequest, this, &CanvasModel::layerAutoselectRequest); - connect(m_paintengine, &PaintEngine::resized, this, &CanvasModel::onCanvasResize); + connect(m_paintengine, &PaintEngine::resized, this, &CanvasModel::onCanvasResize, Qt::QueuedConnection); connect(m_paintengine, &PaintEngine::layersChanged, m_layerlist, &LayerListModel::setLayers); connect(m_paintengine, &PaintEngine::timelineChanged, m_timeline, &TimelineModel::setTimeline); connect(m_paintengine, &PaintEngine::frameVisibilityChanged, m_layerlist, &LayerListModel::setLayersVisibleInFrame); diff --git a/src/libclient/canvas/paintengine.cpp b/src/libclient/canvas/paintengine.cpp index f89622ee35..603d4f7fb9 100644 --- a/src/libclient/canvas/paintengine.cpp +++ b/src/libclient/canvas/paintengine.cpp @@ -32,26 +32,26 @@ PaintEngine::PaintEngine( : QObject(parent) , m_acls{} , m_snapshotQueue{snapshotMaxCount, snapshotMinDelayMs} - , m_paintEngine{m_acls, m_snapshotQueue, wantCanvasHistoryDump, PaintEngine::onPlayback, PaintEngine::onDumpPlayback, this} + , m_paintEngine{ + m_acls, m_snapshotQueue, wantCanvasHistoryDump, // + PaintEngine::onRenderTile, PaintEngine::onRenderResize, this, // + PaintEngine::onPlayback, PaintEngine::onDumpPlayback, this} , m_fps{fps} , m_timerId{0} - , m_changedTileBounds{} - , m_lastRefreshAreaTileBounds{} - , m_lastRefreshAreaTileBoundsTouched{false} , m_cache{} , m_painter{} - , m_painterMutex{nullptr} + , m_cacheMutex{nullptr} , m_sampleColorLastDiameter(-1) , m_undoDepthLimit{DP_UNDO_DEPTH_DEFAULT} , m_updateLayersVisibleInFrame{false} { - m_painterMutex = DP_mutex_new(); + m_cacheMutex = DP_mutex_new(); start(); } PaintEngine::~PaintEngine() { - DP_mutex_free(m_painterMutex); + DP_mutex_free(m_cacheMutex); } void PaintEngine::setFps(int fps) @@ -91,11 +91,12 @@ void PaintEngine::reset( DP_Player *player) { drawdance::MessageList localResetImage = m_paintEngine.reset( - m_acls, m_snapshotQueue, localUserId, PaintEngine::onPlayback, + m_acls, m_snapshotQueue, localUserId, PaintEngine::onRenderTile, + PaintEngine::onRenderResize, this, PaintEngine::onPlayback, PaintEngine::onDumpPlayback, this, canvasState, player); + DP_mutex_lock(m_cacheMutex); m_cache = QPixmap{}; - m_lastRefreshAreaTileBounds = QRect{}; - m_lastRefreshAreaTileBoundsTouched = false; + DP_mutex_unlock(m_cacheMutex); m_undoDepthLimit = DP_UNDO_DEPTH_DEFAULT; start(); emit aclsChanged(m_acls, DP_ACL_STATE_CHANGE_MASK, true); @@ -107,12 +108,14 @@ void PaintEngine::reset( void PaintEngine::timerEvent(QTimerEvent *) { DP_PERF_SCOPE("tick"); - m_changedTileBounds = QRect{}; + DP_Rect tileBounds = { + m_canvasViewTileArea.left(), m_canvasViewTileArea.top(), + m_canvasViewTileArea.right(), m_canvasViewTileArea.bottom()}; DP_paint_engine_tick( - m_paintEngine.get(), &PaintEngine::onCatchup, - &PaintEngine::onResetLockChanged, &PaintEngine::onRecorderStateChanged, - &PaintEngine::onResized, &PaintEngine::onTileChanged, - &PaintEngine::onLayerPropsChanged, &PaintEngine::onAnnotationsChanged, + m_paintEngine.get(), tileBounds, m_renderOutsideView, + &PaintEngine::onCatchup, &PaintEngine::onResetLockChanged, + &PaintEngine::onRecorderStateChanged, &PaintEngine::onLayerPropsChanged, + &PaintEngine::onAnnotationsChanged, &PaintEngine::onDocumentMetadataChanged, &PaintEngine::onTimelineChanged, &PaintEngine::onCursorMoved, &PaintEngine::onDefaultLayer, &PaintEngine::onUndoDepthLimitSet, this); @@ -120,15 +123,6 @@ void PaintEngine::timerEvent(QTimerEvent *) if(m_updateLayersVisibleInFrame) { updateLayersVisibleInFrame(); } - - if(m_changedTileBounds.isValid()) { - QRect changedArea{ - m_changedTileBounds.x() * DP_TILE_SIZE, - m_changedTileBounds.y() * DP_TILE_SIZE, - m_changedTileBounds.width() * DP_TILE_SIZE, - m_changedTileBounds.height() * DP_TILE_SIZE}; - emit areaChanged(changedArea); - } } void PaintEngine::updateLayersVisibleInFrame() @@ -382,15 +376,27 @@ void PaintEngine::setInspectContextId(unsigned int contextId) QColor PaintEngine::sampleColor(int x, int y, int layerId, int diameter) { - drawdance::LayerContent lc = - layerId == 0 ? m_paintEngine.renderContent() - : viewCanvasState().searchLayerContent(layerId); - if(lc.isNull()) { - return Qt::transparent; + if(layerId == 0) { + // TODO: take diameter into account. + DP_mutex_lock(m_cacheMutex); + QColor color; + if(x < m_cache.width() && y < m_cache.height()) { + color = m_cache.copy(x, y, 1, 1).toImage().pixelColor(0, 0); + } else { + color = Qt::transparent; + } + DP_mutex_unlock(m_cacheMutex); + return color; } else { - return lc.sampleColorAt( - m_sampleColorStampBuffer, x, y, diameter, true, - m_sampleColorLastDiameter); + drawdance::LayerContent lc = + viewCanvasState().searchLayerContent(layerId); + if(lc.isNull()) { + return Qt::transparent; + } else { + return lc.sampleColorAt( + m_sampleColorStampBuffer, x, y, diameter, true, + m_sampleColorLastDiameter); + } } } @@ -561,57 +567,24 @@ void PaintEngine::clearDabsPreview() m_paintEngine.clearDabsPreview(); } -const QPixmap &PaintEngine::getPixmapView(const QRect &refreshArea) -{ - QRect refreshAreaTileBounds{ - QPoint{ - refreshArea.left() / DP_TILE_SIZE, - refreshArea.top() / DP_TILE_SIZE}, - QPoint{ - refreshArea.right() / DP_TILE_SIZE, - refreshArea.bottom() / DP_TILE_SIZE}}; - if(refreshAreaTileBounds == m_lastRefreshAreaTileBounds) { - if(m_lastRefreshAreaTileBoundsTouched) { - renderTileBounds(refreshAreaTileBounds); - m_lastRefreshAreaTileBoundsTouched = false; - } - } else { - renderTileBounds(refreshAreaTileBounds); - m_lastRefreshAreaTileBounds = refreshAreaTileBounds; - m_lastRefreshAreaTileBoundsTouched = false; - } - return m_cache; -} - -const QPixmap &PaintEngine::getPixmap() -{ - renderEverything(); - m_lastRefreshAreaTileBoundsTouched = false; - return m_cache; -} - -void PaintEngine::renderTileBounds(const QRect &tileBounds) +void PaintEngine::withPixmap(std::function fn) const { - DP_PaintEngine *pe = m_paintEngine.get(); - DP_paint_engine_prepare_render(pe, &PaintEngine::onRenderSize, this); - if(!m_cache.isNull() && m_painter.begin(&m_cache)) { - m_painter.setCompositionMode(QPainter::CompositionMode_Source); - DP_paint_engine_render_tile_bounds( - pe, tileBounds.left(), tileBounds.top(), tileBounds.right(), - tileBounds.bottom(), &PaintEngine::onRenderTile, this); - m_painter.end(); - } + DP_mutex_lock(m_cacheMutex); + fn(m_cache); + DP_mutex_unlock(m_cacheMutex); } -void PaintEngine::renderEverything() +void PaintEngine::setCanvasViewArea(const QRect &area) { - DP_PaintEngine *pe = m_paintEngine.get(); - DP_paint_engine_prepare_render(pe, &PaintEngine::onRenderSize, this); - if(!m_cache.isNull() && m_painter.begin(&m_cache)) { - m_painter.setCompositionMode(QPainter::CompositionMode_Source); - DP_paint_engine_render_everything(pe, &PaintEngine::onRenderTile, this); - m_painter.end(); - } + // We can't use QPoint::operator/ because that rounds instead of truncates. + m_canvasViewTileArea = QRect{ + QPoint{area.left() / DP_TILE_SIZE, area.top() / DP_TILE_SIZE}, + QPoint{area.right() / DP_TILE_SIZE, area.bottom() / DP_TILE_SIZE}}; + DP_Rect tileBounds = { + m_canvasViewTileArea.left(), m_canvasViewTileArea.top(), + m_canvasViewTileArea.right(), m_canvasViewTileArea.bottom()}; + DP_paint_engine_change_bounds( + m_paintEngine.get(), tileBounds, m_renderOutsideView); } int PaintEngine::frameCount() const @@ -636,7 +609,7 @@ QImage PaintEngine::getLayerImage(int id, const QRect &rect) const } QImage PaintEngine::getFrameImage( - const drawdance::ViewModeBuffer &vmb, int index, const QRect &rect) const + drawdance::ViewModeBuffer &vmb, int index, const QRect &rect) const { drawdance::CanvasState cs = viewCanvasState(); QRect area = rect.isNull() ? QRect{0, 0, cs.width(), cs.height()} : rect; @@ -714,23 +687,6 @@ void PaintEngine::onRecorderStateChanged(void *user, bool started) emit pe->recorderStateChanged(started); } -void PaintEngine::onResized( - void *user, int offsetX, int offsetY, int prevWidth, int prevHeight) -{ - PaintEngine *pe = static_cast(user); - pe->resized(offsetX, offsetY, QSize{prevWidth, prevHeight}); -} - -void PaintEngine::onTileChanged(void *user, int x, int y) -{ - PaintEngine *pe = static_cast(user); - pe->m_changedTileBounds |= QRect{x, y, 1, 1}; - if(!pe->m_lastRefreshAreaTileBoundsTouched && - pe->m_lastRefreshAreaTileBounds.contains(x, y)) { - pe->m_lastRefreshAreaTileBoundsTouched = true; - } -} - void PaintEngine::onLayerPropsChanged(void *user, DP_LayerPropsList *lpl) { PaintEngine *pe = static_cast(user); @@ -766,29 +722,45 @@ void PaintEngine::onCursorMoved( emit pe->cursorMoved(flags, contextId, layerId, x, y); } -void PaintEngine::onRenderSize(void *user, int width, int height) +void PaintEngine::onRenderResize( + void *user, int width, int height, int prevWidth, int prevHeight, + int offsetX, int offsetY) { PaintEngine *pe = static_cast(user); QSize size{width, height}; - if(pe->m_cache.size() != size) { - pe->m_cache = QPixmap{size}; + DP_mutex_lock(pe->m_cacheMutex); + QSize cacheSize = pe->m_cache.size(); + if(cacheSize != size) { + QPixmap pixmap{size}; + pixmap.fill(QColor(100, 100, 100)); + QPainter &painter = pe->m_painter; + painter.begin(&pixmap); + painter.drawPixmap( + QRect{QPoint{offsetX, offsetY}, cacheSize}, pe->m_cache, + QRect{QPoint{0, 0}, cacheSize}); + painter.end(); + pe->m_cache = std::move(pixmap); } + DP_mutex_unlock(pe->m_cacheMutex); + emit pe->resized(offsetX, offsetY, QSize{prevWidth, prevHeight}); } void PaintEngine::onRenderTile( - void *user, int x, int y, DP_Pixel8 *pixels, int threadIndex) + void *user, int tileX, int tileY, DP_Pixel8 *pixels) { - // My initial idea was to use an array of QPainters to spew pixels into the - // pixmap in parallel, but Qt doesn't support multiple painters on a single - // pixmap. So we have to use a single painter and lock its usage instead. - Q_UNUSED(threadIndex); + PaintEngine *pe = static_cast(user); + QRect area{ + tileX * DP_TILE_SIZE, tileY * DP_TILE_SIZE, DP_TILE_SIZE, DP_TILE_SIZE}; QImage image{ reinterpret_cast(pixels), DP_TILE_SIZE, DP_TILE_SIZE, QImage::Format_RGB32}; - PaintEngine *pe = static_cast(user); - DP_mutex_lock(pe->m_painterMutex); - pe->m_painter.drawImage(x * DP_TILE_SIZE, y * DP_TILE_SIZE, image); - DP_mutex_unlock(pe->m_painterMutex); + DP_mutex_lock(pe->m_cacheMutex); + QPainter &painter = pe->m_painter; + painter.begin(&pe->m_cache); + painter.drawImage(area.x(), area.y(), image); + painter.end(); + DP_mutex_unlock(pe->m_cacheMutex); + emit pe->areaChanged(area); } diff --git a/src/libclient/canvas/paintengine.h b/src/libclient/canvas/paintengine.h index 31aca47800..51878f47ad 100644 --- a/src/libclient/canvas/paintengine.h +++ b/src/libclient/canvas/paintengine.h @@ -10,6 +10,7 @@ extern "C" { #include #include #include +#include #include "libclient/drawdance/aclstate.h" #include "libclient/drawdance/canvashistory.h" @@ -47,18 +48,14 @@ class PaintEngine final : public QObject { drawdance::CanvasState::null(), DP_Player *player = nullptr); - /** - * @brief Get a reference to the view cache pixmap while makign sure at - * least the given area has been refreshed - * - * Should only be called by the CanvasItem, since the last refresh area is - * cached and shouldn't change much. - */ - const QPixmap &getPixmapView(const QRect &refreshArea); + void withPixmap(std::function fn) const; + + void setCanvasViewArea(const QRect &area); - //! Get a reference to the view cache pixmap while making sure the whole - //! pixmap is refreshed - const QPixmap &getPixmap(); + void setRenderOutsideView(bool renderOutsideView) + { + m_renderOutsideView = renderOutsideView; + } //! Get the number of frames in an animated canvas int frameCount() const; @@ -72,7 +69,7 @@ class PaintEngine final : public QObject { //! Render a frame QImage getFrameImage( - const drawdance::ViewModeBuffer &vmb, int index, + drawdance::ViewModeBuffer &vmb, int index, const QRect &rect = QRect()) const; //! Receive and handle messages, returns how many messages were actually @@ -253,9 +250,6 @@ class PaintEngine final : public QObject { static void onCatchup(void *user, int progress); static void onResetLockChanged(void *user, bool locked); static void onRecorderStateChanged(void *user, bool started); - static void onResized( - void *user, int offsetX, int offsetY, int prevWidth, int prevHeight); - static void onTileChanged(void *user, int x, int y); static void onLayerPropsChanged(void *user, DP_LayerPropsList *lpl); static void onAnnotationsChanged(void *user, DP_AnnotationList *al); static void onDocumentMetadataChanged(void *user, DP_DocumentMetadata *dm); @@ -263,13 +257,13 @@ class PaintEngine final : public QObject { static void onCursorMoved( void *user, unsigned int flags, unsigned int contextId, int layerId, int x, int y); - static void onRenderSize(void *user, int width, int height); + static void onRenderResize( + void *user, int width, int height, int prevWidth, int prevHeight, + int offsetX, int offsetY); static void - onRenderTile(void *user, int x, int y, DP_Pixel8 *pixels, int threadIndex); + onRenderTile(void *user, int tileX, int tileY, DP_Pixel8 *pixels); void start(); - void renderTileBounds(const QRect &tileBounds); - void renderEverything(); void updateLayersVisibleInFrame(); @@ -278,12 +272,11 @@ class PaintEngine final : public QObject { drawdance::PaintEngine m_paintEngine; int m_fps; int m_timerId; - QRect m_changedTileBounds; - QRect m_lastRefreshAreaTileBounds; - bool m_lastRefreshAreaTileBoundsTouched; QPixmap m_cache; QPainter m_painter; - DP_Mutex *m_painterMutex; + DP_Mutex *m_cacheMutex; + QRect m_canvasViewTileArea; + bool m_renderOutsideView = false; uint16_t m_sampleColorStampBuffer[DP_DRAW_CONTEXT_STAMP_BUFFER_SIZE]; int m_sampleColorLastDiameter; int m_undoDepthLimit; diff --git a/src/libclient/drawdance/canvasstate.cpp b/src/libclient/drawdance/canvasstate.cpp index 178eac5b81..1158de9723 100644 --- a/src/libclient/drawdance/canvasstate.cpp +++ b/src/libclient/drawdance/canvasstate.cpp @@ -221,7 +221,6 @@ LayerContent CanvasState::searchLayerContent(int layerId) const } } - DP_FloodFillResult CanvasState::floodFill( int x, int y, const QColor &fillColor, double tolerance, int layerId, int sizeLimit, int gap, int expand, int featherRadius, diff --git a/src/libclient/drawdance/paintengine.cpp b/src/libclient/drawdance/paintengine.cpp index 313bc1d89b..a19cfdcb20 100644 --- a/src/libclient/drawdance/paintengine.cpp +++ b/src/libclient/drawdance/paintengine.cpp @@ -14,14 +14,17 @@ extern "C" { namespace drawdance { -PaintEngine::PaintEngine(AclState &acls, SnapshotQueue &sq, - bool wantCanvasHistoryDump, DP_PaintEnginePlaybackFn playbackFn, - DP_PaintEngineDumpPlaybackFn dumpPlaybackFn, void *playbackUser, - const CanvasState &canvasState) +PaintEngine::PaintEngine( + AclState &acls, SnapshotQueue &sq, bool wantCanvasHistoryDump, + DP_RendererTileFn rendererTileFn, DP_RendererResizeFn rendererResizeFn, + void *rendererUser, DP_PaintEnginePlaybackFn playbackFn, + DP_PaintEngineDumpPlaybackFn dumpPlaybackFn, void *playbackUser, + const CanvasState &canvasState) : m_paintDc{DrawContextPool::acquire()} , m_previewDc{DrawContextPool::acquire()} , m_data(DP_paint_engine_new_inc(m_paintDc.get(), m_previewDc.get(), - acls.get(), canvasState.get(), DP_snapshot_queue_on_save_point, sq.get(), + acls.get(), canvasState.get(), rendererTileFn, rendererResizeFn, + rendererUser, DP_snapshot_queue_on_save_point, sq.get(), wantCanvasHistoryDump, getDumpDir().toUtf8().constData(), &PaintEngine::getTimeMs, nullptr, nullptr, playbackFn, dumpPlaybackFn, playbackUser)) @@ -40,7 +43,8 @@ DP_PaintEngine *PaintEngine::get() MessageList PaintEngine::reset( AclState &acls, SnapshotQueue &sq, uint8_t localUserId, - DP_PaintEnginePlaybackFn playbackFn, + DP_RendererTileFn rendererTileFn, DP_RendererResizeFn rendererResizeFn, + void *rendererUser, DP_PaintEnginePlaybackFn playbackFn, DP_PaintEngineDumpPlaybackFn dumpPlaybackFn, void *playbackUser, const CanvasState &canvasState, DP_Player *player) { @@ -50,7 +54,8 @@ MessageList PaintEngine::reset( DP_paint_engine_free_join(m_data); acls.reset(localUserId); m_data = DP_paint_engine_new_inc(m_paintDc.get(), m_previewDc.get(), - acls.get(), canvasState.get(), DP_snapshot_queue_on_save_point, sq.get(), + acls.get(), canvasState.get(), rendererTileFn, rendererResizeFn, + rendererUser, DP_snapshot_queue_on_save_point, sq.get(), wantCanvasHistoryDump, getDumpDir().toUtf8().constData(), &PaintEngine::getTimeMs, nullptr, player, playbackFn, dumpPlaybackFn, playbackUser); @@ -62,12 +67,6 @@ int PaintEngine::renderThreadCount() const return DP_paint_engine_render_thread_count(m_data); } -LayerContent PaintEngine::renderContent() const -{ - DP_TransientLayerContent *tlc = DP_paint_engine_render_content_noinc(m_data); - return LayerContent::inc(reinterpret_cast(tlc)); -} - void PaintEngine::setLocalDrawingInProgress(bool localDrawingInProgress) { DP_paint_engine_local_drawing_in_progress_set(m_data, localDrawingInProgress); diff --git a/src/libclient/drawdance/paintengine.h b/src/libclient/drawdance/paintengine.h index 1677b79254..c2be7fc0bc 100644 --- a/src/libclient/drawdance/paintengine.h +++ b/src/libclient/drawdance/paintengine.h @@ -33,7 +33,8 @@ class PaintEngine { PaintEngine( AclState &acls, SnapshotQueue &sq, bool wantCanvasHistoryDump, - DP_PaintEnginePlaybackFn playbackFn, + DP_RendererTileFn rendererTileFn, DP_RendererResizeFn rendererResizeFn, + void *rendererUser, DP_PaintEnginePlaybackFn playbackFn, DP_PaintEngineDumpPlaybackFn dumpPlaybackFn, void *playbackUser, const CanvasState &canvasState = CanvasState::null()); @@ -47,15 +48,14 @@ class PaintEngine { DP_PaintEngine *get(); MessageList reset(AclState &acls, SnapshotQueue &sq, uint8_t localUserId, - DP_PaintEnginePlaybackFn playbackFn, + DP_RendererTileFn rendererTileFn, DP_RendererResizeFn rendererResizeFn, + void *rendererUser, DP_PaintEnginePlaybackFn playbackFn, DP_PaintEngineDumpPlaybackFn dumpPlaybackFn, void *playbackUser, const CanvasState &canvasState = CanvasState::null(), DP_Player *player = nullptr); int renderThreadCount() const; - LayerContent renderContent() const; - void setLocalDrawingInProgress(bool localDrawingInProgress); void setWantCanvasHistoryDump(bool wantCanvasHistoryDump); diff --git a/src/libclient/drawdance/viewmode.cpp b/src/libclient/drawdance/viewmode.cpp index 5f425be5b3..d2a009f190 100644 --- a/src/libclient/drawdance/viewmode.cpp +++ b/src/libclient/drawdance/viewmode.cpp @@ -1,26 +1,22 @@ // SPDX-License-Identifier: GPL-3.0-or-later -extern "C" { -#include "dpengine/view_mode.h" -} - #include "libclient/drawdance/viewmode.h" namespace drawdance { ViewModeBuffer::ViewModeBuffer() - : m_data{DP_view_mode_buffer_new()} { + DP_view_mode_buffer_init(&m_data); } ViewModeBuffer::~ViewModeBuffer() { - DP_view_mode_buffer_free(m_data); + DP_view_mode_buffer_dispose(&m_data); } -DP_ViewModeBuffer *ViewModeBuffer::get() const +DP_ViewModeBuffer *ViewModeBuffer::get() { - return m_data; + return &m_data; } } diff --git a/src/libclient/drawdance/viewmode.h b/src/libclient/drawdance/viewmode.h index 2567f73cb2..fb0d0b0350 100644 --- a/src/libclient/drawdance/viewmode.h +++ b/src/libclient/drawdance/viewmode.h @@ -3,7 +3,9 @@ #ifndef DRAWDANCE_VIEWMODE_H #define DRAWDANCE_VIEWMODE_H -struct DP_ViewModeBuffer; +extern "C" { +#include "dpengine/view_mode.h" +} namespace drawdance { @@ -17,10 +19,10 @@ class ViewModeBuffer final { ViewModeBuffer &operator=(const ViewModeBuffer &) = delete; ViewModeBuffer &operator=(ViewModeBuffer &&) = delete; - DP_ViewModeBuffer *get() const; + DP_ViewModeBuffer *get(); private: - DP_ViewModeBuffer *m_data; + DP_ViewModeBuffer m_data; }; }