diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-04-14 16:19:42 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-04-15 10:23:06 +0300 |
commit | fefbd1c259c912fe126a5f34245a8b4c494cb753 (patch) | |
tree | 4f1add012361fc9c8430ee43987562252d123bb9 /src | |
parent | d3b4292242dd938503a31ba68066d53b29f364a4 (diff) |
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.
Diffstat (limited to 'src')
-rw-r--r-- | src/app.c | 2 | ||||
-rw-r--r-- | src/gmdocument.c | 24 | ||||
-rw-r--r-- | src/gmdocument.h | 6 | ||||
-rw-r--r-- | src/periodic.c | 13 | ||||
-rw-r--r-- | src/periodic.h | 6 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 276 | ||||
-rw-r--r-- | src/ui/visbuf.c | 149 | ||||
-rw-r--r-- | src/ui/visbuf.h | 10 |
8 files changed, 369 insertions, 117 deletions
@@ -906,7 +906,7 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
906 | iApp *d = &app_; | 906 | iApp *d = &app_; |
907 | SDL_Event ev; | 907 | SDL_Event ev; |
908 | iBool gotEvents = iFalse; | 908 | iBool gotEvents = iFalse; |
909 | postCommands_Periodic(&d->periodic); | 909 | dispatchCommands_Periodic(&d->periodic); |
910 | while (nextEvent_App_(d, eventMode, &ev)) { | 910 | while (nextEvent_App_(d, eventMode, &ev)) { |
911 | #if defined (iPlatformAppleMobile) | 911 | #if defined (iPlatformAppleMobile) |
912 | if (processEvent_iOS(&ev)) { | 912 | 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 | |||
1494 | } | 1494 | } |
1495 | } | 1495 | } |
1496 | 1496 | ||
1497 | static iBool isValidRun_GmDocument_(const iGmDocument *d, const iGmRun *run) { | ||
1498 | return run >= (const iGmRun *) constAt_Array(&d->layout, 0) && | ||
1499 | run < (const iGmRun *) constEnd_Array(&d->layout); | ||
1500 | } | ||
1501 | |||
1502 | const iGmRun *renderProgressive_GmDocument(const iGmDocument *d, const iGmRun *first, int dir, | ||
1503 | size_t maxCount, | ||
1504 | iRangei visRangeY, iGmDocumentRenderFunc render, | ||
1505 | void *context) { | ||
1506 | const iGmRun *run = first; | ||
1507 | while (isValidRun_GmDocument_(d, run)) { | ||
1508 | if ((dir < 0 && bottom_Rect(run->visBounds) < visRangeY.start) || | ||
1509 | (dir > 0 && top_Rect(run->visBounds) >= visRangeY.end)) { | ||
1510 | break; | ||
1511 | } | ||
1512 | if (maxCount-- == 0) { | ||
1513 | break; | ||
1514 | } | ||
1515 | render(context, run); | ||
1516 | run += dir; | ||
1517 | } | ||
1518 | return isValidRun_GmDocument_(d, run) ? run : NULL; | ||
1519 | } | ||
1520 | |||
1497 | iInt2 size_GmDocument(const iGmDocument *d) { | 1521 | iInt2 size_GmDocument(const iGmDocument *d) { |
1498 | return d->size; | 1522 | return d->size; |
1499 | } | 1523 | } |
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 *); | |||
176 | const iMedia * constMedia_GmDocument (const iGmDocument *); | 176 | const iMedia * constMedia_GmDocument (const iGmDocument *); |
177 | 177 | ||
178 | void render_GmDocument (const iGmDocument *, iRangei visRangeY, | 178 | void render_GmDocument (const iGmDocument *, iRangei visRangeY, |
179 | iGmDocumentRenderFunc render, void *); | 179 | iGmDocumentRenderFunc render, void *); /* includes partial overlaps */ |
180 | const iGmRun * renderProgressive_GmDocument(const iGmDocument *d, const iGmRun *first, int dir, | ||
181 | size_t maxCount, | ||
182 | iRangei visRangeY, iGmDocumentRenderFunc render, | ||
183 | void *context); | ||
180 | iInt2 size_GmDocument (const iGmDocument *); | 184 | iInt2 size_GmDocument (const iGmDocument *); |
181 | const iGmRun * siteBanner_GmDocument (const iGmDocument *); | 185 | const iGmRun * siteBanner_GmDocument (const iGmDocument *); |
182 | iBool hasSiteBanner_GmDocument (const iGmDocument *); | 186 | 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 | |||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | 21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ |
22 | 22 | ||
23 | #include "periodic.h" | 23 | #include "periodic.h" |
24 | #include "ui/widget.h" | ||
24 | #include "app.h" | 25 | #include "app.h" |
25 | 26 | ||
26 | #include <the_Foundation/string.h> | 27 | #include <the_Foundation/string.h> |
27 | #include <the_Foundation/thread.h> | 28 | #include <the_Foundation/thread.h> |
29 | #include <SDL_events.h> | ||
28 | #include <SDL_timer.h> | 30 | #include <SDL_timer.h> |
29 | 31 | ||
30 | iDeclareType(PeriodicCommand) | 32 | iDeclareType(PeriodicCommand) |
@@ -54,7 +56,7 @@ iDefineTypeConstructionArgs(PeriodicCommand, (iAny *ctx, const char *cmd), ctx, | |||
54 | 56 | ||
55 | static const uint32_t postingInterval_Periodic_ = 500; | 57 | static const uint32_t postingInterval_Periodic_ = 500; |
56 | 58 | ||
57 | iBool postCommands_Periodic(iPeriodic *d) { | 59 | iBool dispatchCommands_Periodic(iPeriodic *d) { |
58 | const uint32_t now = SDL_GetTicks(); | 60 | const uint32_t now = SDL_GetTicks(); |
59 | if (now - d->lastPostTime < postingInterval_Periodic_) { | 61 | if (now - d->lastPostTime < postingInterval_Periodic_) { |
60 | return iFalse; | 62 | return iFalse; |
@@ -63,7 +65,14 @@ iBool postCommands_Periodic(iPeriodic *d) { | |||
63 | iBool wasPosted = iFalse; | 65 | iBool wasPosted = iFalse; |
64 | lock_Mutex(d->mutex); | 66 | lock_Mutex(d->mutex); |
65 | iConstForEach(Array, i, &d->commands.values) { | 67 | iConstForEach(Array, i, &d->commands.values) { |
66 | postCommandString_App(&((const iPeriodicCommand *) i.value)->command); | 68 | const iPeriodicCommand *pc = i.value; |
69 | const SDL_UserEvent ev = { | ||
70 | .type = SDL_USEREVENT, | ||
71 | .code = command_UserEventCode, | ||
72 | .data1 = (void *) cstr_String(&pc->command) | ||
73 | }; | ||
74 | iAssert(isInstance_Object(pc->context, &Class_Widget)); | ||
75 | dispatchEvent_Widget(pc->context, (const SDL_Event *) &ev); | ||
67 | wasPosted = iTrue; | 76 | wasPosted = iTrue; |
68 | } | 77 | } |
69 | unlock_Mutex(d->mutex); | 78 | 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) { | |||
40 | return isEmpty_SortedArray(&d->commands); | 40 | return isEmpty_SortedArray(&d->commands); |
41 | } | 41 | } |
42 | 42 | ||
43 | void add_Periodic (iPeriodic *, iAny *context, const char *command); | 43 | void add_Periodic (iPeriodic *, iAnyObject *context, const char *command); |
44 | void remove_Periodic (iPeriodic *, iAny *context); | 44 | void remove_Periodic (iPeriodic *, iAnyObject *context); |
45 | 45 | ||
46 | iBool postCommands_Periodic (iPeriodic *); | 46 | 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 | |||
20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | 20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | 21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ |
22 | 22 | ||
23 | /* TODO: This file is a little too large. DocumentWidget could be split into | 23 | /* TODO: This file is a little (!) too large. DocumentWidget could be split into |
24 | a couple of smaller objects. One for rendering the document, for instance. */ | 24 | a couple of smaller objects. One for rendering the document, for instance. */ |
25 | 25 | ||
26 | #include "documentwidget.h" | 26 | #include "documentwidget.h" |
@@ -41,6 +41,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
41 | #include "labelwidget.h" | 41 | #include "labelwidget.h" |
42 | #include "media.h" | 42 | #include "media.h" |
43 | #include "paint.h" | 43 | #include "paint.h" |
44 | #include "periodic.h" | ||
44 | #include "mediaui.h" | 45 | #include "mediaui.h" |
45 | #include "scrollwidget.h" | 46 | #include "scrollwidget.h" |
46 | #include "touch.h" | 47 | #include "touch.h" |
@@ -156,12 +157,14 @@ struct Impl_DrawBufs { | |||
156 | int flags; | 157 | int flags; |
157 | SDL_Texture * sideIconBuf; | 158 | SDL_Texture * sideIconBuf; |
158 | iTextBuf * timestampBuf; | 159 | iTextBuf * timestampBuf; |
160 | iRangei lastVis; | ||
159 | }; | 161 | }; |
160 | 162 | ||
161 | static void init_DrawBufs(iDrawBufs *d) { | 163 | static void init_DrawBufs(iDrawBufs *d) { |
162 | d->flags = 0; | 164 | d->flags = 0; |
163 | d->sideIconBuf = NULL; | 165 | d->sideIconBuf = NULL; |
164 | d->timestampBuf = NULL; | 166 | d->timestampBuf = NULL; |
167 | iZap(d->lastVis); | ||
165 | } | 168 | } |
166 | 169 | ||
167 | static void deinit_DrawBufs(iDrawBufs *d) { | 170 | static void deinit_DrawBufs(iDrawBufs *d) { |
@@ -175,9 +178,23 @@ iDefineTypeConstruction(DrawBufs) | |||
175 | 178 | ||
176 | /*----------------------------------------------------------------------------------------------*/ | 179 | /*----------------------------------------------------------------------------------------------*/ |
177 | 180 | ||
181 | iDeclareType(VisBufMeta) | ||
182 | |||
183 | struct Impl_VisBufMeta { | ||
184 | iGmRunRange runsDrawn; | ||
185 | }; | ||
186 | |||
187 | static void visBufInvalidated_(iVisBuf *d, size_t index) { | ||
188 | iVisBufMeta *meta = d->buffers[index].user; | ||
189 | iZap(meta->runsDrawn); | ||
190 | } | ||
191 | |||
192 | /*----------------------------------------------------------------------------------------------*/ | ||
193 | |||
178 | static void animate_DocumentWidget_ (void *ticker); | 194 | static void animate_DocumentWidget_ (void *ticker); |
179 | static void animateMedia_DocumentWidget_ (iDocumentWidget *d); | 195 | static void animateMedia_DocumentWidget_ (iDocumentWidget *d); |
180 | static void updateSideIconBuf_DocumentWidget_ (const iDocumentWidget *d); | 196 | static void updateSideIconBuf_DocumentWidget_ (const iDocumentWidget *d); |
197 | static void prerender_DocumentWidget_ (iAny *); | ||
181 | 198 | ||
182 | static const int smoothDuration_DocumentWidget_ = 600; /* milliseconds */ | 199 | static const int smoothDuration_DocumentWidget_ = 600; /* milliseconds */ |
183 | static const int outlineMinWidth_DocumentWdiget_ = 45; /* times gap_UI */ | 200 | static const int outlineMinWidth_DocumentWdiget_ = 45; /* times gap_UI */ |
@@ -251,8 +268,8 @@ struct Impl_DocumentWidget { | |||
251 | const iGmRun * hoverAltPre; /* for drawing alt text */ | 268 | const iGmRun * hoverAltPre; /* for drawing alt text */ |
252 | const iGmRun * hoverLink; | 269 | const iGmRun * hoverLink; |
253 | const iGmRun * contextLink; | 270 | const iGmRun * contextLink; |
254 | const iGmRun * firstVisibleRun; | 271 | iGmRunRange visibleRuns; |
255 | const iGmRun * lastVisibleRun; | 272 | iGmRunRange renderRuns; |
256 | iClick click; | 273 | iClick click; |
257 | iInt2 contextPos; /* coordinates of latest right click */ | 274 | iInt2 contextPos; /* coordinates of latest right click */ |
258 | iString pendingGotoHeading; | 275 | iString pendingGotoHeading; |
@@ -265,6 +282,7 @@ struct Impl_DocumentWidget { | |||
265 | iWidget * playerMenu; | 282 | iWidget * playerMenu; |
266 | iWidget * copyMenu; | 283 | iWidget * copyMenu; |
267 | iVisBuf * visBuf; | 284 | iVisBuf * visBuf; |
285 | iGmRunRange * visBufMeta; | ||
268 | iPtrSet * invalidRuns; | 286 | iPtrSet * invalidRuns; |
269 | iDrawBufs * drawBufs; /* dynamic state for drawing */ | 287 | iDrawBufs * drawBufs; /* dynamic state for drawing */ |
270 | iTranslation * translation; | 288 | iTranslation * translation; |
@@ -306,10 +324,17 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
306 | d->hoverAltPre = NULL; | 324 | d->hoverAltPre = NULL; |
307 | d->hoverLink = NULL; | 325 | d->hoverLink = NULL; |
308 | d->contextLink = NULL; | 326 | d->contextLink = NULL; |
309 | d->firstVisibleRun = NULL; | 327 | iZap(d->renderRuns); |
310 | d->lastVisibleRun = NULL; | 328 | iZap(d->visibleRuns); |
311 | d->visBuf = new_VisBuf(); | 329 | d->visBuf = new_VisBuf(); { |
312 | d->invalidRuns = new_PtrSet(); | 330 | d->visBufMeta = malloc(sizeof(iVisBufMeta) * numBuffers_VisBuf); |
331 | /* Additional metadata for each buffer. */ | ||
332 | d->visBuf->bufferInvalidated = visBufInvalidated_; | ||
333 | for (size_t i = 0; i < numBuffers_VisBuf; i++) { | ||
334 | d->visBuf->buffers[i].user = d->visBufMeta + i; | ||
335 | } | ||
336 | } | ||
337 | d->invalidRuns = new_PtrSet(); | ||
313 | init_Anim(&d->sideOpacity, 0); | 338 | init_Anim(&d->sideOpacity, 0); |
314 | init_Anim(&d->altTextOpacity, 0); | 339 | init_Anim(&d->altTextOpacity, 0); |
315 | d->sourceStatus = none_GmStatusCode; | 340 | d->sourceStatus = none_GmStatusCode; |
@@ -349,9 +374,12 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
349 | 374 | ||
350 | void deinit_DocumentWidget(iDocumentWidget *d) { | 375 | void deinit_DocumentWidget(iDocumentWidget *d) { |
351 | removeTicker_App(animate_DocumentWidget_, d); | 376 | removeTicker_App(animate_DocumentWidget_, d); |
377 | removeTicker_App(prerender_DocumentWidget_, d); | ||
378 | remove_Periodic(periodic_App(), d); | ||
352 | delete_Translation(d->translation); | 379 | delete_Translation(d->translation); |
353 | delete_DrawBufs(d->drawBufs); | 380 | delete_DrawBufs(d->drawBufs); |
354 | delete_VisBuf(d->visBuf); | 381 | delete_VisBuf(d->visBuf); |
382 | free(d->visBufMeta); | ||
355 | delete_PtrSet(d->invalidRuns); | 383 | delete_PtrSet(d->invalidRuns); |
356 | iRelease(d->media); | 384 | iRelease(d->media); |
357 | iRelease(d->request); | 385 | iRelease(d->request); |
@@ -486,10 +514,10 @@ static iRangei visibleRange_DocumentWidget_(const iDocumentWidget *d) { | |||
486 | static void addVisible_DocumentWidget_(void *context, const iGmRun *run) { | 514 | static void addVisible_DocumentWidget_(void *context, const iGmRun *run) { |
487 | iDocumentWidget *d = context; | 515 | iDocumentWidget *d = context; |
488 | if (~run->flags & decoration_GmRunFlag && !run->mediaId) { | 516 | if (~run->flags & decoration_GmRunFlag && !run->mediaId) { |
489 | if (!d->firstVisibleRun) { | 517 | if (!d->visibleRuns.start) { |
490 | d->firstVisibleRun = run; | 518 | d->visibleRuns.start = run; |
491 | } | 519 | } |
492 | d->lastVisibleRun = run; | 520 | d->visibleRuns.end = run; |
493 | } | 521 | } |
494 | if (run->preId) { | 522 | if (run->preId) { |
495 | pushBack_PtrArray(&d->visiblePre, run); | 523 | pushBack_PtrArray(&d->visiblePre, run); |
@@ -743,14 +771,14 @@ static void animateMedia_DocumentWidget_(iDocumentWidget *d) { | |||
743 | 771 | ||
744 | static iRangecc currentHeading_DocumentWidget_(const iDocumentWidget *d) { | 772 | static iRangecc currentHeading_DocumentWidget_(const iDocumentWidget *d) { |
745 | iRangecc heading = iNullRange; | 773 | iRangecc heading = iNullRange; |
746 | if (d->firstVisibleRun) { | 774 | if (d->visibleRuns.start) { |
747 | iConstForEach(Array, i, headings_GmDocument(d->doc)) { | 775 | iConstForEach(Array, i, headings_GmDocument(d->doc)) { |
748 | const iGmHeading *head = i.value; | 776 | const iGmHeading *head = i.value; |
749 | if (head->level == 0) { | 777 | if (head->level == 0) { |
750 | if (head->text.start <= d->firstVisibleRun->text.start) { | 778 | if (head->text.start <= d->visibleRuns.start->text.start) { |
751 | heading = head->text; | 779 | heading = head->text; |
752 | } | 780 | } |
753 | if (d->lastVisibleRun && head->text.start > d->lastVisibleRun->text.start) { | 781 | if (d->visibleRuns.end && head->text.start > d->visibleRuns.end->text.start) { |
754 | break; | 782 | break; |
755 | } | 783 | } |
756 | } | 784 | } |
@@ -766,6 +794,14 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) { | |||
766 | !isSuccess_GmStatusCode(d->sourceStatus)); | 794 | !isSuccess_GmStatusCode(d->sourceStatus)); |
767 | const iRangei visRange = visibleRange_DocumentWidget_(d); | 795 | const iRangei visRange = visibleRange_DocumentWidget_(d); |
768 | const iRect bounds = bounds_Widget(as_Widget(d)); | 796 | const iRect bounds = bounds_Widget(as_Widget(d)); |
797 | if (!equal_Rangei(visRange, d->drawBufs->lastVis)) { | ||
798 | /* After scrolling/resizing stops, begin prendering the visbuf contents. | ||
799 | `Periodic` is used for detecting when the visible range has been modified. */ | ||
800 | removeTicker_App(prerender_DocumentWidget_, d); | ||
801 | remove_Periodic(periodic_App(), d); | ||
802 | add_Periodic(periodic_App(), d, | ||
803 | format_CStr("document.render from:%d to:%d", visRange.start, visRange.end)); | ||
804 | } | ||
769 | setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax_DocumentWidget_(d) }); | 805 | setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax_DocumentWidget_(d) }); |
770 | const int docSize = size_GmDocument(d->doc).y; | 806 | const int docSize = size_GmDocument(d->doc).y; |
771 | setThumb_ScrollWidget(d->scroll, | 807 | setThumb_ScrollWidget(d->scroll, |
@@ -777,7 +813,7 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) { | |||
777 | clear_PtrArray(&d->visibleMedia); | 813 | clear_PtrArray(&d->visibleMedia); |
778 | const iRangecc oldHeading = currentHeading_DocumentWidget_(d); | 814 | const iRangecc oldHeading = currentHeading_DocumentWidget_(d); |
779 | /* Scan for visible runs. */ { | 815 | /* Scan for visible runs. */ { |
780 | d->firstVisibleRun = NULL; | 816 | iZap(d->visibleRuns); |
781 | render_GmDocument(d->doc, visRange, addVisible_DocumentWidget_, d); | 817 | render_GmDocument(d->doc, visRange, addVisible_DocumentWidget_, d); |
782 | } | 818 | } |
783 | const iRangecc newHeading = currentHeading_DocumentWidget_(d); | 819 | const iRangecc newHeading = currentHeading_DocumentWidget_(d); |
@@ -898,8 +934,8 @@ static void documentRunsInvalidated_DocumentWidget_(iDocumentWidget *d) { | |||
898 | d->hoverAltPre = NULL; | 934 | d->hoverAltPre = NULL; |
899 | d->hoverLink = NULL; | 935 | d->hoverLink = NULL; |
900 | d->contextLink = NULL; | 936 | d->contextLink = NULL; |
901 | d->firstVisibleRun = NULL; | 937 | iZap(d->visibleRuns); |
902 | d->lastVisibleRun = NULL; | 938 | iZap(d->renderRuns); |
903 | } | 939 | } |
904 | 940 | ||
905 | void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) { | 941 | void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) { |
@@ -1675,7 +1711,7 @@ static iBool updateDocumentWidthRetainingScrollPosition_DocumentWidget_(iDocumen | |||
1675 | } | 1711 | } |
1676 | /* Font changes (i.e., zooming) will keep the view centered, otherwise keep the top | 1712 | /* Font changes (i.e., zooming) will keep the view centered, otherwise keep the top |
1677 | of the visible area fixed. */ | 1713 | of the visible area fixed. */ |
1678 | const iGmRun *run = keepCenter ? middleRun_DocumentWidget_(d) : d->firstVisibleRun; | 1714 | const iGmRun *run = keepCenter ? middleRun_DocumentWidget_(d) : d->visibleRuns.start; |
1679 | const char * runLoc = (run ? run->text.start : NULL); | 1715 | const char * runLoc = (run ? run->text.start : NULL); |
1680 | int voffset = 0; | 1716 | int voffset = 0; |
1681 | if (!keepCenter && run) { | 1717 | if (!keepCenter && run) { |
@@ -1727,7 +1763,17 @@ static iBool handlePinch_DocumentWidget_(iDocumentWidget *d, const char *cmd) { | |||
1727 | 1763 | ||
1728 | static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) { | 1764 | static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) { |
1729 | iWidget *w = as_Widget(d); | 1765 | iWidget *w = as_Widget(d); |
1730 | if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "font.changed")) { | 1766 | if (equal_Command(cmd, "document.render")) { |
1767 | const iRangei vis = { argLabel_Command(cmd, "from"), argLabel_Command(cmd, "to") }; | ||
1768 | const iRangei current = visibleRange_DocumentWidget_(d); | ||
1769 | if (equal_Rangei(vis, current)) { | ||
1770 | remove_Periodic(periodic_App(), d); | ||
1771 | /* Scrolling has stopped, begin filling up the buffer. */ | ||
1772 | addTicker_App(prerender_DocumentWidget_, d); | ||
1773 | } | ||
1774 | return iTrue; | ||
1775 | } | ||
1776 | else if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "font.changed")) { | ||
1731 | /* Alt/Option key may be involved in window size changes. */ | 1777 | /* Alt/Option key may be involved in window size changes. */ |
1732 | setLinkNumberMode_DocumentWidget_(d, iFalse); | 1778 | setLinkNumberMode_DocumentWidget_(d, iFalse); |
1733 | d->phoneToolbar = findWidget_App("toolbar"); | 1779 | d->phoneToolbar = findWidget_App("toolbar"); |
@@ -3103,6 +3149,8 @@ iDeclareType(DrawContext) | |||
3103 | struct Impl_DrawContext { | 3149 | struct Impl_DrawContext { |
3104 | const iDocumentWidget *widget; | 3150 | const iDocumentWidget *widget; |
3105 | iRect widgetBounds; | 3151 | iRect widgetBounds; |
3152 | iRect docBounds; | ||
3153 | iRangei vis; | ||
3106 | iInt2 viewPos; /* document area origin */ | 3154 | iInt2 viewPos; /* document area origin */ |
3107 | iPaint paint; | 3155 | iPaint paint; |
3108 | iBool inSelectMark; | 3156 | iBool inSelectMark; |
@@ -3110,6 +3158,7 @@ struct Impl_DrawContext { | |||
3110 | iBool showLinkNumbers; | 3158 | iBool showLinkNumbers; |
3111 | iRect firstMarkRect; | 3159 | iRect firstMarkRect; |
3112 | iRect lastMarkRect; | 3160 | iRect lastMarkRect; |
3161 | iGmRunRange runsDrawn; | ||
3113 | }; | 3162 | }; |
3114 | 3163 | ||
3115 | static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iColorId color, | 3164 | 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 | |||
3269 | static void drawRun_DrawContext_(void *context, const iGmRun *run) { | 3318 | static void drawRun_DrawContext_(void *context, const iGmRun *run) { |
3270 | iDrawContext *d = context; | 3319 | iDrawContext *d = context; |
3271 | const iInt2 origin = d->viewPos; | 3320 | const iInt2 origin = d->viewPos; |
3321 | /* Keep track of the drawn visible runs. */ { | ||
3322 | if (!d->runsDrawn.start || run < d->runsDrawn.start) { | ||
3323 | d->runsDrawn.start = run; | ||
3324 | } | ||
3325 | if (!d->runsDrawn.end || run > d->runsDrawn.end) { | ||
3326 | d->runsDrawn.end = run; | ||
3327 | } | ||
3328 | } | ||
3272 | if (run->mediaType == image_GmRunMediaType) { | 3329 | if (run->mediaType == image_GmRunMediaType) { |
3273 | SDL_Texture *tex = imageTexture_Media(media_GmDocument(d->widget->doc), run->mediaId); | 3330 | SDL_Texture *tex = imageTexture_Media(media_GmDocument(d->widget->doc), run->mediaId); |
3274 | const iRect dst = moved_Rect(run->visBounds, origin); | 3331 | const iRect dst = moved_Rect(run->visBounds, origin); |
@@ -3291,6 +3348,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
3291 | /* Media UIs are drawn afterwards as a dynamic overlay. */ | 3348 | /* Media UIs are drawn afterwards as a dynamic overlay. */ |
3292 | return; | 3349 | return; |
3293 | } | 3350 | } |
3351 | // printf(" drawRun: {%s}\n", cstr_Rangecc(run->text)); | ||
3294 | enum iColorId fg = run->color; | 3352 | enum iColorId fg = run->color; |
3295 | const iGmDocument *doc = d->widget->doc; | 3353 | const iGmDocument *doc = d->widget->doc; |
3296 | iBool isHover = | 3354 | iBool isHover = |
@@ -3644,48 +3702,100 @@ static void drawPin_(iPaint *p, iRect rangeRect, int dir) { | |||
3644 | init1_I2(gap_UI * 2)), pinColor); | 3702 | init1_I2(gap_UI * 2)), pinColor); |
3645 | } | 3703 | } |
3646 | 3704 | ||
3647 | static void draw_DocumentWidget_(const iDocumentWidget *d) { | 3705 | static iBool render_DocumentWidget_(const iDocumentWidget *d, iDrawContext *ctx, iBool prerenderExtra) { |
3648 | const iWidget *w = constAs_Widget(d); | 3706 | iBool didDraw = iFalse; |
3649 | const iRect bounds = bounds_Widget(w); | 3707 | const iRect bounds = bounds_Widget(constAs_Widget(d)); |
3650 | iVisBuf * visBuf = d->visBuf; /* will be updated now */ | ||
3651 | if (width_Rect(bounds) <= 0) { | ||
3652 | return; | ||
3653 | } | ||
3654 | draw_Widget(w); | ||
3655 | if (d->drawBufs->flags & updateTimestampBuf_DrawBufsFlag) { | ||
3656 | updateTimestampBuf_DocumentWidget_(d); | ||
3657 | } | ||
3658 | if (d->drawBufs->flags & updateSideBuf_DrawBufsFlag) { | ||
3659 | updateSideIconBuf_DocumentWidget_(d); | ||
3660 | } | ||
3661 | allocVisBuffer_DocumentWidget_(d); | ||
3662 | const iRect ctxWidgetBounds = init_Rect( | 3708 | const iRect ctxWidgetBounds = init_Rect( |
3663 | 0, 0, width_Rect(bounds) - constAs_Widget(d->scroll)->rect.size.x, height_Rect(bounds)); | 3709 | 0, 0, width_Rect(bounds) - constAs_Widget(d->scroll)->rect.size.x, height_Rect(bounds)); |
3664 | const iRect docBounds = documentBounds_DocumentWidget_(d); | ||
3665 | iDrawContext ctx = { | ||
3666 | .widget = d, | ||
3667 | .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0, | ||
3668 | }; | ||
3669 | const iBool isTouchSelecting = (flags_Widget(w) & touchDrag_WidgetFlag) != 0; | ||
3670 | /* Currently visible region. */ | ||
3671 | const iRangei vis = visibleRange_DocumentWidget_(d); | ||
3672 | const iRangei full = { 0, size_GmDocument(d->doc).y }; | 3710 | const iRangei full = { 0, size_GmDocument(d->doc).y }; |
3711 | const iRangei vis = ctx->vis; | ||
3712 | iVisBuf *visBuf = d->visBuf; /* will be updated now */ | ||
3713 | /* Swap buffers around to have room available both before and after the visible region. */ | ||
3714 | allocVisBuffer_DocumentWidget_(d); | ||
3673 | reposition_VisBuf(visBuf, vis); | 3715 | reposition_VisBuf(visBuf, vis); |
3674 | iRangei invalidRange[iElemCount(d->visBuf->buffers)]; | ||
3675 | invalidRanges_VisBuf(visBuf, full, invalidRange); | ||
3676 | /* Redraw the invalid ranges. */ { | 3716 | /* Redraw the invalid ranges. */ { |
3677 | iPaint *p = &ctx.paint; | 3717 | iPaint *p = &ctx->paint; |
3678 | init_Paint(p); | 3718 | init_Paint(p); |
3679 | iForIndices(i, visBuf->buffers) { | 3719 | iForIndices(i, visBuf->buffers) { |
3680 | iVisBufTexture *buf = &visBuf->buffers[i]; | 3720 | iVisBufTexture *buf = &visBuf->buffers[i]; |
3681 | ctx.widgetBounds = moved_Rect(ctxWidgetBounds, init_I2(0, -buf->origin)); | 3721 | iVisBufMeta * meta = buf->user; |
3682 | ctx.viewPos = init_I2(left_Rect(docBounds) - left_Rect(bounds), -buf->origin); | 3722 | const iRangei bufRange = bufferRange_VisBuf(visBuf, i); |
3683 | if (!isEmpty_Rangei(invalidRange[i])) { | 3723 | const iRangei bufVisRange = intersect_Rangei(bufRange, vis); |
3684 | beginTarget_Paint(p, buf->texture); | 3724 | ctx->widgetBounds = moved_Rect(ctxWidgetBounds, init_I2(0, -buf->origin)); |
3725 | ctx->viewPos = init_I2(left_Rect(ctx->docBounds) - left_Rect(bounds), -buf->origin); | ||
3726 | //printf(" buffer %zu: invalid range %d...%d\n", i, invalidRange[i].start, invalidRange[i].end); | ||
3727 | if (!prerenderExtra && !isEmpty_Range(&bufVisRange)) { | ||
3728 | didDraw = iTrue; | ||
3685 | if (isEmpty_Rangei(buf->validRange)) { | 3729 | if (isEmpty_Rangei(buf->validRange)) { |
3686 | fillRect_Paint(p, (iRect){ zero_I2(), visBuf->texSize }, tmBackground_ColorId); | 3730 | /* Fill the required currently visible range (vis). */ |
3731 | const iRangei bufVisRange = intersect_Rangei(bufRange, vis); | ||
3732 | if (!isEmpty_Range(&bufVisRange)) { | ||
3733 | beginTarget_Paint(p, buf->texture); | ||
3734 | fillRect_Paint(p, (iRect){ zero_I2(), visBuf->texSize }, tmBackground_ColorId); | ||
3735 | iZap(ctx->runsDrawn); | ||
3736 | render_GmDocument(d->doc, bufVisRange, drawRun_DrawContext_, ctx); | ||
3737 | meta->runsDrawn = ctx->runsDrawn; | ||
3738 | meta->runsDrawn.start--; | ||
3739 | meta->runsDrawn.end++; | ||
3740 | buf->validRange = bufVisRange; | ||
3741 | // printf(" buffer %zu valid %d...%d\n", i, bufRange.start, bufRange.end); | ||
3742 | } | ||
3743 | } | ||
3744 | else { | ||
3745 | /* Progressively fill the required runs. */ | ||
3746 | if (meta->runsDrawn.start) { | ||
3747 | beginTarget_Paint(p, buf->texture); | ||
3748 | meta->runsDrawn.start = renderProgressive_GmDocument(d->doc, meta->runsDrawn.start, | ||
3749 | -1, iInvalidSize, | ||
3750 | bufVisRange, | ||
3751 | drawRun_DrawContext_, | ||
3752 | ctx); | ||
3753 | buf->validRange.start = bufVisRange.start; | ||
3754 | } | ||
3755 | if (meta->runsDrawn.end) { | ||
3756 | beginTarget_Paint(p, buf->texture); | ||
3757 | meta->runsDrawn.end = renderProgressive_GmDocument(d->doc, meta->runsDrawn.end, | ||
3758 | +1, iInvalidSize, | ||
3759 | bufVisRange, | ||
3760 | drawRun_DrawContext_, | ||
3761 | ctx); | ||
3762 | buf->validRange.end = bufVisRange.end; | ||
3763 | } | ||
3764 | } | ||
3765 | } | ||
3766 | /* Progressively draw the rest of the buffer if it isn't fully valid. */ | ||
3767 | if (prerenderExtra && !equal_Rangei(bufRange, buf->validRange)) { | ||
3768 | const iGmRun *next; | ||
3769 | if (meta->runsDrawn.start) { | ||
3770 | const iRangei upper = intersect_Rangei(bufRange, (iRangei){ full.start, buf->validRange.start }); | ||
3771 | if (upper.end > upper.start) { | ||
3772 | beginTarget_Paint(p, buf->texture); | ||
3773 | next = renderProgressive_GmDocument(d->doc, meta->runsDrawn.start, | ||
3774 | -1, 1, upper, | ||
3775 | drawRun_DrawContext_, | ||
3776 | ctx); | ||
3777 | if (meta->runsDrawn.start != next) { | ||
3778 | meta->runsDrawn.start = next; | ||
3779 | didDraw = iTrue; | ||
3780 | } | ||
3781 | buf->validRange.start = bufRange.start; | ||
3782 | } | ||
3783 | } | ||
3784 | if (meta->runsDrawn.end) { | ||
3785 | const iRangei lower = intersect_Rangei(bufRange, (iRangei){ buf->validRange.end, full.end }); | ||
3786 | if (lower.end > lower.start) { | ||
3787 | beginTarget_Paint(p, buf->texture); | ||
3788 | next = renderProgressive_GmDocument(d->doc, meta->runsDrawn.end, | ||
3789 | +1, 1, lower, | ||
3790 | drawRun_DrawContext_, | ||
3791 | ctx); | ||
3792 | if (meta->runsDrawn.end != next) { | ||
3793 | meta->runsDrawn.end = next; | ||
3794 | didDraw = iTrue; | ||
3795 | } | ||
3796 | buf->validRange.end = bufRange.end; | ||
3797 | } | ||
3687 | } | 3798 | } |
3688 | render_GmDocument(d->doc, invalidRange[i], drawRun_DrawContext_, &ctx); | ||
3689 | } | 3799 | } |
3690 | /* Draw any invalidated runs that fall within this buffer. */ { | 3800 | /* Draw any invalidated runs that fall within this buffer. */ { |
3691 | const iRangei bufRange = { buf->origin, buf->origin + visBuf->texSize.y }; | 3801 | const iRangei bufRange = { buf->origin, buf->origin + visBuf->texSize.y }; |
@@ -3694,7 +3804,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { | |||
3694 | const iGmRun *run = *r.value; | 3804 | const iGmRun *run = *r.value; |
3695 | if (isOverlapping_Rangei(bufRange, ySpan_Rect(run->visBounds))) { | 3805 | if (isOverlapping_Rangei(bufRange, ySpan_Rect(run->visBounds))) { |
3696 | beginTarget_Paint(p, buf->texture); | 3806 | beginTarget_Paint(p, buf->texture); |
3697 | fillRect_Paint(&ctx.paint, | 3807 | fillRect_Paint(p, |
3698 | init_Rect(0, | 3808 | init_Rect(0, |
3699 | run->visBounds.pos.y - buf->origin, | 3809 | run->visBounds.pos.y - buf->origin, |
3700 | visBuf->texSize.x, | 3810 | visBuf->texSize.x, |
@@ -3707,21 +3817,61 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { | |||
3707 | const iGmRun *run = *r.value; | 3817 | const iGmRun *run = *r.value; |
3708 | if (isOverlapping_Rangei(bufRange, ySpan_Rect(run->visBounds))) { | 3818 | if (isOverlapping_Rangei(bufRange, ySpan_Rect(run->visBounds))) { |
3709 | beginTarget_Paint(p, buf->texture); | 3819 | beginTarget_Paint(p, buf->texture); |
3710 | drawRun_DrawContext_(&ctx, run); | 3820 | drawRun_DrawContext_(ctx, run); |
3711 | } | 3821 | } |
3712 | } | 3822 | } |
3713 | } | 3823 | } |
3714 | endTarget_Paint(&ctx.paint); | 3824 | endTarget_Paint(p); |
3715 | } | 3825 | } |
3716 | validate_VisBuf(visBuf); | ||
3717 | clear_PtrSet(d->invalidRuns); | 3826 | clear_PtrSet(d->invalidRuns); |
3718 | } | 3827 | } |
3828 | return didDraw; | ||
3829 | } | ||
3830 | |||
3831 | static void prerender_DocumentWidget_(iAny *context) { | ||
3832 | const iDocumentWidget *d = context; | ||
3833 | iDrawContext ctx = { | ||
3834 | .widget = d, | ||
3835 | .docBounds = documentBounds_DocumentWidget_(d), | ||
3836 | .vis = visibleRange_DocumentWidget_(d), | ||
3837 | .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0 | ||
3838 | }; | ||
3839 | // printf("%u prerendering\n", SDL_GetTicks()); | ||
3840 | if (render_DocumentWidget_(d, &ctx, iTrue /* just fill up progressively */)) { | ||
3841 | /* Something was drawn, should check if there is still more to do. */ | ||
3842 | addTicker_App(prerender_DocumentWidget_, context); | ||
3843 | } | ||
3844 | } | ||
3845 | |||
3846 | static void draw_DocumentWidget_(const iDocumentWidget *d) { | ||
3847 | const iWidget *w = constAs_Widget(d); | ||
3848 | const iRect bounds = bounds_Widget(w); | ||
3849 | if (width_Rect(bounds) <= 0) { | ||
3850 | return; | ||
3851 | } | ||
3852 | draw_Widget(w); | ||
3853 | if (d->drawBufs->flags & updateTimestampBuf_DrawBufsFlag) { | ||
3854 | updateTimestampBuf_DocumentWidget_(d); | ||
3855 | } | ||
3856 | if (d->drawBufs->flags & updateSideBuf_DrawBufsFlag) { | ||
3857 | updateSideIconBuf_DocumentWidget_(d); | ||
3858 | } | ||
3859 | const iRect docBounds = documentBounds_DocumentWidget_(d); | ||
3860 | const iRangei vis = visibleRange_DocumentWidget_(d); | ||
3861 | iDrawContext ctx = { | ||
3862 | .widget = d, | ||
3863 | .docBounds = docBounds, | ||
3864 | .vis = vis, | ||
3865 | .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0, | ||
3866 | }; | ||
3867 | render_DocumentWidget_(d, &ctx, iFalse /* just the mandatory parts */); | ||
3719 | setClip_Paint(&ctx.paint, bounds); | 3868 | setClip_Paint(&ctx.paint, bounds); |
3720 | const int yTop = docBounds.pos.y - value_Anim(&d->scrollY); | 3869 | const int yTop = docBounds.pos.y - value_Anim(&d->scrollY); |
3721 | draw_VisBuf(visBuf, init_I2(bounds.pos.x, yTop)); | 3870 | draw_VisBuf(d->visBuf, init_I2(bounds.pos.x, yTop)); |
3722 | /* Text markers. */ | 3871 | /* Text markers. */ |
3872 | const iBool isTouchSelecting = (flags_Widget(w) & touchDrag_WidgetFlag) != 0; | ||
3723 | if (!isEmpty_Range(&d->foundMark) || !isEmpty_Range(&d->selectMark)) { | 3873 | if (!isEmpty_Range(&d->foundMark) || !isEmpty_Range(&d->selectMark)) { |
3724 | SDL_Renderer *render = renderer_Window(get_Window()); | 3874 | SDL_Renderer *render = renderer_Window(get_Window()); |
3725 | ctx.firstMarkRect = zero_Rect(); | 3875 | ctx.firstMarkRect = zero_Rect(); |
3726 | ctx.lastMarkRect = zero_Rect(); | 3876 | ctx.lastMarkRect = zero_Rect(); |
3727 | SDL_SetRenderDrawBlendMode(render, | 3877 | SDL_SetRenderDrawBlendMode(render, |
@@ -3729,15 +3879,15 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { | |||
3729 | : SDL_BLENDMODE_BLEND); | 3879 | : SDL_BLENDMODE_BLEND); |
3730 | ctx.viewPos = topLeft_Rect(docBounds); | 3880 | ctx.viewPos = topLeft_Rect(docBounds); |
3731 | /* Marker starting outside the visible range? */ | 3881 | /* Marker starting outside the visible range? */ |
3732 | if (d->firstVisibleRun) { | 3882 | if (d->visibleRuns.start) { |
3733 | if (!isEmpty_Range(&d->selectMark) && | 3883 | if (!isEmpty_Range(&d->selectMark) && |
3734 | d->selectMark.start < d->firstVisibleRun->text.start && | 3884 | d->selectMark.start < d->visibleRuns.start->text.start && |
3735 | d->selectMark.end > d->firstVisibleRun->text.start) { | 3885 | d->selectMark.end > d->visibleRuns.start->text.start) { |
3736 | ctx.inSelectMark = iTrue; | 3886 | ctx.inSelectMark = iTrue; |
3737 | } | 3887 | } |
3738 | if (isEmpty_Range(&d->foundMark) && | 3888 | if (isEmpty_Range(&d->foundMark) && |
3739 | d->foundMark.start < d->firstVisibleRun->text.start && | 3889 | d->foundMark.start < d->visibleRuns.start->text.start && |
3740 | d->foundMark.end > d->firstVisibleRun->text.start) { | 3890 | d->foundMark.end > d->visibleRuns.start->text.start) { |
3741 | ctx.inFoundMark = iTrue; | 3891 | ctx.inFoundMark = iTrue; |
3742 | } | 3892 | } |
3743 | } | 3893 | } |
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) | |||
29 | void init_VisBuf(iVisBuf *d) { | 29 | void init_VisBuf(iVisBuf *d) { |
30 | d->texSize = zero_I2(); | 30 | d->texSize = zero_I2(); |
31 | iZap(d->buffers); | 31 | iZap(d->buffers); |
32 | iZap(d->vis); | ||
33 | d->bufferInvalidated = NULL; | ||
32 | } | 34 | } |
33 | 35 | ||
34 | void deinit_VisBuf(iVisBuf *d) { | 36 | void deinit_VisBuf(iVisBuf *d) { |
@@ -36,9 +38,14 @@ void deinit_VisBuf(iVisBuf *d) { | |||
36 | } | 38 | } |
37 | 39 | ||
38 | void invalidate_VisBuf(iVisBuf *d) { | 40 | void invalidate_VisBuf(iVisBuf *d) { |
41 | int origin = d->vis.start - d->texSize.y; | ||
39 | iForIndices(i, d->buffers) { | 42 | iForIndices(i, d->buffers) { |
40 | d->buffers[i].origin = i * d->texSize.y; | 43 | d->buffers[i].origin = origin; |
44 | origin += d->texSize.y; | ||
41 | iZap(d->buffers[i].validRange); | 45 | iZap(d->buffers[i].validRange); |
46 | if (d->bufferInvalidated) { | ||
47 | d->bufferInvalidated(d, i); | ||
48 | } | ||
42 | } | 49 | } |
43 | } | 50 | } |
44 | 51 | ||
@@ -58,9 +65,13 @@ void alloc_VisBuf(iVisBuf *d, const iInt2 size, int granularity) { | |||
58 | texSize.x, | 65 | texSize.x, |
59 | texSize.y); | 66 | texSize.y); |
60 | SDL_SetTextureBlendMode(tex->texture, SDL_BLENDMODE_NONE); | 67 | SDL_SetTextureBlendMode(tex->texture, SDL_BLENDMODE_NONE); |
61 | tex->origin = i * texSize.y; | 68 | // tex->origin = i * texSize.y; |
62 | iZap(tex->validRange); | 69 | // iZap(tex->validRange); |
70 | // if (d->invalidUserData) { | ||
71 | // d->invalidUserData(i, d->buffers[i].user); | ||
72 | // } | ||
63 | } | 73 | } |
74 | invalidate_VisBuf(d); | ||
64 | } | 75 | } |
65 | } | 76 | } |
66 | 77 | ||
@@ -72,53 +83,74 @@ void dealloc_VisBuf(iVisBuf *d) { | |||
72 | } | 83 | } |
73 | } | 84 | } |
74 | 85 | ||
75 | void reposition_VisBuf(iVisBuf *d, const iRangei vis) { | 86 | #if 0 |
76 | d->vis = vis; | 87 | static size_t findMostDistant_VisBuf_(const iVisBuf *d, const size_t *avail, size_t numAvail, |
77 | iRangei good = { 0, 0 }; | 88 | const iRangei vis) { |
78 | size_t avail[iElemCount(d->buffers)], numAvail = 0; | 89 | size_t chosen = 0; |
79 | /* Check which buffers are available for reuse. */ { | 90 | int distChosen = iAbsi(d->buffers[0].origin - vis.start); |
80 | iForIndices(i, d->buffers) { | 91 | printf(" avail (got %zu): %zu", numAvail, avail[0]); |
81 | iVisBufTexture *buf = d->buffers + i; | 92 | for (size_t i = 1; i < numAvail; i++) { |
82 | const iRangei region = { buf->origin, buf->origin + d->texSize.y }; | 93 | printf(" %zu", avail[i]); |
83 | if (isEmpty_Rangei(buf->validRange) || | 94 | const int dist = iAbsi(d->buffers[i].origin - vis.start); |
84 | buf->validRange.start >= vis.end || buf->validRange.end <= vis.start) { | 95 | if (dist > distChosen) { |
85 | avail[numAvail++] = i; | 96 | chosen = i; |
86 | iZap(buf->validRange); | 97 | distChosen = dist; |
87 | } | ||
88 | else { | ||
89 | good = union_Rangei(good, region); | ||
90 | } | ||
91 | } | 98 | } |
92 | } | 99 | } |
100 | printf("\n chose index %zu (%d)\n", chosen, distChosen); | ||
101 | return chosen; | ||
102 | } | ||
103 | |||
104 | static size_t take_(size_t *avail, size_t *numAvail, size_t index) { | ||
105 | const size_t value = avail[index]; | ||
106 | memmove(avail + index, avail + index + 1, sizeof(size_t) * (*numAvail - index - 1)); | ||
107 | (*numAvail)--; | ||
108 | return value; | ||
109 | } | ||
110 | #endif | ||
111 | |||
112 | iBool reposition_VisBuf(iVisBuf *d, const iRangei vis) { | ||
113 | if (equal_Rangei(vis, d->vis)) { | ||
114 | return iFalse; | ||
115 | } | ||
116 | d->vis = vis; | ||
93 | iBool wasChanged = iFalse; | 117 | iBool wasChanged = iFalse; |
94 | iBool doReset = (numAvail == iElemCount(d->buffers)); | 118 | const size_t lastPos = iElemCount(d->buffers) - 1; |
95 | /* Try to extend to cover the visible range. */ | 119 | if (d->buffers[0].origin > vis.end || d->buffers[lastPos].origin + d->texSize.y <= vis.start) { |
96 | while (!doReset && vis.start < good.start) { | 120 | /* All buffers outside the visible region. */ |
97 | if (numAvail == 0) { | 121 | invalidate_VisBuf(d); |
98 | doReset = iTrue; | ||
99 | break; | ||
100 | } | ||
101 | good.start -= d->texSize.y; | ||
102 | d->buffers[avail[--numAvail]].origin = good.start; | ||
103 | wasChanged = iTrue; | 122 | wasChanged = iTrue; |
104 | } | 123 | } |
105 | while (!doReset && vis.end > good.end) { | 124 | else { |
106 | if (numAvail == 0) { | 125 | /* Roll up. */ |
107 | doReset = iTrue; | 126 | while (d->buffers[0].origin > vis.start) { |
108 | break; | 127 | SDL_Texture *last = d->buffers[lastPos].texture; |
128 | void * user = d->buffers[lastPos].user; | ||
129 | memmove(d->buffers + 1, d->buffers, sizeof(iVisBufTexture) * lastPos); | ||
130 | d->buffers[0].texture = last; | ||
131 | d->buffers[0].user = user; | ||
132 | d->buffers[0].origin = d->buffers[1].origin - d->texSize.y; | ||
133 | iZap(d->buffers[0].validRange); | ||
134 | if (d->bufferInvalidated) { | ||
135 | d->bufferInvalidated(d, 0); | ||
136 | } | ||
137 | wasChanged = iTrue; | ||
109 | } | 138 | } |
110 | d->buffers[avail[--numAvail]].origin = good.end; | 139 | if (!wasChanged) { |
111 | good.end += d->texSize.y; | 140 | /* Roll down. */ |
112 | wasChanged = iTrue; | 141 | while (d->buffers[lastPos].origin + d->texSize.y < vis.end) { |
113 | } | 142 | SDL_Texture *first = d->buffers[0].texture; |
114 | if (doReset) { | 143 | void * user = d->buffers[0].user; |
115 | // puts("VisBuf reset!"); | 144 | memmove(d->buffers, d->buffers + 1, sizeof(iVisBufTexture) * lastPos); |
116 | // fflush(stdout); | 145 | d->buffers[lastPos].texture = first; |
117 | wasChanged = iTrue; | 146 | d->buffers[lastPos].user = user; |
118 | int pos = -1; | 147 | d->buffers[lastPos].origin = d->buffers[lastPos - 1].origin + d->texSize.y; |
119 | iForIndices(i, d->buffers) { | 148 | iZap(d->buffers[lastPos].validRange); |
120 | iZap(d->buffers[i].validRange); | 149 | if (d->bufferInvalidated) { |
121 | d->buffers[i].origin = vis.start + pos++ * d->texSize.y; | 150 | d->bufferInvalidated(d, lastPos); |
151 | } | ||
152 | wasChanged = iTrue; | ||
153 | } | ||
122 | } | 154 | } |
123 | } | 155 | } |
124 | #if 0 | 156 | #if 0 |
@@ -134,6 +166,33 @@ void reposition_VisBuf(iVisBuf *d, const iRangei vis) { | |||
134 | fflush(stdout); | 166 | fflush(stdout); |
135 | } | 167 | } |
136 | #endif | 168 | #endif |
169 | #if !defined (NDEBUG) | ||
170 | /* Buffers must not overlap. */ | ||
171 | iForIndices(m, d->buffers) { | ||
172 | const iRangei M = { d->buffers[m].origin, d->buffers[m].origin + d->texSize.y }; | ||
173 | iForIndices(n, d->buffers) { | ||
174 | if (m == n) continue; | ||
175 | const iRangei N = { d->buffers[n].origin, d->buffers[n].origin + d->texSize.y }; | ||
176 | const iRangei is = intersect_Rangei(M, N); | ||
177 | if (size_Range(&is) != 0) { | ||
178 | printf("buffers %zu (%i) and %zu (%i) overlap\n", | ||
179 | m, M.start, n, N.start); | ||
180 | fflush(stdout); | ||
181 | } | ||
182 | iAssert(size_Range(&is) == 0); | ||
183 | } | ||
184 | } | ||
185 | #endif | ||
186 | return iTrue; /* at least the visible range changed */ | ||
187 | } | ||
188 | |||
189 | iRangei allocRange_VisBuf(const iVisBuf *d) { | ||
190 | return (iRangei){ d->buffers[0].origin, | ||
191 | d->buffers[iElemCount(d->buffers) - 1].origin + d->texSize.y }; | ||
192 | } | ||
193 | |||
194 | iRangei bufferRange_VisBuf(const iVisBuf *d, size_t index) { | ||
195 | return (iRangei){ d->buffers[index].origin, d->buffers[index].origin + d->texSize.y }; | ||
137 | } | 196 | } |
138 | 197 | ||
139 | void invalidRanges_VisBuf(const iVisBuf *d, const iRangei full, iRangei *out_invalidRanges) { | 198 | void invalidRanges_VisBuf(const iVisBuf *d, const iRangei full, iRangei *out_invalidRanges) { |
@@ -177,5 +236,5 @@ void draw_VisBuf(const iVisBuf *d, iInt2 topLeft) { | |||
177 | dst.y += get_Window()->root->rect.size.y / 4; | 236 | dst.y += get_Window()->root->rect.size.y / 4; |
178 | #endif | 237 | #endif |
179 | SDL_RenderCopy(render, buf->texture, NULL, &dst); | 238 | SDL_RenderCopy(render, buf->texture, NULL, &dst); |
180 | } | 239 | } |
181 | } | 240 | } |
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 { | |||
33 | SDL_Texture *texture; | 33 | SDL_Texture *texture; |
34 | int origin; | 34 | int origin; |
35 | iRangei validRange; | 35 | iRangei validRange; |
36 | void *user; | ||
36 | }; | 37 | }; |
37 | 38 | ||
39 | #define numBuffers_VisBuf ((size_t) 4) | ||
40 | |||
38 | struct Impl_VisBuf { | 41 | struct Impl_VisBuf { |
39 | iInt2 texSize; | 42 | iInt2 texSize; |
40 | iRangei vis; | 43 | iRangei vis; |
41 | iVisBufTexture buffers[4]; | 44 | iVisBufTexture buffers[numBuffers_VisBuf]; |
45 | void (*bufferInvalidated)(iVisBuf *, size_t index); | ||
42 | }; | 46 | }; |
43 | 47 | ||
44 | iDeclareTypeConstruction(VisBuf) | 48 | iDeclareTypeConstruction(VisBuf) |
@@ -46,8 +50,10 @@ iDeclareTypeConstruction(VisBuf) | |||
46 | void invalidate_VisBuf (iVisBuf *); | 50 | void invalidate_VisBuf (iVisBuf *); |
47 | void alloc_VisBuf (iVisBuf *, const iInt2 size, int granularity); | 51 | void alloc_VisBuf (iVisBuf *, const iInt2 size, int granularity); |
48 | void dealloc_VisBuf (iVisBuf *); | 52 | void dealloc_VisBuf (iVisBuf *); |
49 | void reposition_VisBuf (iVisBuf *, const iRangei vis); | 53 | iBool reposition_VisBuf (iVisBuf *, const iRangei vis); /* returns true if `vis` changes */ |
50 | void validate_VisBuf (iVisBuf *); | 54 | void validate_VisBuf (iVisBuf *); |
51 | 55 | ||
56 | iRangei allocRange_VisBuf (const iVisBuf *); | ||
57 | iRangei bufferRange_VisBuf (const iVisBuf *, size_t index); | ||
52 | void invalidRanges_VisBuf (const iVisBuf *, const iRangei full, iRangei *out_invalidRanges); | 58 | void invalidRanges_VisBuf (const iVisBuf *, const iRangei full, iRangei *out_invalidRanges); |
53 | void draw_VisBuf (const iVisBuf *, iInt2 topLeft); | 59 | void draw_VisBuf (const iVisBuf *, iInt2 topLeft); |