summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-09-11 23:46:49 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-09-11 23:46:49 +0300
commit949947908e70217af7545f0edc43bcdfd3dea92e (patch)
treecf6b5f9f9ba6d410a93a3e1dc7935dd4951ef280
parentd36cabcc327d586a13d30c53c579927a4837e4a3 (diff)
ListWidget: Improved scroll buffering
Using less memory and doing less copying/drawing.
-rw-r--r--src/ui/listwidget.c250
1 files changed, 199 insertions, 51 deletions
diff --git a/src/ui/listwidget.c b/src/ui/listwidget.c
index d021543c..985ba2e7 100644
--- a/src/ui/listwidget.c
+++ b/src/ui/listwidget.c
@@ -50,6 +50,16 @@ enum iBufferValidity {
50 full_BufferValidity, 50 full_BufferValidity,
51}; 51};
52 52
53#define numVisBuffers_ListWidget_ 3
54
55iDeclareType(ListVisBuffer)
56
57struct Impl_ListVisBuffer {
58 SDL_Texture *texture;
59 int origin;
60 iRangei validRange;
61};
62
53struct Impl_ListWidget { 63struct Impl_ListWidget {
54 iWidget widget; 64 iWidget widget;
55 iScrollWidget *scroll; 65 iScrollWidget *scroll;
@@ -59,10 +69,12 @@ struct Impl_ListWidget {
59 size_t hoverItem; 69 size_t hoverItem;
60 iClick click; 70 iClick click;
61 iIntSet invalidItems; 71 iIntSet invalidItems;
62 SDL_Texture *visBuffer[2]; 72// SDL_Texture *visBuffer[numVisBuffers_ListWidget_];
63 int visBufferIndex; 73// int visBufferIndex;
64 int visBufferScrollY; 74// int visBufferScrollY[3];
65 enum iBufferValidity visBufferValid; 75// enum iBufferValidity visBufferValid;
76 iInt2 visBufSize;
77 iListVisBuffer visBuffers[numVisBuffers_ListWidget_];
66}; 78};
67 79
68void init_ListWidget(iListWidget *d) { 80void init_ListWidget(iListWidget *d) {
@@ -78,21 +90,23 @@ void init_ListWidget(iListWidget *d) {
78 d->hoverItem = iInvalidPos; 90 d->hoverItem = iInvalidPos;
79 init_Click(&d->click, d, SDL_BUTTON_LEFT); 91 init_Click(&d->click, d, SDL_BUTTON_LEFT);
80 init_IntSet(&d->invalidItems); 92 init_IntSet(&d->invalidItems);
81 iZap(d->visBuffer); 93 d->visBufSize = zero_I2();
82 d->visBufferIndex = 0; 94 iZap(d->visBuffers);
83 d->visBufferValid = none_BufferValidity;
84 d->visBufferScrollY = 0;
85} 95}
86 96
87void deinit_ListWidget(iListWidget *d) { 97void deinit_ListWidget(iListWidget *d) {
88 clear_ListWidget(d); 98 clear_ListWidget(d);
89 deinit_PtrArray(&d->items); 99 deinit_PtrArray(&d->items);
90 SDL_DestroyTexture(d->visBuffer[0]); 100 iForIndices(i, d->visBuffers) {
91 SDL_DestroyTexture(d->visBuffer[1]); 101 SDL_DestroyTexture(d->visBuffers[i].texture);
102 }
92} 103}
93 104
94void invalidate_ListWidget(iListWidget *d) { 105void invalidate_ListWidget(iListWidget *d) {
95 d->visBufferValid = none_BufferValidity; 106 //d->visBufferValid = none_BufferValidity;
107 iForIndices(i, d->visBuffers) {
108 iZap(d->visBuffers[i].validRange);
109 }
96 clear_IntSet(&d->invalidItems); /* all will be drawn */ 110 clear_IntSet(&d->invalidItems); /* all will be drawn */
97 refresh_Widget(as_Widget(d)); 111 refresh_Widget(as_Widget(d));
98} 112}
@@ -159,7 +173,7 @@ int scrollPos_ListWidget(const iListWidget *d) {
159void setScrollPos_ListWidget(iListWidget *d, int pos) { 173void setScrollPos_ListWidget(iListWidget *d, int pos) {
160 d->scrollY = pos; 174 d->scrollY = pos;
161 d->hoverItem = iInvalidPos; 175 d->hoverItem = iInvalidPos;
162 d->visBufferValid = partial_BufferValidity; 176// d->visBufferValid = partial_BufferValidity;
163 refresh_Widget(as_Widget(d)); 177 refresh_Widget(as_Widget(d));
164} 178}
165 179
@@ -177,7 +191,7 @@ void scrollOffset_ListWidget(iListWidget *d, int offset) {
177 d->hoverItem = iInvalidPos; 191 d->hoverItem = iInvalidPos;
178 } 192 }
179 updateVisible_ListWidget(d); 193 updateVisible_ListWidget(d);
180 d->visBufferValid = partial_BufferValidity; 194// d->visBufferValid = partial_BufferValidity;
181 refresh_Widget(as_Widget(d)); 195 refresh_Widget(as_Widget(d));
182 } 196 }
183} 197}
@@ -317,20 +331,25 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) {
317} 331}
318 332
319static void allocVisBuffer_ListWidget_(iListWidget *d) { 333static void allocVisBuffer_ListWidget_(iListWidget *d) {
320 const iInt2 size = innerBounds_Widget(as_Widget(d)).size; 334 /* Make sure two buffers cover the entire visible area. */
321 if (!d->visBuffer[0] || !isEqual_I2(size_SDLTexture(d->visBuffer[0]), size)) { 335 const iInt2 size = div_I2(addY_I2(innerBounds_Widget(as_Widget(d)).size, 1), init_I2(1, 2));
322 iForIndices(i, d->visBuffer) { 336 if (!d->visBuffers[0].texture || !isEqual_I2(size, d->visBufSize)) {
323 if (d->visBuffer[i]) { 337 d->visBufSize = size;
324 SDL_DestroyTexture(d->visBuffer[i]); 338 iForIndices(i, d->visBuffers) {
339 if (d->visBuffers[i].texture) {
340 SDL_DestroyTexture(d->visBuffers[i].texture);
325 } 341 }
326 d->visBuffer[i] = SDL_CreateTexture(renderer_Window(get_Window()), 342 d->visBuffers[i].texture =
327 SDL_PIXELFORMAT_RGBA8888, 343 SDL_CreateTexture(renderer_Window(get_Window()),
328 SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET, 344 SDL_PIXELFORMAT_RGBA8888,
329 size.x, 345 SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET,
330 size.y); 346 size.x,
331 SDL_SetTextureBlendMode(d->visBuffer[i], SDL_BLENDMODE_NONE); 347 size.y);
348 SDL_SetTextureBlendMode(d->visBuffers[i].texture, SDL_BLENDMODE_NONE);
349 d->visBuffers[i].origin = i * size.y;
350 iZap(d->visBuffers[i].validRange);
332 } 351 }
333 d->visBufferValid = none_BufferValidity; 352 //d->visBufferValid = none_BufferValidity;
334 } 353 }
335} 354}
336 355
@@ -340,32 +359,157 @@ static void drawItem_ListWidget_(const iListWidget *d, iPaint *p, size_t index,
340 const iRect bufBounds = { zero_I2(), bounds.size }; 359 const iRect bufBounds = { zero_I2(), bounds.size };
341 const iListItem *item = constAt_PtrArray(&d->items, index); 360 const iListItem *item = constAt_PtrArray(&d->items, index);
342 const iRect itemRect = { pos, init_I2(width_Rect(bounds), d->itemHeight) }; 361 const iRect itemRect = { pos, init_I2(width_Rect(bounds), d->itemHeight) };
343 setClip_Paint(p, intersect_Rect(itemRect, bufBounds)); 362// setClip_Paint(p, intersect_Rect(itemRect, bufBounds));
344 if (d->visBufferValid) { 363// if (d->visBufferValid) {
345 fillRect_Paint(p, itemRect, w->bgColor); 364// fillRect_Paint(p, itemRect, w->bgColor);
346 } 365// }
347 class_ListItem(item)->draw(item, p, itemRect, d); 366 class_ListItem(item)->draw(item, p, itemRect, d);
348 unsetClip_Paint(p); 367// unsetClip_Paint(p);
349} 368}
350 369
351static const iListItem *item_ListWidget_(const iListWidget *d, size_t pos) { 370static const iListItem *item_ListWidget_(const iListWidget *d, size_t pos) {
352 return constAt_PtrArray(&d->items, pos); 371 return constAt_PtrArray(&d->items, pos);
353} 372}
354 373
374#if 0
375static size_t findBuffer_ListWidget_(const iListWidget *d, int top, const iRangei vis) {
376 size_t avail = iInvalidPos;
377 iForIndices(i, d->visBuffers) {
378 const iListVisBuffer *buf = d->visBuffers + i;
379 const iRangei bufRange = { buf->scrollY, buf->scrollY + d->visBufSize.y };
380 if (top >= bufRange.start && top < bufRange.end) {
381 return i;
382 }
383 if (buf->scrollY >= vis.end || buf->scrollY + d->visBufSize.y <= vis.start) {
384 avail = i; /* Outside currently visible region. */
385 }
386 }
387 iAssert(avail != iInvalidPos);
388 return avail;
389}
390#endif
391
355static void draw_ListWidget_(const iListWidget *d) { 392static void draw_ListWidget_(const iListWidget *d) {
356 const iWidget *w = constAs_Widget(d); 393 const iWidget *w = constAs_Widget(d);
357 const iRect bounds = innerBounds_Widget(w); 394 const iRect bounds = innerBounds_Widget(w);
358 if (!bounds.size.y || !bounds.size.x) return; 395 if (!bounds.size.y || !bounds.size.x) return;
359 iPaint p; 396 iPaint p;
360 init_Paint(&p); 397 init_Paint(&p);
361 SDL_Renderer *render = renderer_Window(get_Window()); 398 SDL_Renderer *render = renderer_Window(get_Window());
362 drawBackground_Widget(w); 399 drawBackground_Widget(w);
363 if (d->visBufferValid != full_BufferValidity || !isEmpty_IntSet(&d->invalidItems)) { 400 iListWidget *m = iConstCast(iListWidget *, d);
364 iListWidget *m = iConstCast(iListWidget *, d); 401 allocVisBuffer_ListWidget_(m);
365 allocVisBuffer_ListWidget_(m); 402 /* Update invalid regions/items. */
366 iAssert(d->visBuffer); 403 if (d->visBufSize.y > 0) {
367 const int vbSrc = d->visBufferIndex; 404 /*if (d->visBufferValid != full_BufferValidity || !isEmpty_IntSet(&d->invalidItems)) */
368 const int vbDst = d->visBufferIndex ^ 1; 405 iAssert(d->visBuffers[0].texture);
406 iAssert(d->visBuffers[1].texture);
407 iAssert(d->visBuffers[2].texture);
408// const int vbSrc = d->visBufferIndex;
409// const int vbDst = d->visBufferIndex ^ 1;
410 const int bottom = numItems_ListWidget(d) * d->itemHeight;
411 const iRangei vis = { d->scrollY, d->scrollY + bounds.size.y };
412 iRangei good = { 0, 0 };
413 printf("visBufSize.y = %d\n", d->visBufSize.y);
414 size_t avail[3], numAvail = 0;
415 /* Check which buffers are available for reuse. */ {
416 iForIndices(i, d->visBuffers) {
417 iListVisBuffer *buf = m->visBuffers + i;
418 const iRangei region = { buf->origin, buf->origin + d->visBufSize.y };
419 if (region.start >= vis.end || region.end <= vis.start) {
420 avail[numAvail++] = i;
421 iZap(buf->validRange);
422 }
423 else {
424 good = union_Rangei(good, region);
425 }
426 }
427 }
428 if (numAvail == numVisBuffers_ListWidget_) {
429 /* All buffers are outside the visible range, do a reset. */
430 m->visBuffers[0].origin = vis.start;
431 m->visBuffers[1].origin = vis.start + d->visBufSize.y;
432 }
433 else {
434 /* Extend to cover the visible range. */
435 while (vis.start < good.start) {
436 iAssert(numAvail > 0);
437 m->visBuffers[avail[--numAvail]].origin = good.start - d->visBufSize.y;
438 good.start -= d->visBufSize.y;
439 }
440 while (vis.end > good.end) {
441 iAssert(numAvail > 0);
442 m->visBuffers[avail[--numAvail]].origin = good.end;
443 good.end += d->visBufSize.y;
444 }
445 }
446 /* Check which parts are invalid. */
447 iRangei invalidRange[3];
448 iForIndices(i, d->visBuffers) {
449 const iListVisBuffer *buf = d->visBuffers + i;
450 const iRangei region = intersect_Rangei(vis, (iRangei){ buf->origin, buf->origin + d->visBufSize.y });
451 const iRangei before = { 0, buf->validRange.start };
452 const iRangei after = { buf->validRange.end, bottom };
453 invalidRange[i] = intersect_Rangei(before, region);
454 if (isEmpty_Rangei(invalidRange[i])) {
455 invalidRange[i] = intersect_Rangei(after, region);
456 }
457 }
458 iForIndices(i, d->visBuffers) {
459 iListVisBuffer *buf = m->visBuffers + i;
460 printf("%zu: orig %d, invalid %d ... %d\n", i, buf->origin, invalidRange[i].start, invalidRange[i].end);
461 iRanges drawItems = { iMax(0, buf->origin) / d->itemHeight,
462 iMax(0, buf->origin + d->visBufSize.y) / d->itemHeight + 1 };
463 iBool isTargetSet = iFalse;
464 if (isEmpty_Rangei(buf->validRange)) {
465 isTargetSet = iTrue;
466 beginTarget_Paint(&p, buf->texture);
467 fillRect_Paint(&p, (iRect){ zero_I2(), d->visBufSize }, w->bgColor);
468 }
469 iConstForEach(IntSet, v, &d->invalidItems) {
470 const size_t index = *v.value;
471 if (contains_Range(&drawItems, index)) {
472 const iListItem *item = constAt_PtrArray(&d->items, index);
473 const iRect itemRect = { init_I2(0, index * d->itemHeight - buf->origin),
474 init_I2(d->visBufSize.x, d->itemHeight) };
475 if (!isTargetSet) {
476 beginTarget_Paint(&p, buf->texture);
477 isTargetSet = iTrue;
478 }
479 fillRect_Paint(&p, itemRect, w->bgColor);
480 class_ListItem(item)->draw(item, &p, itemRect, d);
481 printf("- drawing invalid item %zu\n", index);
482 }
483 }
484 if (!isEmpty_Rangei(invalidRange[i])) {
485 if (!isTargetSet) {
486 beginTarget_Paint(&p, buf->texture);
487 isTargetSet = iTrue;
488 }
489 /* Visible range is not fully covered. Fill in the new items. */
490// fillRect_Paint(&p, (iRect){ init_I2(0, invalidRange[i].start - buf->origin),
491// init_I2(d->visBufSize.x, invalidRange[i].end - buf->origin) },
492// w->bgColor);
493 drawItems.start = invalidRange[i].start / d->itemHeight;
494 drawItems.end = invalidRange[i].end / d->itemHeight + 1;
495 for (size_t j = drawItems.start; j < drawItems.end && j < size_PtrArray(&d->items); j++) {
496 const iListItem *item = constAt_PtrArray(&d->items, j);
497 const iRect itemRect = { init_I2(0, j * d->itemHeight - buf->origin),
498 init_I2(d->visBufSize.x, d->itemHeight) };
499 fillRect_Paint(&p, itemRect, w->bgColor);
500 class_ListItem(item)->draw(item, &p, itemRect, d);
501 printf("- drawing item %zu\n", j);
502 }
503 }
504 /* TODO: Redraw invalidated items. */
505 if (isTargetSet) {
506 endTarget_Paint(&p);
507 }
508 buf->validRange =
509 intersect_Rangei(vis, (iRangei){ buf->origin, buf->origin + d->visBufSize.y });
510 fflush(stdout);
511 }
512#if 0
369 beginTarget_Paint(&p, d->visBuffer[vbDst]); 513 beginTarget_Paint(&p, d->visBuffer[vbDst]);
370 const iRect bufBounds = (iRect){ zero_I2(), bounds.size }; 514 const iRect bufBounds = (iRect){ zero_I2(), bounds.size };
371 iRanges invalidRange = { 0, 0 }; 515 iRanges invalidRange = { 0, 0 };
@@ -390,15 +534,6 @@ static void draw_ListWidget_(const iListWidget *d) {
390 invalidRange.start = d->scrollY / d->itemHeight; 534 invalidRange.start = d->scrollY / d->itemHeight;
391 invalidRange.end = d->visBufferScrollY / d->itemHeight + 1; 535 invalidRange.end = d->visBufferScrollY / d->itemHeight + 1;
392 } 536 }
393#if 0
394 /* Separators may consist of multiple items. */
395 if (item_ListWidget_(d, invalidRange.start)->isSeparator) {
396 invalidRange.start--;
397 }
398 if (item_ListWidget_(d, invalidRange.end)->isSeparator) {
399 invalidRange.end++;
400 }
401#endif
402 } 537 }
403 /* Draw items. */ { 538 /* Draw items. */ {
404 const iRanges visRange = visRange_ListWidget_(d); 539 const iRanges visRange = visRange_ListWidget_(d);
@@ -429,12 +564,25 @@ static void draw_ListWidget_(const iListWidget *d) {
429 } 564 }
430 endTarget_Paint(&p); 565 endTarget_Paint(&p);
431 /* Update state. */ 566 /* Update state. */
432 m->visBufferValid = iTrue; 567// m->visBufferValid = iTrue;
433 m->visBufferScrollY = m->scrollY; 568// m->visBufferScrollY = m->scrollY;
434 m->visBufferIndex = vbDst; 569// m->visBufferIndex = vbDst;
570#endif
435 clear_IntSet(&m->invalidItems); 571 clear_IntSet(&m->invalidItems);
436 } 572 }
437 SDL_RenderCopy(render, d->visBuffer[d->visBufferIndex], NULL, (const SDL_Rect *) &bounds); 573 //SDL_RenderCopy(render, d->visBuffer[d->visBufferIndex], NULL, (const SDL_Rect *) &bounds);
574 setClip_Paint(&p, bounds_Widget(w));
575 iForIndices(i, d->visBuffers) {
576 const iListVisBuffer *buf = d->visBuffers + i;
577 SDL_RenderCopy(render,
578 buf->texture,
579 NULL,
580 &(SDL_Rect){ left_Rect(bounds),
581 top_Rect(bounds) - d->scrollY + buf->origin,
582 d->visBufSize.x,
583 d->visBufSize.y });
584 }
585 unsetClip_Paint(&p);
438 drawChildren_Widget(w); 586 drawChildren_Widget(w);
439} 587}
440 588