diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-09-11 23:46:49 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-09-11 23:46:49 +0300 |
commit | 949947908e70217af7545f0edc43bcdfd3dea92e (patch) | |
tree | cf6b5f9f9ba6d410a93a3e1dc7935dd4951ef280 | |
parent | d36cabcc327d586a13d30c53c579927a4837e4a3 (diff) |
ListWidget: Improved scroll buffering
Using less memory and doing less copying/drawing.
-rw-r--r-- | src/ui/listwidget.c | 250 |
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 | |||
55 | iDeclareType(ListVisBuffer) | ||
56 | |||
57 | struct Impl_ListVisBuffer { | ||
58 | SDL_Texture *texture; | ||
59 | int origin; | ||
60 | iRangei validRange; | ||
61 | }; | ||
62 | |||
53 | struct Impl_ListWidget { | 63 | struct 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 | ||
68 | void init_ListWidget(iListWidget *d) { | 80 | void 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 | ||
87 | void deinit_ListWidget(iListWidget *d) { | 97 | void 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 | ||
94 | void invalidate_ListWidget(iListWidget *d) { | 105 | void 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) { | |||
159 | void setScrollPos_ListWidget(iListWidget *d, int pos) { | 173 | void 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 | ||
319 | static void allocVisBuffer_ListWidget_(iListWidget *d) { | 333 | static 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 | ||
351 | static const iListItem *item_ListWidget_(const iListWidget *d, size_t pos) { | 370 | static 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 | ||
375 | static 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 | |||
355 | static void draw_ListWidget_(const iListWidget *d) { | 392 | static 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 | ||