From fefbd1c259c912fe126a5f34245a8b4c494cb753 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Wed, 14 Apr 2021 16:19:42 +0300 Subject: Progressive document rendering VisBuf now guarantees that all the buffers are sequentially ordered. This ensure complete utilization of all the buffers. Previously some buffers were not used at all, or allocated to the same origin as another one (!). DocumentWidget is able to progressively fill up a buffer while scrolling. After any change to the visible region has been detected (and there are no ongoing changes), prerender the remaining runs one at a time. This solves the issue where one text run would be always unnecessarily rendered while scrolling, even if the scroll distance was just one pixel. --- src/app.c | 2 +- src/gmdocument.c | 24 +++++ src/gmdocument.h | 6 +- src/periodic.c | 13 ++- src/periodic.h | 6 +- src/ui/documentwidget.c | 276 +++++++++++++++++++++++++++++++++++++----------- src/ui/visbuf.c | 149 ++++++++++++++++++-------- src/ui/visbuf.h | 10 +- 8 files changed, 369 insertions(+), 117 deletions(-) (limited to 'src') diff --git a/src/app.c b/src/app.c index 11c79bcb..bc15183a 100644 --- a/src/app.c +++ b/src/app.c @@ -906,7 +906,7 @@ void processEvents_App(enum iAppEventMode eventMode) { iApp *d = &app_; SDL_Event ev; iBool gotEvents = iFalse; - postCommands_Periodic(&d->periodic); + dispatchCommands_Periodic(&d->periodic); while (nextEvent_App_(d, eventMode, &ev)) { #if defined (iPlatformAppleMobile) if (processEvent_iOS(&ev)) { diff --git a/src/gmdocument.c b/src/gmdocument.c index e756a9d9..1e05f438 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c @@ -1494,6 +1494,30 @@ void render_GmDocument(const iGmDocument *d, iRangei visRangeY, iGmDocumentRende } } +static iBool isValidRun_GmDocument_(const iGmDocument *d, const iGmRun *run) { + return run >= (const iGmRun *) constAt_Array(&d->layout, 0) && + run < (const iGmRun *) constEnd_Array(&d->layout); +} + +const iGmRun *renderProgressive_GmDocument(const iGmDocument *d, const iGmRun *first, int dir, + size_t maxCount, + iRangei visRangeY, iGmDocumentRenderFunc render, + void *context) { + const iGmRun *run = first; + while (isValidRun_GmDocument_(d, run)) { + if ((dir < 0 && bottom_Rect(run->visBounds) < visRangeY.start) || + (dir > 0 && top_Rect(run->visBounds) >= visRangeY.end)) { + break; + } + if (maxCount-- == 0) { + break; + } + render(context, run); + run += dir; + } + return isValidRun_GmDocument_(d, run) ? run : NULL; +} + iInt2 size_GmDocument(const iGmDocument *d) { return d->size; } diff --git a/src/gmdocument.h b/src/gmdocument.h index ce148164..5d34dbaf 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h @@ -176,7 +176,11 @@ iMedia * media_GmDocument (iGmDocument *); const iMedia * constMedia_GmDocument (const iGmDocument *); void render_GmDocument (const iGmDocument *, iRangei visRangeY, - iGmDocumentRenderFunc render, void *); + iGmDocumentRenderFunc render, void *); /* includes partial overlaps */ +const iGmRun * renderProgressive_GmDocument(const iGmDocument *d, const iGmRun *first, int dir, + size_t maxCount, + iRangei visRangeY, iGmDocumentRenderFunc render, + void *context); iInt2 size_GmDocument (const iGmDocument *); const iGmRun * siteBanner_GmDocument (const iGmDocument *); iBool hasSiteBanner_GmDocument (const iGmDocument *); diff --git a/src/periodic.c b/src/periodic.c index 29c26de9..9e9dfdba 100644 --- a/src/periodic.c +++ b/src/periodic.c @@ -21,10 +21,12 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "periodic.h" +#include "ui/widget.h" #include "app.h" #include #include +#include #include iDeclareType(PeriodicCommand) @@ -54,7 +56,7 @@ iDefineTypeConstructionArgs(PeriodicCommand, (iAny *ctx, const char *cmd), ctx, static const uint32_t postingInterval_Periodic_ = 500; -iBool postCommands_Periodic(iPeriodic *d) { +iBool dispatchCommands_Periodic(iPeriodic *d) { const uint32_t now = SDL_GetTicks(); if (now - d->lastPostTime < postingInterval_Periodic_) { return iFalse; @@ -63,7 +65,14 @@ iBool postCommands_Periodic(iPeriodic *d) { iBool wasPosted = iFalse; lock_Mutex(d->mutex); iConstForEach(Array, i, &d->commands.values) { - postCommandString_App(&((const iPeriodicCommand *) i.value)->command); + const iPeriodicCommand *pc = i.value; + const SDL_UserEvent ev = { + .type = SDL_USEREVENT, + .code = command_UserEventCode, + .data1 = (void *) cstr_String(&pc->command) + }; + iAssert(isInstance_Object(pc->context, &Class_Widget)); + dispatchEvent_Widget(pc->context, (const SDL_Event *) &ev); wasPosted = iTrue; } unlock_Mutex(d->mutex); diff --git a/src/periodic.h b/src/periodic.h index db90b848..8886617b 100644 --- a/src/periodic.h +++ b/src/periodic.h @@ -40,7 +40,7 @@ iLocalDef iBool isEmpty_Periodic(const iPeriodic *d) { return isEmpty_SortedArray(&d->commands); } -void add_Periodic (iPeriodic *, iAny *context, const char *command); -void remove_Periodic (iPeriodic *, iAny *context); +void add_Periodic (iPeriodic *, iAnyObject *context, const char *command); +void remove_Periodic (iPeriodic *, iAnyObject *context); -iBool postCommands_Periodic (iPeriodic *); +iBool dispatchCommands_Periodic( iPeriodic *); diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 70499ed5..ddca557b 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -20,7 +20,7 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -/* TODO: This file is a little too large. DocumentWidget could be split into +/* TODO: This file is a little (!) too large. DocumentWidget could be split into a couple of smaller objects. One for rendering the document, for instance. */ #include "documentwidget.h" @@ -41,6 +41,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "labelwidget.h" #include "media.h" #include "paint.h" +#include "periodic.h" #include "mediaui.h" #include "scrollwidget.h" #include "touch.h" @@ -156,12 +157,14 @@ struct Impl_DrawBufs { int flags; SDL_Texture * sideIconBuf; iTextBuf * timestampBuf; + iRangei lastVis; }; static void init_DrawBufs(iDrawBufs *d) { d->flags = 0; d->sideIconBuf = NULL; d->timestampBuf = NULL; + iZap(d->lastVis); } static void deinit_DrawBufs(iDrawBufs *d) { @@ -175,9 +178,23 @@ iDefineTypeConstruction(DrawBufs) /*----------------------------------------------------------------------------------------------*/ +iDeclareType(VisBufMeta) + +struct Impl_VisBufMeta { + iGmRunRange runsDrawn; +}; + +static void visBufInvalidated_(iVisBuf *d, size_t index) { + iVisBufMeta *meta = d->buffers[index].user; + iZap(meta->runsDrawn); +} + +/*----------------------------------------------------------------------------------------------*/ + static void animate_DocumentWidget_ (void *ticker); static void animateMedia_DocumentWidget_ (iDocumentWidget *d); static void updateSideIconBuf_DocumentWidget_ (const iDocumentWidget *d); +static void prerender_DocumentWidget_ (iAny *); static const int smoothDuration_DocumentWidget_ = 600; /* milliseconds */ static const int outlineMinWidth_DocumentWdiget_ = 45; /* times gap_UI */ @@ -251,8 +268,8 @@ struct Impl_DocumentWidget { const iGmRun * hoverAltPre; /* for drawing alt text */ const iGmRun * hoverLink; const iGmRun * contextLink; - const iGmRun * firstVisibleRun; - const iGmRun * lastVisibleRun; + iGmRunRange visibleRuns; + iGmRunRange renderRuns; iClick click; iInt2 contextPos; /* coordinates of latest right click */ iString pendingGotoHeading; @@ -265,6 +282,7 @@ struct Impl_DocumentWidget { iWidget * playerMenu; iWidget * copyMenu; iVisBuf * visBuf; + iGmRunRange * visBufMeta; iPtrSet * invalidRuns; iDrawBufs * drawBufs; /* dynamic state for drawing */ iTranslation * translation; @@ -306,10 +324,17 @@ void init_DocumentWidget(iDocumentWidget *d) { d->hoverAltPre = NULL; d->hoverLink = NULL; d->contextLink = NULL; - d->firstVisibleRun = NULL; - d->lastVisibleRun = NULL; - d->visBuf = new_VisBuf(); - d->invalidRuns = new_PtrSet(); + iZap(d->renderRuns); + iZap(d->visibleRuns); + d->visBuf = new_VisBuf(); { + d->visBufMeta = malloc(sizeof(iVisBufMeta) * numBuffers_VisBuf); + /* Additional metadata for each buffer. */ + d->visBuf->bufferInvalidated = visBufInvalidated_; + for (size_t i = 0; i < numBuffers_VisBuf; i++) { + d->visBuf->buffers[i].user = d->visBufMeta + i; + } + } + d->invalidRuns = new_PtrSet(); init_Anim(&d->sideOpacity, 0); init_Anim(&d->altTextOpacity, 0); d->sourceStatus = none_GmStatusCode; @@ -349,9 +374,12 @@ void init_DocumentWidget(iDocumentWidget *d) { void deinit_DocumentWidget(iDocumentWidget *d) { removeTicker_App(animate_DocumentWidget_, d); + removeTicker_App(prerender_DocumentWidget_, d); + remove_Periodic(periodic_App(), d); delete_Translation(d->translation); - delete_DrawBufs(d->drawBufs); + delete_DrawBufs(d->drawBufs); delete_VisBuf(d->visBuf); + free(d->visBufMeta); delete_PtrSet(d->invalidRuns); iRelease(d->media); iRelease(d->request); @@ -486,10 +514,10 @@ static iRangei visibleRange_DocumentWidget_(const iDocumentWidget *d) { static void addVisible_DocumentWidget_(void *context, const iGmRun *run) { iDocumentWidget *d = context; if (~run->flags & decoration_GmRunFlag && !run->mediaId) { - if (!d->firstVisibleRun) { - d->firstVisibleRun = run; + if (!d->visibleRuns.start) { + d->visibleRuns.start = run; } - d->lastVisibleRun = run; + d->visibleRuns.end = run; } if (run->preId) { pushBack_PtrArray(&d->visiblePre, run); @@ -743,14 +771,14 @@ static void animateMedia_DocumentWidget_(iDocumentWidget *d) { static iRangecc currentHeading_DocumentWidget_(const iDocumentWidget *d) { iRangecc heading = iNullRange; - if (d->firstVisibleRun) { + if (d->visibleRuns.start) { iConstForEach(Array, i, headings_GmDocument(d->doc)) { const iGmHeading *head = i.value; if (head->level == 0) { - if (head->text.start <= d->firstVisibleRun->text.start) { + if (head->text.start <= d->visibleRuns.start->text.start) { heading = head->text; } - if (d->lastVisibleRun && head->text.start > d->lastVisibleRun->text.start) { + if (d->visibleRuns.end && head->text.start > d->visibleRuns.end->text.start) { break; } } @@ -766,6 +794,14 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) { !isSuccess_GmStatusCode(d->sourceStatus)); const iRangei visRange = visibleRange_DocumentWidget_(d); const iRect bounds = bounds_Widget(as_Widget(d)); + if (!equal_Rangei(visRange, d->drawBufs->lastVis)) { + /* After scrolling/resizing stops, begin prendering the visbuf contents. + `Periodic` is used for detecting when the visible range has been modified. */ + removeTicker_App(prerender_DocumentWidget_, d); + remove_Periodic(periodic_App(), d); + add_Periodic(periodic_App(), d, + format_CStr("document.render from:%d to:%d", visRange.start, visRange.end)); + } setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax_DocumentWidget_(d) }); const int docSize = size_GmDocument(d->doc).y; setThumb_ScrollWidget(d->scroll, @@ -777,7 +813,7 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) { clear_PtrArray(&d->visibleMedia); const iRangecc oldHeading = currentHeading_DocumentWidget_(d); /* Scan for visible runs. */ { - d->firstVisibleRun = NULL; + iZap(d->visibleRuns); render_GmDocument(d->doc, visRange, addVisible_DocumentWidget_, d); } const iRangecc newHeading = currentHeading_DocumentWidget_(d); @@ -898,8 +934,8 @@ static void documentRunsInvalidated_DocumentWidget_(iDocumentWidget *d) { d->hoverAltPre = NULL; d->hoverLink = NULL; d->contextLink = NULL; - d->firstVisibleRun = NULL; - d->lastVisibleRun = NULL; + iZap(d->visibleRuns); + iZap(d->renderRuns); } void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) { @@ -1675,7 +1711,7 @@ static iBool updateDocumentWidthRetainingScrollPosition_DocumentWidget_(iDocumen } /* Font changes (i.e., zooming) will keep the view centered, otherwise keep the top of the visible area fixed. */ - const iGmRun *run = keepCenter ? middleRun_DocumentWidget_(d) : d->firstVisibleRun; + const iGmRun *run = keepCenter ? middleRun_DocumentWidget_(d) : d->visibleRuns.start; const char * runLoc = (run ? run->text.start : NULL); int voffset = 0; if (!keepCenter && run) { @@ -1727,7 +1763,17 @@ static iBool handlePinch_DocumentWidget_(iDocumentWidget *d, const char *cmd) { static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) { iWidget *w = as_Widget(d); - if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "font.changed")) { + if (equal_Command(cmd, "document.render")) { + const iRangei vis = { argLabel_Command(cmd, "from"), argLabel_Command(cmd, "to") }; + const iRangei current = visibleRange_DocumentWidget_(d); + if (equal_Rangei(vis, current)) { + remove_Periodic(periodic_App(), d); + /* Scrolling has stopped, begin filling up the buffer. */ + addTicker_App(prerender_DocumentWidget_, d); + } + return iTrue; + } + else if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "font.changed")) { /* Alt/Option key may be involved in window size changes. */ setLinkNumberMode_DocumentWidget_(d, iFalse); d->phoneToolbar = findWidget_App("toolbar"); @@ -3103,6 +3149,8 @@ iDeclareType(DrawContext) struct Impl_DrawContext { const iDocumentWidget *widget; iRect widgetBounds; + iRect docBounds; + iRangei vis; iInt2 viewPos; /* document area origin */ iPaint paint; iBool inSelectMark; @@ -3110,6 +3158,7 @@ struct Impl_DrawContext { iBool showLinkNumbers; iRect firstMarkRect; iRect lastMarkRect; + iGmRunRange runsDrawn; }; static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iColorId color, @@ -3269,6 +3318,14 @@ static void drawBannerRun_DrawContext_(iDrawContext *d, const iGmRun *run, iInt2 static void drawRun_DrawContext_(void *context, const iGmRun *run) { iDrawContext *d = context; const iInt2 origin = d->viewPos; + /* Keep track of the drawn visible runs. */ { + if (!d->runsDrawn.start || run < d->runsDrawn.start) { + d->runsDrawn.start = run; + } + if (!d->runsDrawn.end || run > d->runsDrawn.end) { + d->runsDrawn.end = run; + } + } if (run->mediaType == image_GmRunMediaType) { SDL_Texture *tex = imageTexture_Media(media_GmDocument(d->widget->doc), run->mediaId); const iRect dst = moved_Rect(run->visBounds, origin); @@ -3291,6 +3348,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { /* Media UIs are drawn afterwards as a dynamic overlay. */ return; } +// printf(" drawRun: {%s}\n", cstr_Rangecc(run->text)); enum iColorId fg = run->color; const iGmDocument *doc = d->widget->doc; iBool isHover = @@ -3644,48 +3702,100 @@ static void drawPin_(iPaint *p, iRect rangeRect, int dir) { init1_I2(gap_UI * 2)), pinColor); } -static void draw_DocumentWidget_(const iDocumentWidget *d) { - const iWidget *w = constAs_Widget(d); - const iRect bounds = bounds_Widget(w); - iVisBuf * visBuf = d->visBuf; /* will be updated now */ - if (width_Rect(bounds) <= 0) { - return; - } - draw_Widget(w); - if (d->drawBufs->flags & updateTimestampBuf_DrawBufsFlag) { - updateTimestampBuf_DocumentWidget_(d); - } - if (d->drawBufs->flags & updateSideBuf_DrawBufsFlag) { - updateSideIconBuf_DocumentWidget_(d); - } - allocVisBuffer_DocumentWidget_(d); +static iBool render_DocumentWidget_(const iDocumentWidget *d, iDrawContext *ctx, iBool prerenderExtra) { + iBool didDraw = iFalse; + const iRect bounds = bounds_Widget(constAs_Widget(d)); const iRect ctxWidgetBounds = init_Rect( 0, 0, width_Rect(bounds) - constAs_Widget(d->scroll)->rect.size.x, height_Rect(bounds)); - const iRect docBounds = documentBounds_DocumentWidget_(d); - iDrawContext ctx = { - .widget = d, - .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0, - }; - const iBool isTouchSelecting = (flags_Widget(w) & touchDrag_WidgetFlag) != 0; - /* Currently visible region. */ - const iRangei vis = visibleRange_DocumentWidget_(d); const iRangei full = { 0, size_GmDocument(d->doc).y }; + const iRangei vis = ctx->vis; + iVisBuf *visBuf = d->visBuf; /* will be updated now */ + /* Swap buffers around to have room available both before and after the visible region. */ + allocVisBuffer_DocumentWidget_(d); reposition_VisBuf(visBuf, vis); - iRangei invalidRange[iElemCount(d->visBuf->buffers)]; - invalidRanges_VisBuf(visBuf, full, invalidRange); /* Redraw the invalid ranges. */ { - iPaint *p = &ctx.paint; + iPaint *p = &ctx->paint; init_Paint(p); iForIndices(i, visBuf->buffers) { - iVisBufTexture *buf = &visBuf->buffers[i]; - ctx.widgetBounds = moved_Rect(ctxWidgetBounds, init_I2(0, -buf->origin)); - ctx.viewPos = init_I2(left_Rect(docBounds) - left_Rect(bounds), -buf->origin); - if (!isEmpty_Rangei(invalidRange[i])) { - beginTarget_Paint(p, buf->texture); + iVisBufTexture *buf = &visBuf->buffers[i]; + iVisBufMeta * meta = buf->user; + const iRangei bufRange = bufferRange_VisBuf(visBuf, i); + const iRangei bufVisRange = intersect_Rangei(bufRange, vis); + ctx->widgetBounds = moved_Rect(ctxWidgetBounds, init_I2(0, -buf->origin)); + ctx->viewPos = init_I2(left_Rect(ctx->docBounds) - left_Rect(bounds), -buf->origin); + //printf(" buffer %zu: invalid range %d...%d\n", i, invalidRange[i].start, invalidRange[i].end); + if (!prerenderExtra && !isEmpty_Range(&bufVisRange)) { + didDraw = iTrue; if (isEmpty_Rangei(buf->validRange)) { - fillRect_Paint(p, (iRect){ zero_I2(), visBuf->texSize }, tmBackground_ColorId); + /* Fill the required currently visible range (vis). */ + const iRangei bufVisRange = intersect_Rangei(bufRange, vis); + if (!isEmpty_Range(&bufVisRange)) { + beginTarget_Paint(p, buf->texture); + fillRect_Paint(p, (iRect){ zero_I2(), visBuf->texSize }, tmBackground_ColorId); + iZap(ctx->runsDrawn); + render_GmDocument(d->doc, bufVisRange, drawRun_DrawContext_, ctx); + meta->runsDrawn = ctx->runsDrawn; + meta->runsDrawn.start--; + meta->runsDrawn.end++; + buf->validRange = bufVisRange; + // printf(" buffer %zu valid %d...%d\n", i, bufRange.start, bufRange.end); + } + } + else { + /* Progressively fill the required runs. */ + if (meta->runsDrawn.start) { + beginTarget_Paint(p, buf->texture); + meta->runsDrawn.start = renderProgressive_GmDocument(d->doc, meta->runsDrawn.start, + -1, iInvalidSize, + bufVisRange, + drawRun_DrawContext_, + ctx); + buf->validRange.start = bufVisRange.start; + } + if (meta->runsDrawn.end) { + beginTarget_Paint(p, buf->texture); + meta->runsDrawn.end = renderProgressive_GmDocument(d->doc, meta->runsDrawn.end, + +1, iInvalidSize, + bufVisRange, + drawRun_DrawContext_, + ctx); + buf->validRange.end = bufVisRange.end; + } + } + } + /* Progressively draw the rest of the buffer if it isn't fully valid. */ + if (prerenderExtra && !equal_Rangei(bufRange, buf->validRange)) { + const iGmRun *next; + if (meta->runsDrawn.start) { + const iRangei upper = intersect_Rangei(bufRange, (iRangei){ full.start, buf->validRange.start }); + if (upper.end > upper.start) { + beginTarget_Paint(p, buf->texture); + next = renderProgressive_GmDocument(d->doc, meta->runsDrawn.start, + -1, 1, upper, + drawRun_DrawContext_, + ctx); + if (meta->runsDrawn.start != next) { + meta->runsDrawn.start = next; + didDraw = iTrue; + } + buf->validRange.start = bufRange.start; + } + } + if (meta->runsDrawn.end) { + const iRangei lower = intersect_Rangei(bufRange, (iRangei){ buf->validRange.end, full.end }); + if (lower.end > lower.start) { + beginTarget_Paint(p, buf->texture); + next = renderProgressive_GmDocument(d->doc, meta->runsDrawn.end, + +1, 1, lower, + drawRun_DrawContext_, + ctx); + if (meta->runsDrawn.end != next) { + meta->runsDrawn.end = next; + didDraw = iTrue; + } + buf->validRange.end = bufRange.end; + } } - render_GmDocument(d->doc, invalidRange[i], drawRun_DrawContext_, &ctx); } /* Draw any invalidated runs that fall within this buffer. */ { const iRangei bufRange = { buf->origin, buf->origin + visBuf->texSize.y }; @@ -3694,7 +3804,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { const iGmRun *run = *r.value; if (isOverlapping_Rangei(bufRange, ySpan_Rect(run->visBounds))) { beginTarget_Paint(p, buf->texture); - fillRect_Paint(&ctx.paint, + fillRect_Paint(p, init_Rect(0, run->visBounds.pos.y - buf->origin, visBuf->texSize.x, @@ -3707,21 +3817,61 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { const iGmRun *run = *r.value; if (isOverlapping_Rangei(bufRange, ySpan_Rect(run->visBounds))) { beginTarget_Paint(p, buf->texture); - drawRun_DrawContext_(&ctx, run); + drawRun_DrawContext_(ctx, run); } } } - endTarget_Paint(&ctx.paint); + endTarget_Paint(p); } - validate_VisBuf(visBuf); clear_PtrSet(d->invalidRuns); } + return didDraw; +} + +static void prerender_DocumentWidget_(iAny *context) { + const iDocumentWidget *d = context; + iDrawContext ctx = { + .widget = d, + .docBounds = documentBounds_DocumentWidget_(d), + .vis = visibleRange_DocumentWidget_(d), + .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0 + }; +// printf("%u prerendering\n", SDL_GetTicks()); + if (render_DocumentWidget_(d, &ctx, iTrue /* just fill up progressively */)) { + /* Something was drawn, should check if there is still more to do. */ + addTicker_App(prerender_DocumentWidget_, context); + } +} + +static void draw_DocumentWidget_(const iDocumentWidget *d) { + const iWidget *w = constAs_Widget(d); + const iRect bounds = bounds_Widget(w); + if (width_Rect(bounds) <= 0) { + return; + } + draw_Widget(w); + if (d->drawBufs->flags & updateTimestampBuf_DrawBufsFlag) { + updateTimestampBuf_DocumentWidget_(d); + } + if (d->drawBufs->flags & updateSideBuf_DrawBufsFlag) { + updateSideIconBuf_DocumentWidget_(d); + } + const iRect docBounds = documentBounds_DocumentWidget_(d); + const iRangei vis = visibleRange_DocumentWidget_(d); + iDrawContext ctx = { + .widget = d, + .docBounds = docBounds, + .vis = vis, + .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0, + }; + render_DocumentWidget_(d, &ctx, iFalse /* just the mandatory parts */); setClip_Paint(&ctx.paint, bounds); const int yTop = docBounds.pos.y - value_Anim(&d->scrollY); - draw_VisBuf(visBuf, init_I2(bounds.pos.x, yTop)); + draw_VisBuf(d->visBuf, init_I2(bounds.pos.x, yTop)); /* Text markers. */ + const iBool isTouchSelecting = (flags_Widget(w) & touchDrag_WidgetFlag) != 0; if (!isEmpty_Range(&d->foundMark) || !isEmpty_Range(&d->selectMark)) { - SDL_Renderer *render = renderer_Window(get_Window()); + SDL_Renderer *render = renderer_Window(get_Window()); ctx.firstMarkRect = zero_Rect(); ctx.lastMarkRect = zero_Rect(); SDL_SetRenderDrawBlendMode(render, @@ -3729,15 +3879,15 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { : SDL_BLENDMODE_BLEND); ctx.viewPos = topLeft_Rect(docBounds); /* Marker starting outside the visible range? */ - if (d->firstVisibleRun) { + if (d->visibleRuns.start) { if (!isEmpty_Range(&d->selectMark) && - d->selectMark.start < d->firstVisibleRun->text.start && - d->selectMark.end > d->firstVisibleRun->text.start) { + d->selectMark.start < d->visibleRuns.start->text.start && + d->selectMark.end > d->visibleRuns.start->text.start) { ctx.inSelectMark = iTrue; } if (isEmpty_Range(&d->foundMark) && - d->foundMark.start < d->firstVisibleRun->text.start && - d->foundMark.end > d->firstVisibleRun->text.start) { + d->foundMark.start < d->visibleRuns.start->text.start && + d->foundMark.end > d->visibleRuns.start->text.start) { ctx.inFoundMark = iTrue; } } diff --git a/src/ui/visbuf.c b/src/ui/visbuf.c index 198ad574..a9443b34 100644 --- a/src/ui/visbuf.c +++ b/src/ui/visbuf.c @@ -29,6 +29,8 @@ iDefineTypeConstruction(VisBuf) void init_VisBuf(iVisBuf *d) { d->texSize = zero_I2(); iZap(d->buffers); + iZap(d->vis); + d->bufferInvalidated = NULL; } void deinit_VisBuf(iVisBuf *d) { @@ -36,9 +38,14 @@ void deinit_VisBuf(iVisBuf *d) { } void invalidate_VisBuf(iVisBuf *d) { + int origin = d->vis.start - d->texSize.y; iForIndices(i, d->buffers) { - d->buffers[i].origin = i * d->texSize.y; + d->buffers[i].origin = origin; + origin += d->texSize.y; iZap(d->buffers[i].validRange); + if (d->bufferInvalidated) { + d->bufferInvalidated(d, i); + } } } @@ -58,9 +65,13 @@ void alloc_VisBuf(iVisBuf *d, const iInt2 size, int granularity) { texSize.x, texSize.y); SDL_SetTextureBlendMode(tex->texture, SDL_BLENDMODE_NONE); - tex->origin = i * texSize.y; - iZap(tex->validRange); +// tex->origin = i * texSize.y; +// iZap(tex->validRange); +// if (d->invalidUserData) { +// d->invalidUserData(i, d->buffers[i].user); +// } } + invalidate_VisBuf(d); } } @@ -72,53 +83,74 @@ void dealloc_VisBuf(iVisBuf *d) { } } -void reposition_VisBuf(iVisBuf *d, const iRangei vis) { - d->vis = vis; - iRangei good = { 0, 0 }; - size_t avail[iElemCount(d->buffers)], numAvail = 0; - /* Check which buffers are available for reuse. */ { - iForIndices(i, d->buffers) { - iVisBufTexture *buf = d->buffers + i; - const iRangei region = { buf->origin, buf->origin + d->texSize.y }; - if (isEmpty_Rangei(buf->validRange) || - buf->validRange.start >= vis.end || buf->validRange.end <= vis.start) { - avail[numAvail++] = i; - iZap(buf->validRange); - } - else { - good = union_Rangei(good, region); - } +#if 0 +static size_t findMostDistant_VisBuf_(const iVisBuf *d, const size_t *avail, size_t numAvail, + const iRangei vis) { + size_t chosen = 0; + int distChosen = iAbsi(d->buffers[0].origin - vis.start); + printf(" avail (got %zu): %zu", numAvail, avail[0]); + for (size_t i = 1; i < numAvail; i++) { + printf(" %zu", avail[i]); + const int dist = iAbsi(d->buffers[i].origin - vis.start); + if (dist > distChosen) { + chosen = i; + distChosen = dist; } } + printf("\n chose index %zu (%d)\n", chosen, distChosen); + return chosen; +} + +static size_t take_(size_t *avail, size_t *numAvail, size_t index) { + const size_t value = avail[index]; + memmove(avail + index, avail + index + 1, sizeof(size_t) * (*numAvail - index - 1)); + (*numAvail)--; + return value; +} +#endif + +iBool reposition_VisBuf(iVisBuf *d, const iRangei vis) { + if (equal_Rangei(vis, d->vis)) { + return iFalse; + } + d->vis = vis; iBool wasChanged = iFalse; - iBool doReset = (numAvail == iElemCount(d->buffers)); - /* Try to extend to cover the visible range. */ - while (!doReset && vis.start < good.start) { - if (numAvail == 0) { - doReset = iTrue; - break; - } - good.start -= d->texSize.y; - d->buffers[avail[--numAvail]].origin = good.start; + const size_t lastPos = iElemCount(d->buffers) - 1; + if (d->buffers[0].origin > vis.end || d->buffers[lastPos].origin + d->texSize.y <= vis.start) { + /* All buffers outside the visible region. */ + invalidate_VisBuf(d); wasChanged = iTrue; } - while (!doReset && vis.end > good.end) { - if (numAvail == 0) { - doReset = iTrue; - break; + else { + /* Roll up. */ + while (d->buffers[0].origin > vis.start) { + SDL_Texture *last = d->buffers[lastPos].texture; + void * user = d->buffers[lastPos].user; + memmove(d->buffers + 1, d->buffers, sizeof(iVisBufTexture) * lastPos); + d->buffers[0].texture = last; + d->buffers[0].user = user; + d->buffers[0].origin = d->buffers[1].origin - d->texSize.y; + iZap(d->buffers[0].validRange); + if (d->bufferInvalidated) { + d->bufferInvalidated(d, 0); + } + wasChanged = iTrue; } - d->buffers[avail[--numAvail]].origin = good.end; - good.end += d->texSize.y; - wasChanged = iTrue; - } - if (doReset) { -// puts("VisBuf reset!"); -// fflush(stdout); - wasChanged = iTrue; - int pos = -1; - iForIndices(i, d->buffers) { - iZap(d->buffers[i].validRange); - d->buffers[i].origin = vis.start + pos++ * d->texSize.y; + if (!wasChanged) { + /* Roll down. */ + while (d->buffers[lastPos].origin + d->texSize.y < vis.end) { + SDL_Texture *first = d->buffers[0].texture; + void * user = d->buffers[0].user; + memmove(d->buffers, d->buffers + 1, sizeof(iVisBufTexture) * lastPos); + d->buffers[lastPos].texture = first; + d->buffers[lastPos].user = user; + d->buffers[lastPos].origin = d->buffers[lastPos - 1].origin + d->texSize.y; + iZap(d->buffers[lastPos].validRange); + if (d->bufferInvalidated) { + d->bufferInvalidated(d, lastPos); + } + wasChanged = iTrue; + } } } #if 0 @@ -134,6 +166,33 @@ void reposition_VisBuf(iVisBuf *d, const iRangei vis) { fflush(stdout); } #endif +#if !defined (NDEBUG) + /* Buffers must not overlap. */ + iForIndices(m, d->buffers) { + const iRangei M = { d->buffers[m].origin, d->buffers[m].origin + d->texSize.y }; + iForIndices(n, d->buffers) { + if (m == n) continue; + const iRangei N = { d->buffers[n].origin, d->buffers[n].origin + d->texSize.y }; + const iRangei is = intersect_Rangei(M, N); + if (size_Range(&is) != 0) { + printf("buffers %zu (%i) and %zu (%i) overlap\n", + m, M.start, n, N.start); + fflush(stdout); + } + iAssert(size_Range(&is) == 0); + } + } +#endif + return iTrue; /* at least the visible range changed */ +} + +iRangei allocRange_VisBuf(const iVisBuf *d) { + return (iRangei){ d->buffers[0].origin, + d->buffers[iElemCount(d->buffers) - 1].origin + d->texSize.y }; +} + +iRangei bufferRange_VisBuf(const iVisBuf *d, size_t index) { + return (iRangei){ d->buffers[index].origin, d->buffers[index].origin + d->texSize.y }; } void invalidRanges_VisBuf(const iVisBuf *d, const iRangei full, iRangei *out_invalidRanges) { @@ -177,5 +236,5 @@ void draw_VisBuf(const iVisBuf *d, iInt2 topLeft) { dst.y += get_Window()->root->rect.size.y / 4; #endif SDL_RenderCopy(render, buf->texture, NULL, &dst); - } + } } diff --git a/src/ui/visbuf.h b/src/ui/visbuf.h index b285e90b..7efa36a7 100644 --- a/src/ui/visbuf.h +++ b/src/ui/visbuf.h @@ -33,12 +33,16 @@ struct Impl_VisBufTexture { SDL_Texture *texture; int origin; iRangei validRange; + void *user; }; +#define numBuffers_VisBuf ((size_t) 4) + struct Impl_VisBuf { iInt2 texSize; iRangei vis; - iVisBufTexture buffers[4]; + iVisBufTexture buffers[numBuffers_VisBuf]; + void (*bufferInvalidated)(iVisBuf *, size_t index); }; iDeclareTypeConstruction(VisBuf) @@ -46,8 +50,10 @@ iDeclareTypeConstruction(VisBuf) void invalidate_VisBuf (iVisBuf *); void alloc_VisBuf (iVisBuf *, const iInt2 size, int granularity); void dealloc_VisBuf (iVisBuf *); -void reposition_VisBuf (iVisBuf *, const iRangei vis); +iBool reposition_VisBuf (iVisBuf *, const iRangei vis); /* returns true if `vis` changes */ void validate_VisBuf (iVisBuf *); +iRangei allocRange_VisBuf (const iVisBuf *); +iRangei bufferRange_VisBuf (const iVisBuf *, size_t index); void invalidRanges_VisBuf (const iVisBuf *, const iRangei full, iRangei *out_invalidRanges); void draw_VisBuf (const iVisBuf *, iInt2 topLeft); -- cgit v1.2.3