| 1 | /* |
| 2 | gfx.c: software drawing on the canvas. |
| 3 | */ |
| 4 | #include <stdlib.h> |
| 5 | #include <string.h> |
| 6 | #include <math.h> |
| 7 | #include "gfx.h" |
| 8 | #include "gpu.h" |
| 9 |
|
| 10 | #define STBI_ONLY_JPEG |
| 11 | #define STBI_ONLY_PNG |
| 12 | #define STBI_NO_SIMD |
| 13 | #define STBI_NO_STDIO |
| 14 | #define STBI_MAX_DIMENSIONS 2048 // artwork comes from the network (and is asked for at 400 px): refuse giant images |
| 15 | #define STB_IMAGE_IMPLEMENTATION |
| 16 | #include "thirdparty/stb_image.h" |
| 17 |
|
| 18 | static uint32_t *cv; |
| 19 | static int cw, ch; |
| 20 | static int clx0, cly0, clx1, cly1; // clip, x1/y1 exclusive |
| 21 | static int gpu_on; // the calls build the GPU's list (the end of this file) |
| 22 |
|
| 23 | // the GPU path's versions (below) |
| 24 | static void G_Rect(float x, float y, float w, float h, uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3); |
| 25 | static void G_Round_Rect(int x, int y, int w, int h, int r, uint32_t c); |
| 26 | static void G_Mask(const uint8_t *mask, int mw, int mh, int pitch, float x, float y, float dw, float dh, uint32_t c); |
| 27 | static void G_Image(const image_t *img, int x, int y, int w, int h, int radius, int alpha); |
| 28 | static void G_Image_Part(const image_t *img, int sx, int sy, int sw, int sh, int x, int y, int w, int h, uint32_t top, uint32_t bottom); |
| 29 | static void G_Tint(const image_t *img, int sx, int sw, int x, int y, uint32_t c); |
| 30 | static int G_Icon(int icon, int x, int y, int sz, uint32_t c); |
| 31 | static void G_Noise_Gradient(int x, int y, int w, int h, int slot); |
| 32 |
|
| 33 | void gfx_init(uint32_t *canvas, int w, int h) |
| 34 | { |
| 35 | if (gpu_on && !canvas) { cw = w; ch = h; gfx_unclip(); return; } |
| 36 | cv = canvas; |
| 37 | cw = w; |
| 38 | ch = h; |
| 39 | gfx_unclip(); |
| 40 | } |
| 41 |
|
| 42 | uint32_t *gfx_canvas(void) { return cv; } |
| 43 |
|
| 44 | static void Raw_gfx_clip(int x, int y, int w, int h) |
| 45 | { |
| 46 | clx0 = x < 0 ? 0 : x; |
| 47 | cly0 = y < 0 ? 0 : y; |
| 48 | clx1 = x + w > cw ? cw : x + w; |
| 49 | cly1 = y + h > ch ? ch : y + h; |
| 50 | } |
| 51 |
|
| 52 | static void Raw_gfx_clip_get(int *x, int *y, int *w, int *h) |
| 53 | { |
| 54 | *x = clx0; *y = cly0; *w = clx1 - clx0; *h = cly1 - cly0; |
| 55 | } |
| 56 |
|
| 57 | void gfx_unclip(void) |
| 58 | { |
| 59 | clx0 = cly0 = 0; |
| 60 | clx1 = cw; |
| 61 | cly1 = ch; |
| 62 | } |
| 63 |
|
| 64 | static inline uint32_t blend(uint32_t d, uint32_t s, int a) |
| 65 | { |
| 66 | // a: 0..255 |
| 67 | if (a >= 255) return s | 0xff000000; |
| 68 | if (a <= 0) return d; |
| 69 | uint32_t rb = (((s & 0xff00ff) * a) + ((d & 0xff00ff) * (255 - a))) >> 8; |
| 70 | uint32_t g = (((s & 0x00ff00) * a) + ((d & 0x00ff00) * (255 - a))) >> 8; |
| 71 | return 0xff000000 | (rb & 0xff00ff) | (g & 0x00ff00); |
| 72 | } |
| 73 |
|
| 74 | static inline void plot(int x, int y, uint32_t c, int a) |
| 75 | { |
| 76 | if (x < clx0 || x >= clx1 || y < cly0 || y >= cly1 || a <= 0) |
| 77 | return; |
| 78 | uint32_t *p = &cv[y * cw + x]; |
| 79 | *p = blend(*p, c, a); |
| 80 | } |
| 81 |
|
| 82 | // a row of one colour, two pixels a store |
| 83 | static inline void Fill(uint32_t *p, int n, uint32_t c) |
| 84 | { |
| 85 | if (((uintptr_t)p & 4) && n > 0) { *p++ = c; n--; } |
| 86 | uint64_t cc = ((uint64_t)c << 32) | c; |
| 87 | uint64_t *q = (uint64_t *)p; |
| 88 | int pairs = n >> 1; |
| 89 | for (int i = 0; i < pairs; i++) q[i] = cc; |
| 90 | if (n & 1) p[n - 1] = c; |
| 91 | } |
| 92 |
|
| 93 | void gfx_clear(uint32_t c) |
| 94 | { |
| 95 | if (gpu_on) { |
| 96 | int x0 = clx0, y0 = cly0, x1 = clx1, y1 = cly1; |
| 97 | gfx_unclip(); // (a clear isn't clipped) |
| 98 | c |= 0xff000000; |
| 99 | G_Rect(0, 0, cw, ch, c, c, c, c); |
| 100 | clx0 = x0; cly0 = y0; clx1 = x1; cly1 = y1; |
| 101 | return; |
| 102 | } |
| 103 | Fill(cv, cw * ch, c | 0xff000000); |
| 104 | } |
| 105 |
|
| 106 | static void Raw_gfx_rect(int x, int y, int w, int h, uint32_t c) |
| 107 | { |
| 108 | if (gpu_on) { if (c >> 24) G_Rect(x, y, w, h, c, c, c, c); return; } |
| 109 | int a = c >> 24; |
| 110 | int x0 = x < clx0 ? clx0 : x, y0 = y < cly0 ? cly0 : y; |
| 111 | int x1 = x + w > clx1 ? clx1 : x + w, y1 = y + h > cly1 ? cly1 : y + h; |
| 112 | if (x0 >= x1 || y0 >= y1 || !a) |
| 113 | return; |
| 114 | for (int j = y0; j < y1; j++) { |
| 115 | uint32_t *p = &cv[j * cw + x0]; |
| 116 | if (a == 255) { |
| 117 | Fill(p, x1 - x0, c | 0xff000000); |
| 118 | } else { |
| 119 | for (int i = x0; i < x1; i++, p++) *p = blend(*p, c, a); |
| 120 | } |
| 121 | } |
| 122 | } |
| 123 |
|
| 124 | // coverage of a pixel in a rounded box's corner (0..255) |
| 125 | static inline int corner_cov(float px, float py, float cx, float cy, float r) |
| 126 | { |
| 127 | float dx = px - cx, dy = py - cy; |
| 128 | float d = sqrtf(dx * dx + dy * dy); |
| 129 | float cov = r + 0.5f - d; |
| 130 | if (cov >= 1.0f) return 255; |
| 131 | if (cov <= 0.0f) return 0; |
| 132 | return (int)(cov * 255.0f); |
| 133 | } |
| 134 |
|
| 135 | // corner coverage tables, one per radius: [j * r + i] for the top-left |
| 136 | // corner's pixel (i, j). Built once: the PPU is slow at square roots. |
| 137 | #define MAX_LUT_R 256 |
| 138 | static uint8_t *cov_lut[MAX_LUT_R + 1]; |
| 139 |
|
| 140 | static const uint8_t *Cov(int r) |
| 141 | { |
| 142 | if (r <= 0 || r > MAX_LUT_R) return NULL; |
| 143 | if (!cov_lut[r]) { |
| 144 | uint8_t *t = malloc(r * r); |
| 145 | if (!t) return NULL; |
| 146 | for (int j = 0; j < r; j++) |
| 147 | for (int i = 0; i < r; i++) |
| 148 | t[j * r + i] = (uint8_t)corner_cov(i + 0.5f, j + 0.5f, r, r, r); |
| 149 | cov_lut[r] = t; |
| 150 | } |
| 151 | return cov_lut[r]; |
| 152 | } |
| 153 |
|
| 154 | static void Raw_gfx_round_rect(int x, int y, int w, int h, int r, uint32_t c) |
| 155 | { |
| 156 | int a = c >> 24; |
| 157 | if (r <= 0) { Raw_gfx_rect(x, y, w, h, c); return; } |
| 158 | if (gpu_on) { G_Round_Rect(x, y, w, h, r, c); return; } |
| 159 | if (r * 2 > w) r = w / 2; |
| 160 | if (r * 2 > h) r = h / 2; |
| 161 | // middle band and the side bands between the corners |
| 162 | Raw_gfx_rect(x, y + r, w, h - 2 * r, c); |
| 163 | Raw_gfx_rect(x + r, y, w - 2 * r, r, c); |
| 164 | Raw_gfx_rect(x + r, y + h - r, w - 2 * r, r, c); |
| 165 | const uint8_t *lut = Cov(r); |
| 166 | for (int j = 0; j < r; j++) { |
| 167 | for (int i = 0; i < r; i++) { |
| 168 | int cov = lut ? lut[j * r + i] : corner_cov(i + 0.5f, j + 0.5f, r, r, r); |
| 169 | if (!cov) continue; |
| 170 | int aa = a * cov / 255; |
| 171 | plot(x + i, y + j, c, aa); |
| 172 | plot(x + w - 1 - i, y + j, c, aa); |
| 173 | plot(x + i, y + h - 1 - j, c, aa); |
| 174 | plot(x + w - 1 - i, y + h - 1 - j, c, aa); |
| 175 | } |
| 176 | } |
| 177 | } |
| 178 |
|
| 179 | static inline uint32_t lerp_c(uint32_t a, uint32_t b, int t) // t 0..256 |
| 180 | { |
| 181 | int aa = a >> 24, ab = b >> 24; |
| 182 | int ra = (a >> 16) & 255, rb = (b >> 16) & 255; |
| 183 | int ga = (a >> 8) & 255, gb = (b >> 8) & 255; |
| 184 | int ba = a & 255, bb = b & 255; |
| 185 | return argb(aa + (ab - aa) * t / 256, ra + (rb - ra) * t / 256, ga + (gb - ga) * t / 256, ba + (bb - ba) * t / 256); |
| 186 | } |
| 187 |
|
| 188 | void gfx_vgradient(int x, int y, int w, int h, uint32_t top, uint32_t bottom) |
| 189 | { |
| 190 | if (gpu_on) { if (w > 0 && h > 0) G_Rect(x, y, w, h, top, top, bottom, bottom); return; } |
| 191 | for (int j = 0; j < h; j++) |
| 192 | Raw_gfx_rect(x, y + j, w, 1, lerp_c(top, bottom, h > 1 ? j * 256 / (h - 1) : 0)); |
| 193 | } |
| 194 |
|
| 195 | /* |
| 196 | A big gradient with a fine grain over it: dark gradients across 720 rows |
| 197 | step only every 30 rows or so and show bands; adding noise below one |
| 198 | level before rounding hides the steps. Built once per size and colours |
| 199 | (two cached), then copied in rows, so it costs a memcpy a frame. |
| 200 | */ |
| 201 | static uint8_t noise_tile[64 * 64]; |
| 202 | static int noise_ready; |
| 203 |
|
| 204 | static void Noise_Init(void) |
| 205 | { |
| 206 | uint32_t r = 0x9e3779b9; |
| 207 | for (int i = 0; i < 64 * 64; i++) { |
| 208 | // triangular: the sum of two uniforms, 0..510, so the grain has no hard edges |
| 209 | r ^= r << 13; r ^= r >> 17; r ^= r << 5; |
| 210 | int a = r & 255; |
| 211 | r ^= r << 13; r ^= r >> 17; r ^= r << 5; |
| 212 | noise_tile[i] = (uint8_t)((a + (int)(r & 255)) >> 1); |
| 213 | } |
| 214 | noise_ready = 1; |
| 215 | } |
| 216 |
|
| 217 | static struct { int w, h; uint32_t top, bottom, used; uint32_t *px; int fresh; } grad_cache[2]; |
| 218 | static uint32_t grad_clock; |
| 219 |
|
| 220 | void gfx_vgradient_noise(int x, int y, int w, int h, uint32_t top, uint32_t bottom) |
| 221 | { |
| 222 | if (w <= 0 || h <= 0) return; |
| 223 | if (!noise_ready) Noise_Init(); |
| 224 | int k, slot = -1; |
| 225 | for (k = 0; k < 2; k++) |
| 226 | if (grad_cache[k].px && grad_cache[k].w == w && grad_cache[k].h == h |
| 227 | && grad_cache[k].top == top && grad_cache[k].bottom == bottom) slot = k; |
| 228 | if (slot < 0) { |
| 229 | slot = grad_cache[0].used <= grad_cache[1].used ? 0 : 1; |
| 230 | if (grad_cache[slot].w * grad_cache[slot].h < w * h || !grad_cache[slot].px) { |
| 231 | free(grad_cache[slot].px); |
| 232 | grad_cache[slot].px = malloc((size_t)w * h * 4); |
| 233 | if (!grad_cache[slot].px) { grad_cache[slot].w = 0; gfx_vgradient(x, y, w, h, top, bottom); return; } |
| 234 | } |
| 235 | grad_cache[slot].w = w; grad_cache[slot].h = h; |
| 236 | grad_cache[slot].fresh = 1; |
| 237 | grad_cache[slot].top = top; grad_cache[slot].bottom = bottom; |
| 238 | uint32_t *o = grad_cache[slot].px; |
| 239 | int r0 = (top >> 16) & 255, g0 = (top >> 8) & 255, b0 = top & 255; |
| 240 | int r1 = (bottom >> 16) & 255, g1 = (bottom >> 8) & 255, b1 = bottom & 255; |
| 241 | for (int j = 0; j < h; j++) { |
| 242 | int t = h > 1 ? j * 65536 / (h - 1) : 0; |
| 243 | // 8.8 fixed point |
| 244 | int R = (r0 << 8) + (int)(((int64_t)(r1 - r0) * t) >> 8); |
| 245 | int G = (g0 << 8) + (int)(((int64_t)(g1 - g0) * t) >> 8); |
| 246 | int B = (b0 << 8) + (int)(((int64_t)(b1 - b0) * t) >> 8); |
| 247 | const uint8_t *nrow = &noise_tile[(j & 63) * 64]; |
| 248 | for (int i = 0; i < w; i++) { |
| 249 | // -1..+1 level: rounds the fraction away, plus a little grain |
| 250 | int n = ((int)nrow[i & 63] - 128) * 2; |
| 251 | int rr = (R + n + 128) >> 8, gg = (G + n + 128) >> 8, bb = (B + n + 128) >> 8; |
| 252 | rr = rr < 0 ? 0 : rr > 255 ? 255 : rr; |
| 253 | gg = gg < 0 ? 0 : gg > 255 ? 255 : gg; |
| 254 | bb = bb < 0 ? 0 : bb > 255 ? 255 : bb; |
| 255 | *o++ = 0xff000000 | (rr << 16) | (gg << 8) | bb; |
| 256 | } |
| 257 | } |
| 258 | } |
| 259 | grad_cache[slot].used = ++grad_clock; |
| 260 | if (gpu_on) { G_Noise_Gradient(x, y, w, h, slot); return; } |
| 261 | const uint32_t *src = grad_cache[slot].px; |
| 262 | for (int j = 0; j < h; j++) { |
| 263 | int yy = y + j; |
| 264 | if (yy < cly0 || yy >= cly1) continue; |
| 265 | int x0 = x < clx0 ? clx0 : x, x1 = x + w > clx1 ? clx1 : x + w; |
| 266 | if (x1 > x0) memcpy(&cv[yy * cw + x0], &src[j * w + (x0 - x)], (size_t)(x1 - x0) * 4); |
| 267 | } |
| 268 | } |
| 269 |
|
| 270 | void gfx_hgradient(int x, int y, int w, int h, uint32_t left, uint32_t right) |
| 271 | { |
| 272 | if (gpu_on) { if (w > 0 && h > 0) G_Rect(x, y, w, h, left, right, left, right); return; } |
| 273 | for (int i = 0; i < w; i++) |
| 274 | Raw_gfx_rect(x + i, y, 1, h, lerp_c(left, right, w > 1 ? i * 256 / (w - 1) : 0)); |
| 275 | } |
| 276 |
|
| 277 | static void Raw_gfx_circle(int cx, int cy, int r, uint32_t c) |
| 278 | { |
| 279 | Raw_gfx_round_rect(cx - r, cy - r, 2 * r, 2 * r, r, c); |
| 280 | } |
| 281 |
|
| 282 | static void ring(float cx, float cy, float r, float thick, uint32_t c) |
| 283 | { |
| 284 | int a = c >> 24; |
| 285 | int R = (int)(r + thick) + 2; |
| 286 | for (int j = -R; j <= R; j++) |
| 287 | for (int i = -R; i <= R; i++) { |
| 288 | float px = i + 0.5f, py = j + 0.5f; |
| 289 | float d = sqrtf(px * px + py * py); |
| 290 | float cov = thick * 0.5f + 0.5f - fabsf(d - r); |
| 291 | if (cov <= 0) continue; |
| 292 | if (cov > 1) cov = 1; |
| 293 | plot((int)cx + i, (int)cy + j, c, (int)(a * cov)); |
| 294 | } |
| 295 | } |
| 296 |
|
| 297 | // a thick line with round ends, anti-aliased |
| 298 | static void capsule(float x0, float y0, float x1, float y1, float thick, uint32_t c) |
| 299 | { |
| 300 | int a = c >> 24; |
| 301 | float hr = thick * 0.5f; |
| 302 | int bx0 = (int)floorf(fminf(x0, x1) - hr - 1), bx1 = (int)ceilf(fmaxf(x0, x1) + hr + 1); |
| 303 | int by0 = (int)floorf(fminf(y0, y1) - hr - 1), by1 = (int)ceilf(fmaxf(y0, y1) + hr + 1); |
| 304 | float dx = x1 - x0, dy = y1 - y0, ll = dx * dx + dy * dy; |
| 305 | for (int j = by0; j <= by1; j++) |
| 306 | for (int i = bx0; i <= bx1; i++) { |
| 307 | float px = i + 0.5f - x0, py = j + 0.5f - y0; |
| 308 | float t = ll > 0 ? (px * dx + py * dy) / ll : 0; |
| 309 | if (t < 0) t = 0; else if (t > 1) t = 1; |
| 310 | float ex = px - t * dx, ey = py - t * dy; |
| 311 | float cov = hr + 0.5f - sqrtf(ex * ex + ey * ey); |
| 312 | if (cov <= 0) continue; |
| 313 | if (cov > 1) cov = 1; |
| 314 | plot(i, j, c, (int)(a * cov)); |
| 315 | } |
| 316 | } |
| 317 |
|
| 318 | // a filled triangle, anti-aliased along its edges |
| 319 | static void triangle(float x0, float y0, float x1, float y1, float x2, float y2, uint32_t c) |
| 320 | { |
| 321 | int a = c >> 24; |
| 322 | int bx0 = (int)floorf(fminf(x0, fminf(x1, x2))) - 1, bx1 = (int)ceilf(fmaxf(x0, fmaxf(x1, x2))) + 1; |
| 323 | int by0 = (int)floorf(fminf(y0, fminf(y1, y2))) - 1, by1 = (int)ceilf(fmaxf(y0, fmaxf(y1, y2))) + 1; |
| 324 | float area = (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0); |
| 325 | float s = area < 0 ? -1.0f : 1.0f; |
| 326 | float ex[3][3] = {{x0, y0, 0}, {x1, y1, 0}, {x2, y2, 0}}; |
| 327 | for (int j = by0; j <= by1; j++) |
| 328 | for (int i = bx0; i <= bx1; i++) { |
| 329 | float px = i + 0.5f, py = j + 0.5f, cov = 1.0f; |
| 330 | for (int k = 0; k < 3; k++) { |
| 331 | float ax = ex[k][0], ay = ex[k][1], bx = ex[(k + 1) % 3][0], by = ex[(k + 1) % 3][1]; |
| 332 | float nx = by - ay, ny = ax - bx, len = sqrtf(nx * nx + ny * ny); |
| 333 | if (len <= 0) continue; |
| 334 | // signed distance inside the edge, in pixels |
| 335 | float d = -s * ((px - ax) * nx + (py - ay) * ny) / len; |
| 336 | float cv_ = d + 0.5f; |
| 337 | if (cv_ < cov) cov = cv_; |
| 338 | } |
| 339 | if (cov <= 0) continue; |
| 340 | if (cov > 1) cov = 1; |
| 341 | plot(i, j, c, (int)(a * cov)); |
| 342 | } |
| 343 | } |
| 344 |
|
| 345 | void gfx_mask(const uint8_t *mask, int mw, int mh, int pitch, int x, int y, uint32_t c) |
| 346 | { |
| 347 | if (gpu_on) { G_Mask(mask, mw, mh, pitch, x, y, mw, mh, c); return; } |
| 348 | int a = c >> 24; |
| 349 | for (int j = 0; j < mh; j++) { |
| 350 | int yy = y + j; |
| 351 | if (yy < cly0 || yy >= cly1) continue; |
| 352 | const uint8_t *m = mask + j * pitch; |
| 353 | for (int i = 0; i < mw; i++) { |
| 354 | int xx = x + i; |
| 355 | if (!m[i] || xx < clx0 || xx >= clx1) continue; |
| 356 | uint32_t *p = &cv[yy * cw + xx]; |
| 357 | *p = blend(*p, c, m[i] * a / 255); |
| 358 | } |
| 359 | } |
| 360 | } |
| 361 |
|
| 362 | static int img_flip; // (Raw_gfx_image: the picture turned round, left to right) |
| 363 |
|
| 364 | static void Raw_gfx_image(const image_t *img, int x, int y, int w, int h, int radius, int alpha) |
| 365 | { |
| 366 | if (!img || w <= 0 || h <= 0) |
| 367 | return; |
| 368 | if (gpu_on) { G_Image(img, x, y, w, h, radius, alpha); return; } |
| 369 | int same = (img->w == w && img->h == h), flip = img_flip; |
| 370 | const uint8_t *lut = radius > 0 ? Cov(radius) : NULL; |
| 371 | if (same && !flip && img->opaque && alpha >= 255) { |
| 372 | // cover art: whole rows copied; with rounded corners only the |
| 373 | // corner pixels are blended (a circle's edge, for artists) |
| 374 | int x0c = x < clx0 ? clx0 : x, x1c = x + w > clx1 ? clx1 : x + w; |
| 375 | for (int j = 0; j < h; j++) { |
| 376 | int yy = y + j; |
| 377 | if (yy < cly0 || yy >= cly1 || x0c >= x1c) continue; |
| 378 | const uint32_t *row = img->px + j * img->w; |
| 379 | uint32_t *drow = &cv[yy * cw]; |
| 380 | int cj = (lut && j < radius) ? j : (lut && j >= h - radius) ? h - 1 - j : -1; |
| 381 | int inset = 0; // columns at each end handled one by one |
| 382 | if (cj >= 0) { |
| 383 | // past the first fully covered pixel of this corner row, it's a plain copy |
| 384 | inset = radius; |
| 385 | for (int i = 0; i < radius; i++) |
| 386 | if (lut[cj * radius + i] == 255) { inset = i; break; } |
| 387 | for (int i = 0; i < inset; i++) { |
| 388 | int cov = lut[cj * radius + i]; |
| 389 | if (!cov) continue; |
| 390 | int xl = x + i, xr = x + w - 1 - i; |
| 391 | if (xl >= x0c && xl < x1c) drow[xl] = blend(drow[xl], row[i], cov); |
| 392 | if (xr >= x0c && xr < x1c) drow[xr] = blend(drow[xr], row[w - 1 - i], cov); |
| 393 | } |
| 394 | } |
| 395 | int a0 = x + inset > x0c ? x + inset : x0c, a1 = x + w - inset < x1c ? x + w - inset : x1c; |
| 396 | if (a1 > a0) |
| 397 | memcpy(&drow[a0], &row[a0 - x], (a1 - a0) * 4); |
| 398 | } |
| 399 | return; |
| 400 | } |
| 401 | if (same && !flip && radius <= 0 && alpha >= 255) { |
| 402 | // straight copy where the image is opaque |
| 403 | int x0 = x < clx0 ? clx0 : x, x1 = x + w > clx1 ? clx1 : x + w; |
| 404 | for (int j = 0; j < h; j++) { |
| 405 | int yy = y + j; |
| 406 | if (yy < cly0 || yy >= cly1 || x0 >= x1) continue; |
| 407 | const uint32_t *sp = img->px + j * img->w + (x0 - x); |
| 408 | uint32_t *dp = &cv[yy * cw + x0]; |
| 409 | for (int i = x0; i < x1; i++, sp++, dp++) { |
| 410 | uint32_t sv = *sp; |
| 411 | int sa = sv >> 24; |
| 412 | *dp = sa == 255 ? sv : blend(*dp, sv, sa); |
| 413 | } |
| 414 | } |
| 415 | return; |
| 416 | } |
| 417 | // 16.16 steps for sampling |
| 418 | int sx = (img->w << 16) / w, sy = (img->h << 16) / h; |
| 419 | for (int j = 0; j < h; j++) { |
| 420 | int yy = y + j; |
| 421 | if (yy < cly0 || yy >= cly1) continue; |
| 422 | int fy = same ? (j << 16) : (j * sy + sy / 2 - 32768); |
| 423 | if (fy < 0) fy = 0; |
| 424 | int y0 = fy >> 16, y1 = y0 + 1 < img->h ? y0 + 1 : y0, ty = (fy >> 8) & 255; |
| 425 | for (int i = 0; i < w; i++) { |
| 426 | int xx = x + i; |
| 427 | if (xx < clx0 || xx >= clx1) continue; |
| 428 | int a = alpha; |
| 429 | if (radius > 0) { |
| 430 | int ci = i < radius ? i : (i >= w - radius ? w - 1 - i : -1); |
| 431 | int cj = j < radius ? j : (j >= h - radius ? h - 1 - j : -1); |
| 432 | if (ci >= 0 && cj >= 0) { |
| 433 | int cov = lut ? lut[cj * radius + ci] : corner_cov(ci + 0.5f, cj + 0.5f, radius, radius, radius); |
| 434 | a = a * cov / 255; |
| 435 | if (!a) continue; |
| 436 | } |
| 437 | } |
| 438 | uint32_t s; |
| 439 | int si = flip ? w - 1 - i : i; // (the source column) |
| 440 | if (same) { |
| 441 | s = img->px[j * img->w + si]; |
| 442 | } else { |
| 443 | int fx = si * sx + sx / 2 - 32768; |
| 444 | if (fx < 0) fx = 0; |
| 445 | int x0 = fx >> 16, x1 = x0 + 1 < img->w ? x0 + 1 : x0, tx = (fx >> 8) & 255; |
| 446 | uint32_t top = lerp_c(img->px[y0 * img->w + x0], img->px[y0 * img->w + x1], tx); |
| 447 | uint32_t bot = lerp_c(img->px[y1 * img->w + x0], img->px[y1 * img->w + x1], tx); |
| 448 | s = lerp_c(top, bot, ty); |
| 449 | } |
| 450 | int sa = (s >> 24) * a / 255; |
| 451 | uint32_t *p = &cv[yy * cw + xx]; |
| 452 | *p = blend(*p, s, sa); |
| 453 | } |
| 454 | } |
| 455 | } |
| 456 |
|
| 457 | void gfx_tint(const image_t *img, int sx, int sw, int x, int y, uint32_t c) |
| 458 | { |
| 459 | // columns sx..sx+sw of img (a frame of a strip) at x, y |
| 460 | int a = c >> 24; |
| 461 | if (!img) return; |
| 462 | if (gpu_on) { G_Tint(img, sx, sw, x, y, c); return; } |
| 463 | for (int j = 0; j < img->h; j++) { |
| 464 | int yy = y + j; |
| 465 | if (yy < cly0 || yy >= cly1) continue; |
| 466 | const uint32_t *sp = img->px + j * img->w + sx; |
| 467 | for (int i = 0; i < sw; i++) { |
| 468 | int xx = x + i; |
| 469 | int m = sp[i] >> 24; |
| 470 | if (!m || xx < clx0 || xx >= clx1) continue; |
| 471 | uint32_t *p = &cv[yy * cw + xx]; |
| 472 | *p = blend(*p, c, m * a / 255); |
| 473 | } |
| 474 | } |
| 475 | } |
| 476 |
|
| 477 | void gfx_icon(int icon, int x, int y, int sz, uint32_t c) |
| 478 | { |
| 479 | if (icon == ICON_REPEAT_ONE) icon = ICON_REPEAT; |
| 480 | if (gpu_on && G_Icon(icon, x, y, sz, c)) return; |
| 481 | float s = sz, X = x, Y = y; |
| 482 | float t = s * 0.1f; // line weight |
| 483 | if (t < 1.6f) t = 1.6f; |
| 484 | switch (icon) { |
| 485 | case ICON_PLAY: |
| 486 | triangle(X + s * 0.25f, Y + s * 0.15f, X + s * 0.25f, Y + s * 0.85f, X + s * 0.85f, Y + s * 0.5f, c); |
| 487 | break; |
| 488 | case ICON_PAUSE: |
| 489 | Raw_gfx_round_rect(x + (int)(s * 0.22f), y + (int)(s * 0.15f), (int)(s * 0.2f), (int)(s * 0.7f), (int)(s * 0.04f), c); |
| 490 | Raw_gfx_round_rect(x + (int)(s * 0.58f), y + (int)(s * 0.15f), (int)(s * 0.2f), (int)(s * 0.7f), (int)(s * 0.04f), c); |
| 491 | break; |
| 492 | case ICON_NEXT: |
| 493 | triangle(X + s * 0.18f, Y + s * 0.2f, X + s * 0.18f, Y + s * 0.8f, X + s * 0.68f, Y + s * 0.5f, c); |
| 494 | Raw_gfx_rect(x + (int)(s * 0.7f), y + (int)(s * 0.2f), (int)(s * 0.12f) + 1, (int)(s * 0.6f), c); |
| 495 | break; |
| 496 | case ICON_PREV: |
| 497 | triangle(X + s * 0.82f, Y + s * 0.2f, X + s * 0.82f, Y + s * 0.8f, X + s * 0.32f, Y + s * 0.5f, c); |
| 498 | Raw_gfx_rect(x + (int)(s * 0.18f), y + (int)(s * 0.2f), (int)(s * 0.12f) + 1, (int)(s * 0.6f), c); |
| 499 | break; |
| 500 | case ICON_SHUFFLE: |
| 501 | capsule(X + s * 0.12f, Y + s * 0.3f, X + s * 0.35f, Y + s * 0.3f, t, c); |
| 502 | capsule(X + s * 0.35f, Y + s * 0.3f, X + s * 0.62f, Y + s * 0.7f, t, c); |
| 503 | capsule(X + s * 0.62f, Y + s * 0.7f, X + s * 0.78f, Y + s * 0.7f, t, c); |
| 504 | capsule(X + s * 0.12f, Y + s * 0.7f, X + s * 0.35f, Y + s * 0.7f, t, c); |
| 505 | capsule(X + s * 0.35f, Y + s * 0.7f, X + s * 0.62f, Y + s * 0.3f, t, c); |
| 506 | capsule(X + s * 0.62f, Y + s * 0.3f, X + s * 0.78f, Y + s * 0.3f, t, c); |
| 507 | triangle(X + s * 0.76f, Y + s * 0.18f, X + s * 0.76f, Y + s * 0.42f, X + s * 0.92f, Y + s * 0.3f, c); |
| 508 | triangle(X + s * 0.76f, Y + s * 0.58f, X + s * 0.76f, Y + s * 0.82f, X + s * 0.92f, Y + s * 0.7f, c); |
| 509 | break; |
| 510 | case ICON_REPEAT: |
| 511 | capsule(X + s * 0.2f, Y + s * 0.55f, X + s * 0.2f, Y + s * 0.35f, t, c); |
| 512 | capsule(X + s * 0.2f, Y + s * 0.35f, X + s * 0.72f, Y + s * 0.35f, t, c); |
| 513 | capsule(X + s * 0.8f, Y + s * 0.45f, X + s * 0.8f, Y + s * 0.65f, t, c); |
| 514 | capsule(X + s * 0.8f, Y + s * 0.65f, X + s * 0.28f, Y + s * 0.65f, t, c); |
| 515 | triangle(X + s * 0.7f, Y + s * 0.22f, X + s * 0.7f, Y + s * 0.48f, X + s * 0.86f, Y + s * 0.35f, c); |
| 516 | triangle(X + s * 0.3f, Y + s * 0.52f, X + s * 0.3f, Y + s * 0.78f, X + s * 0.14f, Y + s * 0.65f, c); |
| 517 | break; |
| 518 | case ICON_NOTE: |
| 519 | Raw_gfx_circle(x + (int)(s * 0.36f), y + (int)(s * 0.72f), (int)(s * 0.15f), c); |
| 520 | capsule(X + s * 0.49f, Y + s * 0.72f, X + s * 0.49f, Y + s * 0.15f, t, c); |
| 521 | capsule(X + s * 0.49f, Y + s * 0.15f, X + s * 0.75f, Y + s * 0.28f, t, c); |
| 522 | break; |
| 523 | case ICON_PERSON: |
| 524 | Raw_gfx_circle(x + sz / 2, y + (int)(s * 0.33f), (int)(s * 0.17f), c); |
| 525 | Raw_gfx_round_rect(x + (int)(s * 0.2f), y + (int)(s * 0.56f), (int)(s * 0.6f), (int)(s * 0.32f), (int)(s * 0.16f), c); |
| 526 | break; |
| 527 | case ICON_ALBUM: |
| 528 | ring(X + s * 0.5f, Y + s * 0.5f, s * 0.3f, t, c); |
| 529 | Raw_gfx_circle(x + sz / 2, y + sz / 2, (int)(s * 0.08f) + 1, c); |
| 530 | break; |
| 531 | case ICON_LIST: |
| 532 | for (int k = 0; k < 3; k++) { |
| 533 | float yy = Y + s * (0.28f + 0.22f * k); |
| 534 | Raw_gfx_circle(x + (int)(s * 0.18f), (int)yy, (int)(t * 0.6f) + 1, c); |
| 535 | capsule(X + s * 0.34f, yy, X + s * 0.84f, yy, t, c); |
| 536 | } |
| 537 | break; |
| 538 | case ICON_GEAR: |
| 539 | for (int k = 0; k < 8; k++) { |
| 540 | float an = k * 3.14159265f / 4; |
| 541 | capsule(X + s * 0.5f + cosf(an) * s * 0.26f, Y + s * 0.5f + sinf(an) * s * 0.26f, |
| 542 | X + s * 0.5f + cosf(an) * s * 0.38f, Y + s * 0.5f + sinf(an) * s * 0.38f, t * 1.4f, c); |
| 543 | } |
| 544 | ring(X + s * 0.5f, Y + s * 0.5f, s * 0.22f, t * 1.2f, c); |
| 545 | break; |
| 546 | case ICON_WAVE: { |
| 547 | static const float hgt[5] = {0.3f, 0.6f, 0.9f, 0.55f, 0.35f}; |
| 548 | for (int k = 0; k < 5; k++) { |
| 549 | float xx = X + s * (0.18f + 0.16f * k), hh = s * hgt[k] * 0.38f; |
| 550 | capsule(xx, Y + s * 0.5f - hh, xx, Y + s * 0.5f + hh, t, c); |
| 551 | } |
| 552 | break; |
| 553 | } |
| 554 | case ICON_HOME: |
| 555 | triangle(X + s * 0.5f, Y + s * 0.14f, X + s * 0.1f, Y + s * 0.5f, X + s * 0.9f, Y + s * 0.5f, c); |
| 556 | Raw_gfx_round_rect(x + (int)(s * 0.22f), y + (int)(s * 0.46f), (int)(s * 0.56f), (int)(s * 0.4f), (int)(s * 0.05f), c); |
| 557 | break; |
| 558 | case ICON_GRID: |
| 559 | for (int k = 0; k < 4; k++) |
| 560 | Raw_gfx_round_rect(x + (int)(s * (0.16f + 0.38f * (k & 1))), y + (int)(s * (0.16f + 0.38f * (k >> 1))), |
| 561 | (int)(s * 0.3f), (int)(s * 0.3f), (int)(s * 0.06f), c); |
| 562 | break; |
| 563 | case ICON_QUEUE: // Up Next: a play mark and three lines |
| 564 | triangle(X + s * 0.1f, Y + s * 0.16f, X + s * 0.1f, Y + s * 0.44f, X + s * 0.34f, Y + s * 0.3f, c); |
| 565 | capsule(X + s * 0.46f, Y + s * 0.3f, X + s * 0.88f, Y + s * 0.3f, t, c); |
| 566 | capsule(X + s * 0.14f, Y + s * 0.53f, X + s * 0.88f, Y + s * 0.53f, t, c); |
| 567 | capsule(X + s * 0.14f, Y + s * 0.76f, X + s * 0.88f, Y + s * 0.76f, t, c); |
| 568 | break; |
| 569 | case ICON_SEARCH: |
| 570 | ring(X + s * 0.42f, Y + s * 0.42f, s * 0.22f, t, c); |
| 571 | capsule(X + s * 0.6f, Y + s * 0.6f, X + s * 0.82f, Y + s * 0.82f, t * 1.2f, c); |
| 572 | break; |
| 573 | } |
| 574 | } |
| 575 |
|
| 576 | image_t *image_new(int w, int h) |
| 577 | { |
| 578 | image_t *img = malloc(sizeof(*img)); |
| 579 | if (!img) return NULL; |
| 580 | img->w = w; |
| 581 | img->h = h; |
| 582 | img->px = malloc((size_t)w * h * 4); |
| 583 | img->opaque = 0; |
| 584 | img->rev = 0; |
| 585 | img->gpu = NULL; |
| 586 | if (!img->px) { free(img); return NULL; } |
| 587 | return img; |
| 588 | } |
| 589 |
|
| 590 | static void G_Image_Forget(image_t *img); |
| 591 |
|
| 592 | void image_touch(image_t *img) { if (img) img->rev++; } |
| 593 |
|
| 594 | void image_free(image_t *img) |
| 595 | { |
| 596 | if (!img) return; |
| 597 | G_Image_Forget(img); |
| 598 | free(img->px); |
| 599 | free(img); |
| 600 | } |
| 601 |
|
| 602 | // for a w x h box (canvas units): at the screen's own resolution when the GPU draws |
| 603 | // (1080p: 1.5 times), but never past the picture's own: an upscale is the GPU's to filter |
| 604 | image_t *image_decode_screen(const void *data, int len, int w, int h) |
| 605 | { |
| 606 | float s = gfx_scale(); |
| 607 | int iw, ih, n; |
| 608 | if (s != 1.0f && w > 0 && h > 0 && stbi_info_from_memory(data, len, &iw, &ih, &n) && iw > 0 && ih > 0) { |
| 609 | double sc = fmax((double)w * s / iw, (double)h * s / ih); // the cover scale, at the screen's size |
| 610 | double k = sc > 1 ? s / sc : s; |
| 611 | w = (int)(w * k + 0.5); |
| 612 | h = (int)(h * k + 0.5); |
| 613 | } |
| 614 | return image_decode(data, len, w > 0 ? w : 1, h > 0 ? h : 1); |
| 615 | } |
| 616 |
|
| 617 | image_t *image_decode(const void *data, int len, int w, int h) |
| 618 | { |
| 619 | int iw, ih, n; |
| 620 | unsigned char *rgba = stbi_load_from_memory(data, len, &iw, &ih, &n, 4); |
| 621 | if (!rgba) |
| 622 | return NULL; |
| 623 | image_t *img = image_new(w, h); |
| 624 | if (!img) { stbi_image_free(rgba); return NULL; } |
| 625 |
|
| 626 | // cover: the largest centred crop with the box's shape |
| 627 | double sc = fmax((double)w / iw, (double)h / ih); |
| 628 | double cw_ = w / sc, ch_ = h / sc; |
| 629 | double ox = (iw - cw_) / 2, oy = (ih - ch_) / 2; |
| 630 | for (int j = 0; j < h; j++) { |
| 631 | // box filter over the source pixels behind each output pixel |
| 632 | int sy0 = (int)(oy + j / sc), sy1 = (int)(oy + (j + 1) / sc); |
| 633 | if (sy1 <= sy0) sy1 = sy0 + 1; |
| 634 | if (sy1 > ih) sy1 = ih; |
| 635 | if (sy0 >= ih) sy0 = ih - 1; |
| 636 | for (int i = 0; i < w; i++) { |
| 637 | int sx0 = (int)(ox + i / sc), sx1 = (int)(ox + (i + 1) / sc); |
| 638 | if (sx1 <= sx0) sx1 = sx0 + 1; |
| 639 | if (sx1 > iw) sx1 = iw; |
| 640 | if (sx0 >= iw) sx0 = iw - 1; |
| 641 | unsigned r = 0, g = 0, b = 0, a = 0, cnt = 0; |
| 642 | for (int yy = sy0; yy < sy1; yy++) { |
| 643 | const unsigned char *p = rgba + ((size_t)yy * iw + sx0) * 4; |
| 644 | for (int xx = sx0; xx < sx1; xx++, p += 4) { |
| 645 | r += p[0]; g += p[1]; b += p[2]; a += p[3]; cnt++; |
| 646 | } |
| 647 | } |
| 648 | if (!cnt) cnt = 1; |
| 649 | img->px[j * w + i] = argb(a / cnt, r / cnt, g / cnt, b / cnt); |
| 650 | } |
| 651 | } |
| 652 | stbi_image_free(rgba); |
| 653 | img->opaque = 1; |
| 654 | for (int i = 0; i < w * h; i++) |
| 655 | if ((img->px[i] >> 24) != 255) { img->opaque = 0; break; } |
| 656 | return img; |
| 657 | } |
| 658 |
|
| 659 | uint32_t image_average(const image_t *img) |
| 660 | { |
| 661 | unsigned long long r = 0, g = 0, b = 0, n = 0; |
| 662 | if (!img) return 0xff202020; |
| 663 | for (int i = 0; i < img->w * img->h; i += 7) { |
| 664 | uint32_t p = img->px[i]; |
| 665 | r += (p >> 16) & 255; g += (p >> 8) & 255; b += p & 255; n++; |
| 666 | } |
| 667 | if (!n) return 0xff202020; |
| 668 | return argb(255, (int)(r / n), (int)(g / n), (int)(b / n)); |
| 669 | } |
| 670 |
|
| 671 | /* |
| 672 | ================== |
| 673 | gfx_image_part: part of an image, its colour multiplied by a vertical blend |
| 674 | ================== |
| 675 | */ |
| 676 | static inline uint32_t Mul_C(uint32_t a, uint32_t b) // per channel a * b / 255 |
| 677 | { |
| 678 | uint32_t r = 0; |
| 679 | for (int sh = 0; sh < 32; sh += 8) r |= ((((a >> sh) & 255) * ((b >> sh) & 255) + 127) / 255) << sh; |
| 680 | return r; |
| 681 | } |
| 682 |
|
| 683 | void gfx_image_part(const image_t *img, int sx, int sy, int sw, int sh, int x, int y, int w, int h, |
| 684 | uint32_t top, uint32_t bottom) |
| 685 | { |
| 686 | if (!img || sw <= 0 || sh <= 0 || w <= 0 || h <= 0) return; |
| 687 | if (gpu_on) { G_Image_Part(img, sx, sy, sw, sh, x, y, w, h, top, bottom); return; } |
| 688 | int plain = top == 0xffffffff && bottom == 0xffffffff && sw == w && sh == h; |
| 689 | for (int j = 0; j < h; j++) { |
| 690 | int yy = y + j; |
| 691 | if (yy < cly0 || yy >= cly1) continue; |
| 692 | int sj = sy + (sh == h ? j : (int)(((long long)j * 2 + 1) * sh / (2 * h))); |
| 693 | if (sj < 0 || sj >= img->h) continue; |
| 694 | const uint32_t *row = img->px + sj * img->w; |
| 695 | int x0 = x < clx0 ? clx0 : x, x1 = x + w > clx1 ? clx1 : x + w; |
| 696 | if (x0 >= x1) continue; |
| 697 | if (plain && img->opaque && sx + (x0 - x) >= 0 && sx + (x1 - x) <= img->w) { |
| 698 | memcpy(&cv[yy * cw + x0], &row[sx + (x0 - x)], (size_t)(x1 - x0) * 4); |
| 699 | continue; |
| 700 | } |
| 701 | // the row's multiplier: top..bottom at pixel centres |
| 702 | uint32_t m = lerp_c(top, bottom, (int)(((long long)j * 2 + 1) * 256 / (2 * h))); |
| 703 | for (int i = x0; i < x1; i++) { |
| 704 | int si = sx + (sw == w ? i - x : (int)(((long long)(i - x) * 2 + 1) * sw / (2 * w))); |
| 705 | if (si < 0 || si >= img->w) continue; |
| 706 | uint32_t s = Mul_C(row[si], m); |
| 707 | uint32_t *p = &cv[yy * cw + i]; |
| 708 | *p = blend(*p, s, s >> 24); |
| 709 | } |
| 710 | } |
| 711 | } |
| 712 |
|
| 713 | /* |
| 714 | ================== |
| 715 | The GPU path: the calls above as a list of quads (gpu.h) |
| 716 |
|
| 717 | Most of the UI uses one texture, the atlas: a white block (plain colour), |
| 718 | then glyphs, icons and rounded corners, each made once on the CPU (at the |
| 719 | display's scale) and kept. So text, panels and icons go in one batch; |
| 720 | only cover art and other pictures add batches of their own. |
| 721 | ================== |
| 722 | */ |
| 723 | #define ATLAS_W 2048 |
| 724 | #define ATLAS_H 2048 |
| 725 | #define MAX_QUADS 24576 |
| 726 | #define MAX_BATCHES 4096 |
| 727 | #define MAP_SIZE 16384 // atlas entries (a power of two) |
| 728 |
|
| 729 | static gquad_t gq[MAX_QUADS]; |
| 730 | static gbatch_t gb[MAX_BATCHES]; |
| 731 | static int nq, nb, g_dropped; |
| 732 | static float g_scale = 1.0f; |
| 733 | static gtex_t atlas; |
| 734 | static int atlas_ok, shelf_x, shelf_y, shelf_h, atlas_reset_due, want_redraw; |
| 735 | static float WU, WV; // the white block's middle |
| 736 | static float g_ox, g_oy; // gfx_offset |
| 737 |
|
| 738 | enum { K_MASK = 1, K_CORNER, K_ICON }; |
| 739 | typedef struct { const void *key; int kind, a, b; int x, y, w, h; } aslot_t; |
| 740 | static aslot_t amap[MAP_SIZE]; |
| 741 | static int acount; |
| 742 |
|
| 743 | typedef struct { gtex_t t; int rev; } gimg_t; |
| 744 | static gtex_t *freeing; // textures let go of: freed once the GPU is done with the frame using them |
| 745 | static int nfreeing, capfreeing; |
| 746 |
|
| 747 | int gfx_gpu(void) { return gpu_on; } |
| 748 | void gfx_offset(float dx, float dy) { g_ox = dx; g_oy = dy; } |
| 749 | float gfx_scale(void) { return gpu_on ? g_scale : 1.0f; } |
| 750 | int gfx_wants_redraw(void) { int w = want_redraw; want_redraw = 0; return w; } |
| 751 |
|
| 752 | static void Atlas_Reset(void) |
| 753 | { |
| 754 | memset(amap, 0, sizeof(amap)); |
| 755 | acount = 0; |
| 756 | // the white block: 4x4 in the corner |
| 757 | uint32_t *px = atlas.px; |
| 758 | int stride = atlas.pitch / 4; |
| 759 | for (int j = 0; j < 4; j++) |
| 760 | for (int i = 0; i < 4; i++) px[j * stride + i] = 0xffffffff; |
| 761 | shelf_x = 5; shelf_y = 0; shelf_h = 5; // (after the block, and a pixel of space) |
| 762 | atlas_reset_due = 0; |
| 763 | } |
| 764 |
|
| 765 | void gfx_gpu_enable(float scale) |
| 766 | { |
| 767 | if (!gpu_tex_alloc(&atlas, ATLAS_W, ATLAS_H)) return; |
| 768 | memset(atlas.px, 0, (size_t)atlas.pitch * ATLAS_H); |
| 769 | atlas_ok = 1; |
| 770 | WU = 2.0f / ATLAS_W; |
| 771 | WV = 2.0f / ATLAS_H; |
| 772 | Atlas_Reset(); |
| 773 | g_scale = scale > 0 ? scale : 1.0f; |
| 774 | gpu_on = 1; |
| 775 | } |
| 776 |
|
| 777 | void gfx_masks_reset(void) { if (gpu_on) atlas_reset_due = 1; } |
| 778 |
|
| 779 | void gfx_frame_begin(void) |
| 780 | { |
| 781 | if (!gpu_on) return; |
| 782 | gpu_frame_begin(); // the last frame's done with everything |
| 783 | for (int i = 0; i < nfreeing; i++) gpu_tex_free(&freeing[i]); |
| 784 | nfreeing = 0; |
| 785 | if (atlas_reset_due) Atlas_Reset(); |
| 786 | nq = nb = 0; |
| 787 | g_dropped = 0; |
| 788 | g_ox = g_oy = 0; |
| 789 | } |
| 790 |
|
| 791 | void gfx_frame_end(void) |
| 792 | { |
| 793 | if (!gpu_on) return; |
| 794 | gpu_frame_submit(gq, nq, gb, nb); |
| 795 | } |
| 796 |
|
| 797 | static void Free_Later(const gtex_t *t) |
| 798 | { |
| 799 | if (nfreeing == capfreeing) { |
| 800 | int nc = capfreeing ? capfreeing * 2 : 32; |
| 801 | gtex_t *n = realloc(freeing, nc * sizeof(gtex_t)); |
| 802 | if (!n) { gpu_tex_free((gtex_t *)t); return; } // (better than a leak) |
| 803 | freeing = n; |
| 804 | capfreeing = nc; |
| 805 | } |
| 806 | freeing[nfreeing++] = *t; |
| 807 | } |
| 808 |
|
| 809 | static void G_Image_Forget(image_t *img) |
| 810 | { |
| 811 | gimg_t *g = img->gpu; |
| 812 | if (!g) return; |
| 813 | Free_Later(&g->t); |
| 814 | free(g); |
| 815 | img->gpu = NULL; |
| 816 | } |
| 817 |
|
| 818 | // the GPU's copy of img, made or brought up to date |
| 819 | static const gtex_t *Tex_Of(const image_t *img_c) |
| 820 | { |
| 821 | image_t *img = (image_t *)img_c; // (the copy is a cache: drawing doesn't change the image) |
| 822 | gimg_t *g = img->gpu; |
| 823 | if (g && (g->t.w != img->w || g->t.h != img->h)) { G_Image_Forget(img); g = NULL; } |
| 824 | if (!g) { |
| 825 | g = calloc(1, sizeof(*g)); |
| 826 | if (!g) return NULL; |
| 827 | if (!gpu_tex_alloc(&g->t, img->w, img->h)) { free(g); want_redraw = 1; return NULL; } |
| 828 | g->rev = img->rev - 1; |
| 829 | img->gpu = g; |
| 830 | } |
| 831 | if (g->rev != img->rev) { |
| 832 | if (g->t.pitch == img->w * 4) memcpy(g->t.px, img->px, (size_t)img->w * img->h * 4); |
| 833 | else for (int j = 0; j < img->h; j++) memcpy((uint8_t *)g->t.px + (size_t)j * g->t.pitch, img->px + (size_t)j * img->w, (size_t)img->w * 4); |
| 834 | g->rev = img->rev; |
| 835 | } |
| 836 | return &g->t; |
| 837 | } |
| 838 |
|
| 839 | // one quad; a new batch when the textures or the clip change |
| 840 | static void Quad(float x0, float y0, float x1, float y1, const gtex_t *t0, float u0, float v0, float u1, float v1, |
| 841 | const gtex_t *t1, float s0, float tt0, float s1, float tt1, uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3) |
| 842 | { |
| 843 | if (x1 <= x0 || y1 <= y0) return; |
| 844 | x0 += g_ox; x1 += g_ox; y0 += g_oy; y1 += g_oy; |
| 845 | if (x1 <= clx0 || x0 >= clx1 || y1 <= cly0 || y0 >= cly1 || clx1 <= clx0 || cly1 <= cly0) return; |
| 846 | if (!((c0 | c1 | c2 | c3) >> 24)) return; |
| 847 | if (nq >= MAX_QUADS) { g_dropped++; want_redraw = 1; return; } |
| 848 | gbatch_t *b = nb ? &gb[nb - 1] : NULL; |
| 849 | if (!b || b->tex0 != t0 || b->tex1 != t1 || b->sx != clx0 || b->sy != cly0 || b->sw != clx1 - clx0 || b->sh != cly1 - cly0) { |
| 850 | if (nb >= MAX_BATCHES) { g_dropped++; want_redraw = 1; return; } |
| 851 | b = &gb[nb++]; |
| 852 | b->first = nq; |
| 853 | b->count = 0; |
| 854 | b->tex0 = t0; |
| 855 | b->tex1 = t1; |
| 856 | b->sx = clx0; b->sy = cly0; b->sw = clx1 - clx0; b->sh = cly1 - cly0; |
| 857 | } |
| 858 | gquad_t *q = &gq[nq++]; |
| 859 | q->x0 = x0; q->y0 = y0; q->x1 = x1; q->y1 = y1; |
| 860 | q->u0 = u0; q->v0 = v0; q->u1 = u1; q->v1 = v1; |
| 861 | q->s0 = s0; q->t0 = tt0; q->s1 = s1; q->t1 = tt1; |
| 862 | q->c[0] = c0; q->c[1] = c1; q->c[2] = c2; q->c[3] = c3; |
| 863 | b->count++; |
| 864 | } |
| 865 |
|
| 866 | static void G_Rect(float x, float y, float w, float h, uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3) |
| 867 | { |
| 868 | if (!atlas_ok) return; |
| 869 | Quad(x, y, x + w, y + h, &atlas, WU, WV, WU, WV, &atlas, WU, WV, WU, WV, c0, c1, c2, c3); |
| 870 | } |
| 871 |
|
| 872 | /* |
| 873 | The atlas: shelves left to right, a row at a time. Each entry has a |
| 874 | border a pixel wide that repeats its edge, so filtering at the edge reads |
| 875 | the entry's own pixels (as a texture of its own clamped at the edge would), |
| 876 | never a neighbour or an empty gap: a corner's straight edges meet the band |
| 877 | beside it without a seam, wherever the chip puts its pixel centres. |
| 878 | */ |
| 879 | static aslot_t *Find(const void *key, int kind, int a, int b, int *fresh) |
| 880 | { |
| 881 | uint32_t h = (uint32_t)(uintptr_t)key * 2654435761u ^ (uint32_t)kind * 40503u ^ (uint32_t)a * 9176u ^ (uint32_t)b * 31u; |
| 882 | for (int probe = 0; probe < MAP_SIZE; probe++) { |
| 883 | aslot_t *s = &amap[(h + probe) & (MAP_SIZE - 1)]; |
| 884 | if (!s->kind) { |
| 885 | if (acount >= MAP_SIZE * 3 / 4) { atlas_reset_due = 1; want_redraw = 1; return NULL; } |
| 886 | s->key = key; s->kind = kind; s->a = a; s->b = b; |
| 887 | s->w = 0; |
| 888 | acount++; |
| 889 | *fresh = 1; |
| 890 | return s; |
| 891 | } |
| 892 | if (s->key == key && s->kind == kind && s->a == a && s->b == b) { *fresh = 0; return s; } |
| 893 | } |
| 894 | return NULL; |
| 895 | } |
| 896 |
|
| 897 | // room for w x h; 0 when the atlas is full (it's emptied before the next frame) |
| 898 | static int Place(aslot_t *s, int w, int h) |
| 899 | { |
| 900 | int bw = w + 2, bh = h + 2; // with the border |
| 901 | if (shelf_x + bw > ATLAS_W) { shelf_y += shelf_h; shelf_x = 0; shelf_h = 0; } |
| 902 | if (shelf_y + bh > ATLAS_H || bw > ATLAS_W) { |
| 903 | atlas_reset_due = 1; |
| 904 | want_redraw = 1; |
| 905 | s->kind = 0; // (not kept: the slot stays free) |
| 906 | acount--; |
| 907 | return 0; |
| 908 | } |
| 909 | s->x = shelf_x + 1; s->y = shelf_y + 1; s->w = w; s->h = h; |
| 910 | shelf_x += bw; |
| 911 | if (bh > shelf_h) shelf_h = bh; |
| 912 | return 1; |
| 913 | } |
| 914 |
|
| 915 | // coverage (0..255) into the atlas as white with that alpha |
| 916 | static void Put_Coverage(const aslot_t *s, const uint8_t *cov, int pitch) |
| 917 | { |
| 918 | uint32_t *px = atlas.px; |
| 919 | int stride = atlas.pitch / 4; |
| 920 | // the entry and its border (the nearest edge pixel again) |
| 921 | for (int j = -1; j <= s->h; j++) { |
| 922 | uint32_t *d = &px[(s->y + j) * stride + s->x]; |
| 923 | const uint8_t *m = cov + (j < 0 ? 0 : j >= s->h ? s->h - 1 : j) * pitch; |
| 924 | for (int i = -1; i <= s->w; i++) d[i] = ((uint32_t)m[i < 0 ? 0 : i >= s->w ? s->w - 1 : i] << 24) | 0xffffff; |
| 925 | } |
| 926 | } |
| 927 |
|
| 928 | static void Atlas_UV(const aslot_t *s, float *u0, float *v0, float *u1, float *v1) |
| 929 | { |
| 930 | *u0 = (float)s->x / ATLAS_W; *v0 = (float)s->y / ATLAS_H; |
| 931 | *u1 = (float)(s->x + s->w) / ATLAS_W; *v1 = (float)(s->y + s->h) / ATLAS_H; |
| 932 | } |
| 933 |
|
| 934 | static void G_Mask(const uint8_t *mask, int mw, int mh, int pitch, float x, float y, float dw, float dh, uint32_t c) |
| 935 | { |
| 936 | if (!atlas_ok || mw <= 0 || mh <= 0 || !(c >> 24)) return; |
| 937 | int fresh; |
| 938 | aslot_t *s = Find(mask, K_MASK, mw, mh, &fresh); |
| 939 | if (!s) return; |
| 940 | if (fresh) { |
| 941 | if (!Place(s, mw, mh)) return; |
| 942 | Put_Coverage(s, mask, pitch); |
| 943 | } |
| 944 | float u0, v0, u1, v1; |
| 945 | Atlas_UV(s, &u0, &v0, &u1, &v1); |
| 946 | Quad(x, y, x + dw, y + dh, &atlas, WU, WV, WU, WV, &atlas, u0, v0, u1, v1, c, c, c, c); |
| 947 | } |
| 948 |
|
| 949 | void gfx_mask_scaled(const uint8_t *mask, int mw, int mh, int pitch, float x, float y, uint32_t c) |
| 950 | { |
| 951 | if (gpu_on) { G_Mask(mask, mw, mh, pitch, x, y, mw / g_scale, mh / g_scale, c); return; } |
| 952 | gfx_mask(mask, mw, mh, pitch, (int)floorf(x + 0.5f), (int)floorf(y + 0.5f), c); |
| 953 | } |
| 954 |
|
| 955 | // a rounded corner, radius r canvas units (made at the display's scale): the top-left one |
| 956 | static aslot_t *Corner(int r) |
| 957 | { |
| 958 | int rp = (int)ceilf(r * g_scale); |
| 959 | if (rp < 1) rp = 1; |
| 960 | int fresh; |
| 961 | aslot_t *s = Find(NULL, K_CORNER, rp, 0, &fresh); |
| 962 | if (!s) return NULL; |
| 963 | if (fresh) { |
| 964 | if (!Place(s, rp, rp)) return NULL; |
| 965 | uint8_t *cov = malloc(rp * rp); |
| 966 | if (!cov) { s->kind = 0; acount--; return NULL; } |
| 967 | for (int j = 0; j < rp; j++) |
| 968 | for (int i = 0; i < rp; i++) cov[j * rp + i] = (uint8_t)corner_cov(i + 0.5f, j + 0.5f, rp, rp, rp); |
| 969 | Put_Coverage(s, cov, rp); |
| 970 | free(cov); |
| 971 | } |
| 972 | return s; |
| 973 | } |
| 974 |
|
| 975 | // the four corners of a w x h box at x, y, radius r: tex0 (t0, its own coordinates across |
| 976 | // the box) times the corner shape |
| 977 | static void Corners(int x, int y, int w, int h, int r, const gtex_t *t0, float u0, float v0, float u1, float v1, uint32_t c) |
| 978 | { |
| 979 | aslot_t *s = Corner(r); |
| 980 | if (!s) return; |
| 981 | float a0, b0, a1, b1; |
| 982 | Atlas_UV(s, &a0, &b0, &a1, &b1); |
| 983 | float du = (u1 - u0) * r / w, dv = (v1 - v0) * r / h; |
| 984 | // top left, top right (mirrored across), bottom left (mirrored down), bottom right |
| 985 | Quad(x, y, x + r, y + r, t0, u0, v0, u0 + du, v0 + dv, &atlas, a0, b0, a1, b1, c, c, c, c); |
| 986 | Quad(x + w - r, y, x + w, y + r, t0, u1 - du, v0, u1, v0 + dv, &atlas, a1, b0, a0, b1, c, c, c, c); |
| 987 | Quad(x, y + h - r, x + r, y + h, t0, u0, v1 - dv, u0 + du, v1, &atlas, a0, b1, a1, b0, c, c, c, c); |
| 988 | Quad(x + w - r, y + h - r, x + w, y + h, t0, u1 - du, v1 - dv, u1, v1, &atlas, a1, b1, a0, b0, c, c, c, c); |
| 989 | } |
| 990 |
|
| 991 | // a box with rounded corners, tex0 across it (u0..u1, v0..v1) times c: the bands between |
| 992 | // the corners, then the corners |
| 993 | static void Rounded(int x, int y, int w, int h, int r, const gtex_t *t0, float u0, float v0, float u1, float v1, uint32_t c) |
| 994 | { |
| 995 | if (r * 2 > w) r = w / 2; |
| 996 | if (r * 2 > h) r = h / 2; |
| 997 | if (r <= 0) { Quad(x, y, x + w, y + h, t0, u0, v0, u1, v1, &atlas, WU, WV, WU, WV, c, c, c, c); return; } |
| 998 | float du = (u1 - u0) * r / w, dv = (v1 - v0) * r / h; |
| 999 | Quad(x, y + r, x + w, y + h - r, t0, u0, v0 + dv, u1, v1 - dv, &atlas, WU, WV, WU, WV, c, c, c, c); |
| 1000 | Quad(x + r, y, x + w - r, y + r, t0, u0 + du, v0, u1 - du, v0 + dv, &atlas, WU, WV, WU, WV, c, c, c, c); |
| 1001 | Quad(x + r, y + h - r, x + w - r, y + h, t0, u0 + du, v1 - dv, u1 - du, v1, &atlas, WU, WV, WU, WV, c, c, c, c); |
| 1002 | Corners(x, y, w, h, r, t0, u0, v0, u1, v1, c); |
| 1003 | } |
| 1004 |
|
| 1005 | static void G_Round_Rect(int x, int y, int w, int h, int r, uint32_t c) |
| 1006 | { |
| 1007 | if (!atlas_ok || !(c >> 24) || w <= 0 || h <= 0) return; |
| 1008 | Rounded(x, y, w, h, r, &atlas, WU, WV, WU, WV, c); |
| 1009 | } |
| 1010 |
|
| 1011 | static void G_Image(const image_t *img, int x, int y, int w, int h, int radius, int alpha) |
| 1012 | { |
| 1013 | if (!atlas_ok || alpha <= 0) return; |
| 1014 | const gtex_t *t = Tex_Of(img); |
| 1015 | if (!t) return; |
| 1016 | uint32_t c = ((uint32_t)(alpha > 255 ? 255 : alpha) << 24) | 0xffffff; |
| 1017 | if (img_flip) Rounded(x, y, w, h, radius, t, 1, 0, 0, 1, c); // (u from 1 to 0: turned round) |
| 1018 | else Rounded(x, y, w, h, radius, t, 0, 0, 1, 1, c); |
| 1019 | } |
| 1020 |
|
| 1021 | static void G_Image_Part(const image_t *img, int sx, int sy, int sw, int sh, int x, int y, int w, int h, uint32_t top, uint32_t bottom) |
| 1022 | { |
| 1023 | if (!atlas_ok) return; |
| 1024 | const gtex_t *t = Tex_Of(img); |
| 1025 | if (!t) return; |
| 1026 | Quad(x, y, x + w, y + h, t, (float)sx / img->w, (float)sy / img->h, (float)(sx + sw) / img->w, (float)(sy + sh) / img->h, |
| 1027 | &atlas, WU, WV, WU, WV, top, top, bottom, bottom); |
| 1028 | } |
| 1029 |
|
| 1030 | // a strip's frame as a stencil (its alpha), in one colour |
| 1031 | static void G_Tint(const image_t *img, int sx, int sw, int x, int y, uint32_t c) |
| 1032 | { |
| 1033 | if (!atlas_ok) return; |
| 1034 | const gtex_t *t = Tex_Of(img); |
| 1035 | if (!t) return; |
| 1036 | Quad(x, y, x + sw, y + img->h, &atlas, WU, WV, WU, WV, t, (float)sx / img->w, 0, (float)(sx + sw) / img->w, 1, c, c, c, c); |
| 1037 | } |
| 1038 |
|
| 1039 | // an icon: drawn once in software (white, at the display's scale) and kept in the atlas |
| 1040 | static int G_Icon(int icon, int x, int y, int sz, uint32_t c) |
| 1041 | { |
| 1042 | if (!atlas_ok) return 0; |
| 1043 | if (!(c >> 24)) return 1; |
| 1044 | int sp = (int)ceilf(sz * g_scale), pad = 2, box = sp + pad * 2; |
| 1045 | int fresh; |
| 1046 | aslot_t *s = Find(NULL, K_ICON, icon, sp, &fresh); |
| 1047 | if (!s) return 1; |
| 1048 | if (fresh) { |
| 1049 | if (!Place(s, box, box)) return 1; |
| 1050 | uint32_t *tmp = calloc((size_t)box * box, 4); |
| 1051 | uint8_t *cov = malloc((size_t)box * box); |
| 1052 | if (!tmp || !cov) { free(tmp); free(cov); s->kind = 0; acount--; return 0; } |
| 1053 | // the software drawing, on a small black canvas of our own |
| 1054 | uint32_t *ocv = cv; |
| 1055 | int ocw = cw, och = ch, ox0 = clx0, oy0 = cly0, ox1 = clx1, oy1 = cly1; |
| 1056 | cv = tmp; cw = box; ch = box; |
| 1057 | clx0 = cly0 = 0; clx1 = cly1 = box; |
| 1058 | gpu_on = 0; |
| 1059 | gfx_icon(icon, pad, pad, sp, 0xffffffff); |
| 1060 | gpu_on = 1; |
| 1061 | cv = ocv; cw = ocw; ch = och; clx0 = ox0; cly0 = oy0; clx1 = ox1; cly1 = oy1; |
| 1062 | for (int i = 0; i < box * box; i++) cov[i] = (uint8_t)((tmp[i] >> 8) & 255); // white over black: any channel is the coverage |
| 1063 | Put_Coverage(s, cov, box); |
| 1064 | free(tmp); |
| 1065 | free(cov); |
| 1066 | } |
| 1067 | float u0, v0, u1, v1; |
| 1068 | Atlas_UV(s, &u0, &v0, &u1, &v1); |
| 1069 | float p = pad / g_scale; |
| 1070 | Quad(x - p, y - p, x - p + box / g_scale, y - p + box / g_scale, &atlas, WU, WV, WU, WV, &atlas, u0, v0, u1, v1, c, c, c, c); |
| 1071 | return 1; |
| 1072 | } |
| 1073 |
|
| 1074 | // the grained gradient behind pages: the cached picture, as a texture |
| 1075 | static void G_Noise_Gradient(int x, int y, int w, int h, int slot) |
| 1076 | { |
| 1077 | static image_t pics[2]; |
| 1078 | image_t *p = &pics[slot]; |
| 1079 | if (p->px != grad_cache[slot].px || p->w != w || p->h != h || grad_cache[slot].fresh) { |
| 1080 | if (p->px != grad_cache[slot].px || p->w != w || p->h != h) G_Image_Forget(p); |
| 1081 | p->px = grad_cache[slot].px; |
| 1082 | p->w = w; p->h = h; |
| 1083 | p->opaque = 1; |
| 1084 | p->rev++; |
| 1085 | grad_cache[slot].fresh = 0; |
| 1086 | } |
| 1087 | G_Image(p, x, y, w, h, 0, 255); |
| 1088 | } |
| 1089 |
|
| 1090 | /* |
| 1091 | ================== |
| 1092 | Right to left (Arabic): with the mirror on, the calls above are drawn mirrored across the |
| 1093 | canvas: a box at x is drawn at the canvas's width - x - its width. Text isn't mirrored here |
| 1094 | (font.c places each line where it goes, and its letters read the right way); pictures keep |
| 1095 | their way round unless drawn with gfx_image_flip (the UI's card art). |
| 1096 | ================== |
| 1097 | */ |
| 1098 | static int mirror; |
| 1099 | void gfx_mirror(int on) { mirror = on; } |
| 1100 | int gfx_mirrored(void) { return mirror; } |
| 1101 | #define MX(x, w) (mirror ? cw - (x) - (w) : (x)) |
| 1102 |
|
| 1103 | void gfx_clip(int x, int y, int w, int h) { Raw_gfx_clip(MX(x, w), y, w, h); } |
| 1104 | void gfx_clip_get(int *x, int *y, int *w, int *h) |
| 1105 | { |
| 1106 | Raw_gfx_clip_get(x, y, w, h); |
| 1107 | if (mirror) *x = cw - *x - *w; |
| 1108 | } |
| 1109 | void gfx_rect(int x, int y, int w, int h, uint32_t c) { Raw_gfx_rect(MX(x, w), y, w, h, c); } |
| 1110 | void gfx_round_rect(int x, int y, int w, int h, int r, uint32_t c) { Raw_gfx_round_rect(MX(x, w), y, w, h, r, c); } |
| 1111 | void gfx_circle(int cx, int cy, int r, uint32_t c) { Raw_gfx_circle(mirror ? cw - cx : cx, cy, r, c); } |
| 1112 | void gfx_image(const image_t *img, int x, int y, int w, int h, int radius, int alpha) |
| 1113 | { |
| 1114 | Raw_gfx_image(img, MX(x, w), y, w, h, radius, alpha); |
| 1115 | } |
| 1116 | void gfx_image_flip(const image_t *img, int x, int y, int w, int h, int radius, int alpha) |
| 1117 | { |
| 1118 | img_flip = mirror; |
| 1119 | Raw_gfx_image(img, MX(x, w), y, w, h, radius, alpha); |
| 1120 | img_flip = 0; |
| 1121 | } |