summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/swgfx.c124
1 files changed, 75 insertions, 49 deletions
diff --git a/src/swgfx.c b/src/swgfx.c
index 612f82d..3bec663 100644
--- a/src/swgfx.c
+++ b/src/swgfx.c
@@ -297,15 +297,12 @@ static inline sgVec2i Clip(const swgfx* gfx, const sgVec2i p) {
297 return max2i(lower, min2i(upper, p)); 297 return max2i(lower, min2i(upper, p));
298} 298}
299 299
300static inline R BarycentricInterp1(sgVec3 bar, R a, R b, R c) { 300static inline R BarycentricInterp(sgVec3 bar, R a, R b, R c) {
301 return bar.x*a + bar.y*b + bar.z*c; 301 return bar.x*a + bar.y*b + bar.z*c;
302} 302}
303static inline sgVec2 BarycentricInterp2(sgVec3 bar, sgVec2 a, sgVec2 b, sgVec2 c) { 303static inline sgVec2 BarycentricInterp2(sgVec3 bar, sgVec2 a, sgVec2 b, sgVec2 c) {
304 return add2(add2(scale2(a, bar.x), scale2(b, bar.y)), scale2(c, bar.z)); 304 return add2(add2(scale2(a, bar.x), scale2(b, bar.y)), scale2(c, bar.z));
305} 305}
306static inline sgVec2 PerspectiveInterp2(sgVec3 bar, sgVec3 depths, R z, sgVec2 a, sgVec2 b, sgVec2 c) {
307 return scale2(BarycentricInterp2(div3(bar, depths), a, b, c), z);
308}
309 306
310static inline R f(sgVec2 a, sgVec2 b, sgVec2 p) { 307static inline R f(sgVec2 a, sgVec2 b, sgVec2 p) {
311 return (a.y - b.y)*p.x + (b.x - a.x)*p.y + a.x*b.y - b.x*a.y; 308 return (a.y - b.y)*p.x + (b.x - a.x)*p.y + a.x*b.y - b.x*a.y;
@@ -327,9 +324,7 @@ static inline sgVec3 Barycentric(sgVec2 p0, sgVec2 p1, sgVec2 p2, sgVec2 p) {
327 return (sgVec3){a,b,c}; 324 return (sgVec3){a,b,c};
328} 325}
329 326
330// Input triangle in screen space. Retains Z for depth testing and vertex 327static void DrawTriangle2(swgfx* gfx, const sgTri2* const tri) {
331// attribute interpolation.
332static void DrawTriangle2(swgfx* gfx, const sgTri3* const tri) {
333 assert(gfx); 328 assert(gfx);
334 assert(tri); 329 assert(tri);
335 const sgVec2 p0 = (sgVec2){tri->p0.pos.x, tri->p0.pos.y}; 330 const sgVec2 p0 = (sgVec2){tri->p0.pos.x, tri->p0.pos.y};
@@ -355,68 +350,104 @@ static void DrawTriangle2(swgfx* gfx, const sgTri3* const tri) {
355 // So, e.g., if a >= 0 and b >= 0, then we have c <= 1, but we could also have c <= 0. 350 // So, e.g., if a >= 0 and b >= 0, then we have c <= 1, but we could also have c <= 0.
356 // In the case c <= 0, then point is outside the triangle. 351 // In the case c <= 0, then point is outside the triangle.
357 if ((bar.x >= 0) && (bar.y >= 0) && (bar.z >= 0)) { 352 if ((bar.x >= 0) && (bar.y >= 0) && (bar.z >= 0)) {
358 const R z = BarycentricInterp1(bar, tri->p0.pos.z, tri->p1.pos.z, tri->p2.pos.z); 353 assert((bar.x + bar.y + bar.z - 1e7) <= 1.f);
359 R* depth = Depth(gfx, x, y); 354 const sgVec2 uv = BarycentricInterp2(bar, tri->p0.uv, tri->p1.uv, tri->p2.uv);
360 if ((0.f <= z) && (z <= 1.f) && (z <= *depth)) { 355 const sgPixel colour = Sample(gfx->texture, uv);
361 *depth = z; 356 SetPixel(gfx, (sgVec2i){x,y}, colour);
362 const sgVec3 depths = (sgVec3){tri->p0.pos.z, tri->p1.pos.z, tri->p2.pos.z};
363 const sgVec2 uv = PerspectiveInterp2(bar, depths, z, tri->p0.uv, tri->p1.uv, tri->p2.uv);
364 const sgPixel colour = Sample(gfx->texture, uv);
365 //const sgPixel colour = (sgPixel){255, 0, 255, 255};
366 // TODO: When doing lighting, need to tone-map here.
367 /*const int r = (int)(uv.x * 255.f);
368 const int g = (int)(uv.y * 255.f);
369 const sgPixel colour = (sgPixel){r, g, 255, 255};*/
370 const sgVec2i pix = (sgVec2i){x,y};
371 SetPixel(gfx, pix, colour);
372 }
373 } 357 }
374 } 358 }
375 } 359 }
376} 360}
377 361
378static inline sgVec3 PerspDivide(sgVec4 v) { 362static inline sgVec4 PerspDivide(sgVec4 v) {
379 return (sgVec3){v.x / v.w, v.y / v.w, v.z / v.w}; 363 return (sgVec4){v.x / v.w, v.y / v.w, v.z / v.w, v.w};
380} 364}
381 365
382// TODO: Compute a viewport matrix in sgViewport() instead. 366// TODO: Compute a viewport matrix in sgViewport() instead.
383static inline sgVec3 ViewportTransform(sgViewport_t vp, sgVec3 ndc) { 367static inline sgVec4 ViewportTransform(sgViewport_t vp, sgVec4 ndc) {
384 return (sgVec3){ 368 return (sgVec4){
385 .x = (ndc.x+1.f) * ((R)vp.width/2.f) + (R)vp.x0, 369 .x = (ndc.x+1.f) * ((R)vp.width/2.f) + (R)vp.x0,
386 .y = (ndc.y+1.f) * ((R)vp.height/2.f) + (R)vp.y0, 370 .y = (ndc.y+1.f) * ((R)vp.height/2.f) + (R)vp.y0,
387 .z = ndc.z}; 371 .z = ndc.z*0.5f + 0.5f,
372 .w = ndc.w};
388} 373}
389 374
390static inline sgVec3 ViewportToWindow(sgViewport_t vp, sgVec3 p) { 375static inline sgVec4 ViewportToWindow(sgViewport_t vp, sgVec4 p) {
391 return (sgVec3){p.x, (R)vp.height - p.y, p.z}; 376 return (sgVec4){p.x, (R)vp.height - p.y, p.z, p.w};
392} 377}
393 378
394static inline sgVec3 TransformPosition(const swgfx* gfx, sgVec3 p) { 379static inline sgVec4 TransformPosition(const swgfx* gfx, sgVec3 p) {
395 assert(gfx); 380 assert(gfx);
396 // Model to clip space. 381 // Model to clip space.
397 const sgVec4 p_clip = Mat4MulVec4(gfx->mvp, Vec4FromVec3(p, 1)); 382 const sgVec4 p_clip = Mat4MulVec4(gfx->mvp, Vec4FromVec3(p, 1));
398 // TODO: Backface culling. 383 // TODO: Backface culling.
399 // Perspective divide. 384 // Perspective divide.
400 const sgVec3 p_ndc = PerspDivide(p_clip); 385 const sgVec4 p_ndc = PerspDivide(p_clip);
401 // TODO: Clip. 386 // TODO: Clip.
402 const sgVec3 p_vp = ViewportTransform(gfx->viewport, p_ndc); 387 const sgVec4 p_vp = ViewportTransform(gfx->viewport, p_ndc);
403 return ViewportToWindow(gfx->viewport, p_vp); 388 return ViewportToWindow(gfx->viewport, p_vp);
404} 389}
405 390
406static void DrawTriangle3(swgfx* gfx, const sgTri3* const tri) { 391static void DrawTriangle3(swgfx* gfx, const sgTri3* const tri) {
407 assert(gfx); 392 assert(gfx);
408 assert(tri); 393 assert(tri);
409 const sgVec3 p0 = TransformPosition(gfx, tri->p0.pos); 394 // TODO: Inline the transform here and interleave its operations to perform
410 const sgVec3 p1 = TransformPosition(gfx, tri->p1.pos); 395 // backface culling and clipping as early as possible.
411 const sgVec3 p2 = TransformPosition(gfx, tri->p2.pos); 396 const sgVec4 p0 = TransformPosition(gfx, tri->p0.pos);
412 const sgVec2 uv0 = tri->p0.uv; 397 const sgVec4 p1 = TransformPosition(gfx, tri->p1.pos);
413 const sgVec2 uv1 = tri->p1.uv; 398 const sgVec4 p2 = TransformPosition(gfx, tri->p2.pos);
414 const sgVec2 uv2 = tri->p2.uv; 399 const sgVec2 p0_2d = (sgVec2){p0.x, p0.y};
415 const sgTri3 tri_screen = (sgTri3){ 400 const sgVec2 p1_2d = (sgVec2){p1.x, p1.y};
416 (sgVert3){p0, uv0}, 401 const sgVec2 p2_2d = (sgVec2){p2.x, p2.y};
417 (sgVert3){p1, uv1}, 402 const sgAABB2 bbox = TriangleAabb2(p0_2d, p1_2d, p2_2d);
418 (sgVert3){p2, uv2}}; 403 // We consider (x,y) to be the pixel center.
419 DrawTriangle2(gfx, &tri_screen); 404 // Draw all pixels touched by the bounding box. TODO: Multi-sampling.
405 sgVec2i pmin = (sgVec2i){(int)bbox.pmin.x, (int)bbox.pmin.y};
406 sgVec2i pmax = (sgVec2i){(int)(bbox.pmax.x + 0.5f), (int)(bbox.pmax.y + 0.5f)};
407 // Clip to screen space.
408 pmin = Clip(gfx, pmin);
409 pmax = Clip(gfx, pmax);
410 // Setup for perspective texture mapping.
411 // 'w' is view-space z.
412 const sgVec3 depths = (sgVec3){p0.z, p1.z, p2.z};
413 const sgVec3 one_over_zs = (sgVec3){1.f / p0.w, 1.f / p1.w, 1.f/ p2.w};
414 const sgVec3 u_over_zs = (sgVec3){tri->p0.uv.x / p0.w, tri->p1.uv.x / p1.w, tri->p2.uv.x / p2.w};
415 const sgVec3 v_over_zs = (sgVec3){tri->p0.uv.y / p0.w, tri->p1.uv.y / p1.w, tri->p2.uv.y / p2.w};
416 // Draw.
417 for (int y = pmin.y; y <= pmax.y; ++y) {
418 for (int x = pmin.x; x <= pmax.x; ++x) {
419 const sgVec2 p = (sgVec2){(R)x, (R)y};
420 // TODO: there is an incremental optimization to computing barycentric coordinates;
421 // read more about it.
422 const sgVec3 bar = Barycentric(p0_2d, p1_2d, p2_2d, p);
423 // We need to check the third coordinate.
424 // a + b + c = 1
425 // So, e.g., if a >= 0 and b >= 0, then we have c <= 1, but we could also have c <= 0.
426 // In the case c <= 0, then point is outside the triangle.
427 if ((bar.x >= 0) && (bar.y >= 0) && (bar.z >= 0)) {
428 assert((bar.x + bar.y + bar.z - 1e7) <= 1.f);
429 const R p_one_over_z = dot3(bar, one_over_zs);
430 const R p_u_over_z = dot3(bar, u_over_zs);
431 const R p_v_over_z = dot3(bar, v_over_zs);
432 const R p_depth = dot3(bar, depths);
433 const R z = 1.f / p_one_over_z;
434 const sgVec2 uv = (sgVec2){p_u_over_z * z, p_v_over_z * z};
435 R* depth = Depth(gfx, x, y);
436 if ((0.f <= p_depth) && (p_depth <= 1.f) && (p_depth <= *depth)) {
437 *depth = p_depth;
438 const sgPixel colour = Sample(gfx->texture, uv);
439 // TODO: When doing lighting, need to tone-map here.
440 /*const int d = (int)(z*255.f);
441 const sgPixel colour = (sgPixel){d,d,d,255};*/
442 //const sgPixel colour = (sgPixel){255, 0, 255, 255};
443 /*const int r = (int)(uv.x * 255.f);
444 const int g = (int)(uv.y * 255.f);
445 const sgPixel colour = (sgPixel){r, g, 255, 255};*/
446 SetPixel(gfx, (sgVec2i){x,y}, colour);
447 }
448 }
449 }
450 }
420} 451}
421 452
422#define is_pow2_or_0(X) ((X & (X - 1)) == 0) 453#define is_pow2_or_0(X) ((X & (X - 1)) == 0)
@@ -585,12 +616,7 @@ void sgPixels(swgfx* gfx, size_t count, const sgVec2i* positions, sgPixel colour
585void sgTriangles2(swgfx* gfx, size_t count, const sgTri2* tris) { 616void sgTriangles2(swgfx* gfx, size_t count, const sgTri2* tris) {
586 assert(gfx); 617 assert(gfx);
587 for (size_t i = 0; i < count; ++i) { 618 for (size_t i = 0; i < count; ++i) {
588 const sgTri3 tri3 = (sgTri3) { 619 DrawTriangle2(gfx, &tris[i]);
589 .p0 = (sgVert3){.pos = (sgVec3){tris[i].p0.pos.x, tris[i].p0.pos.y, 0}, .uv = tris[i].p0.uv},
590 .p1 = (sgVert3){.pos = (sgVec3){tris[i].p1.pos.x, tris[i].p1.pos.y, 0}, .uv = tris[i].p1.uv},
591 .p2 = (sgVert3){.pos = (sgVec3){tris[i].p2.pos.x, tris[i].p2.pos.y, 0}, .uv = tris[i].p2.uv},
592 };
593 DrawTriangle2(gfx, &tri3);
594 } 620 }
595} 621}
596 622