1 /* Copyright 2020 Jaakko Keränen <jaakko.keranen@iki.fi>
2 
3 Redistribution and use in source and binary forms, with or without
4 modification, are permitted provided that the following conditions are met:
5 
6 1. Redistributions of source code must retain the above copyright notice, this
7    list of conditions and the following disclaimer.
8 2. Redistributions in binary form must reproduce the above copyright notice,
9    this list of conditions and the following disclaimer in the documentation
10    and/or other materials provided with the distribution.
11 
12 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
16 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19 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
21 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 
23 #include "visbuf.h"
24 #include "window.h"
25 #include "util.h"
26 
iDefineTypeConstruction(VisBuf)27 iDefineTypeConstruction(VisBuf)
28 
29 void init_VisBuf(iVisBuf *d) {
30     d->texSize = zero_I2();
31     iZap(d->buffers);
32     iZap(d->vis);
33     d->bufferInvalidated = NULL;
34 }
35 
deinit_VisBuf(iVisBuf * d)36 void deinit_VisBuf(iVisBuf *d) {
37     dealloc_VisBuf(d);
38 }
39 
invalidate_VisBuf(iVisBuf * d)40 void invalidate_VisBuf(iVisBuf *d) {
41     int origin = iMax(0, d->vis.start - d->texSize.y);
42     iForIndices(i, d->buffers) {
43         d->buffers[i].origin = origin;
44         origin += d->texSize.y;
45         iZap(d->buffers[i].validRange);
46         if (d->bufferInvalidated) {
47             d->bufferInvalidated(d, i);
48         }
49     }
50 }
51 
alloc_VisBuf(iVisBuf * d,const iInt2 size,int granularity)52 void alloc_VisBuf(iVisBuf *d, const iInt2 size, int granularity) {
53     const iInt2 texSize = init_I2(size.x, (size.y / 2 / granularity + 1) * granularity);
54     if (!d->buffers[0].texture || !isEqual_I2(texSize, d->texSize)) {
55         d->texSize = texSize;
56         iForIndices(i, d->buffers) {
57             iVisBufTexture *tex = &d->buffers[i];
58             if (tex->texture) {
59                 SDL_DestroyTexture(tex->texture);
60             }
61             SDL_Renderer *rend = renderer_Window(get_Window());
62             tex->texture =
63                 SDL_CreateTexture(rend,
64                                   SDL_PIXELFORMAT_RGBA8888,
65                                   SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET,
66                                   texSize.x,
67                                   texSize.y);
68             SDL_SetTextureBlendMode(tex->texture, SDL_BLENDMODE_NONE);
69         }
70         invalidate_VisBuf(d);
71     }
72 }
73 
dealloc_VisBuf(iVisBuf * d)74 void dealloc_VisBuf(iVisBuf *d) {
75     d->texSize = zero_I2();
76     iForIndices(i, d->buffers) {
77         SDL_DestroyTexture(d->buffers[i].texture);
78         d->buffers[i].texture = NULL;
79     }
80 }
81 
82 #if 0
83 static size_t findMostDistant_VisBuf_(const iVisBuf *d, const size_t *avail, size_t numAvail,
84                                       const iRangei vis) {
85     size_t chosen = 0;
86     int distChosen = iAbsi(d->buffers[0].origin - vis.start);
87     printf("  avail (got %zu): %zu", numAvail, avail[0]);
88     for (size_t i = 1; i < numAvail; i++) {
89         printf(" %zu", avail[i]);
90         const int dist = iAbsi(d->buffers[i].origin - vis.start);
91         if (dist > distChosen) {
92             chosen = i;
93             distChosen = dist;
94         }
95     }
96     printf("\n  chose index %zu (%d)\n", chosen, distChosen);
97     return chosen;
98 }
99 
100 static size_t take_(size_t *avail, size_t *numAvail, size_t index) {
101     const size_t value = avail[index];
102     memmove(avail + index, avail + index + 1, sizeof(size_t) * (*numAvail - index - 1));
103     (*numAvail)--;
104     return value;
105 }
106 #endif
107 
roll_VisBuf_(iVisBuf * d,int dir)108 static void roll_VisBuf_(iVisBuf *d, int dir) {
109     const size_t lastPos = iElemCount(d->buffers) - 1;
110     if (dir < 0) {
111         /* Last buffer is moved to the beginning. */
112         SDL_Texture *last = d->buffers[lastPos].texture;
113         void *       user = d->buffers[lastPos].user;
114         memmove(d->buffers + 1, d->buffers, sizeof(iVisBufTexture) * lastPos);
115         d->buffers[0].texture = last;
116         d->buffers[0].user    = user;
117         d->buffers[0].origin  = d->buffers[1].origin - d->texSize.y;
118         iZap(d->buffers[0].validRange);
119         if (d->bufferInvalidated) {
120             d->bufferInvalidated(d, 0);
121         }
122     }
123     else {
124         /* First buffer is moved to the end. */
125         SDL_Texture *first = d->buffers[0].texture;
126         void *       user  = d->buffers[0].user;
127         memmove(d->buffers, d->buffers + 1, sizeof(iVisBufTexture) * lastPos);
128         d->buffers[lastPos].texture = first;
129         d->buffers[lastPos].user    = user;
130         d->buffers[lastPos].origin  = d->buffers[lastPos - 1].origin + d->texSize.y;
131         iZap(d->buffers[lastPos].validRange);
132         if (d->bufferInvalidated) {
133             d->bufferInvalidated(d, lastPos);
134         }
135     }
136 }
137 
reposition_VisBuf(iVisBuf * d,const iRangei vis)138 iBool reposition_VisBuf(iVisBuf *d, const iRangei vis) {
139     if (equal_Rangei(vis, d->vis)) {
140         return iFalse;
141     }
142     const int moveDir = vis.end > d->vis.end ? +1 : -1;
143     d->vis = vis;
144     iBool wasChanged = iFalse;
145     const size_t lastPos = iElemCount(d->buffers) - 1;
146     if (d->buffers[0].origin > vis.end || d->buffers[lastPos].origin + d->texSize.y <= vis.start) {
147         /* All buffers outside the visible region. */
148         invalidate_VisBuf(d);
149         wasChanged = iTrue;
150     }
151     else {
152         /* Check for mandatory rolls. */
153         while (d->buffers[0].origin > vis.start) {
154             roll_VisBuf_(d, -1);
155             wasChanged = iTrue;
156         }
157         if (!wasChanged) {
158             while (d->buffers[lastPos].origin + d->texSize.y < vis.end) {
159                 roll_VisBuf_(d, +1);
160                 wasChanged = iTrue;
161             }
162         }
163         /* Scroll-direction dependent optional rolls, with a bit of overscroll allowed. */
164         if (moveDir > 0 && d->buffers[0].origin + d->texSize.y + d->texSize.y / 4 < vis.start) {
165             roll_VisBuf_(d, +1);
166             wasChanged = iTrue;
167         }
168         else if (moveDir < 0 && d->buffers[lastPos].origin - d->texSize.y / 4 > vis.end) {
169             roll_VisBuf_(d, -1);
170             wasChanged = iTrue;
171         }
172     }
173 #if 0
174     if (wasChanged) {
175         printf("\nVISIBLE RANGE: %d ... %d\n", vis.start, vis.end);
176         iForIndices(i, d->buffers) {
177             const iVisBufTexture *bt = &d->buffers[i];
178             printf(" %zu: buf %5d ... %5d  valid %5d ... %5d\n", i, bt->origin,
179                    bt->origin + d->texSize.y,
180                    bt->validRange.start,
181                    bt->validRange.end);
182         }
183         fflush(stdout);
184     }
185 #endif
186 #if !defined (NDEBUG)
187     /* Buffers must not overlap. */
188     iForIndices(m, d->buffers) {
189         const iRangei M = { d->buffers[m].origin, d->buffers[m].origin + d->texSize.y };
190         iForIndices(n, d->buffers) {
191             if (m == n) continue;
192             const iRangei N = { d->buffers[n].origin, d->buffers[n].origin + d->texSize.y };
193             const iRangei is = intersect_Rangei(M, N);
194             if (size_Range(&is) != 0) {
195                 printf("buffers %zu (%i) and %zu (%i) overlap\n",
196                        m, M.start, n, N.start);
197                 fflush(stdout);
198             }
199             iAssert(size_Range(&is) == 0);
200         }
201     }
202 #endif
203     return iTrue; /* at least the visible range changed */
204 }
205 
allocRange_VisBuf(const iVisBuf * d)206 iRangei allocRange_VisBuf(const iVisBuf *d) {
207     return (iRangei){ d->buffers[0].origin,
208                       d->buffers[iElemCount(d->buffers) - 1].origin + d->texSize.y };
209 }
210 
bufferRange_VisBuf(const iVisBuf * d,size_t index)211 iRangei bufferRange_VisBuf(const iVisBuf *d, size_t index) {
212     return (iRangei){ d->buffers[index].origin, d->buffers[index].origin + d->texSize.y };
213 }
214 
invalidRanges_VisBuf(const iVisBuf * d,const iRangei full,iRangei * out_invalidRanges)215 void invalidRanges_VisBuf(const iVisBuf *d, const iRangei full, iRangei *out_invalidRanges) {
216     iForIndices(i, d->buffers) {
217         const iVisBufTexture *buf = d->buffers + i;
218         const iRangei before = { full.start, buf->validRange.start };
219         const iRangei after  = { buf->validRange.end, full.end };
220         const iRangei region = intersect_Rangei(d->vis, (iRangei){ buf->origin,
221                                                                    buf->origin + d->texSize.y });
222         out_invalidRanges[i] = intersect_Rangei(before, region);
223         if (isEmpty_Rangei(out_invalidRanges[i])) {
224             out_invalidRanges[i] = intersect_Rangei(after, region);
225         }
226     }
227 }
228 
validate_VisBuf(iVisBuf * d)229 void validate_VisBuf(iVisBuf *d) {
230     iForIndices(i, d->buffers) {
231         iVisBufTexture *buf = &d->buffers[i];
232         buf->validRange =
233             intersect_Rangei(d->vis, (iRangei){ buf->origin, buf->origin + d->texSize.y });
234     }
235 }
236 
237 //#define DEBUG_SCALE 0.5f
238 
draw_VisBuf(const iVisBuf * d,const iInt2 topLeft,const iRangei yClipBounds)239 void draw_VisBuf(const iVisBuf *d, const iInt2 topLeft, const iRangei yClipBounds) {
240     SDL_Renderer *render = renderer_Window(get_Window());
241     iForIndices(i, d->buffers) {
242         const iVisBufTexture *buf = d->buffers + i;
243         SDL_Rect dst = { topLeft.x,
244                          topLeft.y + buf->origin,
245                          d->texSize.x,
246                          d->texSize.y };
247         if (dst.y >= yClipBounds.end || dst.y + dst.h < yClipBounds.start) {
248 #if !defined (DEBUG_SCALE)
249             continue; /* Outside the clipping area. */
250 #endif
251         }
252 #if defined (DEBUG_SCALE)
253         dst.w *= DEBUG_SCALE;
254         dst.h *= DEBUG_SCALE;
255         dst.x *= DEBUG_SCALE;
256         dst.y *= DEBUG_SCALE;
257         dst.x += get_Window()->root->rect.size.x / 4;
258         dst.y += get_Window()->root->rect.size.y / 4;
259 #endif
260         SDL_RenderCopy(render, buf->texture, NULL, &dst);
261 #if defined (DEBUG_SCALE)
262         SDL_SetRenderDrawColor(render, 0, 0, 255, 255);
263         SDL_RenderDrawRect(render, &dst);
264 #endif
265     }
266 #if defined (DEBUG_SCALE)
267     SDL_Rect dst = { topLeft.x, yClipBounds.start, d->texSize.x, 2 * d->texSize.y };
268     dst.w *= DEBUG_SCALE;
269     dst.h *= DEBUG_SCALE;
270     dst.x *= DEBUG_SCALE;
271     dst.y *= DEBUG_SCALE;
272     dst.x += get_Window()->root->rect.size.x / 4;
273     dst.y += get_Window()->root->rect.size.y / 4;
274     SDL_SetRenderDrawColor(render, 255, 255, 255, 255);
275     SDL_RenderDrawRect(render, &dst);
276 #endif
277 }
278