diff options
| author | 3gg <3gg@shellblade.net> | 2026-02-01 18:35:45 -0800 |
|---|---|---|
| committer | 3gg <3gg@shellblade.net> | 2026-02-01 18:35:45 -0800 |
| commit | 9e5db7c33df67d80f49f4efd7b8ace5f27e2e7cb (patch) | |
| tree | 921efcb6a702f799c1ac1ad5e4bb8fbc621b1363 | |
| parent | ffe168d1fab26173409a6df9488b5b8762fe6ab3 (diff) | |
Fix bilinear filter
| -rw-r--r-- | src/swgfx.c | 38 |
1 files changed, 21 insertions, 17 deletions
diff --git a/src/swgfx.c b/src/swgfx.c index 2262dd8..85844e0 100644 --- a/src/swgfx.c +++ b/src/swgfx.c | |||
| @@ -53,12 +53,13 @@ typedef struct swgfx { | |||
| 53 | 53 | ||
| 54 | static inline int mod(int a, int m) { return (m + (a % m)) % m; } | 54 | static inline int mod(int a, int m) { return (m + (a % m)) % m; } |
| 55 | static inline R frac(R a) { return a - (R)((int)a); } | 55 | static inline R frac(R a) { return a - (R)((int)a); } |
| 56 | |||
| 57 | static inline int imin(int a, int b) { return (a <= b) ? a : b; } | 56 | static inline int imin(int a, int b) { return (a <= b) ? a : b; } |
| 58 | static inline int imax(int a, int b) { return (a >= b) ? a : b; } | 57 | static inline int imax(int a, int b) { return (a >= b) ? a : b; } |
| 59 | static inline R rmin(R a, R b) { return (a <= b) ? a : b; } | 58 | |
| 60 | static inline R rmax(R a, R b) { return (a >= b) ? a : b; } | 59 | static inline R rmin(R a, R b) { return (a <= b) ? a : b; } |
| 61 | static inline R lerp(R a, R b, R t) { return a + t*(b-a); } | 60 | static inline R rmax(R a, R b) { return (a >= b) ? a : b; } |
| 61 | static inline R lerp(R a, R b, R t) { return a + t*(b-a); } | ||
| 62 | static inline R mod1(R a, R m) { return fmodf(1.f + fmodf(a, m), 1.f); } | ||
| 62 | 63 | ||
| 63 | static inline sgVec2i min2i(sgVec2i a, sgVec2i b) { return (sgVec2i){.x = imin(a.x, b.x), .y = imin(a.y, b.y) }; } | 64 | static inline sgVec2i min2i(sgVec2i a, sgVec2i b) { return (sgVec2i){.x = imin(a.x, b.x), .y = imin(a.y, b.y) }; } |
| 64 | static inline sgVec2i max2i(sgVec2i a, sgVec2i b) { return (sgVec2i){.x = imax(a.x, b.x), .y = imax(a.y, b.y) }; } | 65 | static inline sgVec2i max2i(sgVec2i a, sgVec2i b) { return (sgVec2i){.x = imax(a.x, b.x), .y = imax(a.y, b.y) }; } |
| @@ -69,6 +70,7 @@ static inline sgVec2 sub2(sgVec2 a, sgVec2 b) { return (sgVec2){a.x - b.x, a.y | |||
| 69 | static inline sgVec2 scale2(sgVec2 v, R s) { return (sgVec2){v.x * s, v.y * s}; } | 70 | static inline sgVec2 scale2(sgVec2 v, R s) { return (sgVec2){v.x * s, v.y * s}; } |
| 70 | static inline sgVec2 frac2(sgVec2 v) { return (sgVec2){frac(v.x), frac(v.y)}; } | 71 | static inline sgVec2 frac2(sgVec2 v) { return (sgVec2){frac(v.x), frac(v.y)}; } |
| 71 | static inline sgVec2 lerp2(sgVec2 a, sgVec2 b, R t) { return add2(a, scale2(sub2(b,a), t)); } | 72 | static inline sgVec2 lerp2(sgVec2 a, sgVec2 b, R t) { return add2(a, scale2(sub2(b,a), t)); } |
| 73 | static inline sgVec2 mod2(sgVec2 v, R m) { return (sgVec2){mod1(v.x, m), mod1(v.y, m)}; } | ||
| 72 | 74 | ||
| 73 | static inline sgVec3 add3(sgVec3 a, sgVec3 b) { return (sgVec3){a.x + b.x, a.y + b.y, a.z + b.z}; } | 75 | static inline sgVec3 add3(sgVec3 a, sgVec3 b) { return (sgVec3){a.x + b.x, a.y + b.y, a.z + b.z}; } |
| 74 | static inline sgVec3 neg3(sgVec3 v) { return (sgVec3){-v.x, -v.y, -v.z}; } | 76 | static inline sgVec3 neg3(sgVec3 v) { return (sgVec3){-v.x, -v.y, -v.z}; } |
| @@ -314,33 +316,37 @@ static inline sgVec4 ReadTextureFloat(const sgImage* texture, sgVec2i xy) { | |||
| 314 | return PixelToVec4(ReadTexture(texture, xy)); | 316 | return PixelToVec4(ReadTexture(texture, xy)); |
| 315 | } | 317 | } |
| 316 | 318 | ||
| 319 | static inline sgVec2i UvToIndex(const sgImage* texture, sgVec2 uv) { | ||
| 320 | assert(texture); | ||
| 321 | return (sgVec2i){ | ||
| 322 | (int)(uv.x * (R)(texture->width - 1)), | ||
| 323 | (int)(uv.y * (R)(texture->height - 1))}; | ||
| 324 | } | ||
| 325 | |||
| 317 | static inline sgVec2i TextureRepeat(const sgImage* texture, sgVec2i p) { | 326 | static inline sgVec2i TextureRepeat(const sgImage* texture, sgVec2i p) { |
| 318 | return (sgVec2i){mod(p.x, texture->width), mod(p.y, texture->height)}; | 327 | return (sgVec2i){mod(p.x, texture->width), mod(p.y, texture->height)}; |
| 319 | } | 328 | } |
| 320 | 329 | ||
| 321 | static inline sgPixel SampleNearest(const sgImage* texture, sgVec2 uv) { | 330 | static inline sgPixel FilterNearest(const sgImage* texture, sgVec2 uv) { |
| 322 | assert(texture); | 331 | assert(texture); |
| 323 | assert(texture->pixels); | 332 | assert(texture->pixels); |
| 324 | const sgVec2i xy = { | 333 | const sgVec2i xy = UvToIndex(texture, uv); |
| 325 | (int)(uv.x * (R)texture->width), | ||
| 326 | (int)(uv.y * (R)texture->height)}; | ||
| 327 | const sgVec2i xy2 = TextureRepeat(texture, xy); | 334 | const sgVec2i xy2 = TextureRepeat(texture, xy); |
| 328 | return ReadTexture(texture, xy2); | 335 | return ReadTexture(texture, xy2); |
| 329 | } | 336 | } |
| 330 | 337 | ||
| 331 | static inline sgPixel SampleBilinear(const sgImage* texture, sgVec2 uv) { | 338 | static inline sgPixel FilterBilinear(const sgImage* texture, sgVec2 uv) { |
| 332 | assert(texture); | 339 | assert(texture); |
| 333 | assert(texture->pixels); | 340 | assert(texture->pixels); |
| 334 | #define ADDR(x,y) TextureRepeat(texture, (sgVec2i){x,y}) | 341 | #define ADDR(x,y) TextureRepeat(texture, (sgVec2i){x,y}) |
| 342 | const sgVec2 uv01 = mod2(uv, 1.f); | ||
| 335 | // Find the closest grid vertex, then interpolate the 4 neighbouring pixel | 343 | // Find the closest grid vertex, then interpolate the 4 neighbouring pixel |
| 336 | // centers. | 344 | // centers. |
| 337 | const sgVec2i tl = ADDR( | 345 | const sgVec2i tl = UvToIndex(texture, uv01); |
| 338 | (int)(uv.x * (R)(texture->width - 1)), | ||
| 339 | (int)(uv.y * (R)(texture->height - 1))); | ||
| 340 | const sgVec2i tr = ADDR(tl.x+1, tl.y); | 346 | const sgVec2i tr = ADDR(tl.x+1, tl.y); |
| 341 | const sgVec2i bl = ADDR(tl.x, tl.y+1); | 347 | const sgVec2i bl = ADDR(tl.x, tl.y+1); |
| 342 | const sgVec2i br = ADDR(tl.x+1, tl.y+1); | 348 | const sgVec2i br = ADDR(tl.x+1, tl.y+1); |
| 343 | const sgVec2 t = frac2(uv); | 349 | const sgVec2 t = frac2(uv01); |
| 344 | const sgVec4 tl_pix = ReadTextureFloat(texture, tl); | 350 | const sgVec4 tl_pix = ReadTextureFloat(texture, tl); |
| 345 | const sgVec4 tr_pix = ReadTextureFloat(texture, tr); | 351 | const sgVec4 tr_pix = ReadTextureFloat(texture, tr); |
| 346 | const sgVec4 bl_pix = ReadTextureFloat(texture, bl); | 352 | const sgVec4 bl_pix = ReadTextureFloat(texture, bl); |
| @@ -355,10 +361,8 @@ static inline sgPixel SampleBilinear(const sgImage* texture, sgVec2 uv) { | |||
| 355 | // TODO: Clamping and other addressing strategies. | 361 | // TODO: Clamping and other addressing strategies. |
| 356 | static inline sgPixel Sample(const sgImage* texture, sgVec2 uv) { | 362 | static inline sgPixel Sample(const sgImage* texture, sgVec2 uv) { |
| 357 | // TODO: Add a member to sgImage that determines how it should be filtered. | 363 | // TODO: Add a member to sgImage that determines how it should be filtered. |
| 358 | return SampleNearest(texture, uv); | 364 | //return FilterNearest(texture, uv); |
| 359 | // TODO: Debug bilinear. It's producing random colours on the ground, likely | 365 | return FilterBilinear(texture, uv); |
| 360 | // out-of-bounds memory accesses. | ||
| 361 | //return SampleBilinear(texture, uv); | ||
| 362 | } | 366 | } |
| 363 | 367 | ||
| 364 | static inline sgAABB2 TriangleAabb2(sgVec2 p0, sgVec2 p1, sgVec2 p2) { | 368 | static inline sgAABB2 TriangleAabb2(sgVec2 p0, sgVec2 p1, sgVec2 p2) { |
