diff options
| author | 3gg <3gg@shellblade.net> | 2026-01-04 20:45:07 -0800 |
|---|---|---|
| committer | 3gg <3gg@shellblade.net> | 2026-01-04 20:45:07 -0800 |
| commit | 454f82f12325b07b234d06dbd526f68cf00fdd55 (patch) | |
| tree | a40a2974dc950c8ad6ec09858a4d87a70a1d194a /src/swgfx.c | |
| parent | 7e48c57e1c76d4ef874a2e15bfe9fd43e33f60a8 (diff) | |
Diffstat (limited to 'src/swgfx.c')
| -rw-r--r-- | src/swgfx.c | 95 |
1 files changed, 79 insertions, 16 deletions
diff --git a/src/swgfx.c b/src/swgfx.c index 7ade051..c54fdc1 100644 --- a/src/swgfx.c +++ b/src/swgfx.c | |||
| @@ -46,11 +46,15 @@ typedef struct swgfx { | |||
| 46 | // before rendering the model's triangles. | 46 | // before rendering the model's triangles. |
| 47 | sgMat4 viewProj; // View-projection matrix. | 47 | sgMat4 viewProj; // View-projection matrix. |
| 48 | sgMat4 mvp; // Model-view-projection matrix. | 48 | sgMat4 mvp; // Model-view-projection matrix. |
| 49 | const sgTexture_t* texture;// User-specified texture. | 49 | const sgImage* texture;// User-specified texture. |
| 50 | sgTexture_t defaultTexture; // A default for when no texture is provided. | 50 | sgImage defaultTexture; // A default for when no texture is provided. |
| 51 | sgPixel defaultPixel; // The single-pixel of the default texture. | 51 | sgPixel defaultPixel; // The single-pixel of the default texture. |
| 52 | } swgfx; | 52 | } swgfx; |
| 53 | 53 | ||
| 54 | static inline int mod(int a, int m) { return (m + (a % m)) % m; } | ||
| 55 | |||
| 56 | static inline R frac(R a) { return a - (R)((int)a); } | ||
| 57 | |||
| 54 | static inline R rmin(R a, R b) { return (a <= b) ? a : b; } | 58 | static inline R rmin(R a, R b) { return (a <= b) ? a : b; } |
| 55 | static inline R rmax(R a, R b) { return (a >= b) ? a : b; } | 59 | static inline R rmax(R a, R b) { return (a >= b) ? a : b; } |
| 56 | static inline int imin(int a, int b) { return (a <= b) ? a : b; } | 60 | static inline int imin(int a, int b) { return (a <= b) ? a : b; } |
| @@ -61,6 +65,8 @@ static inline sgVec2 max2(sgVec2 a, sgVec2 b) { return (sgVec2){.x = rmax(a.x, | |||
| 61 | static inline sgVec2i min2i(sgVec2i a, sgVec2i b) { return (sgVec2i){.x = imin(a.x, b.x), .y = imin(a.y, b.y) }; } | 65 | static inline sgVec2i min2i(sgVec2i a, sgVec2i b) { return (sgVec2i){.x = imin(a.x, b.x), .y = imin(a.y, b.y) }; } |
| 62 | static inline sgVec2i max2i(sgVec2i a, sgVec2i b) { return (sgVec2i){.x = imax(a.x, b.x), .y = imax(a.y, b.y) }; } | 66 | static inline sgVec2i max2i(sgVec2i a, sgVec2i b) { return (sgVec2i){.x = imax(a.x, b.x), .y = imax(a.y, b.y) }; } |
| 63 | 67 | ||
| 68 | static inline sgVec2 frac2(sgVec2 v) { return (sgVec2){frac(v.x), frac(v.y)}; } | ||
| 69 | |||
| 64 | static inline sgVec2 add2(sgVec2 a, sgVec2 b) { return (sgVec2){a.x + b.x, a.y + b.y}; } | 70 | static inline sgVec2 add2(sgVec2 a, sgVec2 b) { return (sgVec2){a.x + b.x, a.y + b.y}; } |
| 65 | static inline sgVec2 scale2(sgVec2 v, R s) { return (sgVec2){v.x * s, v.y * s}; } | 71 | static inline sgVec2 scale2(sgVec2 v, R s) { return (sgVec2){v.x * s, v.y * s}; } |
| 66 | 72 | ||
| @@ -71,6 +77,14 @@ static inline R dot3(sgVec3 a, sgVec3 b) { return a.x * b.x + a.y * b.y + a.z * | |||
| 71 | static inline R normsq3(sgVec3 v) { return v.x * v.x + v.y * v.y + v.z * v.z; } | 77 | static inline R normsq3(sgVec3 v) { return v.x * v.x + v.y * v.y + v.z * v.z; } |
| 72 | static inline R norm3 (sgVec3 v) { return (R)sqrt(normsq3(v)); } | 78 | static inline R norm3 (sgVec3 v) { return (R)sqrt(normsq3(v)); } |
| 73 | 79 | ||
| 80 | static inline sgVec4 lerp4(sgVec4 a, sgVec4 b, R t) { | ||
| 81 | return (sgVec4){ | ||
| 82 | .x = a.x + t * (b.x - a.x), | ||
| 83 | .y = a.y + t * (b.y - a.y), | ||
| 84 | .z = a.z + t * (b.z - a.z), | ||
| 85 | .w = a.w + t * (b.w - a.w)}; | ||
| 86 | } | ||
| 87 | |||
| 74 | static inline sgVec3 cross3(sgVec3 a, sgVec3 b) { | 88 | static inline sgVec3 cross3(sgVec3 a, sgVec3 b) { |
| 75 | return (sgVec3) { | 89 | return (sgVec3) { |
| 76 | a.y * b.z - a.z * b.y, | 90 | a.y * b.z - a.z * b.y, |
| @@ -237,6 +251,13 @@ static inline sgMat4 Mat4Perspective(R fovy, R aspect, R near, R far) { | |||
| 237 | 0, 0, -1, 0); | 251 | 0, 0, -1, 0); |
| 238 | } | 252 | } |
| 239 | 253 | ||
| 254 | static inline sgVec4 PixelToVec4(sgPixel p) { | ||
| 255 | return (sgVec4){(R)p.r / 255.f, (R)p.g / 255.f, (R)p.b / 255.f, (R)p.a / 255.f}; | ||
| 256 | } | ||
| 257 | static inline sgPixel Vec4ToPixel(sgVec4 p) { | ||
| 258 | return (sgPixel){(int)(p.x * 255.f), (int)(p.y * 255.f), (int)(p.z * 255.f), (int)(p.w * 255.f)}; | ||
| 259 | } | ||
| 260 | |||
| 240 | #ifndef _NDEBUG | 261 | #ifndef _NDEBUG |
| 241 | static bool InBounds(int width, int height, int x, int y) { | 262 | static bool InBounds(int width, int height, int x, int y) { |
| 242 | return (0 <= x) && (x < width) && | 263 | return (0 <= x) && (x < width) && |
| @@ -258,28 +279,70 @@ static inline R* Depth(swgfx* gfx, int x, int y) { | |||
| 258 | return gfx->depth + (y * gfx->dims.x) + x; | 279 | return gfx->depth + (y * gfx->dims.x) + x; |
| 259 | } | 280 | } |
| 260 | 281 | ||
| 261 | void SetPixel(swgfx* gfx, const sgVec2i p, sgPixel colour) { | 282 | static inline void SetPixel(swgfx* gfx, const sgVec2i p, sgPixel colour) { |
| 262 | assert(gfx); | 283 | assert(gfx); |
| 263 | *Pixel(gfx, p.x, p.y) = colour; | 284 | *Pixel(gfx, p.x, p.y) = colour; |
| 264 | } | 285 | } |
| 265 | 286 | ||
| 266 | void SetDepth(swgfx* gfx, const sgVec2i p, R depth) { | 287 | static inline void SetDepth(swgfx* gfx, const sgVec2i p, R depth) { |
| 267 | assert(gfx); | 288 | assert(gfx); |
| 268 | *Depth(gfx, p.x, p.y) = depth; | 289 | *Depth(gfx, p.x, p.y) = depth; |
| 269 | } | 290 | } |
| 270 | 291 | ||
| 271 | // TODO: Mipmapping. | 292 | static inline sgPixel ReadTexture(const sgImage* texture, sgVec2i xy) { |
| 272 | sgPixel Sample(const sgTexture_t* texture, sgVec2 uv) { | 293 | assert(texture); |
| 294 | assert(texture->pixels); | ||
| 295 | assert(InBounds(texture->width, texture->height, xy.x, xy.y)); | ||
| 296 | return texture->pixels[xy.y * texture->width + xy.x]; | ||
| 297 | } | ||
| 298 | // Output normalized to [0,1]. | ||
| 299 | static inline sgVec4 ReadTextureFloat(const sgImage* texture, sgVec2i xy) { | ||
| 300 | return PixelToVec4(ReadTexture(texture, xy)); | ||
| 301 | } | ||
| 302 | |||
| 303 | static inline sgVec2i TextureRepeat(const sgImage* texture, sgVec2i p) { | ||
| 304 | return (sgVec2i){mod(p.x, texture->width), mod(p.y, texture->height)}; | ||
| 305 | } | ||
| 306 | |||
| 307 | static inline sgPixel SampleNearest(const sgImage* texture, sgVec2 uv) { | ||
| 308 | assert(texture); | ||
| 309 | assert(texture->pixels); | ||
| 310 | const sgVec2i xy = { | ||
| 311 | (int)(uv.x * (R)texture->width), | ||
| 312 | (int)(uv.y * (R)texture->height)}; | ||
| 313 | const sgVec2i xy2 = TextureRepeat(texture, xy); | ||
| 314 | return ReadTexture(texture, xy2); | ||
| 315 | } | ||
| 316 | |||
| 317 | static inline sgPixel SampleBilinear(const sgImage* texture, sgVec2 uv) { | ||
| 273 | assert(texture); | 318 | assert(texture); |
| 274 | assert(texture->pixels); | 319 | assert(texture->pixels); |
| 275 | #define INDEX(X,Y) texture->pixels[(Y) * texture->width + (X)] | 320 | #define ADDR(x,y) TextureRepeat(texture, (sgVec2i){x,y}) |
| 276 | // Doing a nearest sample for now. TODO: Other sampling strategies. | 321 | // Find the closest grid vertex, then interpolate the 4 neighbouring pixel |
| 277 | const int x = (int)(uv.x * (R)texture->width); | 322 | // centers. |
| 278 | const int y = (int)(uv.y * (R)texture->height); | 323 | const sgVec2i tl = ADDR( |
| 279 | // Repeat for now. TODO: Clamping and other strategies. | 324 | (int)(uv.x * (R)(texture->width - 1)), |
| 280 | const int xx = x % texture->width; | 325 | (int)(uv.y * (R)(texture->height - 1))); |
| 281 | const int yy = y % texture->height; | 326 | const sgVec2i tr = ADDR(tl.x+1, tl.y); |
| 282 | return INDEX(xx,yy); | 327 | const sgVec2i bl = ADDR(tl.x, tl.y+1); |
| 328 | const sgVec2i br = ADDR(tl.x+1, tl.y+1); | ||
| 329 | const sgVec2 t = frac2(uv); | ||
| 330 | const sgVec4 tl_pix = ReadTextureFloat(texture, tl); | ||
| 331 | const sgVec4 tr_pix = ReadTextureFloat(texture, tr); | ||
| 332 | const sgVec4 bl_pix = ReadTextureFloat(texture, bl); | ||
| 333 | const sgVec4 br_pix = ReadTextureFloat(texture, br); | ||
| 334 | const sgVec4 x1 = lerp4(tl_pix, tr_pix, t.x); | ||
| 335 | const sgVec4 x2 = lerp4(bl_pix, br_pix, t.x); | ||
| 336 | const sgVec4 y = lerp4(x1, x2, t.y); | ||
| 337 | return Vec4ToPixel(y); | ||
| 338 | } | ||
| 339 | |||
| 340 | // TODO: Mipmapping. | ||
| 341 | // TODO: Clamping and other addressing strategies. | ||
| 342 | static inline sgPixel Sample(const sgImage* texture, sgVec2 uv) { | ||
| 343 | // TODO: Add a member to sgImage that determines how it should be filtered. | ||
| 344 | //return SampleNearest(texture, uv); | ||
| 345 | return SampleBilinear(texture, uv); | ||
| 283 | } | 346 | } |
| 284 | 347 | ||
| 285 | static inline sgAABB2 TriangleAabb2(sgVec2 p0, sgVec2 p1, sgVec2 p2) { | 348 | static inline sgAABB2 TriangleAabb2(sgVec2 p0, sgVec2 p1, sgVec2 p2) { |
| @@ -489,7 +552,7 @@ swgfx* sgNew(int width, int height, void* mem) { | |||
| 489 | gfx->colour = SG_ALLOC(&aligned, width * height, sgPixel); | 552 | gfx->colour = SG_ALLOC(&aligned, width * height, sgPixel); |
| 490 | gfx->depth = SG_ALLOC(&aligned, width * height, R); | 553 | gfx->depth = SG_ALLOC(&aligned, width * height, R); |
| 491 | gfx->defaultPixel = (sgPixel){255, 255, 255, 255}; | 554 | gfx->defaultPixel = (sgPixel){255, 255, 255, 255}; |
| 492 | gfx->defaultTexture = (sgTexture_t){ | 555 | gfx->defaultTexture = (sgImage){ |
| 493 | .width = 1, | 556 | .width = 1, |
| 494 | .height = 1, | 557 | .height = 1, |
| 495 | .pixels = &gfx->defaultPixel, | 558 | .pixels = &gfx->defaultPixel, |
| @@ -578,7 +641,7 @@ void sgViewport(swgfx* gfx, int x0, int y0, int width, int height) { | |||
| 578 | gfx->viewport = (sgViewport_t){x0, y0, width, height}; | 641 | gfx->viewport = (sgViewport_t){x0, y0, width, height}; |
| 579 | } | 642 | } |
| 580 | 643 | ||
| 581 | void sgTexture(swgfx* gfx, const sgTexture_t* texture) { | 644 | void sgTexture(swgfx* gfx, const sgImage* texture) { |
| 582 | assert(gfx); | 645 | assert(gfx); |
| 583 | assert(texture); | 646 | assert(texture); |
| 584 | gfx->texture = texture; | 647 | gfx->texture = texture; |
