From e0dd69febc8f73b0e39e14d070ecf6b73bbe2d4f Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 14 Feb 2026 19:06:49 -0800 Subject: Use float colour. First step towards directional lighting --- src/swgfx.c | 191 ++++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 107 insertions(+), 84 deletions(-) (limited to 'src/swgfx.c') diff --git a/src/swgfx.c b/src/swgfx.c index 6074eac..33b453d 100644 --- a/src/swgfx.c +++ b/src/swgfx.c @@ -42,11 +42,11 @@ typedef struct sgTexture { typedef struct swgfx { sgVec2i dims; // Colour buffer dimensions. - sgPixel* colour; // Colour buffer. + sgColour4* colour; // Colour buffer. R* depth; // Depth buffer. sgTextureId* texture; // Texture ID buffer. sgVec2* texcoords; // Texture coords buffer. - sgPixel* albedo; // Albedo buffer. + sgColour3* albedo; // Albedo buffer. sgVec3* normals; // Normals buffer. sgViewport_t viewport; sgMat4 model; // Model matrix. @@ -63,7 +63,7 @@ typedef struct swgfx { sgMat4 mvp; // Model-view-projection matrix. sgTexture* textureRegister; // Indexed by texture id. sgTextureId activeTexture; - sgPixel defaultPixel; // The single-pixel of the default texture. + sgTexel defaultPixel; // The single-pixel of the default texture. sgImage defaultImage; // Image for the default texture. sgCounters counters; } swgfx; @@ -133,7 +133,7 @@ static inline sgVec3 normalize3(sgVec3 v) { return (n > 0) ? (sgVec3){v.x / n, v.y / n, v.z / n} : (sgVec3){0, 0, 0}; } -static inline sgVec2 Vec2FromVec4(sgVec4 v) { return (sgVec2){v.x, v.y}; } +static inline sgVec3 Vec3FromVec4(sgVec4 v) { return (sgVec3){v.x, v.y, v.z}; } static inline sgVec4 Vec4FromVec3(sgVec3 v, R w) { return (sgVec4){v.x, v.y, v.z, w}; } static inline sgMat4 Mat4( @@ -288,17 +288,20 @@ static inline sgMat4 Mat4Perspective(R fovy, R aspect, R near, R far) { 0, 0, -1, 0); } -static inline sgVec3 PixelToVec3(sgPixel p) { - return (sgVec3){(R)p.r / 255.f, (R)p.g / 255.f, (R)p.b / 255.f}; +static inline uint8_t ColourToUint(R x) { + return (uint8_t)(x * 255.f); } -static inline sgPixel Vec3ToPixel(sgVec3 p, R a) { - return (sgPixel){(uint8_t)(p.x * 255.f), (uint8_t)(p.y * 255.f), (uint8_t)(p.z * 255.f), (uint8_t)(a * 255.f)}; +static inline R UintToColour(uint8_t x) { + return (R)x / 255.f; } -static inline sgVec4 PixelToVec4(sgPixel p) { - return (sgVec4){(R)p.r / 255.f, (R)p.g / 255.f, (R)p.b / 255.f, (R)p.a / 255.f}; +static inline sgTexel Colour3ToTexel(sgColour3 c, R a) { + return (sgTexel){ColourToUint(c.r), ColourToUint(c.g), ColourToUint(c.b), ColourToUint(a)}; } -static inline sgPixel Vec4ToPixel(sgVec4 p) { - return (sgPixel){(uint8_t)(p.x * 255.f), (uint8_t)(p.y * 255.f), (uint8_t)(p.z * 255.f), (uint8_t)(p.w * 255.f)}; +static inline sgColour4 TexelToColour(sgTexel p) { + return (sgColour4){UintToColour(p.r), UintToColour(p.g), UintToColour(p.b), UintToColour(p.a)}; +} +static inline sgColour3 TexelToColour3(sgTexel p) { + return (sgColour3){UintToColour(p.r), UintToColour(p.g), UintToColour(p.b)}; } #ifndef _NDEBUG @@ -308,7 +311,7 @@ static bool InBounds(int width, int height, int x, int y) { } #endif // _NDEBUG -static inline sgPixel* Pixel(swgfx* gfx, int x, int y) { +static inline sgColour4* Colour(swgfx* gfx, int x, int y) { assert(gfx); assert(gfx->colour); assert(InBounds(gfx->dims.x, gfx->dims.y, x, y)); @@ -322,9 +325,9 @@ static inline const R* Depth(swgfx* gfx, int x, int y) { return gfx->depth + (y * gfx->dims.x) + x; } -static inline void SetPixelColour(swgfx* gfx, const sgVec2i p, sgPixel colour) { +static inline void SetPixelColour(swgfx* gfx, const sgVec2i p, sgColour4 colour) { assert(gfx); - *Pixel(gfx, p.x, p.y) = colour; + *Colour(gfx, p.x, p.y) = colour; #if SWGFX_PROFILING gfx->counters.pixels++; #endif // SWGFX_PROFILING @@ -342,65 +345,66 @@ static inline void SetPixelDeferred(swgfx* gfx, const sgVec2i p, R depth, sgText #endif // SWGFX_PROFILING } -static inline sgPixel ReadTexture(const sgImage* texture, sgVec2i xy) { - assert(texture); - assert(texture->pixels); - assert(InBounds(texture->width, texture->height, xy.x, xy.y)); - return texture->pixels[xy.y * texture->width + xy.x]; +static inline sgTexel ReadImage(const sgImage* image, sgVec2i xy) { + assert(image); + assert(image->pixels); + assert(InBounds(image->width, image->height, xy.x, xy.y)); + return image->pixels[xy.y * image->width + xy.x]; } // Output normalized to [0,1]. -static inline sgVec4 ReadTextureFloat(const sgImage* texture, sgVec2i xy) { - return PixelToVec4(ReadTexture(texture, xy)); +static inline sgColour4 ReadImageFloat(const sgImage* image, sgVec2i xy) { + return TexelToColour(ReadImage(image, xy)); } -static inline sgVec2i UvToIndex(const sgImage* texture, sgVec2 uv) { - assert(texture); +static inline sgVec2i UvToIndex(const sgImage* image, sgVec2 uv) { + assert(image); return (sgVec2i){ - (int)(uv.x * (R)(texture->width - 1)), - (int)(uv.y * (R)(texture->height - 1))}; + (int)(uv.x * (R)(image->width - 1)), + (int)(uv.y * (R)(image->height - 1))}; } static inline sgVec2i TextureRepeat(const sgImage* texture, sgVec2i p) { return (sgVec2i){mod(p.x, texture->width), mod(p.y, texture->height)}; } -static inline sgPixel FilterNearest(const sgImage* texture, sgVec2 uv) { - assert(texture); - assert(texture->pixels); - const sgVec2i xy = UvToIndex(texture, uv); - const sgVec2i xy2 = TextureRepeat(texture, xy); - return ReadTexture(texture, xy2); +static inline sgColour4 FilterNearest(const sgImage* image, sgVec2 uv) { + assert(image); + assert(image->pixels); + const sgVec2i xy = UvToIndex(image, uv); + const sgVec2i xy2 = TextureRepeat(image, xy); + return ReadImageFloat(image, xy2); } -static inline sgPixel FilterBilinear(const sgImage* texture, sgVec2 uv) { - assert(texture); - assert(texture->pixels); -#define ADDR(x,y) TextureRepeat(texture, (sgVec2i){x,y}) +static inline sgColour4 FilterBilinear(const sgImage* image, sgVec2 uv) { + assert(image); + assert(image->pixels); +#define ADDR(x,y) TextureRepeat(image, (sgVec2i){x,y}) const sgVec2 uv01 = mod2(uv, 1.f); // Find the closest grid vertex, then interpolate the 4 neighbouring pixel // centers. - const sgVec2i tl = UvToIndex(texture, uv01); + const sgVec2i tl = UvToIndex(image, uv01); const sgVec2i tr = ADDR(tl.x+1, tl.y); const sgVec2i bl = ADDR(tl.x, tl.y+1); const sgVec2i br = ADDR(tl.x+1, tl.y+1); const sgVec2 t = frac2(uv01); - const sgVec4 tl_pix = ReadTextureFloat(texture, tl); - const sgVec4 tr_pix = ReadTextureFloat(texture, tr); - const sgVec4 bl_pix = ReadTextureFloat(texture, bl); - const sgVec4 br_pix = ReadTextureFloat(texture, br); + const sgVec4 tl_pix = ReadImageFloat(image, tl); + const sgVec4 tr_pix = ReadImageFloat(image, tr); + const sgVec4 bl_pix = ReadImageFloat(image, bl); + const sgVec4 br_pix = ReadImageFloat(image, br); const sgVec4 x1 = lerp4(tl_pix, tr_pix, t.x); const sgVec4 x2 = lerp4(bl_pix, br_pix, t.x); const sgVec4 y = lerp4(x1, x2, t.y); - return Vec4ToPixel(y); + return y; } // TODO: Mipmapping. // TODO: Clamping and other addressing strategies. -static inline sgPixel Sample(const sgImage* texture, sgTextureFilter filter, sgVec2 uv) { - switch (filter) { - case sgNearest: return FilterNearest(texture, uv); - case sgBilinear: return FilterBilinear(texture, uv); - default: assert(false); return (sgPixel){0}; +static inline sgColour4 Sample(const sgTexture* texture, sgVec2 uv) { + assert(texture); + switch (texture->filter) { + case sgNearest: return FilterNearest(texture->image, uv); + case sgBilinear: return FilterBilinear(texture->image, uv); + default: assert(false); return (sgColour4){0}; } } @@ -473,7 +477,7 @@ static void DrawTriangle2(swgfx* gfx, const sgTri2* const tri) { if ((bar.x >= 0) && (bar.y >= 0) && (bar.z >= 0)) { assert((bar.x + bar.y + bar.z - 1e7) <= 1.f); const sgVec2 uv = BarycentricInterp2(bar, tri->p0.uv, tri->p1.uv, tri->p2.uv); - const sgPixel colour = Sample(texture->image, texture->filter, uv); + const sgColour4 colour = Sample(texture, uv); SetPixelColour(gfx, (sgVec2i){x,y}, colour); } } @@ -754,11 +758,11 @@ static void* Alloc(void** ppMem, size_t count, size_t size) { size_t sgMem(int width, int height) { const int N = width * height; return Align(sizeof(swgfx)) + - Align(N * sizeof(sgPixel)) + // Colour buffer. + Align(N * sizeof(sgColour4)) + // Colour buffer. Align(N * sizeof(R)) + // Depth buffer. Align(N * sizeof(sgTextureId)) + // Texture ID buffer. Align(N * sizeof(sgVec2)) + // Texture coords buffer. - Align(N * sizeof(sgPixel)) + // Albedo buffer. + Align(N * sizeof(sgColour3)) + // Albedo buffer. Align(N * sizeof(sgVec3)) + // Normals buffer. Align(SWGFX_TEXTURE_REGISTER_SIZE * sizeof(sgTexture)) + // Texture register. (SG_ALIGN - 1); // To make room to align allocations within the buffer. @@ -769,11 +773,11 @@ swgfx* sgNew(int width, int height, void* mem) { void* aligned = AlignPtr(mem); // Uses the extra room we made in sgMem(). swgfx* gfx = SG_ALLOC(&aligned, 1, swgfx); gfx->dims = (sgVec2i){width, height}; - gfx->colour = SG_ALLOC(&aligned, N, sgPixel); + gfx->colour = SG_ALLOC(&aligned, N, sgColour4); gfx->depth = SG_ALLOC(&aligned, N, R); gfx->texture = SG_ALLOC(&aligned, N, sgTextureId); gfx->texcoords = SG_ALLOC(&aligned, N, sgVec2); - gfx->albedo = SG_ALLOC(&aligned, N, sgPixel); + gfx->albedo = SG_ALLOC(&aligned, N, sgColour3); gfx->normals = SG_ALLOC(&aligned, N, sgVec3); gfx->textureRegister = SG_ALLOC(&aligned, SWGFX_TEXTURE_REGISTER_SIZE, sgTexture); gfx->activeTexture = DefaultTextureId; @@ -797,7 +801,7 @@ void sgDel(swgfx** ppSwgfx) { } } -sgPixel* sgColourBuffer(swgfx* gfx) { +sgColour4* sgColourBuffer(swgfx* gfx) { assert(gfx); return gfx->colour; } @@ -812,19 +816,19 @@ void sgPresent(swgfx* gfx, sgVec2i dimensions, sgScreenPixel* screen) { const int sx = dimensions.x / gfx->dims.x; const int sy = dimensions.y / gfx->dims.y; - const sgPixel* src = gfx->colour; + const sgColour4* src = gfx->colour; for (int y = 0; y < gfx->dims.y; ++y, src += gfx->dims.x) { // Replicate each row 'sy' times. for (int yy = 0; yy < sy; ++yy) { - const sgPixel* src_col = src; + const sgColour4* src_col = src; for (int x = 0; x < gfx->dims.x; ++x, ++src_col) { // Replicate each column 'sx' times. for (int xx = 0; xx < sx; ++xx, ++screen) { - screen->r = src_col->r; - screen->g = src_col->g; - screen->b = src_col->b; - screen->a = src_col->a; + screen->r = ColourToUint(src_col->r); + screen->g = ColourToUint(src_col->g); + screen->b = ColourToUint(src_col->b); + screen->a = ColourToUint(src_col->a); } } } @@ -907,7 +911,7 @@ void sgClear(swgfx* gfx) { } } -void sgPixels(swgfx* gfx, size_t count, const sgVec2i* positions, sgPixel colour) { +void sgPixels(swgfx* gfx, size_t count, const sgVec2i* positions, sgColour4 colour) { assert(gfx); for (size_t i = 0; i < count; ++i) { SetPixelColour(gfx, positions[i], colour); @@ -984,14 +988,6 @@ void sgTrianglesIndexedNonUniform(swgfx* gfx, size_t numTris, const sgTriIdx* tr } } -static void ImageExp(sgPixel* pixels, int width, int height, R exp) { - assert(pixels); - for (int i = 0; i < width * height; ++i) { - sgPixel* p = &pixels[i]; - *p = Vec3ToPixel(exp3(PixelToVec3(*p), exp), p->a); - } -} - void sgLighting(swgfx* gfx) { assert(gfx); const int N = gfx->dims.x * gfx->dims.y; @@ -1001,21 +997,40 @@ void sgLighting(swgfx* gfx) { const sgTextureId texid = gfx->texture[i]; const sgTexture* texture = &gfx->textureRegister[texid]; const sgVec2 uv = gfx->texcoords[i]; - sgPixel* albedo = &gfx->albedo[i]; - *albedo = Sample(texture->image, texture->filter, uv); + sgColour3* albedo = &gfx->albedo[i]; + *albedo = Vec3FromVec4(Sample(texture, uv)); } } } -void sgAmbient(swgfx* gfx, sgVec3 ambient) { +void sgAmbient(swgfx* gfx, sgColour3 ambient) { assert(gfx); const int N = gfx->dims.x * gfx->dims.y; for (int i = 0; i < N; ++i) { const R depth = gfx->depth[i]; if (depth != DepthClearValue) { - const sgPixel* albedo = &gfx->albedo[i]; - sgPixel* colour = &gfx->colour[i]; - *colour = Vec3ToPixel(mul3(PixelToVec3(*albedo), ambient), (R)albedo->a/255.f); + const sgColour3* albedo = &gfx->albedo[i]; + sgColour4* colour = &gfx->colour[i]; + *colour = Vec4FromVec3(mul3(*albedo, ambient), 1.f); + } + } +} + +void sgDirectional(swgfx* gfx, sgColour3 lightColour, sgVec3 direction) { + assert(gfx); + const sgVec3 L = neg3(direction); + const int N = gfx->dims.x * gfx->dims.y; + for (int i = 0; i < N; ++i) { + const R depth = gfx->depth[i]; + if (depth != DepthClearValue) { + const sgColour3* albedo = &gfx->albedo[i]; + const sgVec3* normal = &gfx->normals[i]; + const R NdotL = fmaxf(0.f, dot3(*normal, L)); + sgColour4* colour = &gfx->colour[i]; + // TODO: Here and in sgAmbient() we need to accumulate colour, so this + // should be +=. But colour also needs to be sgVec4, not sgPixel. We + // should transform sgVec4 to sgPixel colour on sgPresent() instead. + *colour = Vec4FromVec3(scale3(mul3(*albedo, lightColour), NdotL), 1.f); } } } @@ -1026,9 +1041,9 @@ void sgDepth(swgfx* gfx) { for (int i = 0; i < N; ++i) { const R depth = gfx->depth[i]; if (depth != DepthClearValue) { - sgPixel* colour = &gfx->colour[i]; - const R d = gfx->depth[i]; - *colour = Vec3ToPixel((sgVec3){d,d,d}, 1.f); + sgColour4* colour = &gfx->colour[i]; + const R d = gfx->depth[i]; + *colour = (sgColour4){d,d,d, 1.f}; } } } @@ -1039,23 +1054,31 @@ void sgNormals(swgfx* gfx) { for (int i = 0; i < N; ++i) { const R depth = gfx->depth[i]; if (depth != DepthClearValue) { - sgPixel* colour = &gfx->colour[i]; + sgColour4* colour = &gfx->colour[i]; const sgVec3* normal = &gfx->normals[i]; - *colour = Vec3ToPixel(max3(Zero3, *normal), 1.f); + *colour = Vec4FromVec3(max3(Zero3, *normal), 1.f); } } } -void sgGamma(swgfx* gfx, sgPixel* pixels, int width, int height) { +void sgGamma(swgfx* gfx, sgTexel* texels, int width, int height) { assert(gfx); - assert(pixels); - ImageExp(pixels, width, height, 2.2f); + assert(texels); + constexpr R exp = 2.2f; + for (int i = 0; i < width * height; ++i) { + sgTexel* const p = &texels[i]; + *p = Colour3ToTexel(exp3(TexelToColour3(*p), exp), p->a); + } } -void sgGammaInv(swgfx* gfx, sgPixel* pixels, int width, int height) { +void sgGammaInv(swgfx* gfx, sgColour4* pixels, int width, int height) { assert(gfx); assert(pixels); - ImageExp(pixels, width, height, 1.0f/2.2f); + constexpr R exp = 1.0f/2.2f; + for (int i = 0; i < width * height; ++i) { + sgColour4* const p = &pixels[i]; + *p = Vec4FromVec3(exp3(Vec3FromVec4(*p), exp), p->a); + } } sgCounters sgGetCounters(const swgfx* gfx) { -- cgit v1.2.3